Skip to content

Commit 2a2940e

Browse files
committed
Merge pull request #787 from dotty-staging/fix-#779-recursive-hk-types
Fix #779 recursive hk types
2 parents 4f2e912 + ef4ecc1 commit 2a2940e

File tree

3 files changed

+34
-5
lines changed

3 files changed

+34
-5
lines changed

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,17 @@ class TypeApplications(val self: Type) extends AnyVal {
202202
if (args.isEmpty || ctx.erasedTypes) self
203203
else {
204204
val res = instantiate(self, self)
205-
if (res.isInstantiatedLambda) res.select(tpnme.Apply) else res
205+
if (res.isInstantiatedLambda)
206+
// Note: isInstantiatedLambda needs to be conservative, using isSafeLambda
207+
// in order to avoid cyclic reference errors. But this means that some fully
208+
// instantiated types will remain unprojected, which essentially means
209+
// that they stay as higher-kinded types. checkNonCyclic checks the type again
210+
// and potentially inserts an #Apply then. Hopefully, this catches all types
211+
// that fall through the hole. Not adding an #Apply typically manifests itself
212+
// with a <:< failure of two types that "look the same". An example is #779,
213+
// where compiling scala.immutable.Map gives a bounds violation.
214+
res.select(tpnme.Apply)
215+
else res
206216
}
207217
}
208218

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
4343
* for more complicated types.
4444
*/
4545
final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type = {
46-
val m = if (pre.isStable || !ctx.phase.isTyper) null else new AsSeenFromMap(pre, cls)
46+
val m = if (isLegalPrefix(pre)) null else new AsSeenFromMap(pre, cls)
4747
var res = asSeenFrom(tp, pre, cls, m)
4848
if (m != null && m.unstable) asSeenFrom(tp, SkolemType(pre), cls) else res
4949
}
@@ -61,7 +61,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
6161
if ((pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass))
6262
tp
6363
else if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists) {
64-
if (theMap != null && theMap.currentVariance <= 0 && !pre.isStable)
64+
if (theMap != null && theMap.currentVariance <= 0 && !isLegalPrefix(pre))
6565
theMap.unstable = true
6666
pre match {
6767
case SuperType(thispre, _) => thispre
@@ -111,6 +111,9 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
111111
}
112112
}
113113

114+
private def isLegalPrefix(pre: Type)(implicit ctx: Context) =
115+
pre.isStable || !ctx.phase.isTyper
116+
114117
/** The TypeMap handling the asSeenFrom in more complicated cases */
115118
class AsSeenFromMap(pre: Type, cls: Symbol) extends TypeMap {
116119
def apply(tp: Type) = asSeenFrom(tp, pre, cls, this)

src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,18 @@ object Checking {
115115
val parent1 = this(parent)
116116
val saved = cycleOK
117117
cycleOK = nestedCycleOK
118-
try tp.derivedRefinedType(parent1, name, this(tp.refinedInfo))
119-
finally cycleOK = saved
118+
119+
/** A derived refined type with two possible tweaks:
120+
* (1) LazyRefs in parents are pulled out,
121+
* (2) #Apply is added if the type is a fully applied type lambda.
122+
*/
123+
def derivedType(p: Type): Type = p match {
124+
case p: LazyRef => LazyRef(() => derivedType(p.ref))
125+
case _ =>
126+
val res = tp.derivedRefinedType(p, name, this(tp.refinedInfo))
127+
if (res.isSafeLambda && res.typeParams.isEmpty) res.select(tpnme.Apply) else res
128+
}
129+
try derivedType(parent1) finally cycleOK = saved
120130
case tp @ TypeRef(pre, name) =>
121131
try {
122132
// A prefix is interesting if it might contain (transitively) a reference
@@ -130,6 +140,9 @@ object Checking {
130140
case SuperType(thistp, _) => isInteresting(thistp)
131141
case AndType(tp1, tp2) => isInteresting(tp1) || isInteresting(tp2)
132142
case OrType(tp1, tp2) => isInteresting(tp1) && isInteresting(tp2)
143+
case _: RefinedType => false
144+
// Note: it's important not to visit parents of RefinedTypes,
145+
// since otherwise spurious #Apply projections might be inserted.
133146
case _ => false
134147
}
135148
// If prefix is interesting, check info of typeref recursively, marking the referred symbol
@@ -158,6 +171,9 @@ object Checking {
158171
* @pre sym is not yet initialized (i.e. its type is a Completer).
159172
* @return `info` where every legal F-bounded reference is proctected
160173
* by a `LazyRef`, or `ErrorType` if a cycle was detected and reported.
174+
* Furthermore: Add an #Apply to a fully instantiated type lambda, if none was
175+
* given before. This is necessary here because sometimes type lambdas are not
176+
* recognized when they are first formed.
161177
*/
162178
def checkNonCyclic(sym: Symbol, info: Type, reportErrors: Boolean)(implicit ctx: Context): Type = {
163179
val checker = new CheckNonCyclicMap(sym, reportErrors)(ctx.addMode(Mode.CheckCyclic))

0 commit comments

Comments
 (0)