From d9ef9740dc2c012c5aad64748df5519170e3a1fb Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 12 Jun 2022 10:27:18 +0200 Subject: [PATCH 1/4] Refine Matchtype checking Take up #13780 again, but refine it so that abstract types are allowed in match type reduction as long as they uniquely instantiate type parameters of the type pattern. Fixes #11982 --- .../tools/dotc/core/MatchTypeTrace.scala | 9 ++ .../dotty/tools/dotc/core/TypeComparer.scala | 97 +++++++++++++------ .../dotty/tools/dotc/CompilationTests.scala | 1 + tests/neg/6570-1.check | 30 ++++++ tests/neg/6570-1.scala | 2 +- tests/neg/6570.scala | 6 +- tests/neg/i11982.check | 39 ++++++++ tests/neg/i11982.scala | 27 ++++++ tests/neg/i13780.check | 34 +++++++ tests/neg/i13780.scala | 30 ++++++ tests/neg/wildcard-match.scala | 46 +++++++++ .../typeclass-derivation1.scala | 0 12 files changed, 288 insertions(+), 33 deletions(-) create mode 100644 tests/neg/6570-1.check create mode 100644 tests/neg/i11982.check create mode 100644 tests/neg/i11982.scala create mode 100644 tests/neg/i13780.check create mode 100644 tests/neg/i13780.scala create mode 100644 tests/neg/wildcard-match.scala rename tests/{run => run-custom-args}/typeclass-derivation1.scala (100%) diff --git a/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala b/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala index bc08dbc36eea..7e1326d39a52 100644 --- a/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala +++ b/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala @@ -4,6 +4,7 @@ package core import Types._, Contexts._, Symbols._, Decorators._ import util.Property +import Names.Name /** A utility module to produce match type reduction traces in error messages. */ @@ -13,6 +14,7 @@ object MatchTypeTrace: case TryReduce(scrut: Type) case NoMatches(scrut: Type, cases: List[Type]) case Stuck(scrut: Type, stuckCase: Type, otherCases: List[Type]) + case NoInstance(scrut: Type, stuckCase: Type, pname: Name, bounds: TypeBounds) case EmptyScrutinee(scrut: Type) import TraceEntry._ @@ -62,6 +64,9 @@ object MatchTypeTrace: def stuck(scrut: Type, stuckCase: Type, otherCases: List[Type])(using Context) = matchTypeFail(Stuck(scrut, stuckCase, otherCases)) + def noInstance(scrut: Type, stuckCase: Type, pname: Name, bounds: TypeBounds)(using Context) = + matchTypeFail(NoInstance(scrut, stuckCase, pname, bounds)) + /** Record a failure that scrutinee `scrut` is provably empty. * Only the first failure is recorded. */ @@ -114,6 +119,10 @@ object MatchTypeTrace: | Therefore, reduction cannot advance to the remaining case$s | | ${casesText(otherCases)}""" + case NoInstance(scrut, stuckCase, pname, bounds) => + i""" failed since selector $scrut + | does not uniquely determine parameter $pname in ${caseText(stuckCase)} + | The computed bounds for the parameter are: $bounds.""" def noMatchesText(scrut: Type, cases: List[Type])(using Context): String = i"""failed since selector $scrut diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 134a5006b887..d7ddf0d761c4 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -60,6 +60,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling /** Indicates whether the subtype check used GADT bounds */ private var GADTused: Boolean = false + protected var canWidenAbstract: Boolean = true + private var myInstance: TypeComparer = this def currentInstance: TypeComparer = myInstance @@ -757,9 +759,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def tryBaseType(cls2: Symbol) = { val base = nonExprBaseType(tp1, cls2) - if (base.exists && (base `ne` tp1)) - isSubType(base, tp2, if (tp1.isRef(cls2)) approx else approx.addLow) || - base.isInstanceOf[OrType] && fourthTry + if base.exists && (base ne tp1) + && (!caseLambda.exists || canWidenAbstract || tp1.widen.underlyingClassRef(refinementOK = true).exists) + then + isSubType(base, tp2, if (tp1.isRef(cls2)) approx else approx.addLow) + || base.isInstanceOf[OrType] && fourthTry // if base is a disjunction, this might have come from a tp1 type that // expands to a match type. In this case, we should try to reduce the type // and compare the redux. This is done in fourthTry @@ -776,7 +780,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling || narrowGADTBounds(tp1, tp2, approx, isUpper = true)) && (tp2.isAny || GADTusage(tp1.symbol)) - isSubType(hi1, tp2, approx.addLow) || compareGADT || tryLiftedToThis1 + (!caseLambda.exists || canWidenAbstract) && isSubType(hi1, tp2, approx.addLow) + || compareGADT + || tryLiftedToThis1 case _ => // `Mode.RelaxedOverriding` is only enabled when checking Java overriding // in explicit nulls, and `Null` becomes a bottom type, which allows @@ -2845,7 +2851,16 @@ object TypeComparer { comparing(_.tracked(op)) } +object TrackingTypeComparer: + enum MatchResult: + case Reduced(tp: Type) + case Disjoint + case Stuck + case NoInstance(param: Name, bounds: TypeBounds) + class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) { + import TrackingTypeComparer.* + init(initctx) override def trackingTypeComparer = this @@ -2883,15 +2898,25 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) { } def matchCases(scrut: Type, cases: List[Type])(using Context): Type = { - def paramInstances = new TypeAccumulator[Array[Type]] { - def apply(inst: Array[Type], t: Type) = t match { - case t @ TypeParamRef(b, n) if b `eq` caseLambda => - inst(n) = approximation(t, fromBelow = variance >= 0).simplified - inst + + def paramInstances(canApprox: Boolean) = new TypeAccumulator[Array[Type]]: + def apply(insts: Array[Type], t: Type) = t match + case param @ TypeParamRef(b, n) if b eq caseLambda => + insts(n) = { + if canApprox then + approximation(param, fromBelow = variance >= 0) + else constraint.entry(param) match + case entry: TypeBounds => + val lo = fullLowerBound(param) + val hi = fullUpperBound(param) + if isSubType(hi, lo) then lo else TypeBounds(lo, hi) + case inst => + assert(inst.exists, i"param = $param\nconstraint = $constraint") + inst + }.simplified + insts case _ => - foldOver(inst, t) - } - } + foldOver(insts, t) def instantiateParams(inst: Array[Type]) = new TypeMap { def apply(t: Type) = t match { @@ -2907,7 +2932,7 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) { * None if the match fails and we should consider the following cases * because scrutinee and pattern do not overlap */ - def matchCase(cas: Type): Option[Type] = trace(i"match case $cas vs $scrut", matchTypes) { + def matchCase(cas: Type): MatchResult = trace(i"match case $cas vs $scrut", matchTypes) { val cas1 = cas match { case cas: HKTypeLambda => caseLambda = constrained(cas) @@ -2918,34 +2943,48 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) { val defn.MatchCase(pat, body) = cas1: @unchecked - if (isSubType(scrut, pat)) - // `scrut` is a subtype of `pat`: *It's a Match!* - Some { - caseLambda match { - case caseLambda: HKTypeLambda => - val instances = paramInstances(new Array(caseLambda.paramNames.length), pat) - instantiateParams(instances)(body).simplified - case _ => - body - } - } + def matches(canWidenAbstract: Boolean): Boolean = + val saved = this.canWidenAbstract + this.canWidenAbstract = canWidenAbstract + try necessarySubType(scrut, pat) + finally this.canWidenAbstract = saved + + def redux(canApprox: Boolean): MatchResult = + caseLambda match + case caseLambda: HKTypeLambda => + val instances = paramInstances(canApprox)(new Array(caseLambda.paramNames.length), pat) + instances.indices.find(instances(_).isInstanceOf[TypeBounds]) match + case Some(i) if !canApprox => + MatchResult.NoInstance(caseLambda.paramNames(i), instances(i).bounds) + case _ => + MatchResult.Reduced(instantiateParams(instances)(body).simplified) + case _ => + MatchResult.Reduced(body) + + if caseLambda.exists && matches(canWidenAbstract = false) then + redux(canApprox = true) + else if matches(canWidenAbstract = true) then + redux(canApprox = false) else if (provablyDisjoint(scrut, pat)) // We found a proof that `scrut` and `pat` are incompatible. // The search continues. - None + MatchResult.Disjoint else - Some(NoType) + MatchResult.Stuck } def recur(remaining: List[Type]): Type = remaining match case cas :: remaining1 => matchCase(cas) match - case None => + case MatchResult.Disjoint => recur(remaining1) - case Some(NoType) => + case MatchResult.Stuck => MatchTypeTrace.stuck(scrut, cas, remaining1) NoType - case Some(tp) => + case MatchResult.NoInstance(pname, bounds) => + MatchTypeTrace.noInstance(scrut, cas, pname, bounds) + NoType + case MatchResult.Reduced(tp) => tp case Nil => val casesText = MatchTypeTrace.noMatchesText(scrut, cases) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 6ad524e07930..e64a06e684a4 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -200,6 +200,7 @@ class CompilationTests { @Test def runAll: Unit = { implicit val testGroup: TestGroup = TestGroup("runAll") aggregateTests( + compileFile("tests/run-custom-args/typeclass-derivation1.scala", defaultOptions.without(yCheckOptions*)), compileFile("tests/run-custom-args/tuple-cons.scala", allowDeepSubtypes), compileFile("tests/run-custom-args/i5256.scala", allowDeepSubtypes), compileFile("tests/run-custom-args/no-useless-forwarders.scala", defaultOptions and "-Xmixin-force-forwarders:false"), diff --git a/tests/neg/6570-1.check b/tests/neg/6570-1.check new file mode 100644 index 000000000000..023d34a4a27d --- /dev/null +++ b/tests/neg/6570-1.check @@ -0,0 +1,30 @@ +-- [E007] Type Mismatch Error: tests/neg/6570-1.scala:23:27 ------------------------------------------------------------ +23 | def thing = new Trait1 {} // error + | ^ + | Found: Object with Trait1 {...} + | Required: N[Box[Int & String]] + | + | Note: a match type could not be fully reduced: + | + | trying to reduce N[Box[Int & String]] + | failed since selector Box[Int & String] + | is uninhabited (there are no values of that type). + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/6570-1.scala:36:54 ------------------------------------------------------------ +36 | def foo[T <: Cov[Box[Int]]](c: Root[T]): Trait2 = c.thing // error + | ^^^^^^^ + | Found: M[T] + | Required: Trait2 + | + | where: T is a type in method foo with bounds <: Cov[Box[Int]] + | + | + | Note: a match type could not be fully reduced: + | + | trying to reduce M[T] + | failed since selector T + | does not uniquely determine parameter x in case Cov[x] => N[x] + | The computed bounds for the parameter are: >: Box[Int]. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/6570-1.scala b/tests/neg/6570-1.scala index b24d09782039..0903d188cb16 100644 --- a/tests/neg/6570-1.scala +++ b/tests/neg/6570-1.scala @@ -33,7 +33,7 @@ class Asploder extends Root[Cov[Box[Int & String]]] { } object Main { - def foo[T <: Cov[Box[Int]]](c: Root[T]): Trait2 = c.thing + def foo[T <: Cov[Box[Int]]](c: Root[T]): Trait2 = c.thing // error def explode = foo(new Asploder) def main(args: Array[String]): Unit = diff --git a/tests/neg/6570.scala b/tests/neg/6570.scala index d0b55fe739de..f36471868d9b 100644 --- a/tests/neg/6570.scala +++ b/tests/neg/6570.scala @@ -21,7 +21,7 @@ object UpperBoundParametricVariant { trait Child[A <: Cov[Int]] extends Root[A] // we reduce `M[T]` to `Trait2`, even though we cannot be certain of that - def foo[T <: Cov[Int]](c: Child[T]): Trait2 = c.thing + def foo[T <: Cov[Int]](c: Child[T]): Trait2 = c.thing // error class Asploder extends Child[Cov[String & Int]] { def thing = new Trait1 {} // error @@ -42,7 +42,7 @@ object InheritanceVariant { trait Child extends Root { type B <: { type A <: Int } } - def foo(c: Child): Trait2 = c.thing + def foo(c: Child): Trait2 = c.thing // error class Asploder extends Child { type B = { type A = String & Int } @@ -98,7 +98,7 @@ object UpperBoundVariant { trait Child extends Root { type A <: Cov[Int] } - def foo(c: Child): Trait2 = c.thing + def foo(c: Child): Trait2 = c.thing // error class Asploder extends Child { type A = Cov[String & Int] diff --git a/tests/neg/i11982.check b/tests/neg/i11982.check new file mode 100644 index 000000000000..b87c9376e91a --- /dev/null +++ b/tests/neg/i11982.check @@ -0,0 +1,39 @@ +-- [E057] Type Mismatch Error: tests/neg/i11982.scala:9:34 ------------------------------------------------------------- +9 | b: ValueOf[Tuple.Head[Tuple.Tail[X]]] // error + | ^ + | Type argument Tuple.Tail[X] does not conform to upper bound NonEmptyTuple + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Tuple.Tail[X] + | failed since selector X + | does not uniquely determine parameter xs in case _ *: xs => xs + | The computed bounds for the parameter are: >: Any *: EmptyTuple.type <: Tuple. + | + | longer explanation available when compiling with `-explain` +-- [E057] Type Mismatch Error: tests/neg/i11982.scala:10:38 ------------------------------------------------------------ +10 | ): Tuple2[Tuple.Head[X], Tuple.Head[Tuple.Tail[X]]] = // error + | ^ + | Type argument Tuple.Tail[X] does not conform to upper bound NonEmptyTuple + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Tuple.Tail[X] + | failed since selector X + | does not uniquely determine parameter xs in case _ *: xs => xs + | The computed bounds for the parameter are: >: Any *: EmptyTuple.type <: Tuple. + | + | longer explanation available when compiling with `-explain` +-- [E057] Type Mismatch Error: tests/neg/i11982.scala:12:25 ------------------------------------------------------------ +12 | type BB = Tuple.Head[Tuple.Tail[X]] // error + | ^ + | Type argument Tuple.Tail[X] does not conform to upper bound NonEmptyTuple + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Tuple.Tail[X] + | failed since selector X + | does not uniquely determine parameter xs in case _ *: xs => xs + | The computed bounds for the parameter are: >: Any *: EmptyTuple.type <: Tuple. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i11982.scala b/tests/neg/i11982.scala new file mode 100644 index 000000000000..7a01f84bc157 --- /dev/null +++ b/tests/neg/i11982.scala @@ -0,0 +1,27 @@ +package tuplefun +object Unpair { + + def pair[A, B](using a: ValueOf[A], b: ValueOf[B]): Tuple2[A, B] = + (a.value, b.value) + + def unpair[X <: Tuple2[?, ?]]( + using a: ValueOf[Tuple.Head[X]], + b: ValueOf[Tuple.Head[Tuple.Tail[X]]] // error + ): Tuple2[Tuple.Head[X], Tuple.Head[Tuple.Tail[X]]] = // error + type AA = Tuple.Head[X] + type BB = Tuple.Head[Tuple.Tail[X]] // error + pair[AA, BB](using a, b) +} + +object UnpairApp { + import Unpair._ + + type Tshape = ("msg", 42) + + // the following won't compile when in the same file as Unpair + val p1: ("msg", 42) = unpair[Tshape] + + @main def pairHello: Unit = + assert(p1 == ("msg", 42)) + println(p1) +} \ No newline at end of file diff --git a/tests/neg/i13780.check b/tests/neg/i13780.check new file mode 100644 index 000000000000..fdb8f0ee6c90 --- /dev/null +++ b/tests/neg/i13780.check @@ -0,0 +1,34 @@ +-- [E007] Type Mismatch Error: tests/neg/i13780.scala:18:31 ------------------------------------------------------------ +18 | def int[X <: Y]: Int = unpair[X] // error + | ^^^^^^^^^ + | Found: Head[X] + | Required: Int + | + | where: X is a type in method int with bounds <: B.this.Y + | + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Head[X] + | failed since selector X + | does not uniquely determine parameter a in case (a, b) => a + | The computed bounds for the parameter are: >: Int. + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/i13780.scala:23:37 ------------------------------------------------------------ +23 | def string[X <: Y]: String = unpair[X] // error + | ^^^^^^^^^ + | Found: Head[X] + | Required: String + | + | where: X is a type in method string with bounds <: C.this.Y + | + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Head[X] + | failed since selector X + | does not uniquely determine parameter a in case (a, b) => a + | The computed bounds for the parameter are: >: String. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i13780.scala b/tests/neg/i13780.scala new file mode 100644 index 000000000000..7e7e2e3ecb74 --- /dev/null +++ b/tests/neg/i13780.scala @@ -0,0 +1,30 @@ +type Head[X] = X match { + case Tuple2[a, b] => a +} + +trait Z { + type Y + def unpair[X <: Y]: Head[X] +} + +class A extends Z { + type Y <: Tuple2[Any, Any] + def unpair[X <: Y]: Head[X] = "" + def any[X <: Y]: Any = unpair[X] +} + +class B extends A { this: Z => + type Y = Tuple2[Int, Int] + def int[X <: Y]: Int = unpair[X] // error +} + +class C extends A { this: Z => + type Y = Tuple2[String, String] + def string[X <: Y]: String = unpair[X] // error +} + +object Main { + def main(args: Array[String]): Unit = { + println((new B).int + 1) // would give ClassCastException + } +} \ No newline at end of file diff --git a/tests/neg/wildcard-match.scala b/tests/neg/wildcard-match.scala new file mode 100644 index 000000000000..326a97485bd2 --- /dev/null +++ b/tests/neg/wildcard-match.scala @@ -0,0 +1,46 @@ +class Box[T](x: T) + +class Cov[+T](x: T) +class Contrav[-T](x: T) + +type BoxElem[X] = X match + case Box[a] => a + +type BoxToList[X] = X match + case Box[a] => List[a] + +type CovElem[X] = X match + case Cov[a] => a + +type CovToList[X] = X match + case Cov[a] => List[a] + +type ContravElem[X] = X match + case Contrav[a] => a + +type ContravToList[X] = X match + case Contrav[a] => List[a] + +class C + +def f[X <: Box[C], Y <: Cov[C], Z <: Contrav[C]] = + def a: BoxElem[X] = ??? // OK + val _: C = a + + def a1: CovElem[Y] = ??? + val _: C = a1 // error + + def a2: ContravElem[Z] = ??? + val _: C = a2 // error + + def b: BoxToList[X] = ??? // OK + val _: List[C] = b + + def b1: CovToList[Y] = ??? + val _: List[C] = b1 // error + + def b2: ContravElem[Z] = ??? + val _: List[C] = b2 // error + + + diff --git a/tests/run/typeclass-derivation1.scala b/tests/run-custom-args/typeclass-derivation1.scala similarity index 100% rename from tests/run/typeclass-derivation1.scala rename to tests/run-custom-args/typeclass-derivation1.scala From 2df96f9006c60abd3f1c71fd31fdd84b150808d8 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 12 Jun 2022 15:30:56 +0200 Subject: [PATCH 2/4] Allow uninstantiated match parameters in some cases Allow uninstantiated match parameters if the result of the match does not depend on them. --- .../tools/dotc/core/MatchTypeTrace.scala | 17 ++++--- .../dotty/tools/dotc/core/TypeComparer.scala | 34 +++++++------- .../src/dotty/tools/dotc/core/Types.scala | 2 +- tests/neg/6570-1.check | 18 ++++---- tests/neg/i11982.check | 41 ++--------------- tests/neg/i11982.scala | 8 ++-- tests/neg/i11982a.check | 45 +++++++++++++++++++ tests/neg/i11982a.scala | 14 ++++++ tests/neg/i13780.check | 14 ++++-- 9 files changed, 116 insertions(+), 77 deletions(-) create mode 100644 tests/neg/i11982a.check create mode 100644 tests/neg/i11982a.scala diff --git a/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala b/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala index 7e1326d39a52..062ddd5e846c 100644 --- a/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala +++ b/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala @@ -14,7 +14,7 @@ object MatchTypeTrace: case TryReduce(scrut: Type) case NoMatches(scrut: Type, cases: List[Type]) case Stuck(scrut: Type, stuckCase: Type, otherCases: List[Type]) - case NoInstance(scrut: Type, stuckCase: Type, pname: Name, bounds: TypeBounds) + case NoInstance(scrut: Type, stuckCase: Type, fails: List[(Name, TypeBounds)]) case EmptyScrutinee(scrut: Type) import TraceEntry._ @@ -64,8 +64,8 @@ object MatchTypeTrace: def stuck(scrut: Type, stuckCase: Type, otherCases: List[Type])(using Context) = matchTypeFail(Stuck(scrut, stuckCase, otherCases)) - def noInstance(scrut: Type, stuckCase: Type, pname: Name, bounds: TypeBounds)(using Context) = - matchTypeFail(NoInstance(scrut, stuckCase, pname, bounds)) + def noInstance(scrut: Type, stuckCase: Type, fails: List[(Name, TypeBounds)])(using Context) = + matchTypeFail(NoInstance(scrut, stuckCase, fails)) /** Record a failure that scrutinee `scrut` is provably empty. * Only the first failure is recorded. @@ -87,7 +87,7 @@ object MatchTypeTrace: case _ => op - private def caseText(tp: Type)(using Context): String = tp match + def caseText(tp: Type)(using Context): String = tp match case tp: HKTypeLambda => caseText(tp.resultType) case defn.MatchCase(any, body) if any eq defn.AnyType => i"case _ => $body" case defn.MatchCase(pat, body) => i"case $pat => $body" @@ -119,10 +119,13 @@ object MatchTypeTrace: | Therefore, reduction cannot advance to the remaining case$s | | ${casesText(otherCases)}""" - case NoInstance(scrut, stuckCase, pname, bounds) => + case NoInstance(scrut, stuckCase, fails) => + def params = if fails.length == 1 then "parameter" else "parameters" i""" failed since selector $scrut - | does not uniquely determine parameter $pname in ${caseText(stuckCase)} - | The computed bounds for the parameter are: $bounds.""" + | does not uniquely determine $params ${fails.map(_._1)}%, % in + | ${caseText(stuckCase)} + | The computed bounds for the $params are: + | ${fails.map((name, bounds) => i"$name$bounds")}%\n %""" def noMatchesText(scrut: Type, cases: List[Type])(using Context): String = i"""failed since selector $scrut diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index d7ddf0d761c4..e86c17d5a96c 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2856,7 +2856,7 @@ object TrackingTypeComparer: case Reduced(tp: Type) case Disjoint case Stuck - case NoInstance(param: Name, bounds: TypeBounds) + case NoInstance(fails: List[(Name, TypeBounds)]) class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) { import TrackingTypeComparer.* @@ -2902,25 +2902,25 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) { def paramInstances(canApprox: Boolean) = new TypeAccumulator[Array[Type]]: def apply(insts: Array[Type], t: Type) = t match case param @ TypeParamRef(b, n) if b eq caseLambda => - insts(n) = { + insts(n) = if canApprox then - approximation(param, fromBelow = variance >= 0) + approximation(param, fromBelow = variance >= 0).simplified else constraint.entry(param) match case entry: TypeBounds => val lo = fullLowerBound(param) val hi = fullUpperBound(param) - if isSubType(hi, lo) then lo else TypeBounds(lo, hi) + if isSubType(hi, lo) then lo.simplified else Range(lo, hi) case inst => assert(inst.exists, i"param = $param\nconstraint = $constraint") - inst - }.simplified + inst.simplified insts case _ => foldOver(insts, t) - def instantiateParams(inst: Array[Type]) = new TypeMap { + def instantiateParams(insts: Array[Type]) = new ApproximatingTypeMap { + variance = 0 def apply(t: Type) = t match { - case t @ TypeParamRef(b, n) if b `eq` caseLambda => inst(n) + case t @ TypeParamRef(b, n) if b `eq` caseLambda => insts(n) case t: LazyRef => apply(t.ref) case _ => mapOver(t) } @@ -2953,11 +2953,15 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) { caseLambda match case caseLambda: HKTypeLambda => val instances = paramInstances(canApprox)(new Array(caseLambda.paramNames.length), pat) - instances.indices.find(instances(_).isInstanceOf[TypeBounds]) match - case Some(i) if !canApprox => - MatchResult.NoInstance(caseLambda.paramNames(i), instances(i).bounds) - case _ => - MatchResult.Reduced(instantiateParams(instances)(body).simplified) + instantiateParams(instances)(body) match + case Range(lo, hi) => + MatchResult.NoInstance { + caseLambda.paramNames.zip(instances).collect { + case (name, Range(lo, hi)) => (name, TypeBounds(lo, hi)) + } + } + case redux => + MatchResult.Reduced(redux.simplified) case _ => MatchResult.Reduced(body) @@ -2981,8 +2985,8 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) { case MatchResult.Stuck => MatchTypeTrace.stuck(scrut, cas, remaining1) NoType - case MatchResult.NoInstance(pname, bounds) => - MatchTypeTrace.noInstance(scrut, cas, pname, bounds) + case MatchResult.NoInstance(fails) => + MatchTypeTrace.noInstance(scrut, cas, fails) NoType case MatchResult.Reduced(tp) => tp diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8b9f8690604e..5ecf43921f20 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5989,7 +5989,7 @@ object Types { /** A range of possible types between lower bound `lo` and upper bound `hi`. * Only used internally in `ApproximatingTypeMap`. */ - private case class Range(lo: Type, hi: Type) extends UncachedGroundType { + case class Range(lo: Type, hi: Type) extends UncachedGroundType { assert(!lo.isInstanceOf[Range]) assert(!hi.isInstanceOf[Range]) diff --git a/tests/neg/6570-1.check b/tests/neg/6570-1.check index 023d34a4a27d..fa53e71cbb6b 100644 --- a/tests/neg/6570-1.check +++ b/tests/neg/6570-1.check @@ -14,17 +14,19 @@ -- [E007] Type Mismatch Error: tests/neg/6570-1.scala:36:54 ------------------------------------------------------------ 36 | def foo[T <: Cov[Box[Int]]](c: Root[T]): Trait2 = c.thing // error | ^^^^^^^ - | Found: M[T] - | Required: Trait2 + | Found: M[T] + | Required: Trait2 | - | where: T is a type in method foo with bounds <: Cov[Box[Int]] + | where: T is a type in method foo with bounds <: Cov[Box[Int]] | | - | Note: a match type could not be fully reduced: + | Note: a match type could not be fully reduced: | - | trying to reduce M[T] - | failed since selector T - | does not uniquely determine parameter x in case Cov[x] => N[x] - | The computed bounds for the parameter are: >: Box[Int]. + | trying to reduce M[T] + | failed since selector T + | does not uniquely determine parameter x in + | case Cov[x] => N[x] + | The computed bounds for the parameter are: + | x >: Box[Int] | | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i11982.check b/tests/neg/i11982.check index b87c9376e91a..d18fc31d68e7 100644 --- a/tests/neg/i11982.check +++ b/tests/neg/i11982.check @@ -1,39 +1,4 @@ --- [E057] Type Mismatch Error: tests/neg/i11982.scala:9:34 ------------------------------------------------------------- -9 | b: ValueOf[Tuple.Head[Tuple.Tail[X]]] // error - | ^ - | Type argument Tuple.Tail[X] does not conform to upper bound NonEmptyTuple - | - | Note: a match type could not be fully reduced: - | - | trying to reduce Tuple.Tail[X] - | failed since selector X - | does not uniquely determine parameter xs in case _ *: xs => xs - | The computed bounds for the parameter are: >: Any *: EmptyTuple.type <: Tuple. - | - | longer explanation available when compiling with `-explain` --- [E057] Type Mismatch Error: tests/neg/i11982.scala:10:38 ------------------------------------------------------------ -10 | ): Tuple2[Tuple.Head[X], Tuple.Head[Tuple.Tail[X]]] = // error +-- Error: tests/neg/i11982.scala:22:38 --------------------------------------------------------------------------------- +22 | val p1: ("msg", 42) = unpair[Tshape] // error: no singleton value for Any | ^ - | Type argument Tuple.Tail[X] does not conform to upper bound NonEmptyTuple - | - | Note: a match type could not be fully reduced: - | - | trying to reduce Tuple.Tail[X] - | failed since selector X - | does not uniquely determine parameter xs in case _ *: xs => xs - | The computed bounds for the parameter are: >: Any *: EmptyTuple.type <: Tuple. - | - | longer explanation available when compiling with `-explain` --- [E057] Type Mismatch Error: tests/neg/i11982.scala:12:25 ------------------------------------------------------------ -12 | type BB = Tuple.Head[Tuple.Tail[X]] // error - | ^ - | Type argument Tuple.Tail[X] does not conform to upper bound NonEmptyTuple - | - | Note: a match type could not be fully reduced: - | - | trying to reduce Tuple.Tail[X] - | failed since selector X - | does not uniquely determine parameter xs in case _ *: xs => xs - | The computed bounds for the parameter are: >: Any *: EmptyTuple.type <: Tuple. - | - | longer explanation available when compiling with `-explain` + | No singleton value available for Any. diff --git a/tests/neg/i11982.scala b/tests/neg/i11982.scala index 7a01f84bc157..e8ef12ef34e0 100644 --- a/tests/neg/i11982.scala +++ b/tests/neg/i11982.scala @@ -6,10 +6,10 @@ object Unpair { def unpair[X <: Tuple2[?, ?]]( using a: ValueOf[Tuple.Head[X]], - b: ValueOf[Tuple.Head[Tuple.Tail[X]]] // error - ): Tuple2[Tuple.Head[X], Tuple.Head[Tuple.Tail[X]]] = // error + b: ValueOf[Tuple.Head[Tuple.Tail[X]]] + ): Tuple2[Tuple.Head[X], Tuple.Head[Tuple.Tail[X]]] = type AA = Tuple.Head[X] - type BB = Tuple.Head[Tuple.Tail[X]] // error + type BB = Tuple.Head[Tuple.Tail[X]] pair[AA, BB](using a, b) } @@ -19,7 +19,7 @@ object UnpairApp { type Tshape = ("msg", 42) // the following won't compile when in the same file as Unpair - val p1: ("msg", 42) = unpair[Tshape] + val p1: ("msg", 42) = unpair[Tshape] // error: no singleton value for Any @main def pairHello: Unit = assert(p1 == ("msg", 42)) diff --git a/tests/neg/i11982a.check b/tests/neg/i11982a.check new file mode 100644 index 000000000000..bc07c82059cc --- /dev/null +++ b/tests/neg/i11982a.check @@ -0,0 +1,45 @@ +-- [E057] Type Mismatch Error: tests/neg/i11982a.scala:9:34 ------------------------------------------------------------ +9 | b: ValueOf[Tuple.Head[Tuple.Tail[X]]] // error + | ^ + | Type argument Tuple.Tail[X] does not conform to upper bound NonEmptyTuple + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Tuple.Tail[X] + | failed since selector X + | does not uniquely determine parameter xs in + | case _ *: xs => xs + | The computed bounds for the parameter are: + | xs >: Any *: EmptyTuple.type <: Tuple + | + | longer explanation available when compiling with `-explain` +-- [E057] Type Mismatch Error: tests/neg/i11982a.scala:10:38 ----------------------------------------------------------- +10 | ): Tuple2[Tuple.Head[X], Tuple.Head[Tuple.Tail[X]]] = // error + | ^ + | Type argument Tuple.Tail[X] does not conform to upper bound NonEmptyTuple + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Tuple.Tail[X] + | failed since selector X + | does not uniquely determine parameter xs in + | case _ *: xs => xs + | The computed bounds for the parameter are: + | xs >: Any *: EmptyTuple.type <: Tuple + | + | longer explanation available when compiling with `-explain` +-- [E057] Type Mismatch Error: tests/neg/i11982a.scala:12:25 ----------------------------------------------------------- +12 | type BB = Tuple.Head[Tuple.Tail[X]] // error + | ^ + | Type argument Tuple.Tail[X] does not conform to upper bound NonEmptyTuple + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Tuple.Tail[X] + | failed since selector X + | does not uniquely determine parameter xs in + | case _ *: xs => xs + | The computed bounds for the parameter are: + | xs >: Any *: EmptyTuple.type <: Tuple + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i11982a.scala b/tests/neg/i11982a.scala new file mode 100644 index 000000000000..53126eaf3986 --- /dev/null +++ b/tests/neg/i11982a.scala @@ -0,0 +1,14 @@ +package tuplefun +object Unpair { + + def pair[A, B](using a: ValueOf[A], b: ValueOf[B]): Tuple2[A, B] = + (a.value, b.value) + + def unpair[X <: Tuple2[?, ?]]( + using a: ValueOf[Tuple.Head[X]], + b: ValueOf[Tuple.Head[Tuple.Tail[X]]] // error + ): Tuple2[Tuple.Head[X], Tuple.Head[Tuple.Tail[X]]] = // error + type AA = Tuple.Head[X] + type BB = Tuple.Head[Tuple.Tail[X]] // error + pair[AA, BB](using a, b) +} \ No newline at end of file diff --git a/tests/neg/i13780.check b/tests/neg/i13780.check index fdb8f0ee6c90..56b6a67ac8e7 100644 --- a/tests/neg/i13780.check +++ b/tests/neg/i13780.check @@ -11,8 +11,11 @@ | | trying to reduce Head[X] | failed since selector X - | does not uniquely determine parameter a in case (a, b) => a - | The computed bounds for the parameter are: >: Int. + | does not uniquely determine parameters a, b in + | case (a, b) => a + | The computed bounds for the parameters are: + | a >: Int + | b >: Int | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg/i13780.scala:23:37 ------------------------------------------------------------ @@ -28,7 +31,10 @@ | | trying to reduce Head[X] | failed since selector X - | does not uniquely determine parameter a in case (a, b) => a - | The computed bounds for the parameter are: >: String. + | does not uniquely determine parameters a, b in + | case (a, b) => a + | The computed bounds for the parameters are: + | a >: String + | b >: String | | longer explanation available when compiling with `-explain` From 6aba5f7d3db2f033c233d7f9a61579c3c5e6e06d Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 12 Jun 2022 17:30:01 +0200 Subject: [PATCH 3/4] Propagate Range through MatchType cases --- compiler/src/dotty/tools/dotc/core/Types.scala | 8 +++++++- tests/neg/i15352.scala | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i15352.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5ecf43921f20..1ea32f87b0a2 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5953,7 +5953,13 @@ object Types { case _ => scrutinee match case Range(lo, hi) => range(bound.bounds.lo, bound.bounds.hi) - case _ => tp.derivedMatchType(bound, scrutinee, cases) + case _ => + if cases.exists(isRange) then + Range( + tp.derivedMatchType(bound, scrutinee, cases.map(lower)), + tp.derivedMatchType(bound, scrutinee, cases.map(upper))) + else + tp.derivedMatchType(bound, scrutinee, cases) override protected def derivedSkolemType(tp: SkolemType, info: Type): Type = if info eq tp.info then tp diff --git a/tests/neg/i15352.scala b/tests/neg/i15352.scala new file mode 100644 index 000000000000..5e888b19417d --- /dev/null +++ b/tests/neg/i15352.scala @@ -0,0 +1,17 @@ +import scala.compiletime.* +import scala.compiletime.ops.int.* + +abstract sealed class HList : + def ::[H](head: H): HNonEmpty[H, this.type] = HNonEmpty(head, this) + +case object HNil extends HList +case class HNonEmpty[H, T <: HList](head: H, tail: T) extends HList : + type Elem[N <: Int] = + N match + case 0 => H + case S[n1] => Elem[n1] + + inline def apply[N <: Int](n: N): Elem[N] = + n match + case _: 0 => head + case _: S[n1] => tail.asInstanceOf[HNonEmpty[?, ?]].apply(constValue[n1]) // error \ No newline at end of file From e9c453941aa521f29c3d25903ba7dcfb5297c038 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 13 Jun 2022 09:46:32 +0200 Subject: [PATCH 4/4] Fix comments --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index e86c17d5a96c..09e81d340fc1 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2926,12 +2926,7 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) { } } - /** Match a single case. - * @return Some(tp) if the match succeeds with type `tp` - * Some(NoType) if the match fails, and there is an overlap between pattern and scrutinee - * None if the match fails and we should consider the following cases - * because scrutinee and pattern do not overlap - */ + /** Match a single case. */ def matchCase(cas: Type): MatchResult = trace(i"match case $cas vs $scrut", matchTypes) { val cas1 = cas match { case cas: HKTypeLambda =>