From 56ac7fdef3c88ad78bc7e070b474f6fd3a801773 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 27 Sep 2023 13:57:16 +0100 Subject: [PATCH 1/2] Fix exhaustivity due to separate TypeVar lambdas [Cherry-picked d5a6d4f35dce17ebe4bcc23c0c204c586f042ef7] --- .../src/dotty/tools/dotc/transform/patmat/Space.scala | 6 +++--- tests/pos/i14224.1.scala | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 tests/pos/i14224.1.scala diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index b6bf8c550a57..0165121c9779 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -485,8 +485,8 @@ object SpaceEngine { erase(parent, inArray, isValue, isTyped) case tref: TypeRef if tref.symbol.isPatternBound => - if inArray then tref.underlying - else if isValue then tref.superType + if inArray then erase(tref.underlying, inArray, isValue, isTyped) + else if isValue then erase(tref.superType, inArray, isValue, isTyped) else WildcardType case _ => tp @@ -540,7 +540,7 @@ object SpaceEngine { val mt: MethodType = unapp.widen match { case mt: MethodType => mt case pt: PolyType => - val tvars = pt.paramInfos.map(newTypeVar(_)) + val tvars = constrained(pt, EmptyTree)._2.tpes val mt = pt.instantiate(tvars).asInstanceOf[MethodType] scrutineeTp <:< mt.paramInfos(0) // force type inference to infer a narrower type: could be singleton diff --git a/tests/pos/i14224.1.scala b/tests/pos/i14224.1.scala new file mode 100644 index 000000000000..c0eaa2eedbcd --- /dev/null +++ b/tests/pos/i14224.1.scala @@ -0,0 +1,11 @@ +//> using options -Werror + +// Derived from the extensive test in the gist in i14224 +// Minimising to the false positive in SealedTrait1.either + +sealed trait Foo[A, A1 <: A] +final case class Bar[A, A1 <: A](value: A1) extends Foo[A, A1] + +class Main: + def test[A, A1 <: A](foo: Foo[A, A1]): A1 = foo match + case Bar(v) => v From fe3c152c53e9de08037e44257255a0fa9a8f2ec6 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 28 Sep 2023 11:30:32 +0100 Subject: [PATCH 2/2] Rework ProtoType's constrained API Remove the single use overload, replace with a much more used alternative Also return TypeVars instead of TypeTrees, so we don't have to unwrap the useless wrapper a bunch of times, and instead we wrap the few times we really do want to. [Cherry-picked 5b57e09d8bdd6f5a7c5f0b23a6f6e450bca3e90b] --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 5 ++-- .../dotty/tools/dotc/core/TypeComparer.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 3 ++ .../dotc/transform/SyntheticMembers.scala | 9 ++---- .../tools/dotc/transform/TypeTestsCasts.scala | 2 +- .../tools/dotc/transform/patmat/Space.scala | 2 +- .../dotty/tools/dotc/typer/Inferencing.scala | 2 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 30 +++++++++---------- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- compiler/test/dotty/tools/SignatureTest.scala | 4 +-- .../tools/dotc/core/ConstraintsTest.scala | 19 +++++------- 11 files changed, 37 insertions(+), 43 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 590747738431..4884888a8959 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -759,7 +759,7 @@ object Trees { /** A type tree that represents an existing or inferred type */ case class TypeTree[+T <: Untyped]()(implicit @constructorOnly src: SourceFile) extends DenotingTree[T] with TypTree[T] { - type ThisTree[+T <: Untyped] = TypeTree[T] + type ThisTree[+T <: Untyped] <: TypeTree[T] override def isEmpty: Boolean = !hasType override def toString: String = s"TypeTree${if (hasType) s"[$typeOpt]" else ""}" @@ -783,7 +783,8 @@ object Trees { * - as a (result-)type of an inferred ValDef or DefDef. * Every TypeVar is created as the type of one InferredTypeTree. */ - class InferredTypeTree[+T <: Untyped](implicit @constructorOnly src: SourceFile) extends TypeTree[T] + class InferredTypeTree[+T <: Untyped](implicit @constructorOnly src: SourceFile) extends TypeTree[T]: + type ThisTree[+T <: Untyped] <: InferredTypeTree[T] /** ref.type */ case class SingletonTypeTree[+T <: Untyped] private[ast] (ref: Tree[T])(implicit @constructorOnly src: SourceFile) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 1e86c5039c7d..2dc938a581a1 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3125,7 +3125,7 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) { def matchCase(cas: Type): MatchResult = trace(i"$scrut match ${MatchTypeTrace.caseText(cas)}", matchTypes, show = true) { val cas1 = cas match { case cas: HKTypeLambda => - caseLambda = constrained(cas) + caseLambda = constrained(cas, ast.tpd.EmptyTree)._1 caseLambda.resultType case _ => cas diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 545bb138969a..c3e15ea86a4b 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4885,6 +4885,9 @@ object Types { if (inst.exists) inst else origin } + def wrapInTypeTree(owningTree: Tree)(using Context): InferredTypeTree = + new InferredTypeTree().withSpan(owningTree.span).withType(this) + override def computeHash(bs: Binders): Int = identityHash(bs) override def equals(that: Any): Boolean = this.eq(that.asInstanceOf[AnyRef]) diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 1560ae6e5618..b42fd93319f5 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -501,12 +501,9 @@ class SyntheticMembers(thisPhase: DenotTransformer) { (rawRef, rawInfo) baseInfo match case tl: PolyType => - val (tl1, tpts) = constrained(tl, untpd.EmptyTree, alwaysAddTypeVars = true) - val targs = - for (tpt <- tpts) yield - tpt.tpe match { - case tvar: TypeVar => tvar.instantiate(fromBelow = false) - } + val tvars = constrained(tl) + val targs = for tvar <- tvars yield + tvar.instantiate(fromBelow = false) (baseRef.appliedTo(targs), extractParams(tl.instantiate(targs))) case methTpe => (baseRef, extractParams(methTpe)) diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index f682b54ae731..2c552eec6d12 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -82,7 +82,7 @@ object TypeTestsCasts { case tp: TypeProxy => underlyingLambda(tp.superType) } val typeLambda = underlyingLambda(tycon) - val tvars = constrained(typeLambda, untpd.EmptyTree, alwaysAddTypeVars = true)._2.map(_.tpe) + val tvars = constrained(typeLambda) val P1 = tycon.appliedTo(tvars) debug.println("before " + ctx.typerState.constraint.show) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 0165121c9779..b17216d19b03 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -540,7 +540,7 @@ object SpaceEngine { val mt: MethodType = unapp.widen match { case mt: MethodType => mt case pt: PolyType => - val tvars = constrained(pt, EmptyTree)._2.tpes + val tvars = constrained(pt) val mt = pt.instantiate(tvars).asInstanceOf[MethodType] scrutineeTp <:< mt.paramInfos(0) // force type inference to infer a narrower type: could be singleton diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 080048a9e91e..bcb33dc60956 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -317,7 +317,7 @@ object Inferencing { def inferTypeParams(tree: Tree, pt: Type)(using Context): Tree = tree.tpe match case tl: TypeLambda => val (tl1, tvars) = constrained(tl, tree) - var tree1 = AppliedTypeTree(tree.withType(tl1), tvars) + val tree1 = AppliedTypeTree(tree.withType(tl1), tvars.map(_.wrapInTypeTree(tree))) tree1.tpe <:< pt if isFullyDefined(tree1.tpe, force = ForceDegree.failBottom) then tree1 diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 31cca0301d7f..f190c5b011ed 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -713,7 +713,7 @@ object ProtoTypes { tl: TypeLambda, owningTree: untpd.Tree, alwaysAddTypeVars: Boolean, nestingLevel: Int = ctx.nestingLevel - ): (TypeLambda, List[TypeTree]) = { + ): (TypeLambda, List[TypeVar]) = { val state = ctx.typerState val addTypeVars = alwaysAddTypeVars || !owningTree.isEmpty if (tl.isInstanceOf[PolyType]) @@ -721,33 +721,31 @@ object ProtoTypes { s"inconsistent: no typevars were added to committable constraint ${state.constraint}") // hk type lambdas can be added to constraints without typevars during match reduction - def newTypeVars(tl: TypeLambda): List[TypeTree] = - for (paramRef <- tl.paramRefs) - yield { - val tt = InferredTypeTree().withSpan(owningTree.span) + def newTypeVars(tl: TypeLambda): List[TypeVar] = + for paramRef <- tl.paramRefs + yield val tvar = TypeVar(paramRef, state, nestingLevel) state.ownedVars += tvar - tt.withType(tvar) - } + tvar val added = state.constraint.ensureFresh(tl) - val tvars = if (addTypeVars) newTypeVars(added) else Nil - TypeComparer.addToConstraint(added, tvars.tpes.asInstanceOf[List[TypeVar]]) + val tvars = if addTypeVars then newTypeVars(added) else Nil + TypeComparer.addToConstraint(added, tvars) (added, tvars) } - def constrained(tl: TypeLambda, owningTree: untpd.Tree)(using Context): (TypeLambda, List[TypeTree]) = + def constrained(tl: TypeLambda, owningTree: untpd.Tree)(using Context): (TypeLambda, List[TypeVar]) = constrained(tl, owningTree, alwaysAddTypeVars = tl.isInstanceOf[PolyType] && ctx.typerState.isCommittable) - /** Same as `constrained(tl, EmptyTree)`, but returns just the created type lambda */ - def constrained(tl: TypeLambda)(using Context): TypeLambda = - constrained(tl, EmptyTree)._1 + /** Same as `constrained(tl, EmptyTree, alwaysAddTypeVars = true)`, but returns just the created type vars. */ + def constrained(tl: TypeLambda)(using Context): List[TypeVar] = + constrained(tl, EmptyTree, alwaysAddTypeVars = true)._2 /** Instantiate `tl` with fresh type variables added to the constraint. */ def instantiateWithTypeVars(tl: TypeLambda)(using Context): Type = - val targs = constrained(tl, ast.tpd.EmptyTree, alwaysAddTypeVars = true)._2 - tl.instantiate(targs.tpes) + val tvars = constrained(tl) + tl.instantiate(tvars) /** A fresh type variable added to the current constraint. * @param bounds The initial bounds of the variable @@ -766,7 +764,7 @@ object ProtoTypes { pt => bounds :: Nil, pt => represents.orElse(defn.AnyType)) constrained(poly, untpd.EmptyTree, alwaysAddTypeVars = true, nestingLevel) - ._2.head.tpe.asInstanceOf[TypeVar] + ._2.head /** If `param` was created using `newTypeVar(..., represents = X)`, returns X. * This is used in: diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 13544c32cac2..982895294cf0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4260,7 +4260,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer var typeArgs = tree match case Select(qual, nme.CONSTRUCTOR) => qual.tpe.widenDealias.argTypesLo.map(TypeTree(_)) case _ => Nil - if typeArgs.isEmpty then typeArgs = constrained(poly, tree)._2 + if typeArgs.isEmpty then typeArgs = constrained(poly, tree)._2.map(_.wrapInTypeTree(tree)) convertNewGenericArray(readapt(tree.appliedToTypeTrees(typeArgs))) case wtp => val isStructuralCall = wtp.isValueType && isStructuralTermSelectOrApply(tree) diff --git a/compiler/test/dotty/tools/SignatureTest.scala b/compiler/test/dotty/tools/SignatureTest.scala index fdbb1e8f3760..587d7098a0a7 100644 --- a/compiler/test/dotty/tools/SignatureTest.scala +++ b/compiler/test/dotty/tools/SignatureTest.scala @@ -63,7 +63,7 @@ class SignatureTest: | def tuple2(x: Foo *: (T | Tuple) & Foo): Unit = {} |""".stripMargin): val cls = requiredClass("A") - val tvar = constrained(cls.requiredMethod(nme.CONSTRUCTOR).info.asInstanceOf[TypeLambda], untpd.EmptyTree, alwaysAddTypeVars = true)._2.head.tpe + val tvar = constrained(cls.requiredMethod(nme.CONSTRUCTOR).info.asInstanceOf[TypeLambda]).head tvar <:< defn.TupleTypeRef val prefix = cls.typeRef.appliedTo(tvar) @@ -89,7 +89,7 @@ class SignatureTest: | def and(x: T & Foo): Unit = {} |""".stripMargin): val cls = requiredClass("A") - val tvar = constrained(cls.requiredMethod(nme.CONSTRUCTOR).info.asInstanceOf[TypeLambda], untpd.EmptyTree, alwaysAddTypeVars = true)._2.head.tpe + val tvar = constrained(cls.requiredMethod(nme.CONSTRUCTOR).info.asInstanceOf[TypeLambda]).head val prefix = cls.typeRef.appliedTo(tvar) val ref = prefix.select(cls.requiredMethod("and")).asInstanceOf[TermRef] diff --git a/compiler/test/dotty/tools/dotc/core/ConstraintsTest.scala b/compiler/test/dotty/tools/dotc/core/ConstraintsTest.scala index 9ae3fda8c6b9..4ca8e243dc0c 100644 --- a/compiler/test/dotty/tools/dotc/core/ConstraintsTest.scala +++ b/compiler/test/dotty/tools/dotc/core/ConstraintsTest.scala @@ -19,8 +19,7 @@ class ConstraintsTest: @Test def mergeParamsTransitivity: Unit = inCompilerContext(TestConfiguration.basicClasspath, scalaSources = "trait A { def foo[S, T, R]: Any }") { - val tvars = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda], EmptyTree, alwaysAddTypeVars = true)._2 - val List(s, t, r) = tvars.tpes + val List(s, t, r) = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda]) val innerCtx = ctx.fresh.setExploreTyperState() inContext(innerCtx) { @@ -38,8 +37,7 @@ class ConstraintsTest: @Test def mergeBoundsTransitivity: Unit = inCompilerContext(TestConfiguration.basicClasspath, scalaSources = "trait A { def foo[S, T]: Any }") { - val tvars = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda], EmptyTree, alwaysAddTypeVars = true)._2 - val List(s, t) = tvars.tpes + val List(s, t) = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda]) val innerCtx = ctx.fresh.setExploreTyperState() inContext(innerCtx) { @@ -57,10 +55,9 @@ class ConstraintsTest: @Test def validBoundsInit: Unit = inCompilerContext( TestConfiguration.basicClasspath, scalaSources = "trait A { def foo[S >: T <: T | Int, T <: String]: Any }") { - val tvars = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda], EmptyTree, alwaysAddTypeVars = true)._2 - val List(s, t) = tvars.tpes + val List(s, t) = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda]) - val TypeBounds(lo, hi) = ctx.typerState.constraint.entry(t.asInstanceOf[TypeVar].origin): @unchecked + val TypeBounds(lo, hi) = ctx.typerState.constraint.entry(t.origin): @unchecked assert(lo =:= defn.NothingType, i"Unexpected lower bound $lo for $t: ${ctx.typerState.constraint}") assert(hi =:= defn.StringType, i"Unexpected upper bound $hi for $t: ${ctx.typerState.constraint}") // used to be Any } @@ -68,12 +65,11 @@ class ConstraintsTest: @Test def validBoundsUnify: Unit = inCompilerContext( TestConfiguration.basicClasspath, scalaSources = "trait A { def foo[S >: T <: T | Int, T <: String | Int]: Any }") { - val tvars = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda], EmptyTree, alwaysAddTypeVars = true)._2 - val List(s, t) = tvars.tpes + val List(s, t) = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda]) s <:< t - val TypeBounds(lo, hi) = ctx.typerState.constraint.entry(t.asInstanceOf[TypeVar].origin): @unchecked + val TypeBounds(lo, hi) = ctx.typerState.constraint.entry(t.origin): @unchecked assert(lo =:= defn.NothingType, i"Unexpected lower bound $lo for $t: ${ctx.typerState.constraint}") assert(hi =:= (defn.StringType | defn.IntType), i"Unexpected upper bound $hi for $t: ${ctx.typerState.constraint}") } @@ -81,8 +77,7 @@ class ConstraintsTest: @Test def validBoundsReplace: Unit = inCompilerContext( TestConfiguration.basicClasspath, scalaSources = "trait X; trait A { def foo[S <: U | X, T, U]: Any }") { - val tvarTrees = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda], EmptyTree, alwaysAddTypeVars = true)._2 - val tvars @ List(s, t, u) = tvarTrees.tpes.asInstanceOf[List[TypeVar]] + val tvars @ List(s, t, u) = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda]) s =:= t t =:= u