Skip to content

Commit 1fd37a3

Browse files
committed
Use a special type for match aliases
This is a more principled implementation of the situation that match aliases are not full type aliases because one side cannot be freely substituted for the other. Match aliases are checked in some respects like abstract type bounds (in particular, nested cycles are allowed), but are treated as aliases in others (in particular, lower and upper "bound" are always guaranteed to be the same).
1 parent 5bdee62 commit 1fd37a3

13 files changed

+104
-75
lines changed

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,8 @@ class TypeApplications(val self: Type) extends AnyVal {
337337
tl => arg.paramInfos.map(_.subst(arg, tl).bounds),
338338
tl => arg.resultType.subst(arg, tl)
339339
)
340-
case arg @ TypeAlias(alias) =>
341-
arg.derivedTypeAlias(adaptArg(alias))
340+
case arg: AliasingBounds =>
341+
arg.derivedAlias(adaptArg(arg.alias))
342342
case arg @ TypeBounds(lo, hi) =>
343343
arg.derivedTypeBounds(adaptArg(lo), adaptArg(hi))
344344
case _ =>
@@ -401,8 +401,8 @@ class TypeApplications(val self: Type) extends AnyVal {
401401
dealiased.derivedAndType(dealiased.tp1.appliedTo(args), dealiased.tp2.appliedTo(args))
402402
case dealiased: OrType =>
403403
dealiased.derivedOrType(dealiased.tp1.appliedTo(args), dealiased.tp2.appliedTo(args))
404-
case dealiased: TypeAlias =>
405-
dealiased.derivedTypeAlias(dealiased.alias.appliedTo(args))
404+
case dealiased: AliasingBounds =>
405+
dealiased.derivedAlias(dealiased.alias.appliedTo(args))
406406
case dealiased: TypeBounds =>
407407
dealiased.derivedTypeBounds(dealiased.lo.appliedTo(args), dealiased.hi.appliedTo(args))
408408
case dealiased: LazyRef =>
@@ -440,7 +440,7 @@ class TypeApplications(val self: Type) extends AnyVal {
440440
*/
441441
final def toBounds(implicit ctx: Context): TypeBounds = self match {
442442
case self: TypeBounds => self // this can happen for wildcard args
443-
case _ => if (self.isMatch) TypeBounds.upper(self) else TypeAlias(self)
443+
case _ => if (self.isMatch) MatchAlias(self) else TypeAlias(self)
444444
}
445445

446446
/** Translate a type of the form From[T] to To[T], keep other types as they are.

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
340340
recur(tp1.underlying, tp2)
341341
case tp1: WildcardType =>
342342
def compareWild = tp1.optBounds match {
343-
case bounds: TypeBounds => recur(bounds.effectiveLo, tp2)
343+
case bounds: TypeBounds => recur(bounds.lo, tp2)
344344
case _ => true
345345
}
346346
compareWild
@@ -384,7 +384,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
384384
narrowGADTBounds(tp2, tp1, approx, isUpper = false)) &&
385385
GADTusage(tp2.symbol)
386386
}
387-
isSubApproxHi(tp1, info2.effectiveLo) || compareGADT || fourthTry
387+
isSubApproxHi(tp1, info2.lo) || compareGADT || fourthTry
388388

389389
case _ =>
390390
val cls2 = tp2.symbol
@@ -428,7 +428,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
428428
// So if the constraint is not yet frozen, we do the same comparison again
429429
// with a frozen constraint, which means that we get a chance to do the
430430
// widening in `fourthTry` before adding to the constraint.
431-
if (frozenConstraint) isSubType(tp1, bounds(tp2).effectiveLo)
431+
if (frozenConstraint) isSubType(tp1, bounds(tp2).lo)
432432
else isSubTypeWhenFrozen(tp1, tp2)
433433
alwaysTrue || {
434434
if (canConstrain(tp2) && !approx.low)
@@ -814,7 +814,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
814814
if (tyconIsTypeRef) recur(tp1, tp2.superType)
815815
else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2))
816816
else
817-
fallback(tycon2bounds.effectiveLo)
817+
fallback(tycon2bounds.lo)
818818

819819
tycon2 match {
820820
case param2: TypeParamRef =>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
101101
}
102102
case _: ThisType | _: BoundType =>
103103
tp
104-
case tp: TypeAlias =>
105-
tp.derivedTypeAlias(simplify(tp.alias, theMap))
104+
case tp: AliasingBounds =>
105+
tp.derivedAlias(simplify(tp.alias, theMap))
106106
case AndType(l, r) if !ctx.mode.is(Mode.Type) =>
107107
simplify(l, theMap) & simplify(r, theMap)
108108
case OrType(l, r) if !ctx.mode.is(Mode.Type) =>

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

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ object Types {
118118
t.symbol.is(Provisional) ||
119119
apply(x, t.prefix) || {
120120
t.info match {
121-
case TypeAlias(alias) => apply(x, alias)
121+
case info: AliasingBounds => apply(x, info.alias)
122122
case TypeBounds(lo, hi) => apply(apply(x, lo), hi)
123123
case _ => false
124124
}
@@ -319,7 +319,7 @@ object Types {
319319
}
320320

321321
/** Is this an alias TypeBounds? */
322-
final def isAlias: Boolean = this.isInstanceOf[TypeAlias]
322+
final def isTypeAlias: Boolean = this.isInstanceOf[TypeAlias]
323323

324324
/** Is this a MethodType which is from Java */
325325
def isJavaMethod: Boolean = false
@@ -628,8 +628,8 @@ object Types {
628628
val rinfo = tp.refinedInfo
629629
if (name.isTypeName && !pinfo.isInstanceOf[ClassInfo]) { // simplified case that runs more efficiently
630630
val jointInfo =
631-
if (rinfo.isAlias) rinfo
632-
else if (pinfo.isAlias) pinfo
631+
if (rinfo.isTypeAlias) rinfo
632+
else if (pinfo.isTypeAlias) pinfo
633633
else if (ctx.base.pendingMemberSearches.contains(name)) pinfo safe_& rinfo
634634
else pinfo recoverable_& rinfo
635635
pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo)
@@ -1238,7 +1238,7 @@ object Types {
12381238
*/
12391239
@tailrec final def normalizedPrefix(implicit ctx: Context): Type = this match {
12401240
case tp: NamedType =>
1241-
if (tp.symbol.info.isAlias) tp.info.normalizedPrefix else tp.prefix
1241+
if (tp.symbol.info.isTypeAlias) tp.info.normalizedPrefix else tp.prefix
12421242
case tp: ClassInfo =>
12431243
tp.prefix
12441244
case tp: TypeProxy =>
@@ -3085,8 +3085,8 @@ object Types {
30853085

30863086
def derivedLambdaAbstraction(paramNames: List[TypeName], paramInfos: List[TypeBounds], resType: Type)(implicit ctx: Context): Type =
30873087
resType match {
3088-
case resType @ TypeAlias(alias) =>
3089-
resType.derivedTypeAlias(newLikeThis(paramNames, paramInfos, alias))
3088+
case resType: AliasingBounds =>
3089+
resType.derivedAlias(newLikeThis(paramNames, paramInfos, resType.alias))
30903090
case resType @ TypeBounds(lo, hi) =>
30913091
resType.derivedTypeBounds(
30923092
if (lo.isRef(defn.NothingClass)) lo else newLikeThis(paramNames, paramInfos, lo),
@@ -3187,8 +3187,8 @@ object Types {
31873187
override def fromParams[PI <: ParamInfo.Of[TypeName]](params: List[PI], resultType: Type)(implicit ctx: Context): Type = {
31883188
def expand(tp: Type) = super.fromParams(params, tp)
31893189
resultType match {
3190-
case rt: TypeAlias =>
3191-
rt.derivedTypeAlias(expand(rt.alias))
3190+
case rt: AliasingBounds =>
3191+
rt.derivedAlias(expand(rt.alias))
31923192
case rt @ TypeBounds(lo, hi) =>
31933193
rt.derivedTypeBounds(
31943194
if (lo.isRef(defn.NothingClass)) lo else expand(lo), expand(hi))
@@ -3526,6 +3526,14 @@ object Types {
35263526

35273527
// ------ MatchType ---------------------------------------------------------------
35283528

3529+
/** scrutinee match { case_1 ... case_n }
3530+
*
3531+
* where
3532+
*
3533+
* case_i = [X1, ..., Xn] patternType => resultType
3534+
*
3535+
* and `X_1,...X_n` are the type variables bound in `patternType`
3536+
*/
35293537
abstract case class MatchType(scrutinee: Type, cases: List[Type]) extends CachedProxyType with TermType {
35303538
override def computeHash(bs: Binders) = doHash(bs, scrutinee, cases)
35313539

@@ -3781,51 +3789,64 @@ object Types {
37813789
case _ => super.| (that)
37823790
}
37833791

3784-
def effectiveLo(implicit ctx: Context) =
3785-
if (hi.isMatch) hi else lo
3786-
37873792
override def computeHash(bs: Binders) = doHash(bs, lo, hi)
37883793
override def stableHash = lo.stableHash && hi.stableHash
37893794

37903795
override def equals(that: Any): Boolean = equals(that, null)
37913796

37923797
override def iso(that: Any, bs: BinderPairs): Boolean = that match {
3793-
case that: TypeAlias => false
3798+
case that: AliasingBounds => false
37943799
case that: TypeBounds => lo.equals(that.lo, bs) && hi.equals(that.hi, bs)
37953800
case _ => false
37963801
}
37973802

37983803
override def eql(that: Type) = that match {
3799-
case that: TypeAlias => false
3804+
case that: AliasingBounds => false
38003805
case that: TypeBounds => lo.eq(that.lo) && hi.eq(that.hi)
38013806
case _ => false
38023807
}
38033808
}
38043809

38053810
class RealTypeBounds(lo: Type, hi: Type) extends TypeBounds(lo, hi)
38063811

3807-
abstract class TypeAlias(val alias: Type) extends TypeBounds(alias, alias) {
3812+
/** Common supertype of `TypeAlias` and `MatchAlias` */
3813+
abstract class AliasingBounds(val alias: Type) extends TypeBounds(alias, alias) {
38083814

3809-
/** pre: this is a type alias */
3810-
def derivedTypeAlias(alias: Type)(implicit ctx: Context) =
3811-
if (alias eq this.alias) this else TypeAlias(alias)
3815+
def derivedAlias(alias: Type)(implicit ctx: Context): AliasingBounds
38123816

38133817
override def computeHash(bs: Binders) = doHash(bs, alias)
38143818
override def stableHash = alias.stableHash
38153819

38163820
override def iso(that: Any, bs: BinderPairs): Boolean = that match {
3817-
case that: TypeAlias => alias.equals(that.alias, bs)
3821+
case that: AliasingBounds => this.isTypeAlias == that.isTypeAlias && alias.equals(that.alias, bs)
38183822
case _ => false
38193823
}
38203824
// equals comes from case class; no matching override is needed
38213825

38223826
override def eql(that: Type): Boolean = that match {
3823-
case that: TypeAlias => alias.eq(that.alias)
3827+
case that: AliasingBounds => this.isTypeAlias == that.isTypeAlias && alias.eq(that.alias)
38243828
case _ => false
38253829
}
38263830
}
38273831

3828-
class CachedTypeAlias(alias: Type) extends TypeAlias(alias)
3832+
/** = T
3833+
*/
3834+
class TypeAlias(alias: Type) extends AliasingBounds(alias) {
3835+
def derivedAlias(alias: Type)(implicit ctx: Context) =
3836+
if (alias eq this.alias) this else TypeAlias(alias)
3837+
}
3838+
3839+
/** = T where `T` is a `MatchType`
3840+
*
3841+
* Match aliases are treated differently from type aliases. Their sides are mutually
3842+
* subtypes of each other but one side is not generally substitutable for the other.
3843+
* If we assumed full substitutivity, we would have to reject all recursive match
3844+
* aliases (or else take the jump and allow full recursive types).
3845+
*/
3846+
class MatchAlias(alias: Type) extends AliasingBounds(alias) {
3847+
def derivedAlias(alias: Type)(implicit ctx: Context) =
3848+
if (alias eq this.alias) this else MatchAlias(alias)
3849+
}
38293850

38303851
object TypeBounds {
38313852
def apply(lo: Type, hi: Type)(implicit ctx: Context): TypeBounds =
@@ -3836,11 +3857,15 @@ object Types {
38363857
}
38373858

38383859
object TypeAlias {
3839-
def apply(alias: Type)(implicit ctx: Context) =
3840-
unique(new CachedTypeAlias(alias))
3860+
def apply(alias: Type)(implicit ctx: Context) = unique(new TypeAlias(alias))
38413861
def unapply(tp: TypeAlias): Option[Type] = Some(tp.alias)
38423862
}
38433863

3864+
object MatchAlias {
3865+
def apply(alias: Type)(implicit ctx: Context) = unique(new MatchAlias(alias))
3866+
def unapply(tp: MatchAlias): Option[Type] = Some(tp.alias)
3867+
}
3868+
38443869
// ----- Annotated and Import types -----------------------------------------------
38453870

38463871
/** An annotated type tpe @ annot */
@@ -4042,8 +4067,8 @@ object Types {
40424067
def apply(tp: Type): Type = tp match {
40434068
case tp: TypeRef if tp.symbol.is(ClassTypeParam) && tp.symbol.owner == cls =>
40444069
tp.info match {
4045-
case TypeAlias(alias) =>
4046-
mapOver(alias)
4070+
case info: AliasingBounds =>
4071+
mapOver(info.alias)
40474072
case TypeBounds(lo, hi) =>
40484073
range(atVariance(-variance)(apply(lo)), apply(hi))
40494074
case _ =>
@@ -4099,8 +4124,8 @@ object Types {
40994124
tp.derivedRefinedType(parent, tp.refinedName, info)
41004125
protected def derivedRecType(tp: RecType, parent: Type): Type =
41014126
tp.rebind(parent)
4102-
protected def derivedTypeAlias(tp: TypeAlias, alias: Type): Type =
4103-
tp.derivedTypeAlias(alias)
4127+
protected def derivedAlias(tp: AliasingBounds, alias: Type): Type =
4128+
tp.derivedAlias(alias)
41044129
protected def derivedTypeBounds(tp: TypeBounds, lo: Type, hi: Type): Type =
41054130
tp.derivedTypeBounds(lo, hi)
41064131
protected def derivedSuperType(tp: SuperType, thistp: Type, supertp: Type): Type =
@@ -4167,8 +4192,8 @@ object Types {
41674192
case tp: RefinedType =>
41684193
derivedRefinedType(tp, this(tp.parent), this(tp.refinedInfo))
41694194

4170-
case tp: TypeAlias =>
4171-
derivedTypeAlias(tp, atVariance(0)(this(tp.alias)))
4195+
case tp: AliasingBounds =>
4196+
derivedAlias(tp, atVariance(0)(this(tp.alias)))
41724197

41734198
case tp: TypeBounds =>
41744199
variance = -variance
@@ -4387,7 +4412,7 @@ object Types {
43874412
else info match {
43884413
case Range(infoLo: TypeBounds, infoHi: TypeBounds) =>
43894414
assert(variance == 0)
4390-
if (!infoLo.isAlias && !infoHi.isAlias) propagate(infoLo, infoHi)
4415+
if (!infoLo.isTypeAlias && !infoHi.isTypeAlias) propagate(infoLo, infoHi)
43914416
else range(defn.NothingType, tp.parent)
43924417
case Range(infoLo, infoHi) =>
43934418
propagate(infoLo, infoHi)
@@ -4403,13 +4428,13 @@ object Types {
44034428
case _ => tp.rebind(parent)
44044429
}
44054430

4406-
override protected def derivedTypeAlias(tp: TypeAlias, alias: Type) =
4431+
override protected def derivedAlias(tp: AliasingBounds, alias: Type) =
44074432
if (alias eq tp.alias) tp
44084433
else alias match {
44094434
case Range(lo, hi) =>
44104435
if (variance > 0) TypeBounds(lo, hi)
4411-
else range(TypeAlias(lo), TypeAlias(hi))
4412-
case _ => tp.derivedTypeAlias(alias)
4436+
else range(tp.derivedAlias(lo), tp.derivedAlias(hi))
4437+
case _ => tp.derivedAlias(alias)
44134438
}
44144439

44154440
override protected def derivedTypeBounds(tp: TypeBounds, lo: Type, hi: Type) =

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,10 @@ class TreeUnpickler(reader: TastyReader,
325325
case APPLIEDtype =>
326326
readType().appliedTo(until(end)(readType()))
327327
case TYPEBOUNDS =>
328-
TypeBounds(readType(), readType())
328+
val lo = readType()
329+
val hi = readType()
330+
if (lo.isMatch && (lo `eq` hi)) MatchAlias(lo)
331+
else TypeBounds(lo, hi)
329332
case ANNOTATEDtype =>
330333
AnnotatedType(readType(), Annotation(readTerm()))
331334
case ANDtype =>

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
204204
val bounds =
205205
if (constr.contains(tp)) constr.fullBounds(tp.origin)(ctx.addMode(Mode.Printing))
206206
else TypeBounds.empty
207-
if (bounds.isAlias) toText(bounds.lo) ~ (Str("^") provided ctx.settings.YprintDebug.value)
207+
if (bounds.isTypeAlias) toText(bounds.lo) ~ (Str("^") provided ctx.settings.YprintDebug.value)
208208
else if (ctx.settings.YshowVarBounds.value) "(" ~ toText(tp.origin) ~ "?" ~ toText(bounds) ~ ")"
209209
else toText(tp.origin)
210210
}
@@ -316,7 +316,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
316316
/** String representation of a definition's type following its name */
317317
protected def toTextRHS(tp: Type): Text = controlled {
318318
homogenize(tp) match {
319-
case tp: TypeAlias =>
319+
case tp: AliasingBounds =>
320320
" = " ~ toText(tp.alias)
321321
case tp @ TypeBounds(lo, hi) =>
322322
(if (lo isRef defn.NothingClass) Text() else " >: " ~ toText(lo)) ~

compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class ReplPrinter(_ctx: Context) extends DecompilerPrinter(_ctx) {
3232
override def dclText(sym: Symbol): Text = {
3333
toText(sym) ~ {
3434
if (sym.is(Method)) toText(sym.info)
35-
else if (sym.isType && sym.info.isInstanceOf[TypeAlias]) toText(sym.info)
35+
else if (sym.isType && sym.info.isTypeAlias) toText(sym.info)
3636
else if (sym.isType || sym.isClass) ""
3737
else ":" ~~ toText(sym.info)
3838
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -877,7 +877,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
877877
val ttree =
878878
typedType(untpd.rename(tree, tree.name.toTypeName))(nestedCtx)
879879
ttree.tpe match {
880-
case alias: TypeRef if alias.info.isAlias && !nestedCtx.reporter.hasErrors =>
880+
case alias: TypeRef if alias.info.isTypeAlias && !nestedCtx.reporter.hasErrors =>
881881
companionRef(alias) match {
882882
case companion: TermRef => return untpd.ref(companion) withPos tree.pos
883883
case _ =>

0 commit comments

Comments
 (0)