From 7104d2ef669827e0cf38059b1ed1d1d56eb4cbf5 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 4 Jul 2022 11:29:02 +0200 Subject: [PATCH] Reject all explicitly written type references with bad bounds ... except for type parameters, since these will need to be instantiated with good types themselves. Fixes further unsoundness examples added to neg/i15569.scala. --- .../dotty/tools/dotc/transform/PostTyper.scala | 10 +++------- .../src/dotty/tools/dotc/typer/Checking.scala | 10 +++++----- tests/init/neg/i5854.scala | 5 ----- tests/neg-custom-args/fatal-warnings/i13820.scala | 5 ----- tests/neg-strict/i1050.scala | 8 ++++---- tests/{init => }/neg/i11572.scala | 6 +++--- tests/neg/i13820.scala | 5 +++++ tests/neg/i15568.check | 2 +- tests/neg/i15569.scala | 15 +++++++++++++++ tests/neg/i5854.scala | 5 +++++ tests/pos/i13820.scala | 4 ++-- 11 files changed, 43 insertions(+), 32 deletions(-) delete mode 100644 tests/init/neg/i5854.scala delete mode 100644 tests/neg-custom-args/fatal-warnings/i13820.scala rename tests/{init => }/neg/i11572.scala (50%) create mode 100644 tests/neg/i13820.scala create mode 100644 tests/neg/i5854.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 2b72b6c8bdbd..ad51b08c8bad 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -280,7 +280,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase ctx super.transform(tree)(using gadtCtx) case tree: Ident => - if tree.isType then + if tree.isType && !tree.symbol.is(Param) then + Checking.checkGoodBounds(tree.tpe, tree.srcPos) checkNotPackage(tree) else if tree.symbol.is(Inline) && !Inlines.inInlineMethod then @@ -295,6 +296,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase ctx.compilationUnit.needsInlining = true if name.isTypeName then Checking.checkRealizable(qual.tpe, qual.srcPos) + Checking.checkGoodBounds(tree.tpe, tree.srcPos) withMode(Mode.Type)(super.transform(checkNotPackage(tree))) else checkNoConstructorProxy(tree) @@ -345,12 +347,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) for arg <- args do checkInferredWellFormed(arg) - 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. - 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 45302f476d48..7464d6c3afcc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -87,12 +87,12 @@ object Checking { checkBounds(args, tl.paramInfos, _.substParams(tl, _)) def checkGoodBounds(tpe: Type, pos: SrcPos)(using Context): Boolean = - def recur(tp: Type) = tp.dealias match + def recur(tp: Type): Boolean = 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) + recur(tp.info) + case tp @ TypeBounds(lo, hi) if !(lo <:< hi) => + val tpStr = if tp eq tpe then "" else i" $tpe" + report.error(i"type$tpStr has potentially conflicting bounds $tp", pos) false case _ => true diff --git a/tests/init/neg/i5854.scala b/tests/init/neg/i5854.scala deleted file mode 100644 index a52685f63be2..000000000000 --- a/tests/init/neg/i5854.scala +++ /dev/null @@ -1,5 +0,0 @@ -class B { - val a: String = (((1: Any): b.A): Nothing): String - val b: { type A >: Any <: Nothing } = loop() // error - def loop(): Nothing = loop() -} diff --git a/tests/neg-custom-args/fatal-warnings/i13820.scala b/tests/neg-custom-args/fatal-warnings/i13820.scala deleted file mode 100644 index 234c1a55450e..000000000000 --- a/tests/neg-custom-args/fatal-warnings/i13820.scala +++ /dev/null @@ -1,5 +0,0 @@ -trait Expr { type T } - -def foo[A](e: Expr { type T = A }) = e match - case e1: Expr { type T <: Int } => // error: type test cannot be checked at runtime - val i: Int = ??? : e1.T \ No newline at end of file diff --git a/tests/neg-strict/i1050.scala b/tests/neg-strict/i1050.scala index 6962dd44f826..3e8b17fdbe7f 100644 --- a/tests/neg-strict/i1050.scala +++ b/tests/neg-strict/i1050.scala @@ -100,7 +100,7 @@ object Indirect { trait U { trait X { val q: A & B = ??? - type M = q.L + type M = q.L // error: conflicting bounds } final lazy val p: X = ??? def brand(x: Any): p.M = x // error: conflicting bounds @@ -119,7 +119,7 @@ object Indirect2 { } trait X { val q: Y = ??? - type M = q.r.L + type M = q.r.L // error: conflicting bounds } final lazy val p: X = ??? def brand(x: Any): p.M = x // error: conflicting bounds @@ -152,7 +152,7 @@ object Rec2 { } trait X { val q: Y = ??? - type M = q.r.L + type M = q.r.L // error: conflicting bounds } final lazy val p: X = ??? def brand(x: Any): p.M = x // error: conflicting bounds @@ -171,7 +171,7 @@ object Indirect3 { } trait X { val q: Y = ??? - type M = q.r.L + type M = q.r.L // error: conflicting bounds } final lazy val p: X = ??? def brand(x: Any): p.M = x // error: conflicting bounds diff --git a/tests/init/neg/i11572.scala b/tests/neg/i11572.scala similarity index 50% rename from tests/init/neg/i11572.scala rename to tests/neg/i11572.scala index c2ebe6fe47f9..3eff345a8b93 100644 --- a/tests/init/neg/i11572.scala +++ b/tests/neg/i11572.scala @@ -5,13 +5,13 @@ class A { trait Bounded { type T >: Cov[Int] <: Cov[String] } - val t: Bounded = new Bounded { // error + val t: Bounded = new Bounded { // Note: using this instead of t produces an error (as expected) - override type T >: t.T <: t.T + override type T >: t.T <: t.T // error // error (conflicting bounds) } val covInt = new Cov[Int] { override def get: Int = 3 } - val str: String = ((covInt: t.T): Cov[String]).get // ClassCastException: class Integer cannot be cast to class String + val str: String = ((covInt: t.T): Cov[String]).get // error, was ClassCastException: class Integer cannot be cast to class String } diff --git a/tests/neg/i13820.scala b/tests/neg/i13820.scala new file mode 100644 index 000000000000..46dd1d034120 --- /dev/null +++ b/tests/neg/i13820.scala @@ -0,0 +1,5 @@ +trait Expr { type T } + +def foo[A](e: Expr { type T = A }) = e match + case e1: Expr { type T <: Int } => + val i: Int = ??? : e1.T // error: unrealizable bounds diff --git a/tests/neg/i15568.check b/tests/neg/i15568.check index 3a988433d074..931c17dca33b 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 potentially unrealizable bounds >: Int <: String + | type has potentially conflicting bounds >: Int <: String diff --git a/tests/neg/i15569.scala b/tests/neg/i15569.scala index f98345a61691..b0920437dec7 100644 --- a/tests/neg/i15569.scala +++ b/tests/neg/i15569.scala @@ -13,3 +13,18 @@ def andThenSub[A, B, C](f: A <:< B, g: B <:< C): A <:< C = val unsound: Any <:< Nothing = andThenSub[Any, t, Nothing](summon, summon) // error unsound("hi :)") +@main def Test3 = (None: Option[Foo[?]]) match { + case _: Option[Foo[t]] => + val unsound: Nothing = (5 : Any) : t // error + (unsound : Unit => Unit).apply(()) +} + +@main def Test4 = + type t >: Any <: Nothing + val unsound: Nothing = (5 : Any) : t // error + (unsound : Unit => Unit).apply(()) + +@main def Test5 = + type t >: Any <: Nothing + val unsound: List[Nothing] = List(5 : Any) : List[t] // error + (unsound.head : Unit => Unit).apply(()) diff --git a/tests/neg/i5854.scala b/tests/neg/i5854.scala new file mode 100644 index 000000000000..906d08aa1887 --- /dev/null +++ b/tests/neg/i5854.scala @@ -0,0 +1,5 @@ +class B { + val a: String = (((1: Any): b.A): Nothing): String // error + val b: { type A >: Any <: Nothing } = loop() + def loop(): Nothing = loop() +} diff --git a/tests/pos/i13820.scala b/tests/pos/i13820.scala index 1accdee53fb1..a5540fba1f5b 100644 --- a/tests/pos/i13820.scala +++ b/tests/pos/i13820.scala @@ -1,5 +1,5 @@ trait Expr { type T } -def foo[A](e: Expr { type T = A }) = e match +def foo[A <: Int](e: Expr { type T = A }) = e match case e1: Expr { type T <: Int } => - val i: Int = ??? : e1.T \ No newline at end of file + val i: Int = ??? : e1.T