Skip to content

Commit 1e8a653

Browse files
authored
Make aliases of MatchAliases normal TypeAliases (#19871)
Proposes to make `isMatch` true only for `MatchType`s and higher-kinded abstraction of them. As a result, code using `isMatch` to choose between a `TypeAlias` and `MatchAlias` will now use a `TypeAlias` when aliasing a `MatchAlias`. Which in turn allows for better de-aliasing, since `dealias` only de-aliases standard type aliases. `tryNormalize` on `AppliedType` should only attempt reduction if there is an underlying match type. This could previously be identified by a `MatchAlias` tycon. We now need a recursive check. Fixes #19821
2 parents d2a6392 + 1dc5b99 commit 1e8a653

File tree

15 files changed

+123
-59
lines changed

15 files changed

+123
-59
lines changed

compiler/src/dotty/tools/dotc/core/TypeApplications.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ class TypeApplications(val self: Type) extends AnyVal {
461461
*/
462462
final def toBounds(using Context): TypeBounds = self match {
463463
case self: TypeBounds => self // this can happen for wildcard args
464-
case _ => if (self.isMatch) MatchAlias(self) else TypeAlias(self)
464+
case _ => AliasingBounds(self)
465465
}
466466

467467
/** Translate a type of the form From[T] to either To[T] or To[? <: T] (if `wildcardArg` is set). Keep other types as they are.

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1375,7 +1375,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
13751375
* tp1 <:< app2 using isSubType (this might instantiate params in tp2)
13761376
*/
13771377
def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean =
1378-
if ((tycon2bounds.lo `eq` tycon2bounds.hi) && !tycon2bounds.isInstanceOf[MatchAlias])
1378+
if ((tycon2bounds.lo `eq` tycon2bounds.hi) && !tycon2bounds.isMatchAlias)
13791379
if (tyconIsTypeRef) recur(tp1, tp2.superTypeNormalized) && recordGadtUsageIf(MatchType.thatReducesUsingGadt(tp2))
13801380
else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2))
13811381
else

compiler/src/dotty/tools/dotc/core/TypeOps.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ object TypeOps:
143143
defn.MatchCase(simplify(pat, theMap), body)
144144
case tp: AppliedType =>
145145
tp.tycon match
146-
case tycon: TypeRef if tycon.info.isInstanceOf[MatchAlias] =>
146+
case tycon: TypeRef if tp.isMatchAlias =>
147147
isFullyDefined(tp, ForceDegree.all)
148148
case _ =>
149149
val normed = tp.tryNormalize

compiler/src/dotty/tools/dotc/core/Types.scala

+41-29
Original file line numberDiff line numberDiff line change
@@ -456,14 +456,19 @@ object Types extends TypeUtils {
456456
/** Is this a MethodType for which the parameters will not be used? */
457457
def hasErasedParams(using Context): Boolean = false
458458

459-
/** Is this a match type or a higher-kinded abstraction of one?
460-
*/
461-
def isMatch(using Context): Boolean = underlyingMatchType.exists
459+
/** Is this a match type or a higher-kinded abstraction of one? */
460+
def isMatch(using Context): Boolean = stripped match
461+
case tp: MatchType => true
462+
case tp: HKTypeLambda => tp.resType.isMatch
463+
case _ => false
464+
465+
/** Does this application expand to a match type? */
466+
def isMatchAlias(using Context): Boolean = underlyingMatchType.exists
462467

463468
def underlyingMatchType(using Context): Type = stripped match {
464469
case tp: MatchType => tp
465470
case tp: HKTypeLambda => tp.resType.underlyingMatchType
466-
case tp: AppliedType if tp.isMatchAlias => tp.superType.underlyingMatchType
471+
case tp: AppliedType => tp.underlyingMatchType
467472
case _ => NoType
468473
}
469474

@@ -4529,6 +4534,9 @@ object Types extends TypeUtils {
45294534
private var myEvalRunId: RunId = NoRunId
45304535
private var myEvalued: Type = uninitialized
45314536

4537+
private var validUnderlyingMatch: Period = Nowhere
4538+
private var cachedUnderlyingMatch: Type = uninitialized
4539+
45324540
def isGround(acc: TypeAccumulator[Boolean])(using Context): Boolean =
45334541
if myGround == 0 then myGround = if acc.foldOver(true, this) then 1 else -1
45344542
myGround > 0
@@ -4585,31 +4593,38 @@ object Types extends TypeUtils {
45854593
case nil => x
45864594
foldArgs(op(x, tycon), args)
45874595

4596+
/** Exists if the tycon is a TypeRef of an alias with an underlying match type.
4597+
* Anything else should have already been reduced in `appliedTo` by the TypeAssigner.
4598+
*/
4599+
override def underlyingMatchType(using Context): Type =
4600+
if ctx.period != validUnderlyingMatch then
4601+
validUnderlyingMatch = if tycon.isProvisional then Nowhere else ctx.period
4602+
cachedUnderlyingMatch = superType.underlyingMatchType
4603+
cachedUnderlyingMatch
4604+
45884605
override def tryNormalize(using Context): Type = tycon.stripTypeVar match {
45894606
case tycon: TypeRef =>
4590-
def tryMatchAlias = tycon.info match {
4591-
case MatchAlias(alias) =>
4607+
def tryMatchAlias = tycon.info match
4608+
case AliasingBounds(alias) if isMatchAlias =>
45924609
trace(i"normalize $this", typr, show = true) {
45934610
MatchTypeTrace.recurseWith(this) {
45944611
alias.applyIfParameterized(args.map(_.normalized)).tryNormalize
4612+
/* `applyIfParameterized` may reduce several HKTypeLambda applications
4613+
* before the underlying MatchType is reached.
4614+
* Even if they do not involve any match type normalizations yet,
4615+
* we still want to record these reductions in the MatchTypeTrace.
4616+
* They should however only be attempted if they eventually expand
4617+
* to a match type, which is ensured by the `isMatchAlias` guard.
4618+
*/
45954619
}
45964620
}
45974621
case _ =>
45984622
NoType
4599-
}
46004623
tryCompiletimeConstantFold.orElse(tryMatchAlias)
46014624
case _ =>
46024625
NoType
46034626
}
46044627

4605-
/** Does this application expand to a match type? */
4606-
def isMatchAlias(using Context): Boolean = tycon.stripTypeVar match
4607-
case tycon: TypeRef =>
4608-
tycon.info match
4609-
case _: MatchAlias => true
4610-
case _ => false
4611-
case _ => false
4612-
46134628
/** Is this an unreducible application to wildcard arguments?
46144629
* This is the case if tycon is higher-kinded. This means
46154630
* it is a subtype of a hk-lambda, but not a match alias.
@@ -5137,20 +5152,9 @@ object Types extends TypeUtils {
51375152
def apply(bound: Type, scrutinee: Type, cases: List[Type])(using Context): MatchType =
51385153
unique(new CachedMatchType(bound, scrutinee, cases))
51395154

5140-
def thatReducesUsingGadt(tp: Type)(using Context): Boolean = tp match
5141-
case MatchType.InDisguise(mt) => mt.reducesUsingGadt
5142-
case mt: MatchType => mt.reducesUsingGadt
5143-
case _ => false
5144-
5145-
/** Extractor for match types hidden behind an AppliedType/MatchAlias. */
5146-
object InDisguise:
5147-
def unapply(tp: AppliedType)(using Context): Option[MatchType] = tp match
5148-
case AppliedType(tycon: TypeRef, args) => tycon.info match
5149-
case MatchAlias(alias) => alias.applyIfParameterized(args) match
5150-
case mt: MatchType => Some(mt)
5151-
case _ => None
5152-
case _ => None
5153-
case _ => None
5155+
def thatReducesUsingGadt(tp: Type)(using Context): Boolean = tp.underlyingMatchType match
5156+
case mt: MatchType => mt.reducesUsingGadt
5157+
case _ => false
51545158
}
51555159

51565160
enum MatchTypeCasePattern:
@@ -5636,6 +5640,14 @@ object Types extends TypeUtils {
56365640
def lower(lo: Type)(using Context): TypeBounds = apply(lo, defn.AnyType)
56375641
}
56385642

5643+
object AliasingBounds:
5644+
/** A MatchAlias if alias is a match type and a TypeAlias o.w.
5645+
* Note that aliasing a MatchAlias returns a normal TypeAlias.
5646+
*/
5647+
def apply(alias: Type)(using Context): AliasingBounds =
5648+
if alias.isMatch then MatchAlias(alias) else TypeAlias(alias)
5649+
def unapply(tp: AliasingBounds): Option[Type] = Some(tp.alias)
5650+
56395651
object TypeAlias {
56405652
def apply(alias: Type)(using Context): TypeAlias = unique(new TypeAlias(alias))
56415653
def unapply(tp: TypeAlias): Option[Type] = Some(tp.alias)

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

+1-3
Original file line numberDiff line numberDiff line change
@@ -412,9 +412,7 @@ class TreeUnpickler(reader: TastyReader,
412412
readType().appliedTo(until(end)(readType()))
413413
case TYPEBOUNDS =>
414414
val lo = readType()
415-
if nothingButMods(end) then
416-
if lo.isMatch then MatchAlias(readVariances(lo))
417-
else TypeAlias(readVariances(lo))
415+
if nothingButMods(end) then AliasingBounds(readVariances(lo))
418416
else
419417
val hi = readVariances(readType())
420418
createNullableTypeBounds(lo, hi)

compiler/src/dotty/tools/dotc/inlines/Inlines.scala

+2-3
Original file line numberDiff line numberDiff line change
@@ -452,9 +452,8 @@ object Inlines:
452452
unrollTupleTypes(tail).map(head :: _)
453453
case tpe: TermRef if tpe.symbol == defn.EmptyTupleModule =>
454454
Some(Nil)
455-
case tpRef: TypeRef => tpRef.info match
456-
case MatchAlias(alias) => unrollTupleTypes(alias.tryNormalize)
457-
case _ => None
455+
case tpe: AppliedType if tpe.isMatchAlias =>
456+
unrollTupleTypes(tpe.tryNormalize)
458457
case _ =>
459458
None
460459

compiler/src/dotty/tools/dotc/typer/Implicits.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ trait ImplicitRunInfo:
622622
sym.isClass && !isExcluded(sym)
623623
|| sym.isOpaqueAlias
624624
|| sym.is(Deferred, butNot = Param)
625-
|| sym.info.isInstanceOf[MatchAlias]
625+
|| sym.info.isMatchAlias
626626

627627
private def computeIScope(rootTp: Type): OfTypeImplicits =
628628

@@ -636,7 +636,7 @@ trait ImplicitRunInfo:
636636
else if implicitScopeCache.contains(t) then parts += t
637637
else
638638
partSeen += t
639-
t.dealias match
639+
t.dealias.normalized match
640640
case t: TypeRef =>
641641
if isAnchor(t.symbol) then
642642
parts += t
@@ -663,7 +663,6 @@ trait ImplicitRunInfo:
663663
traverseChildren(t)
664664
case t =>
665665
traverseChildren(t)
666-
traverse(t.normalized)
667666
catch case ex: Throwable => handleRecursive("collectParts of", t.show, ex)
668667

669668
def apply(tp: Type): collection.Set[Type] =
@@ -775,6 +774,7 @@ trait ImplicitRunInfo:
775774
* if `T` is of the form `(P#x).type`, the anchors of `P`.
776775
* - If `T` is the this-type of a static object, the anchors of a term reference to that object.
777776
* - If `T` is some other this-type `P.this.type`, the anchors of `P`.
777+
* - If `T` is match type or an applied match alias, the anchors of the normalization of `T`.
778778
* - If `T` is some other type, the union of the anchors of each constituent type of `T`.
779779
*
780780
* The _implicit scope_ of a type `tp` is the smallest set S of term references (i.e. TermRefs)
@@ -787,7 +787,7 @@ trait ImplicitRunInfo:
787787
* - If `T` is a reference to an opaque type alias named `A`, S includes
788788
* a reference to an object `A` defined in the same scope as the type, if it exists,
789789
* as well as the implicit scope of `T`'s underlying type or bounds.
790-
* - If `T` is a reference to an an abstract type or match type alias named `A`,
790+
* - If `T` is a reference to an an abstract type or unreducible match type alias named `A`,
791791
* S includes a reference to an object `A` defined in the same scope as the type,
792792
* if it exists, as well as the implicit scopes of `T`'s lower and upper bound,
793793
* if present.

compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala

+1-3
Original file line numberDiff line numberDiff line change
@@ -519,9 +519,7 @@ trait TypeAssigner {
519519
def assignType(tree: untpd.TypeBoundsTree, lo: Tree, hi: Tree, alias: Tree)(using Context): TypeBoundsTree =
520520
tree.withType(
521521
if !alias.isEmpty then alias.tpe
522-
else if lo eq hi then
523-
if lo.tpe.isMatch then MatchAlias(lo.tpe)
524-
else TypeAlias(lo.tpe)
522+
else if lo eq hi then AliasingBounds(lo.tpe)
525523
else TypeBounds(lo.tpe, hi.tpe))
526524

527525
def assignType(tree: untpd.Bind, sym: Symbol)(using Context): Bind =

compiler/src/dotty/tools/dotc/typer/Typer.scala

+1-3
Original file line numberDiff line numberDiff line change
@@ -1841,11 +1841,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
18411841
case _ => false
18421842
}
18431843

1844-
val result = pt match {
1844+
val result = pt.underlyingMatchType match {
18451845
case mt: MatchType if isMatchTypeShaped(mt) =>
18461846
typedDependentMatchFinish(tree, sel1, selType, tree.cases, mt)
1847-
case MatchType.InDisguise(mt) if isMatchTypeShaped(mt) =>
1848-
typedDependentMatchFinish(tree, sel1, selType, tree.cases, mt)
18491847
case _ =>
18501848
typedMatchFinish(tree, sel1, selType, tree.cases, pt)
18511849
}

compiler/test/dotc/pos-test-pickling.blacklist

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ i17149.scala
6464
tuple-fold.scala
6565
mt-redux-norm.perspective.scala
6666
i18211.scala
67+
10867.scala
6768

6869
# Opaque type
6970
i5720.scala

tests/neg-macros/i11795.scala

-10
This file was deleted.

tests/neg/i20071.scala

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
trait Scope
3+
object Scope:
4+
given i: Int = ???
5+
6+
type ReferencesScope[S] >: Int <: Int
7+
8+
type ScopeToInt[Why] = Why match
9+
case Scope => Int
10+
11+
def foo[T](using d: ReferencesScope[T]): Any = ???
12+
13+
def bar[T](using d: ScopeToInt[T]): Any = ???
14+
15+
def test: Unit =
16+
foo[Scope] // ok
17+
bar[Scope] // error
18+
19+
import Scope.i
20+
bar[Scope] // ok
21+
22+
/*
23+
Before the changes:
24+
`ScopeToInt[Scope]` may or may not be reduced before implicit search,
25+
thereby impacting the scope considered for the search. `Scope.i` is included
26+
iff `Scope` still appears in the type, which is the case only before reduction.
27+
In contrast, `ReferencesScope[Scope]` is ok since it will never lose the anchor.
28+
*/

tests/pos-macros/i11795.scala

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
import scala.quoted._
22
import scala.deriving._
33

4-
def blah2[P <: Product, MEL <: Tuple: Type, MET <: Tuple: Type](m: Mirror.ProductOf[P] { type MirroredElemLabels = MEL; type MirroredElemTypes = MET})(using Quotes) = {
4+
def blah[P <: Product]
5+
(m: Mirror.ProductOf[P])
6+
(using Quotes, Type[m.MirroredElemLabels], Type[m.MirroredElemTypes]) = {
7+
type z = Tuple.Zip[m.MirroredElemLabels, m.MirroredElemTypes]
8+
Type.of[z] // error
9+
()
10+
}
11+
12+
def blah2[P <: Product, MEL <: Tuple: Type, MET <: Tuple: Type]
13+
(m: Mirror.ProductOf[P] { type MirroredElemLabels = MEL; type MirroredElemTypes = MET})
14+
(using Quotes) = {
515
Type.of[Tuple.Zip[MEL, MET]]
616
()
717
}

tests/pos/i15183/test_2.scala

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
// Fails in each cases below
2+
import Decoder.{derived as _, given}
3+
// NOTE Decoder.derived is already in the implicit scope
4+
// but the others require an import as they depend on match type reduction
5+
26
enum Env derives Decoder:
37
case Local,Sit,Prod
48

tests/pos/i19821.scala

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
2+
object Test:
3+
4+
trait T:
5+
type S
6+
type F = T.F[S]
7+
8+
def foo: F
9+
def bar: T.F[S]
10+
11+
object T:
12+
type F[X] = X match
13+
case String => Option[Int]
14+
15+
type G[X] = X match
16+
case Option[x] => Int
17+
18+
val t: T {type S = String} = ???
19+
20+
val b = t.bar
21+
val m1: T.G[b.type] = ???
22+
val _: Int = m1 // Ok
23+
24+
val f = t.foo
25+
val m: T.G[f.type] = ???
26+
val _: Int = m // Error before changes

0 commit comments

Comments
 (0)