Skip to content

Commit 7ecf892

Browse files
committed
Introduce recursive types
Map self-references in refinements to recursive types. This commit does this for refinement types appearing in source. We still have to do it for unpickled refinements. Test apply-equiv got moved to pending because it simulates the old higher-kinded type encoding in source, which relies on the old representation in terms of self-referential refinement types. The plan is not to adapt this encoding to the new representation, but to replace it with a different encoding that makes critical use of the added power of recursive types.
1 parent 870c44a commit 7ecf892

16 files changed

+281
-31
lines changed

src/dotty/tools/dotc/core/Substituters.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,24 @@ trait Substituters { this: Context =>
197197
.mapOver(tp)
198198
}
199199

200+
final def substRecThis(tp: Type, from: Type, to: Type, theMap: SubstRecThisMap): Type =
201+
tp match {
202+
case tp @ RecThis(binder) =>
203+
if (binder eq from) to else tp
204+
case tp: NamedType =>
205+
if (tp.currentSymbol.isStatic) tp
206+
else tp.derivedSelect(substRecThis(tp.prefix, from, to, theMap))
207+
case _: ThisType | _: BoundType | NoPrefix =>
208+
tp
209+
case tp: RefinedType =>
210+
tp.derivedRefinedType(substRecThis(tp.parent, from, to, theMap), tp.refinedName, substRecThis(tp.refinedInfo, from, to, theMap))
211+
case tp: TypeAlias =>
212+
tp.derivedTypeAlias(substRecThis(tp.alias, from, to, theMap))
213+
case _ =>
214+
(if (theMap != null) theMap else new SubstRecThisMap(from, to))
215+
.mapOver(tp)
216+
}
217+
200218
final def substParam(tp: Type, from: ParamType, to: Type, theMap: SubstParamMap): Type =
201219
tp match {
202220
case tp: BoundType =>
@@ -270,6 +288,10 @@ trait Substituters { this: Context =>
270288
def apply(tp: Type): Type = substRefinedThis(tp, from, to, this)
271289
}
272290

291+
final class SubstRecThisMap(from: Type, to: Type) extends DeepTypeMap {
292+
def apply(tp: Type): Type = substRecThis(tp, from, to, this)
293+
}
294+
273295
final class SubstParamMap(from: ParamType, to: Type) extends DeepTypeMap {
274296
def apply(tp: Type) = substParam(tp, from, to, this)
275297
}

src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1117,10 +1117,11 @@ object SymDenotations {
11171117

11181118
def debugString = toString + "#" + symbol.id // !!! DEBUG
11191119

1120-
def hasSkolems(tp: Type): Boolean = tp match {
1120+
def hasSkolems(tp: Type): Boolean = tp match {
11211121
case tp: SkolemType => true
11221122
case tp: NamedType => hasSkolems(tp.prefix)
11231123
case tp: RefinedType => hasSkolems(tp.parent) || hasSkolems(tp.refinedInfo)
1124+
case tp: RecType => hasSkolems(tp.parent)
11241125
case tp: PolyType => tp.paramBounds.exists(hasSkolems) || hasSkolems(tp.resType)
11251126
case tp: MethodType => tp.paramTypes.exists(hasSkolems) || hasSkolems(tp.resType)
11261127
case tp: ExprType => hasSkolems(tp.resType)

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

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,25 @@ object TypeApplicationsNewHK {
2222
import TypeApplications._
2323

2424
object TypeLambda {
25-
def apply(argBindingFns: List[RefinedType => TypeBounds],
26-
bodyFn: RefinedType => Type)(implicit ctx: Context): Type = {
25+
def apply(argBindingFns: List[RecType => TypeBounds],
26+
bodyFn: RecType => Type)(implicit ctx: Context): Type = {
2727
val argNames = argBindingFns.indices.toList.map(tpnme.hkArg)
28-
RefinedType.recursive(bodyFn, argNames, argBindingFns)
28+
var idx = 0
29+
RecType.closeOver(rt =>
30+
(bodyFn(rt) /: argBindingFns) { (parent, argBindingFn) =>
31+
val res = RefinedType(parent, tpnme.hkArg(idx), argBindingFn(rt))
32+
idx += 1
33+
res
34+
})
2935
}
3036

3137
def unapply(tp: Type)(implicit ctx: Context): Option[(List[TypeBounds], Type)] = {
3238
def decompose(t: Type, acc: List[TypeBounds]): (List[TypeBounds], Type) = t match {
3339
case t @ RefinedType(p, rname, rinfo: TypeBounds)
3440
if rname.isHkArgName && rinfo.isBinding =>
3541
decompose(p, rinfo.bounds :: acc)
42+
case t: RecType =>
43+
decompose(t.parent, acc)
3644
case _ =>
3745
(acc, t)
3846
}
@@ -78,13 +86,13 @@ object TypeApplications {
7886
* [v1 X1: B1, ..., vn Xn: Bn] -> T
7987
* ==>
8088
* ([X_i := this.$hk_i] T) { type v_i $hk_i: (new)B_i }
81-
*
89+
*
8290
* [X] -> List[X]
83-
*
91+
*
8492
* List { type List$A = this.$hk_0 } { type $hk_0 }
85-
*
93+
*
8694
* [X] -> X
87-
*
95+
*
8896
* mu(this) this.$hk_0 & { type $hk_0 }
8997
*/
9098
object TypeLambda {
@@ -212,6 +220,10 @@ object TypeApplications {
212220
def argRefs(rt: RefinedType, n: Int)(implicit ctx: Context) =
213221
List.range(0, n).map(i => RefinedThis(rt).select(tpnme.hkArg(i)))
214222

223+
/** The references `<rt>.this.$hk0, ..., <rt>.this.$hk<n-1>`. */
224+
def argRefs(rt: RecType, n: Int)(implicit ctx: Context) =
225+
List.range(0, n).map(i => RecThis(rt).select(tpnme.hkArg(i)))
226+
215227
/** Merge `tp1` and `tp2` under a common lambda, combining them with `op`.
216228
* @param tparams1 The type parameters of `tp1`
217229
* @param tparams2 The type parameters of `tp2`
@@ -400,7 +412,7 @@ class TypeApplications(val self: Type) extends AnyVal {
400412
* but without forcing anything.
401413
*/
402414
def classNotLambda(implicit ctx: Context): Boolean = self.stripTypeVar match {
403-
case self: RefinedType =>
415+
case self: RefinedOrRecType =>
404416
self.parent.classNotLambda
405417
case self: TypeRef =>
406418
self.denot.exists && {
@@ -428,6 +440,14 @@ class TypeApplications(val self: Type) extends AnyVal {
428440
new ctx.SafeSubstMap(tparams, argRefs(rt, tparams.length))
429441
.apply(self).asInstanceOf[T]
430442

443+
/** Replace references to type parameters with references to hk arguments `this.$hk_i`
444+
* Care is needed not to cause cyclic reference errors, hence `SafeSubstMap`.
445+
*/
446+
def recursify[T <: Type](tparams: List[Symbol])(implicit ctx: Context): RecType => T =
447+
(rt: RecType) =>
448+
new ctx.SafeSubstMap(tparams, argRefs(rt, tparams.length))
449+
.apply(self).asInstanceOf[T]
450+
431451
/** Lambda abstract `self` with given type parameters. Examples:
432452
*
433453
* type T[X] = U becomes type T = [X] -> U
@@ -546,6 +566,8 @@ class TypeApplications(val self: Type) extends AnyVal {
546566
arg.prefix.select(boundLambda)
547567
case arg: RefinedType =>
548568
arg.derivedRefinedType(adaptArg(arg.parent), arg.refinedName, arg.refinedInfo)
569+
case arg: RecType =>
570+
arg.derivedRecType(adaptArg(arg.parent))
549571
case arg @ TypeAlias(alias) =>
550572
arg.derivedTypeAlias(adaptArg(alias))
551573
case arg @ TypeBounds(lo, hi) =>
@@ -814,6 +836,8 @@ class TypeApplications(val self: Type) extends AnyVal {
814836
}
815837
case tp: RefinedType =>
816838
recur(tp.refinedInfo) || recur(tp.parent)
839+
case tp: RecType =>
840+
recur(tp.parent)
817841
case tp: TypeBounds =>
818842
recur(tp.lo) || recur(tp.hi)
819843
case tp: AnnotatedType =>

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
378378
isSubRefinements(tp1w.asInstanceOf[RefinedType], tp2, skipped2)
379379
}
380380
compareRefined
381+
case tp2: RecType =>
382+
val tp1stable = ensureStableSingleton(tp1)
383+
isSubType(fixRecs(tp1stable, tp1stable.widenExpr), tp2.substRecThis(tp2, tp1stable))
381384
case OrType(tp21, tp22) =>
382385
// Rewrite T1 <: (T211 & T212) | T22 to T1 <: (T211 | T22) and T1 <: (T212 | T22)
383386
// and analogously for T1 <: T21 | (T221 & T222)
@@ -465,7 +468,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
465468
case _ =>
466469
def isNullable(tp: Type): Boolean = tp.dealias match {
467470
case tp: TypeRef => tp.symbol.isNullableClass
468-
case tp: RefinedType => isNullable(tp.parent)
471+
case tp: RefinedOrRecType => isNullable(tp.parent)
469472
case AndType(tp1, tp2) => isNullable(tp1) && isNullable(tp2)
470473
case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2)
471474
case _ => false
@@ -494,6 +497,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
494497
isNewSubType(tp1.parent, tp2) ||
495498
compareHkLambda(tp1, tp2, inOrder = true) ||
496499
compareAliasedRefined(tp1, tp2, inOrder = true)
500+
case tp1: RecType =>
501+
isNewSubType(tp1.parent, tp2)
497502
case AndType(tp11, tp12) =>
498503
// Rewrite (T111 | T112) & T12 <: T2 to (T111 & T12) <: T2 and (T112 | T12) <: T2
499504
// and analogously for T11 & (T121 | T122) & T12 <: T2
@@ -642,6 +647,25 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
642647
}
643648
}
644649

650+
/** Replace any top-level recursive type `{ z => T }` in `tp` with
651+
* `[z := anchor]T`.
652+
*/
653+
private def fixRecs(anchor: SingletonType, tp: Type): Type = {
654+
def fix(tp: Type): Type = tp.stripTypeVar match {
655+
case tp: RecType => fix(tp.parent).substRecThis(tp, anchor)
656+
case tp @ RefinedType(parent, rname, rinfo) => tp.derivedRefinedType(fix(parent), rname, rinfo)
657+
case tp: PolyParam => fixOrElse(bounds(tp).hi, tp)
658+
case tp: TypeProxy => fixOrElse(tp.underlying, tp)
659+
case tp: AndOrType => tp.derivedAndOrType(fix(tp.tp1), fix(tp.tp2))
660+
case tp => tp
661+
}
662+
def fixOrElse(tp: Type, fallback: Type) = {
663+
val tp1 = fix(tp)
664+
if (tp1 ne tp) tp1 else fallback
665+
}
666+
fix(tp)
667+
}
668+
645669
/** The symbol referred to in the refinement of `rt` */
646670
private def refinedSymbol(rt: RefinedType) = rt.parent.member(rt.refinedName).symbol
647671

@@ -772,7 +796,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
772796

773797
/** A type has been covered previously in subtype checking if it
774798
* is some combination of TypeRefs that point to classes, where the
775-
* combiners are RefinedTypes, AndTypes or AnnotatedTypes.
799+
* combiners are RefinedTypes, RecTypes, AndTypes or AnnotatedTypes.
776800
* One exception: Refinements referring to basetype args are never considered
777801
* to be already covered. This is necessary because such refined types might
778802
* still need to be compared with a compareAliasRefined.
@@ -781,6 +805,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
781805
case tp: TypeRef => tp.symbol.isClass && tp.symbol != NothingClass && tp.symbol != NullClass
782806
case tp: ProtoType => false
783807
case tp: RefinedType => isCovered(tp.parent) && !refinedSymbol(tp).is(BaseTypeArg)
808+
case tp: RecType => isCovered(tp.parent)
784809
case tp: AnnotatedType => isCovered(tp.underlying)
785810
case AndType(tp1, tp2) => isCovered(tp1) && isCovered(tp2)
786811
case _ => false
@@ -1118,6 +1143,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
11181143
case _ =>
11191144
NoType
11201145
}
1146+
case tp1: RecType =>
1147+
tp1.rebind(distributeAnd(tp1.parent, tp2))
11211148
case tp1: TypeBounds =>
11221149
tp2 match {
11231150
case tp2: TypeBounds => tp1 & tp2

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,16 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
232232
}
233233
case _ =>
234234
}
235+
235236
tp1 match {
237+
case tp1: RecType =>
238+
tp1.rebind(approximateOr(tp1.parent, tp2))
236239
case tp1: TypeProxy if !isClassRef(tp1) =>
237240
approximateUnion(next(tp1) | tp2)
238241
case _ =>
239242
tp2 match {
243+
case tp2: RecType =>
244+
tp2.rebind(approximateOr(tp1, tp2.parent))
240245
case tp2: TypeProxy if !isClassRef(tp2) =>
241246
approximateUnion(tp1 | next(tp2))
242247
case _ =>
@@ -252,16 +257,32 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
252257
if (ctx.featureEnabled(defn.LanguageModuleClass, nme.keepUnions)) tp
253258
else tp match {
254259
case tp: OrType =>
255-
approximateOr(tp.tp1, tp.tp2)
260+
approximateOr(tp.tp1, tp.tp2) // Maybe refactor using liftToRec?
256261
case tp @ AndType(tp1, tp2) =>
257262
tp derived_& (approximateUnion(tp1), approximateUnion(tp2))
258263
case tp: RefinedType =>
259264
tp.derivedRefinedType(approximateUnion(tp.parent), tp.refinedName, tp.refinedInfo)
265+
case tp: RecType =>
266+
tp.rebind(approximateUnion(tp.parent))
260267
case _ =>
261268
tp
262269
}
263270
}
264271

272+
/** Not currently needed:
273+
*
274+
def liftToRec(f: (Type, Type) => Type)(tp1: Type, tp2: Type)(implicit ctx: Context) = {
275+
def f2(tp1: Type, tp2: Type): Type = tp2 match {
276+
case tp2: RecType => tp2.rebind(f(tp1, tp2.parent))
277+
case _ => f(tp1, tp2)
278+
}
279+
tp1 match {
280+
case tp1: RecType => tp1.rebind(f2(tp1.parent, tp2))
281+
case _ => f2(tp1, tp2)
282+
}
283+
}
284+
*/
285+
265286
private def enterArgBinding(formal: Symbol, info: Type, cls: ClassSymbol, decls: Scope) = {
266287
val lazyInfo = new LazyType { // needed so we do not force `formal`.
267288
def complete(denot: SymDenotation)(implicit ctx: Context): Unit = {

0 commit comments

Comments
 (0)