diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index f466cee7705a..07a29b1afba8 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -188,7 +188,17 @@ class TypeApplications(val self: Type) extends AnyVal { if (args.isEmpty || ctx.erasedTypes) self else { val res = instantiate(self, self) - if (res.isInstantiatedLambda) res.select(tpnme.Apply) else res + if (res.isInstantiatedLambda) + // Note: isInstantiatedLambda needs to be conservative, using isSafeLambda + // in order to avoid cyclic reference errors. But this means that some fully + // instantiated types will remain unprojected, which essentially means + // that they stay as higher-kinded types. checkNonCyclic checks the type again + // and potentially inserts an #Apply then. Hopefully, this catches all types + // that fall through the hole. Not adding an #Apply typically manifests itself + // with a <:< failure of two types that "look the same". An example is #779, + // where compiling scala.immutable.Map gives a bounds violation. + res.select(tpnme.Apply) + else res } } diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index b61d39749a4b..2a9dbd09c4e3 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -43,7 +43,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. * for more complicated types. */ final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type = { - val m = if (pre.isStable || !ctx.phase.isTyper) null else new AsSeenFromMap(pre, cls) + val m = if (isLegalPrefix(pre)) null else new AsSeenFromMap(pre, cls) var res = asSeenFrom(tp, pre, cls, m) if (m != null && m.unstable) asSeenFrom(tp, SkolemType(pre), cls) else res } @@ -61,7 +61,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. if ((pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass)) tp else if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists) { - if (theMap != null && theMap.currentVariance <= 0 && !pre.isStable) + if (theMap != null && theMap.currentVariance <= 0 && !isLegalPrefix(pre)) theMap.unstable = true pre match { case SuperType(thispre, _) => thispre @@ -111,6 +111,9 @@ trait TypeOps { this: Context => // TODO: Make standalone object. } } + private def isLegalPrefix(pre: Type)(implicit ctx: Context) = + pre.isStable || !ctx.phase.isTyper + /** The TypeMap handling the asSeenFrom in more complicated cases */ class AsSeenFromMap(pre: Type, cls: Symbol) extends TypeMap { def apply(tp: Type) = asSeenFrom(tp, pre, cls, this) diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index cd06618e6d53..3847cb5bef22 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -115,8 +115,18 @@ object Checking { val parent1 = this(parent) val saved = cycleOK cycleOK = nestedCycleOK - try tp.derivedRefinedType(parent1, name, this(tp.refinedInfo)) - finally cycleOK = saved + + /** A derived refined type with two possible tweaks: + * (1) LazyRefs in parents are pulled out, + * (2) #Apply is added if the type is a fully applied type lambda. + */ + def derivedType(p: Type): Type = p match { + case p: LazyRef => LazyRef(() => derivedType(p.ref)) + case _ => + val res = tp.derivedRefinedType(p, name, this(tp.refinedInfo)) + if (res.isSafeLambda && res.typeParams.isEmpty) res.select(tpnme.Apply) else res + } + try derivedType(parent1) finally cycleOK = saved case tp @ TypeRef(pre, name) => try { // A prefix is interesting if it might contain (transitively) a reference @@ -130,6 +140,9 @@ object Checking { case SuperType(thistp, _) => isInteresting(thistp) case AndType(tp1, tp2) => isInteresting(tp1) || isInteresting(tp2) case OrType(tp1, tp2) => isInteresting(tp1) && isInteresting(tp2) + case _: RefinedType => false + // Note: it's important not to visit parents of RefinedTypes, + // since otherwise spurious #Apply projections might be inserted. case _ => false } // If prefix is interesting, check info of typeref recursively, marking the referred symbol @@ -158,6 +171,9 @@ object Checking { * @pre sym is not yet initialized (i.e. its type is a Completer). * @return `info` where every legal F-bounded reference is proctected * by a `LazyRef`, or `ErrorType` if a cycle was detected and reported. + * Furthermore: Add an #Apply to a fully instantiated type lambda, if none was + * given before. This is necessary here because sometimes type lambdas are not + * recognized when they are first formed. */ def checkNonCyclic(sym: Symbol, info: Type, reportErrors: Boolean)(implicit ctx: Context): Type = { val checker = new CheckNonCyclicMap(sym, reportErrors)(ctx.addMode(Mode.CheckCyclic))