Skip to content

Commit 474d997

Browse files
committed
Merge pull request #1186 from dotty-staging/fix-#1185
Improvements to cyclic checking, avoidance, named parameters
2 parents 1a6eedd + f675ad9 commit 474d997

16 files changed

+380
-180
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,9 @@ object SymDenotations {
10681068
/** The type parameters of a class symbol, Nil for all other symbols */
10691069
def typeParams(implicit ctx: Context): List[TypeSymbol] = Nil
10701070

1071+
/** The named type parameters declared or inherited by this symbol */
1072+
def namedTypeParams(implicit ctx: Context): Set[TypeSymbol] = Set()
1073+
10711074
/** The type This(cls), where cls is this class, NoPrefix for all other symbols */
10721075
def thisType(implicit ctx: Context): Type = NoPrefix
10731076

@@ -1201,6 +1204,8 @@ object SymDenotations {
12011204
/** TODO: Document why caches are supposedly safe to use */
12021205
private[this] var myTypeParams: List[TypeSymbol] = _
12031206

1207+
private[this] var myNamedTypeParams: Set[TypeSymbol] = _
1208+
12041209
/** The type parameters of this class */
12051210
override final def typeParams(implicit ctx: Context): List[TypeSymbol] = {
12061211
def computeTypeParams = {
@@ -1213,6 +1218,16 @@ object SymDenotations {
12131218
myTypeParams
12141219
}
12151220

1221+
/** The named type parameters declared or inherited by this class */
1222+
override final def namedTypeParams(implicit ctx: Context): Set[TypeSymbol] = {
1223+
def computeNamedTypeParams: Set[TypeSymbol] =
1224+
if (ctx.erasedTypes || is(Module)) Set() // fast return for modules to avoid scanning package decls
1225+
else memberNames(abstractTypeNameFilter).map(name =>
1226+
info.member(name).symbol.asType).filter(_.is(TypeParam, butNot = ExpandedName)).toSet
1227+
if (myNamedTypeParams == null) myNamedTypeParams = computeNamedTypeParams
1228+
myNamedTypeParams
1229+
}
1230+
12161231
override protected[dotc] final def info_=(tp: Type) = {
12171232
super.info_=(tp)
12181233
myTypeParams = null // changing the info might change decls, and with it typeParams

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,67 @@ class TypeApplications(val self: Type) extends AnyVal {
272272
}
273273
}
274274

275+
/** The named type parameters declared or inherited by this type.
276+
* These are all uninstantiated named type parameters of this type or one
277+
* of its base types.
278+
*/
279+
final def namedTypeParams(implicit ctx: Context): Set[TypeSymbol] = self match {
280+
case self: ClassInfo =>
281+
self.cls.namedTypeParams
282+
case self: RefinedType =>
283+
self.parent.namedTypeParams.filterNot(_.name == self.refinedName)
284+
case self: SingletonType =>
285+
Set()
286+
case self: TypeProxy =>
287+
self.underlying.namedTypeParams
288+
case _ =>
289+
Set()
290+
}
291+
292+
/** The smallest supertype of this type that instantiated none of the named type parameters
293+
* in `params`. That is, for each named type parameter `p` in `params`, either there is
294+
* no type field named `p` in this type, or `p` is a named type parameter of this type.
295+
* The first case is important for the recursive case of AndTypes, because some of their operands might
296+
* be missing the named parameter altogether, but the AndType as a whole can still
297+
* contain it.
298+
*/
299+
final def widenToNamedTypeParams(params: Set[TypeSymbol])(implicit ctx: Context): Type = {
300+
301+
/** Is widening not needed for `tp`? */
302+
def isOK(tp: Type) = {
303+
val ownParams = tp.namedTypeParams
304+
def isMissingOrOpen(param: TypeSymbol) = {
305+
val ownParam = tp.nonPrivateMember(param.name).symbol
306+
!ownParam.exists || ownParams.contains(ownParam.asType)
307+
}
308+
params.forall(isMissingOrOpen)
309+
}
310+
311+
/** Widen type by forming the intersection of its widened parents */
312+
def widenToParents(tp: Type) = {
313+
val parents = tp.parents.map(p =>
314+
tp.baseTypeWithArgs(p.symbol).widenToNamedTypeParams(params))
315+
parents.reduceLeft(ctx.typeComparer.andType(_, _))
316+
}
317+
318+
if (isOK(self)) self
319+
else self match {
320+
case self @ AppliedType(tycon, args) if !isOK(tycon) =>
321+
widenToParents(self)
322+
case self: TypeRef if self.symbol.isClass =>
323+
widenToParents(self)
324+
case self: RefinedType =>
325+
val parent1 = self.parent.widenToNamedTypeParams(params)
326+
if (params.exists(_.name == self.refinedName)) parent1
327+
else self.derivedRefinedType(parent1, self.refinedName, self.refinedInfo)
328+
case self: TypeProxy =>
329+
self.underlying.widenToNamedTypeParams(params)
330+
case self: AndOrType =>
331+
self.derivedAndOrType(
332+
self.tp1.widenToNamedTypeParams(params), self.tp2.widenToNamedTypeParams(params))
333+
}
334+
}
335+
275336
/** The Lambda trait underlying a type lambda */
276337
def LambdaTrait(implicit ctx: Context): Symbol = self.stripTypeVar match {
277338
case RefinedType(parent, tpnme.hkApply) =>

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,15 +1074,29 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
10741074
}
10751075
}
10761076

1077-
/** `op(tp1, tp2)` unless `tp1` and `tp2` are type-constructors.
1077+
/** `op(tp1, tp2)` unless `tp1` and `tp2` are type-constructors with at least
1078+
* some unnamed type parameters.
10781079
* In the latter case, combine `tp1` and `tp2` under a type lambda like this:
10791080
*
10801081
* [X1, ..., Xn] -> op(tp1[X1, ..., Xn], tp2[X1, ..., Xn])
1082+
*
1083+
* Note: There is a tension between named and positional parameters here, which
1084+
* is impossible to resolve completely. Say you have
1085+
*
1086+
* C[type T], D[type U]
1087+
*
1088+
* Then do you expand `C & D` to `[T] -> C[T] & D[T]` or not? Under the named
1089+
* type parameter interpretation, this would be wrong whereas under the traditional
1090+
* higher-kinded interpretation this would be required. The problem arises from
1091+
* allowing both interpretations. A possible remedy is to be somehow stricter
1092+
* in where we allow which interpretation.
10811093
*/
10821094
private def liftIfHK(tp1: Type, tp2: Type, op: (Type, Type) => Type) = {
10831095
val tparams1 = tp1.typeParams
10841096
val tparams2 = tp2.typeParams
1085-
if (tparams1.isEmpty || tparams2.isEmpty) op(tp1, tp2)
1097+
def onlyNamed(tparams: List[TypeSymbol]) = tparams.forall(!_.is(ExpandedName))
1098+
if (tparams1.isEmpty || tparams2.isEmpty ||
1099+
onlyNamed(tparams1) && onlyNamed(tparams2)) op(tp1, tp2)
10861100
else if (tparams1.length != tparams2.length) mergeConflict(tp1, tp2)
10871101
else hkCombine(tp1, tp2, tparams1, tparams2, op)
10881102
}

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

Lines changed: 9 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -122,100 +122,21 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
122122
def currentVariance = variance
123123
}
124124

125-
/** Approximate a type `tp` with a type that does not contain skolem types.
126-
*/
127-
final def deskolemize(tp: Type): Type = deskolemize(tp, 1, Set())
128-
129-
private def deskolemize(tp: Type, variance: Int, seen: Set[SkolemType]): Type = {
130-
def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType, newSeen: Set[SkolemType] = seen) =
131-
if (variance == 0) NoType
132-
else deskolemize(if (variance < 0) lo else hi, variance, newSeen)
133-
tp match {
125+
/** Approximate a type `tp` with a type that does not contain skolem types. */
126+
object deskolemize extends ApproximatingTypeMap {
127+
private var seen: Set[SkolemType] = Set()
128+
def apply(tp: Type) = tp match {
134129
case tp: SkolemType =>
135130
if (seen contains tp) NoType
136-
else approx(hi = tp.info, newSeen = seen + tp)
137-
case tp: NamedType =>
138-
val sym = tp.symbol
139-
if (sym.isStatic) tp
140131
else {
141-
val pre1 = deskolemize(tp.prefix, variance, seen)
142-
if (pre1 eq tp.prefix) tp
143-
else {
144-
val d = tp.prefix.member(tp.name)
145-
d.info match {
146-
case TypeAlias(alias) => deskolemize(alias, variance, seen)
147-
case _ =>
148-
if (pre1.exists && !pre1.isRef(defn.NothingClass)) tp.derivedSelect(pre1)
149-
else {
150-
ctx.log(s"deskolem: $tp: ${tp.info}")
151-
tp.info match {
152-
case TypeBounds(lo, hi) => approx(lo, hi)
153-
case info => approx(defn.NothingType, info)
154-
}
155-
}
156-
}
157-
}
132+
val saved = seen
133+
seen += tp
134+
try approx(hi = tp.info)
135+
finally seen = saved
158136
}
159-
case _: ThisType | _: BoundType | _: SuperType | NoType | NoPrefix =>
160-
tp
161-
case tp: RefinedType =>
162-
val parent1 = deskolemize(tp.parent, variance, seen)
163-
if (parent1.exists) {
164-
val refinedInfo1 = deskolemize(tp.refinedInfo, variance, seen)
165-
if (refinedInfo1.exists)
166-
tp.derivedRefinedType(parent1, tp.refinedName, refinedInfo1)
167-
else
168-
approx(hi = parent1)
169-
}
170-
else approx()
171-
case tp: TypeAlias =>
172-
val alias1 = deskolemize(tp.alias, variance * tp.variance, seen)
173-
if (alias1.exists) tp.derivedTypeAlias(alias1)
174-
else approx(hi = TypeBounds.empty)
175-
case tp: TypeBounds =>
176-
val lo1 = deskolemize(tp.lo, -variance, seen)
177-
val hi1 = deskolemize(tp.hi, variance, seen)
178-
if (lo1.exists && hi1.exists) tp.derivedTypeBounds(lo1, hi1)
179-
else approx(hi =
180-
if (lo1.exists) TypeBounds.lower(lo1)
181-
else if (hi1.exists) TypeBounds.upper(hi1)
182-
else TypeBounds.empty)
183-
case tp: ClassInfo =>
184-
val pre1 = deskolemize(tp.prefix, variance, seen)
185-
if (pre1.exists) tp.derivedClassInfo(pre1)
186-
else NoType
187-
case tp: AndOrType =>
188-
val tp1d = deskolemize(tp.tp1, variance, seen)
189-
val tp2d = deskolemize(tp.tp2, variance, seen)
190-
if (tp1d.exists && tp2d.exists)
191-
tp.derivedAndOrType(tp1d, tp2d)
192-
else if (tp.isAnd)
193-
approx(hi = tp1d & tp2d) // if one of tp1d, tp2d exists, it is the result of tp1d & tp2d
194-
else
195-
approx(lo = tp1d & tp2d)
196-
case tp: WildcardType =>
197-
val bounds1 = deskolemize(tp.optBounds, variance, seen)
198-
if (bounds1.exists) tp.derivedWildcardType(bounds1)
199-
else WildcardType
200137
case _ =>
201138
if (tp.isInstanceOf[MethodicType]) assert(variance != 0, tp)
202-
deskolemizeMap.mapOver(tp, variance, seen)
203-
}
204-
}
205-
206-
object deskolemizeMap extends TypeMap {
207-
private var seen: Set[SkolemType] = _
208-
def apply(tp: Type) = deskolemize(tp, variance, seen)
209-
def mapOver(tp: Type, variance: Int, seen: Set[SkolemType]) = {
210-
val savedVariance = this.variance
211-
val savedSeen = this.seen
212-
this.variance = variance
213-
this.seen = seen
214-
try super.mapOver(tp)
215-
finally {
216-
this.variance = savedVariance
217-
this.seen = savedSeen
218-
}
139+
mapOver(tp)
219140
}
220141
}
221142

0 commit comments

Comments
 (0)