From 7104d2ef669827e0cf38059b1ed1d1d56eb4cbf5 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 4 Jul 2022 11:29:02 +0200 Subject: [PATCH 1/8] 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 From 69858b55763b859f44b961835fa0d2e780921864 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 4 Jul 2022 11:50:26 +0200 Subject: [PATCH 2/8] Undo ZIO changes --- 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 912b4f887912..a7f6e1a3b2e6 160000 --- a/community-build/community-projects/zio +++ b/community-build/community-projects/zio @@ -1 +1 @@ -Subproject commit 912b4f887912792202aa76e93fd19e63bd62f3bc +Subproject commit a7f6e1a3b2e6bc35bed188739120da6cff105dbb From 875b9f8852bc087440852c37c7399626aefd88b3 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 4 Jul 2022 12:24:18 +0200 Subject: [PATCH 3/8] Fix explanation for lower bounds failures Fixes #15575 --- .../dotty/tools/dotc/reporting/messages.scala | 4 +- .../dotty/tools/dotc/CompilationTests.scala | 1 + tests/neg-custom-args/i15575.check | 40 +++++++++++++++++++ tests/neg-custom-args/i15575.scala | 7 ++++ 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 tests/neg-custom-args/i15575.check create mode 100644 tests/neg-custom-args/i15575.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index a424ddf6927e..b75766e35ef7 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1446,7 +1446,9 @@ import transform.SymUtils._ } class DoesNotConformToBound(tpe: Type, which: String, bound: Type)(using Context) - extends TypeMismatchMsg(tpe, bound)(DoesNotConformToBoundID) { + extends TypeMismatchMsg( + if which == "lower" then bound else tpe, + if which == "lower" then tpe else bound)(DoesNotConformToBoundID) { def msg = em"Type argument ${tpe} does not conform to $which bound $bound" } diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 5b18b6c81fe9..1abb1ffd9ea3 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -159,6 +159,7 @@ class CompilationTests { compileFile("tests/neg-custom-args/i12650.scala", allowDeepSubtypes), compileFile("tests/neg-custom-args/i9517.scala", defaultOptions.and("-Xprint-types")), compileFile("tests/neg-custom-args/i11637.scala", defaultOptions.and("-explain")), + compileFile("tests/neg-custom-args/i15575.scala", defaultOptions.and("-explain")), compileFile("tests/neg-custom-args/interop-polytypes.scala", allowDeepSubtypes.and("-Yexplicit-nulls")), compileFile("tests/neg-custom-args/conditionalWarnings.scala", allowDeepSubtypes.and("-deprecation").and("-Xfatal-warnings")), compileFilesInDir("tests/neg-custom-args/isInstanceOf", allowDeepSubtypes and "-Xfatal-warnings"), diff --git a/tests/neg-custom-args/i15575.check b/tests/neg-custom-args/i15575.check new file mode 100644 index 000000000000..f69111efeb96 --- /dev/null +++ b/tests/neg-custom-args/i15575.check @@ -0,0 +1,40 @@ +-- [E057] Type Mismatch Error: tests/neg-custom-args/i15575.scala:3:27 ------------------------------------------------- +3 | def bar[T]: Unit = foo[T & Any] // error + | ^ + | Type argument T & Any does not conform to lower bound Any + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | I tried to show that + | Any + | conforms to + | T & Any + | but the comparison trace ended with `false`: + | + | ==> Any <: T & Any + | ==> Any <: T + | <== Any <: T = false + | <== Any <: T & Any = false + | + | The tests were made under the empty constraint + --------------------------------------------------------------------------------------------------------------------- +-- [E057] Type Mismatch Error: tests/neg-custom-args/i15575.scala:7:14 ------------------------------------------------- +7 | val _ = foo[String] // error + | ^ + | Type argument String does not conform to lower bound CharSequence + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | I tried to show that + | CharSequence + | conforms to + | String + | but the comparison trace ended with `false`: + | + | ==> CharSequence <: String + | ==> CharSequence <: String + | <== CharSequence <: String = false + | <== CharSequence <: String = false + | + | The tests were made under the empty constraint + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-custom-args/i15575.scala b/tests/neg-custom-args/i15575.scala new file mode 100644 index 000000000000..367d0f36f1ed --- /dev/null +++ b/tests/neg-custom-args/i15575.scala @@ -0,0 +1,7 @@ +object Test1: + def foo[T >: Any]: Unit = () + def bar[T]: Unit = foo[T & Any] // error + +object Test2: + def foo[T >: CharSequence]: Unit = () + val _ = foo[String] // error From cac3d783cbadcb882098d157c3d196eca6a4a2d2 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 4 Jul 2022 14:31:54 +0200 Subject: [PATCH 4/8] Flag type creation rather than usage Flag already creations of type that have possibly conflicting bounds. Creation sites are - type declarations other than type parameters - bindings in type patterns --- .../dotty/tools/dotc/reporting/messages.scala | 13 +++++-- .../tools/dotc/transform/PostTyper.scala | 24 +++++++----- .../src/dotty/tools/dotc/typer/Checking.scala | 33 +++++++++-------- tests/neg/i11572.scala | 6 +-- tests/neg/i13820.scala | 5 --- tests/neg/i15568.check | 4 +- tests/neg/i15569.scala | 29 ++++++++++----- tests/neg/i5854.scala | 4 +- tests/pos/i13820.scala | 2 +- tests/semanticdb/expect/i5854.scala | 11 ------ tests/semanticdb/metac.expect | 37 ------------------- 11 files changed, 69 insertions(+), 99 deletions(-) delete mode 100644 tests/neg/i13820.scala delete mode 100644 tests/semanticdb/expect/i5854.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index b75766e35ef7..0a01e4ef4911 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1448,9 +1448,16 @@ import transform.SymUtils._ class DoesNotConformToBound(tpe: Type, which: String, bound: Type)(using Context) extends TypeMismatchMsg( if which == "lower" then bound else tpe, - if which == "lower" then tpe else bound)(DoesNotConformToBoundID) { - def msg = em"Type argument ${tpe} does not conform to $which bound $bound" - } + if which == "lower" then tpe else bound)(DoesNotConformToBoundID): + private def isBounds = tpe match + case TypeBounds(lo, hi) => lo ne hi + case _ => false + override def canExplain = !isBounds + def msg = + if isBounds then + em"Type argument ${tpe} does not overlap with $which bound $bound" + else + em"Type argument ${tpe} does not conform to $which bound $bound" class DoesNotConformToSelfType(category: String, selfType: Type, cls: Symbol, otherSelf: Type, relation: String, other: Symbol)( diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index ad51b08c8bad..de1cd327ac29 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -16,6 +16,7 @@ import ContextFunctionResults.annotateContextResults import config.Printers.typr import util.SrcPos import reporting._ +import NameKinds.WildcardParamName object PostTyper { val name: String = "posttyper" @@ -280,8 +281,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase ctx super.transform(tree)(using gadtCtx) case tree: Ident => - if tree.isType && !tree.symbol.is(Param) then - Checking.checkGoodBounds(tree.tpe, tree.srcPos) + if tree.isType then checkNotPackage(tree) else if tree.symbol.is(Inline) && !Inlines.inInlineMethod then @@ -296,7 +296,6 @@ 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) @@ -399,13 +398,20 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase val reference = ctx.settings.sourceroot.value val relativePath = util.SourceFile.relativePath(ctx.compilationUnit.source, reference) sym.addAnnotation(Annotation.makeSourceFile(relativePath)) - else (tree.rhs, sym.info) match - case (rhs: LambdaTypeTree, bounds: TypeBounds) => - VarianceChecker.checkLambda(rhs, bounds) - if sym.isOpaqueAlias then - VarianceChecker.checkLambda(rhs, TypeBounds.upper(sym.opaqueAlias)) - case _ => + else + if !sym.is(Param) then + Checking.checkGoodBounds(tree.symbol) + (tree.rhs, sym.info) match + case (rhs: LambdaTypeTree, bounds: TypeBounds) => + VarianceChecker.checkLambda(rhs, bounds) + if sym.isOpaqueAlias then + VarianceChecker.checkLambda(rhs, TypeBounds.upper(sym.opaqueAlias)) + case _ => processMemberDef(super.transform(tree)) + case tree: Bind => + if tree.symbol.isType && !tree.symbol.name.is(WildcardParamName) then + Checking.checkGoodBounds(tree.symbol) + super.transform(tree) case tree: New if isCheckable(tree) => Checking.checkInstantiable(tree.tpe, tree.srcPos) super.transform(tree) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 7464d6c3afcc..d33fd63af5a0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -73,11 +73,10 @@ object Checking { showInferred(MissingTypeParameterInTypeApp(arg.tpe), app, tpt)) } for (arg, which, bound) <- TypeOps.boundsViolations(args, boundss, instantiate, app) do - if checkGoodBounds(arg.tpe, arg.srcPos.focus) then - report.error( - showInferred(DoesNotConformToBound(arg.tpe, which, bound), - app, tpt), - arg.srcPos.focus) + 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 @@ -86,17 +85,19 @@ 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 = - def recur(tp: Type): Boolean = tp.dealias match - case tp: TypeRef => - 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 - recur(tpe) + def checkGoodBounds(sym: Symbol)(using Context): Boolean = + val bad = findBadBounds(sym.typeRef) + if bad.exists then + report.error(em"$sym has possibly conflicting bounds $bad", sym.srcPos) + !bad.exists + + /** If `tp` dealiases to a typebounds L..H where not L <:< R + * return the potentially conflicting bounds, otherwise return NoType. + */ + private def findBadBounds(tp: Type)(using Context): Type = tp.dealias match + case tp: TypeRef => findBadBounds(tp.info) + case tp @ TypeBounds(lo, hi) if !(lo <:< hi) => tp + case _ => NoType /** Check applied type trees for well-formedness. This means * - all arguments are within their corresponding bounds diff --git a/tests/neg/i11572.scala b/tests/neg/i11572.scala index 3eff345a8b93..5aba4418cdc1 100644 --- a/tests/neg/i11572.scala +++ b/tests/neg/i11572.scala @@ -3,15 +3,15 @@ class A { def get: X } trait Bounded { - type T >: Cov[Int] <: Cov[String] + type T >: Cov[Int] <: Cov[String] // error } val t: Bounded = new Bounded { // Note: using this instead of t produces an error (as expected) - override type T >: t.T <: t.T // error // error (conflicting bounds) + override type T >: t.T <: t.T } val covInt = new Cov[Int] { override def get: Int = 3 } - val str: String = ((covInt: t.T): Cov[String]).get // error, was ClassCastException: class Integer cannot be cast to class String + val str: String = ((covInt: t.T): Cov[String]).get // ClassCastException: class Integer cannot be cast to class String } diff --git a/tests/neg/i13820.scala b/tests/neg/i13820.scala deleted file mode 100644 index 46dd1d034120..000000000000 --- a/tests/neg/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 } => - val i: Int = ??? : e1.T // error: unrealizable bounds diff --git a/tests/neg/i15568.check b/tests/neg/i15568.check index 931c17dca33b..2fbdd9064aec 100644 --- a/tests/neg/i15568.check +++ b/tests/neg/i15568.check @@ -1,4 +1,4 @@ --- Error: tests/neg/i15568.scala:3:15 ---------------------------------------------------------------------------------- +-- [E057] Type Mismatch Error: tests/neg/i15568.scala:3:15 ------------------------------------------------------------- 3 |type Bar = Foo[? >: Int <: String] // error | ^ - | type has potentially conflicting bounds >: Int <: String + | Type argument >: Int <: String does not overlap with upper bound String diff --git a/tests/neg/i15569.scala b/tests/neg/i15569.scala index b0920437dec7..42451fad13f6 100644 --- a/tests/neg/i15569.scala +++ b/tests/neg/i15569.scala @@ -4,27 +4,36 @@ 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 + case _: Option[Foo[t]] => // error + val unsound: Any <:< Nothing = andThenSub[Any, t, Nothing](summon, summon) unsound("hi :)") } @main def Test2 = - type t >: Any <: Nothing - val unsound: Any <:< Nothing = andThenSub[Any, t, Nothing](summon, summon) // error + type t >: Any <: Nothing // error + val unsound: Any <:< Nothing = andThenSub[Any, t, Nothing](summon, summon) unsound("hi :)") @main def Test3 = (None: Option[Foo[?]]) match { - case _: Option[Foo[t]] => - val unsound: Nothing = (5 : Any) : t // error + case _: Option[Foo[t]] => // error + val unsound: Nothing = (5 : Any) : t (unsound : Unit => Unit).apply(()) } +@main def Test3ok = (None: Option[Foo[?]]) match { + case _: Option[Foo[_]] => // ok +} + @main def Test4 = - type t >: Any <: Nothing - val unsound: Nothing = (5 : Any) : t // error + type t >: Any <: Nothing // error + val unsound: Nothing = (5 : Any) : t (unsound : Unit => Unit).apply(()) @main def Test5 = - type t >: Any <: Nothing - val unsound: List[Nothing] = List(5 : Any) : List[t] // error + type t >: Any <: Nothing // error + val unsound: List[Nothing] = List(5 : Any) : List[t] + (unsound.head : Unit => Unit).apply(()) + +@main def Test6 = + type t[X] >: Any <: Nothing // error + val unsound: List[Nothing] = List(5 : Any) : List[t[String]] (unsound.head : Unit => Unit).apply(()) diff --git a/tests/neg/i5854.scala b/tests/neg/i5854.scala index 906d08aa1887..05fa1033dd2c 100644 --- a/tests/neg/i5854.scala +++ b/tests/neg/i5854.scala @@ -1,5 +1,5 @@ class B { - val a: String = (((1: Any): b.A): Nothing): String // error - val b: { type A >: Any <: Nothing } = loop() + 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/pos/i13820.scala b/tests/pos/i13820.scala index a5540fba1f5b..2dfe6e1e73e7 100644 --- a/tests/pos/i13820.scala +++ b/tests/pos/i13820.scala @@ -1,5 +1,5 @@ trait Expr { type T } -def foo[A <: Int](e: Expr { type T = A }) = e match +def foo[A](e: Expr { type T = A }) = e match case e1: Expr { type T <: Int } => val i: Int = ??? : e1.T diff --git a/tests/semanticdb/expect/i5854.scala b/tests/semanticdb/expect/i5854.scala deleted file mode 100644 index 1a9fdf6c39d0..000000000000 --- a/tests/semanticdb/expect/i5854.scala +++ /dev/null @@ -1,11 +0,0 @@ -package i5854 - -class B { - // Known issue: Can't lookup the symbol of `b.A` - // we have to register the symbol of `b { type A }` to the refinementSymtab first - // then resolve, or assign same semanticdb symbol for both - // fake symbol for b.A, and real symbol of A in 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/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 6504c58b676f..0bd2a9ab2223 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -3923,43 +3923,6 @@ Occurrences: [0:8..0:15): example <- example/ [2:6..2:24): FilenameWithSpaces <- example/FilenameWithSpaces# -expect/i5854.scala ------------------- - -Summary: -Schema => SemanticDB v4 -Uri => i5854.scala -Text => empty -Language => Scala -Symbols => 6 entries -Occurrences => 16 entries - -Symbols: -i5854/B# => class B extends Object { self: B => +4 decls } -i5854/B#``(). => primary ctor (): B -i5854/B#a. => val method a String -i5854/B#b. => val method b Object { type A >: Any <: Nothing } -i5854/B#loop(). => method loop (): Nothing -local0 => type A >: Any <: Nothing - -Occurrences: -[0:8..0:13): i5854 <- i5854/ -[2:6..2:7): B <- i5854/B# -[7:6..7:7): a <- i5854/B#a. -[7:9..7:15): String -> scala/Predef.String# -[7:24..7:27): Any -> scala/Any# -[7:30..7:31): b -> i5854/B#b. -[7:36..7:43): Nothing -> scala/Nothing# -[7:46..7:52): String -> scala/Predef.String# -[8:6..8:7): b <- i5854/B#b. -[8:16..8:17): A <- local0 -[8:21..8:24): Any -> scala/Any# -[8:28..8:35): Nothing -> scala/Nothing# -[8:40..8:44): loop -> i5854/B#loop(). -[9:6..9:10): loop <- i5854/B#loop(). -[9:14..9:21): Nothing -> scala/Nothing# -[9:24..9:28): loop -> i5854/B#loop(). - expect/i9727.scala ------------------ From a24915c9f66afab9c0a13273dee64c79d7bf5c53 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 4 Jul 2022 16:06:35 +0200 Subject: [PATCH 5/8] Don't check bounds for type definitions in abstract classes Don't check bounds for type definitions in abstract classes, traits, or refinements. Bounds violations will be detected when values of these types are created. --- .../tools/dotc/transform/PostTyper.scala | 2 +- tests/{ => init}/neg/i11572.scala | 2 +- tests/{ => init}/neg/i5854.scala | 0 tests/neg-strict/i1050.scala | 8 ++-- tests/semanticdb/expect/i5854.expect.scala | 6 +-- tests/semanticdb/expect/i5854.scala | 7 ++++ tests/semanticdb/metac.expect | 37 +++++++++++++++++++ 7 files changed, 51 insertions(+), 11 deletions(-) rename tests/{ => init}/neg/i11572.scala (90%) rename tests/{ => init}/neg/i5854.scala (100%) create mode 100644 tests/semanticdb/expect/i5854.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index de1cd327ac29..8b7ac94675a1 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -399,7 +399,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase val relativePath = util.SourceFile.relativePath(ctx.compilationUnit.source, reference) sym.addAnnotation(Annotation.makeSourceFile(relativePath)) else - if !sym.is(Param) then + if !sym.is(Param) && !sym.owner.isOneOf(AbstractOrTrait) then Checking.checkGoodBounds(tree.symbol) (tree.rhs, sym.info) match case (rhs: LambdaTypeTree, bounds: TypeBounds) => diff --git a/tests/neg/i11572.scala b/tests/init/neg/i11572.scala similarity index 90% rename from tests/neg/i11572.scala rename to tests/init/neg/i11572.scala index 5aba4418cdc1..3e4e700af73c 100644 --- a/tests/neg/i11572.scala +++ b/tests/init/neg/i11572.scala @@ -5,7 +5,7 @@ class A { trait Bounded { type T >: Cov[Int] <: Cov[String] // error } - val t: Bounded = new Bounded { + val t: Bounded = new Bounded { // error // Note: using this instead of t produces an error (as expected) override type T >: t.T <: t.T } diff --git a/tests/neg/i5854.scala b/tests/init/neg/i5854.scala similarity index 100% rename from tests/neg/i5854.scala rename to tests/init/neg/i5854.scala diff --git a/tests/neg-strict/i1050.scala b/tests/neg-strict/i1050.scala index 3e8b17fdbe7f..6962dd44f826 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 // error: conflicting bounds + type M = q.L } 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 // error: conflicting bounds + type M = q.r.L } 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 // error: conflicting bounds + type M = q.r.L } 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 // error: conflicting bounds + type M = q.r.L } final lazy val p: X = ??? def brand(x: Any): p.M = x // error: conflicting bounds diff --git a/tests/semanticdb/expect/i5854.expect.scala b/tests/semanticdb/expect/i5854.expect.scala index c1878c26706b..08fb2bff2295 100644 --- a/tests/semanticdb/expect/i5854.expect.scala +++ b/tests/semanticdb/expect/i5854.expect.scala @@ -1,11 +1,7 @@ package i5854 class B/*<-i5854::B#*/ { - // Known issue: Can't lookup the symbol of `b.A` - // we have to register the symbol of `b { type A }` to the refinementSymtab first - // then resolve, or assign same semanticdb symbol for both - // fake symbol for b.A, and real symbol of A in b val a/*<-i5854::B#a.*/: String/*->scala::Predef.String#*/ = (((1: Any/*->scala::Any#*/): b/*->i5854::B#b.*/.A): Nothing/*->scala::Nothing#*/): String/*->scala::Predef.String#*/ - val b/*<-i5854::B#b.*/: { type A/*<-local0*/ >: Any/*->scala::Any#*/ <: Nothing/*->scala::Nothing#*/ } = loop/*->i5854::B#loop().*/() // error + val b/*<-i5854::B#b.*/: { type A/*<-local0*/ >: Any/*->scala::Any#*/ <: Nothing/*->scala::Nothing#*/ } = loop/*->i5854::B#loop().*/() // error def loop/*<-i5854::B#loop().*/(): Nothing/*->scala::Nothing#*/ = loop/*->i5854::B#loop().*/() } diff --git a/tests/semanticdb/expect/i5854.scala b/tests/semanticdb/expect/i5854.scala new file mode 100644 index 000000000000..63a79bf4bcce --- /dev/null +++ b/tests/semanticdb/expect/i5854.scala @@ -0,0 +1,7 @@ +package i5854 + +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/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 0bd2a9ab2223..8dd3606404be 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -3923,6 +3923,43 @@ Occurrences: [0:8..0:15): example <- example/ [2:6..2:24): FilenameWithSpaces <- example/FilenameWithSpaces# +expect/i5854.scala +------------------ + +Summary: +Schema => SemanticDB v4 +Uri => i5854.scala +Text => empty +Language => Scala +Symbols => 6 entries +Occurrences => 16 entries + +Symbols: +i5854/B# => class B extends Object { self: B => +4 decls } +i5854/B#``(). => primary ctor (): B +i5854/B#a. => val method a String +i5854/B#b. => val method b Object { type A >: Any <: Nothing } +i5854/B#loop(). => method loop (): Nothing +local0 => type A >: Any <: Nothing + +Occurrences: +[0:8..0:13): i5854 <- i5854/ +[2:6..2:7): B <- i5854/B# +[3:6..3:7): a <- i5854/B#a. +[3:9..3:15): String -> scala/Predef.String# +[3:24..3:27): Any -> scala/Any# +[3:30..3:31): b -> i5854/B#b. +[3:36..3:43): Nothing -> scala/Nothing# +[3:46..3:52): String -> scala/Predef.String# +[4:6..4:7): b <- i5854/B#b. +[4:16..4:17): A <- local0 +[4:21..4:24): Any -> scala/Any# +[4:28..4:35): Nothing -> scala/Nothing# +[4:40..4:44): loop -> i5854/B#loop(). +[5:6..5:10): loop <- i5854/B#loop(). +[5:14..5:21): Nothing -> scala/Nothing# +[5:24..5:28): loop -> i5854/B#loop(). + expect/i9727.scala ------------------ From fbdcf5e17099ee257f5fce3e41ce48ab8819ee50 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 4 Jul 2022 16:13:22 +0200 Subject: [PATCH 6/8] Add test for type projections and fix two other tests --- tests/init/neg/i11572.scala | 2 +- tests/neg/i15569.scala | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/init/neg/i11572.scala b/tests/init/neg/i11572.scala index 3e4e700af73c..59fa3c1fbc43 100644 --- a/tests/init/neg/i11572.scala +++ b/tests/init/neg/i11572.scala @@ -3,7 +3,7 @@ class A { def get: X } trait Bounded { - type T >: Cov[Int] <: Cov[String] // error + type T >: Cov[Int] <: Cov[String] } val t: Bounded = new Bounded { // error // Note: using this instead of t produces an error (as expected) diff --git a/tests/neg/i15569.scala b/tests/neg/i15569.scala index 42451fad13f6..b8e744aa7a81 100644 --- a/tests/neg/i15569.scala +++ b/tests/neg/i15569.scala @@ -37,3 +37,9 @@ def andThenSub[A, B, C](f: A <:< B, g: B <:< C): A <:< C = type t[X] >: Any <: Nothing // error val unsound: List[Nothing] = List(5 : Any) : List[t[String]] (unsound.head : Unit => Unit).apply(()) + +@main def Test7 = + trait A: + type t >: Any <: Nothing + val unsound: List[Nothing] = List(5 : Any) : List[A#t] // error + (unsound.head : Unit => Unit).apply(()) From 116a48b141f93df8d8a1db001c316b0729427308 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 9 Jul 2022 15:28:55 +0200 Subject: [PATCH 7/8] Update compiler/src/dotty/tools/dotc/typer/Checking.scala --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index d33fd63af5a0..2171945cc172 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -91,7 +91,7 @@ object Checking { report.error(em"$sym has possibly conflicting bounds $bad", sym.srcPos) !bad.exists - /** If `tp` dealiases to a typebounds L..H where not L <:< R + /** If `tp` dealiases to a typebounds L..H where not L <:< H * return the potentially conflicting bounds, otherwise return NoType. */ private def findBadBounds(tp: Type)(using Context): Type = tp.dealias match From 7c13eeee1da4917f580dbee4292701ebdef3c3e4 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 9 Jul 2022 20:43:56 +0200 Subject: [PATCH 8/8] Re-instantiate accidentally dropped comment --- tests/semanticdb/expect/i5854.expect.scala | 4 ++++ tests/semanticdb/expect/i5854.scala | 4 ++++ tests/semanticdb/metac.expect | 28 +++++++++++----------- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/tests/semanticdb/expect/i5854.expect.scala b/tests/semanticdb/expect/i5854.expect.scala index 08fb2bff2295..511eee6eae07 100644 --- a/tests/semanticdb/expect/i5854.expect.scala +++ b/tests/semanticdb/expect/i5854.expect.scala @@ -1,6 +1,10 @@ package i5854 class B/*<-i5854::B#*/ { + // Known issue: Can't lookup the symbol of `b.A` + // we have to register the symbol of `b { type A }` to the refinementSymtab first + // then resolve, or assign same semanticdb symbol for both + // fake symbol for b.A, and real symbol of A in b val a/*<-i5854::B#a.*/: String/*->scala::Predef.String#*/ = (((1: Any/*->scala::Any#*/): b/*->i5854::B#b.*/.A): Nothing/*->scala::Nothing#*/): String/*->scala::Predef.String#*/ val b/*<-i5854::B#b.*/: { type A/*<-local0*/ >: Any/*->scala::Any#*/ <: Nothing/*->scala::Nothing#*/ } = loop/*->i5854::B#loop().*/() // error def loop/*<-i5854::B#loop().*/(): Nothing/*->scala::Nothing#*/ = loop/*->i5854::B#loop().*/() diff --git a/tests/semanticdb/expect/i5854.scala b/tests/semanticdb/expect/i5854.scala index 63a79bf4bcce..fe7f5dbba9f1 100644 --- a/tests/semanticdb/expect/i5854.scala +++ b/tests/semanticdb/expect/i5854.scala @@ -1,6 +1,10 @@ package i5854 class B { + // Known issue: Can't lookup the symbol of `b.A` + // we have to register the symbol of `b { type A }` to the refinementSymtab first + // then resolve, or assign same semanticdb symbol for both + // fake symbol for b.A, and real symbol of A in 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/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 8dd3606404be..6504c58b676f 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -3945,20 +3945,20 @@ local0 => type A >: Any <: Nothing Occurrences: [0:8..0:13): i5854 <- i5854/ [2:6..2:7): B <- i5854/B# -[3:6..3:7): a <- i5854/B#a. -[3:9..3:15): String -> scala/Predef.String# -[3:24..3:27): Any -> scala/Any# -[3:30..3:31): b -> i5854/B#b. -[3:36..3:43): Nothing -> scala/Nothing# -[3:46..3:52): String -> scala/Predef.String# -[4:6..4:7): b <- i5854/B#b. -[4:16..4:17): A <- local0 -[4:21..4:24): Any -> scala/Any# -[4:28..4:35): Nothing -> scala/Nothing# -[4:40..4:44): loop -> i5854/B#loop(). -[5:6..5:10): loop <- i5854/B#loop(). -[5:14..5:21): Nothing -> scala/Nothing# -[5:24..5:28): loop -> i5854/B#loop(). +[7:6..7:7): a <- i5854/B#a. +[7:9..7:15): String -> scala/Predef.String# +[7:24..7:27): Any -> scala/Any# +[7:30..7:31): b -> i5854/B#b. +[7:36..7:43): Nothing -> scala/Nothing# +[7:46..7:52): String -> scala/Predef.String# +[8:6..8:7): b <- i5854/B#b. +[8:16..8:17): A <- local0 +[8:21..8:24): Any -> scala/Any# +[8:28..8:35): Nothing -> scala/Nothing# +[8:40..8:44): loop -> i5854/B#loop(). +[9:6..9:10): loop <- i5854/B#loop(). +[9:14..9:21): Nothing -> scala/Nothing# +[9:24..9:28): loop -> i5854/B#loop(). expect/i9727.scala ------------------