Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport "Streamline tryNormalize with underlyingMatchType" to LTS #21990

Closed
wants to merge 9 commits into from
100 changes: 46 additions & 54 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -452,16 +452,14 @@ object Types extends TypeUtils {
/** Is this a MethodType for which the parameters will not be used? */
def hasErasedParams(using Context): Boolean = false

/** Is this a match type or a higher-kinded abstraction of one?
*/
def isMatch(using Context): Boolean = underlyingMatchType.exists
/** Is this a match type or a higher-kinded abstraction of one? */
def isMatch(using Context): Boolean = stripped match
case tp: MatchType => true
case tp: HKTypeLambda => tp.resType.isMatch
case _ => false

def underlyingMatchType(using Context): Type = stripped match {
case tp: MatchType => tp
case tp: HKTypeLambda => tp.resType.underlyingMatchType
case tp: AppliedType if tp.isMatchAlias => tp.superType.underlyingMatchType
case _ => NoType
}
/** Does this application expand to a match type? */
def isMatchAlias(using Context): Boolean = underlyingNormalizable.isMatch

/** Is this a higher-kinded type lambda with given parameter variances?
* These lambdas are used as the RHS of higher-kinded abstract types or
Expand Down Expand Up @@ -1477,19 +1475,24 @@ object Types extends TypeUtils {
}
deskolemizer(this)

/** The result of normalization using `tryNormalize`, or the type itself if
* tryNormlize yields NoType
/** The result of normalization, or the type itself if none apply. */
final def normalized(using Context): Type = tryNormalize.orElse(this)

/** If this type has an underlying match type or applied compiletime.ops,
* then the result after applying all toplevel normalizations, otherwise NoType.
*/
final def normalized(using Context): Type = {
val normed = tryNormalize
if (normed.exists) normed else this
}
def tryNormalize(using Context): Type = underlyingNormalizable match
case mt: MatchType => mt.reduced.normalized
case tp: AppliedType => tp.tryCompiletimeConstantFold
case _ => NoType

/** If this type can be normalized at the top-level by rewriting match types
* of S[n] types, the result after applying all toplevel normalizations,
* otherwise NoType
/** Perform successive strippings, and beta-reductions of applied types until
* a match type or applied compiletime.ops is reached, if any, otherwise NoType.
*/
def tryNormalize(using Context): Type = NoType
def underlyingNormalizable(using Context): Type = stripped.stripLazyRef match
case tp: MatchType => tp
case tp: AppliedType => tp.underlyingNormalizable
case _ => NoType

private def widenDealias1(keep: AnnotatedType => Context ?=> Boolean)(using Context): Type = {
val res = this.widen.dealias1(keep, keepOpaques = false)
Expand Down Expand Up @@ -3106,8 +3109,6 @@ object Types extends TypeUtils {
private var myRef: Type | Null = null
private var computed = false

override def tryNormalize(using Context): Type = ref.tryNormalize

def ref(using Context): Type =
if computed then
if myRef == null then
Expand Down Expand Up @@ -4455,6 +4456,9 @@ object Types extends TypeUtils {
private var myEvalRunId: RunId = NoRunId
private var myEvalued: Type = uninitialized

private var validUnderlyingNormalizable: Period = Nowhere
private var cachedUnderlyingNormalizable: Type = uninitialized

def isGround(acc: TypeAccumulator[Boolean])(using Context): Boolean =
if myGround == 0 then myGround = if acc.foldOver(true, this) then 1 else -1
myGround > 0
Expand Down Expand Up @@ -4511,30 +4515,25 @@ object Types extends TypeUtils {
case nil => x
foldArgs(op(x, tycon), args)

override def tryNormalize(using Context): Type = tycon.stripTypeVar match {
case tycon: TypeRef =>
def tryMatchAlias = tycon.info match {
case MatchAlias(alias) =>
trace(i"normalize $this", typr, show = true) {
MatchTypeTrace.recurseWith(this) {
alias.applyIfParameterized(args.map(_.normalized)).tryNormalize
}
}
case _ =>
NoType
}
tryCompiletimeConstantFold.orElse(tryMatchAlias)
case _ =>
NoType
}
/** Exists if the tycon is a TypeRef of an alias with an underlying match type,
* or a compiletime applied type. Anything else should have already been
* reduced in `appliedTo` by the TypeAssigner. This may reduce several
* HKTypeLambda applications before the underlying normalizable type is reached.
*/
override def underlyingNormalizable(using Context): Type =
if ctx.period != validUnderlyingNormalizable then tycon match
case tycon: TypeRef if defn.isCompiletimeAppliedType(tycon.symbol) =>
cachedUnderlyingNormalizable = this
validUnderlyingNormalizable = ctx.period
case _ =>
cachedUnderlyingNormalizable = superType.underlyingNormalizable
validUnderlyingNormalizable = validSuper
cachedUnderlyingNormalizable

/** Does this application expand to a match type? */
def isMatchAlias(using Context): Boolean = tycon.stripTypeVar match
case tycon: TypeRef =>
tycon.info match
case _: MatchAlias => true
case _ => false
case _ => false
override def tryNormalize(using Context): Type =
if isMatchAlias && MatchTypeTrace.isRecording then
MatchTypeTrace.recurseWith(this)(superType.tryNormalize)
else super.tryNormalize

/** Is this an unreducible application to wildcard arguments?
* This is the case if tycon is higher-kinded. This means
Expand Down Expand Up @@ -4957,12 +4956,6 @@ object Types extends TypeUtils {
private var myReduced: Type | Null = null
private var reductionContext: util.MutableMap[Type, Type] = _

override def tryNormalize(using Context): Type =
try
reduced.normalized
catch
case ex: Throwable =>
handleRecursive("normalizing", s"${scrutinee.show} match ..." , ex)

def reduced(using Context): Type = atPhaseNoLater(elimOpaquePhase) {

Expand Down Expand Up @@ -5037,10 +5030,9 @@ object Types extends TypeUtils {
def apply(bound: Type, scrutinee: Type, cases: List[Type])(using Context): MatchType =
unique(new CachedMatchType(bound, scrutinee, cases))

def thatReducesUsingGadt(tp: Type)(using Context): Boolean = tp match
case MatchType.InDisguise(mt) => mt.reducesUsingGadt
case mt: MatchType => mt.reducesUsingGadt
case _ => false
def thatReducesUsingGadt(tp: Type)(using Context): Boolean = tp.underlyingNormalizable match
case mt: MatchType => mt.reducesUsingGadt
case _ => false

/** Extractor for match types hidden behind an AppliedType/MatchAlias. */
object InDisguise:
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1789,7 +1789,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
case _ => false
}

val result = pt match {
val result = pt.underlyingNormalizable match {
case mt: MatchType if isMatchTypeShaped(mt) =>
typedDependentMatchFinish(tree, sel1, selType, tree.cases, mt)
case MatchType.InDisguise(mt) if isMatchTypeShaped(mt) =>
Expand Down
22 changes: 22 additions & 0 deletions compiler/test/dotc/neg-best-effort-pickling.blacklist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export-in-extension.scala
i12456.scala
i8623.scala
i1642.scala
i16696.scala
constructor-proxy-values.scala
i9328.scala
i15414.scala
i6796.scala
i14013.scala
toplevel-cyclic
curried-dependent-ift.scala
i17121.scala
illegal-match-types.scala
i13780-1.scala
i20317a.scala
i11226.scala
i974.scala

# semantic db generation fails in the first compilation
i1642.scala
i15158.scala
14 changes: 14 additions & 0 deletions tests/neg/i12049d.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- [E007] Type Mismatch Error: tests/neg/i12049d.scala:14:52 -----------------------------------------------------------
14 |val x: M[NotRelevant[Nothing], Relevant[Nothing]] = 2 // error
| ^
| Found: (2 : Int)
| Required: M[NotRelevant[Nothing], Relevant[Nothing]]
|
| Note: a match type could not be fully reduced:
|
| trying to reduce M[NotRelevant[Nothing], Relevant[Nothing]]
| trying to reduce Relevant[Nothing]
| failed since selector Nothing
| is uninhabited (there are no values of that type).
|
| longer explanation available when compiling with `-explain`
14 changes: 14 additions & 0 deletions tests/neg/i12049d.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

trait A
trait B

type M[X, Y] = Y match
case A => Int
case B => String

type Relevant[Z] = Z match
case A => B
type NotRelevant[Z] = Z match
case B => A

val x: M[NotRelevant[Nothing], Relevant[Nothing]] = 2 // error
16 changes: 16 additions & 0 deletions tests/pos/i20482.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
trait WrapperType[A]

case class Foo[A]()

case class Bar[A]()

type FooToBar[D[_]] = [A] =>> D[Unit] match {
case Foo[Unit] => Bar[A]
}

case class Test()
object Test {
implicit val wrapperType: WrapperType[Bar[Test]] = new WrapperType[Bar[Test]] {}
}

val test = summon[WrapperType[FooToBar[Foo][Test]]]
8 changes: 8 additions & 0 deletions tests/pos/matchtype-unusedArg/A_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

type Rec[X] = X match
case Int => Rec[X]

type M[Unused, Y] = Y match
case String => Double

def foo[X](d: M[Rec[X], "hi"]) = ???
2 changes: 2 additions & 0 deletions tests/pos/matchtype-unusedArg/B_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

def Test = foo[Int](3d) // crash before changes
Loading