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 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index a424ddf6927e..0a01e4ef4911 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1446,9 +1446,18 @@ import transform.SymUtils._ } class DoesNotConformToBound(tpe: Type, which: String, bound: Type)(using Context) - extends TypeMismatchMsg(tpe, bound)(DoesNotConformToBoundID) { - def msg = em"Type argument ${tpe} does not conform to $which bound $bound" - } + extends TypeMismatchMsg( + if which == "lower" then bound else tpe, + 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 2b72b6c8bdbd..8b7ac94675a1 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" @@ -345,12 +346,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]) @@ -403,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) && !sym.owner.isOneOf(AbstractOrTrait) 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 45302f476d48..2171945cc172 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) = 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) + 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 <:< H + * 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/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/init/neg/i11572.scala b/tests/init/neg/i11572.scala index c2ebe6fe47f9..59fa3c1fbc43 100644 --- a/tests/init/neg/i11572.scala +++ b/tests/init/neg/i11572.scala @@ -5,7 +5,7 @@ class A { trait Bounded { type T >: Cov[Int] <: Cov[String] } - val t: Bounded = new Bounded { // error + 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/init/neg/i5854.scala b/tests/init/neg/i5854.scala index a52685f63be2..05fa1033dd2c 100644 --- a/tests/init/neg/i5854.scala +++ b/tests/init/neg/i5854.scala @@ -1,5 +1,5 @@ class B { val a: String = (((1: Any): b.A): Nothing): String - val b: { type A >: Any <: Nothing } = loop() // error + 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-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 diff --git a/tests/neg/i15568.check b/tests/neg/i15568.check index 3a988433d074..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 argument has potentially unrealizable 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 f98345a61691..b8e744aa7a81 100644 --- a/tests/neg/i15569.scala +++ b/tests/neg/i15569.scala @@ -4,12 +4,42 @@ 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]] => // 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 // error + val unsound: Nothing = (5 : Any) : t + (unsound : Unit => Unit).apply(()) + +@main def Test5 = + 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(()) + +@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(()) diff --git a/tests/pos/i13820.scala b/tests/pos/i13820.scala index 1accdee53fb1..2dfe6e1e73e7 100644 --- a/tests/pos/i13820.scala +++ b/tests/pos/i13820.scala @@ -2,4 +2,4 @@ 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 \ No newline at end of file + val i: Int = ??? : e1.T diff --git a/tests/semanticdb/expect/i5854.expect.scala b/tests/semanticdb/expect/i5854.expect.scala index c1878c26706b..511eee6eae07 100644 --- a/tests/semanticdb/expect/i5854.expect.scala +++ b/tests/semanticdb/expect/i5854.expect.scala @@ -6,6 +6,6 @@ class B/*<-i5854::B#*/ { // 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 index 1a9fdf6c39d0..fe7f5dbba9f1 100644 --- a/tests/semanticdb/expect/i5854.scala +++ b/tests/semanticdb/expect/i5854.scala @@ -6,6 +6,6 @@ class B { // 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 + val b: { type A >: Any <: Nothing } = loop() // error def loop(): Nothing = loop() }