From 499442052d2016679b248b94d1411ad74562a373 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 2 Jul 2022 10:58:53 +0200 Subject: [PATCH 1/5] Check type arguments for bad bounds Fixes #15569 --- .../dotty/tools/dotc/inlines/InlineReducer.scala | 2 +- .../dotty/tools/dotc/transform/PostTyper.scala | 12 +++++++++++- tests/neg/i15569.scala | 15 +++++++++++++++ tests/neg/tate.scala | 2 +- 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 tests/neg/i15569.scala diff --git a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala index 7be63fc8ba3a..b5e2e7d475ad 100644 --- a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala +++ b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala @@ -14,7 +14,7 @@ import util.SimpleIdentityMap import collection.mutable -/** A utility object offering methods for rewriting inlined code */ +/** A utility class offering methods for rewriting inlined code */ class InlineReducer(inliner: Inliner)(using Context): import tpd.* import Inliner.{isElideableExpr, DefBuffer} diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index dda4b89f6d1c..a5ac0d7ba50f 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -14,6 +14,7 @@ import Decorators._ import Symbols._, SymUtils._, NameOps._ import ContextFunctionResults.annotateContextResults import config.Printers.typr +import util.SrcPos import reporting._ object PostTyper { @@ -180,6 +181,13 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase => Checking.checkAppliedTypesIn(tree) case _ => + private def checkGoodBounds(tpe: Type, pos: SrcPos)(using Context): Unit = tpe.dealias match + case tpe: TypeRef => + checkGoodBounds(tpe.info, pos) + case TypeBounds(lo, hi) if !(lo <:< hi) => + report.error(i"type argument has unrealizable bounds $tpe", pos) + case _ => + private def removeUnwantedAnnotations(sym: Symbol, metaAnnotSym: Symbol, metaAnnotSymBackup: Symbol, keepIfNoRelevantAnnot: Boolean)(using Context): Unit = def shouldKeep(annot: Annotation): Boolean = @@ -342,7 +350,9 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase if tree.symbol.is(Inline) then ctx.compilationUnit.needsInlining = true val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) - args.foreach(checkInferredWellFormed) + for arg <- args do + checkInferredWellFormed(arg) + checkGoodBounds(arg.tpe, arg.srcPos) if (fn.symbol != defn.ChildAnnot.primaryConstructor) // Make an exception for ChildAnnot, which should really have AnyKind bounds Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType]) diff --git a/tests/neg/i15569.scala b/tests/neg/i15569.scala new file mode 100644 index 000000000000..f98345a61691 --- /dev/null +++ b/tests/neg/i15569.scala @@ -0,0 +1,15 @@ +trait Foo[X >: Any <: Nothing] + +def andThenSub[A, B, C](f: A <:< B, g: B <:< C): A <:< C = + f.andThen(g) + +@main def Test = (None: Option[Foo[?]]) match { + case _: Option[Foo[t]] => + val unsound: Any <:< Nothing = andThenSub[Any, t, Nothing](summon, summon) // error + unsound("hi :)") +} +@main def Test2 = + type t >: Any <: Nothing + val unsound: Any <:< Nothing = andThenSub[Any, t, Nothing](summon, summon) // error + unsound("hi :)") + diff --git a/tests/neg/tate.scala b/tests/neg/tate.scala index acf7ee7e3dbf..e168cfa0135e 100644 --- a/tests/neg/tate.scala +++ b/tests/neg/tate.scala @@ -6,6 +6,6 @@ def coerce[T, U](t: T): U = { lazy val bound: Bound[U, _ >: T] = ??? // error: >: T does not conform to upper bound def bind = new Bind[U] {} - bind.bad(bound, t) + bind.bad(bound, t) // error: type argument has unrealizable bounds } } From 337dce8d845da2b1431d7f703b51f7240914ce95 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 2 Jul 2022 11:18:04 +0200 Subject: [PATCH 2/5] Check for good bounds before reporting other bounds violations Fixes #15568 --- .../dotty/tools/dotc/transform/PostTyper.scala | 9 +-------- .../src/dotty/tools/dotc/typer/Checking.scala | 18 ++++++++++++++---- tests/neg/i15568.check | 4 ++++ tests/neg/i15568.scala | 3 +++ 4 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 tests/neg/i15568.check create mode 100644 tests/neg/i15568.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index a5ac0d7ba50f..10dcad13d49f 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -181,13 +181,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase => Checking.checkAppliedTypesIn(tree) case _ => - private def checkGoodBounds(tpe: Type, pos: SrcPos)(using Context): Unit = tpe.dealias match - case tpe: TypeRef => - checkGoodBounds(tpe.info, pos) - case TypeBounds(lo, hi) if !(lo <:< hi) => - report.error(i"type argument has unrealizable bounds $tpe", pos) - case _ => - private def removeUnwantedAnnotations(sym: Symbol, metaAnnotSym: Symbol, metaAnnotSymBackup: Symbol, keepIfNoRelevantAnnot: Boolean)(using Context): Unit = def shouldKeep(annot: Annotation): Boolean = @@ -352,7 +345,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) for arg <- args do checkInferredWellFormed(arg) - checkGoodBounds(arg.tpe, arg.srcPos) + Checking.checkGoodBounds(arg.tpe, arg.srcPos) if (fn.symbol != defn.ChildAnnot.primaryConstructor) // Make an exception for ChildAnnot, which should really have AnyKind bounds Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType]) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index c636510fdb89..e0effa7a67aa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -73,10 +73,11 @@ object Checking { showInferred(MissingTypeParameterInTypeApp(arg.tpe), app, tpt)) } for (arg, which, bound) <- TypeOps.boundsViolations(args, boundss, instantiate, app) do - report.error( - showInferred(DoesNotConformToBound(arg.tpe, which, bound), - app, tpt), - arg.srcPos.focus) + if checkGoodBounds(arg.tpe, arg.srcPos.focus) then + report.error( + showInferred(DoesNotConformToBound(arg.tpe, which, bound), + app, tpt), + arg.srcPos.focus) /** Check that type arguments `args` conform to corresponding bounds in `tl` * Note: This does not check the bounds of AppliedTypeTrees. These @@ -85,6 +86,15 @@ object Checking { def checkBounds(args: List[tpd.Tree], tl: TypeLambda)(using Context): Unit = checkBounds(args, tl.paramInfos, _.substParams(tl, _)) + def checkGoodBounds(tpe: Type, pos: SrcPos)(using Context): Boolean = tpe.dealias match + case tpe: TypeRef => + checkGoodBounds(tpe.info, pos) + case TypeBounds(lo, hi) if !(lo <:< hi) => + report.error(i"type argument has unrealizable bounds $tpe", pos) + false + case _ => + true + /** Check applied type trees for well-formedness. This means * - all arguments are within their corresponding bounds * - if type is a higher-kinded application with wildcard arguments, diff --git a/tests/neg/i15568.check b/tests/neg/i15568.check new file mode 100644 index 000000000000..f6d36f18439c --- /dev/null +++ b/tests/neg/i15568.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/i15568.scala:3:15 ---------------------------------------------------------------------------------- +3 |type Bar = Foo[? >: Int <: String] // error + | ^ + | type argument has unrealizable bounds >: Int <: String diff --git a/tests/neg/i15568.scala b/tests/neg/i15568.scala new file mode 100644 index 000000000000..422713b6a0bc --- /dev/null +++ b/tests/neg/i15568.scala @@ -0,0 +1,3 @@ +trait Foo[X >: Int <: String] + +type Bar = Foo[? >: Int <: String] // error From e748d854a98e5c31a804eb3057fd7b5e85c7d1b8 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 2 Jul 2022 11:30:11 +0200 Subject: [PATCH 3/5] Only check explicitly given type arguments --- compiler/src/dotty/tools/dotc/transform/PostTyper.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 10dcad13d49f..3b2ba332f9a7 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -345,7 +345,11 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) for arg <- args do checkInferredWellFormed(arg) - Checking.checkGoodBounds(arg.tpe, arg.srcPos) + if !arg.span.isSynthetic then + // only check explicit type arguments. We rely on inferred type arguments + // to either have good bounds (if they come from a constraint), or be derived + // from values that recursively need to have good bounds. + Checking.checkGoodBounds(arg.tpe, arg.srcPos) if (fn.symbol != defn.ChildAnnot.primaryConstructor) // Make an exception for ChildAnnot, which should really have AnyKind bounds Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType]) From c5eb52e0cd7156ff27deebbf1654616d239e935d Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 2 Jul 2022 16:02:32 +0200 Subject: [PATCH 4/5] Fix ZIO tests Fix ZIO tests that fail because of tightening of bounds checks --- community-build/community-projects/zio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/zio b/community-build/community-projects/zio index 25977ff09847..912b4f887912 160000 --- a/community-build/community-projects/zio +++ b/community-build/community-projects/zio @@ -1 +1 @@ -Subproject commit 25977ff09847f1e7857f799e0abaf00c82003e76 +Subproject commit 912b4f887912792202aa76e93fd19e63bd62f3bc From 745236af41f4675bad96039594501995b9aea87c Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 2 Jul 2022 16:14:23 +0200 Subject: [PATCH 5/5] Refine condition when to check and improve error message --- .../tools/dotc/transform/PostTyper.scala | 3 ++- .../src/dotty/tools/dotc/typer/Checking.scala | 19 +++++++++++-------- tests/neg/i15568.check | 2 +- tests/neg/tate.scala | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 3b2ba332f9a7..2b72b6c8bdbd 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -345,7 +345,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) for arg <- args do checkInferredWellFormed(arg) - if !arg.span.isSynthetic then + val isInferred = arg.isInstanceOf[InferredTypeTree] || arg.span.isSynthetic + if !isInferred then // only check explicit type arguments. We rely on inferred type arguments // to either have good bounds (if they come from a constraint), or be derived // from values that recursively need to have good bounds. diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index e0effa7a67aa..45302f476d48 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -86,14 +86,17 @@ object Checking { def checkBounds(args: List[tpd.Tree], tl: TypeLambda)(using Context): Unit = checkBounds(args, tl.paramInfos, _.substParams(tl, _)) - def checkGoodBounds(tpe: Type, pos: SrcPos)(using Context): Boolean = tpe.dealias match - case tpe: TypeRef => - checkGoodBounds(tpe.info, pos) - case TypeBounds(lo, hi) if !(lo <:< hi) => - report.error(i"type argument has unrealizable bounds $tpe", pos) - false - case _ => - true + def checkGoodBounds(tpe: Type, pos: SrcPos)(using Context): Boolean = + def recur(tp: Type) = tp.dealias match + case tp: TypeRef => + checkGoodBounds(tp.info, pos) + case TypeBounds(lo, hi) if !(lo <:< hi) => + val argStr = if tp eq tpe then "" else i" $tpe" + report.error(i"type argument$argStr has potentially unrealizable bounds $tp", pos) + false + case _ => + true + recur(tpe) /** Check applied type trees for well-formedness. This means * - all arguments are within their corresponding bounds diff --git a/tests/neg/i15568.check b/tests/neg/i15568.check index f6d36f18439c..3a988433d074 100644 --- a/tests/neg/i15568.check +++ b/tests/neg/i15568.check @@ -1,4 +1,4 @@ -- Error: tests/neg/i15568.scala:3:15 ---------------------------------------------------------------------------------- 3 |type Bar = Foo[? >: Int <: String] // error | ^ - | type argument has unrealizable bounds >: Int <: String + | type argument has potentially unrealizable bounds >: Int <: String diff --git a/tests/neg/tate.scala b/tests/neg/tate.scala index e168cfa0135e..acf7ee7e3dbf 100644 --- a/tests/neg/tate.scala +++ b/tests/neg/tate.scala @@ -6,6 +6,6 @@ def coerce[T, U](t: T): U = { lazy val bound: Bound[U, _ >: T] = ??? // error: >: T does not conform to upper bound def bind = new Bind[U] {} - bind.bad(bound, t) // error: type argument has unrealizable bounds + bind.bad(bound, t) } }