diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 48af80adc3c6..7039fec0875b 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -1165,30 +1165,8 @@ object Denotations { info.asSeenFrom(pre, owner), if (symbol.is(Opaque) || this.prefix != NoPrefix) pre else this.prefix) - pre match { - case pre: ThisType if symbol.isOpaqueAlias && pre.cls == owner => - // This code is necessary to compensate for a "window of vulnerability" with - // opaque types. The problematic sequence is as follows. - // 1. Type a selection `m.this.T` where `T` is an opaque type alias in `m` - // and this is the first access - // 2. `T` will normalize to an abstract type on completion. - // 3. At that time, the default logic in the second case is wrong: `T`'s new info - // is now an abstract type and running it through an asSeenFrom gives nothing. - // We fix this as follows: - // 1. Force opaque normalization as first step - // 2. Read the info from the enclosing object's refinement - symbol.normalizeOpaque() - def findRefined(tp: Type, name: Name): Type = tp match { - case RefinedType(parent, rname, rinfo) => - if (rname == name) rinfo else findRefined(parent, name) - case _ => - symbol.info - } - derived(findRefined(pre.underlying, symbol.name)) - case _ => - if (!owner.membersNeedAsSeenFrom(pre) || symbol.is(NonMember)) this - else derived(symbol.info) - } + if (!owner.membersNeedAsSeenFrom(pre) || symbol.is(NonMember)) this + else derived(symbol.info) } /** Does this denotation have all the `required` flags but none of the `excluded` flags? diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 0b5780a52886..2fae3e464abe 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -426,42 +426,44 @@ object SymDenotations { case _ => unforcedDecls.openForMutations } - /** If this is a synthetic opaque type alias, mark it as Deferred with bounds - * as given by the right hand side's `WithBounds` annotation, if one is present, - * or with empty bounds of the right kind, otherwise. - * At the same time, integrate the original alias as a refinement of the + /** If this is an opaque alias, replace the right hand side `info` + * by appropriate bounds and store `info` in the refinement of the * self type of the enclosing class. + * Otherwise return `info` + * + * @param info Is assumed to be a (lambda-abstracted) right hand side TypeAlias + * of the opaque type definition. */ - final def normalizeOpaque()(implicit ctx: Context) = { - def abstractRHS(tp: Type): Type = tp match { - case tp: HKTypeLambda => tp.derivedLambdaType(resType = abstractRHS(tp.resType)) - case _ => defn.AnyType - } - if (isOpaqueAlias) - info match { - case TypeAlias(alias) => - val (refiningAlias, bounds) = alias match { - case AnnotatedType(alias1, Annotation.WithBounds(bounds)) => - (alias1, bounds) - case _ => - (alias, TypeBounds(defn.NothingType, abstractRHS(alias))) - } - def refineSelfType(selfType: Type) = - RefinedType(selfType, name, TypeAlias(refiningAlias)) - val enclClassInfo = owner.asClass.classInfo - enclClassInfo.selfInfo match { - case self: Type => - owner.info = enclClassInfo.derivedClassInfo( - selfInfo = refineSelfType(self.orElse(defn.AnyType))) - case self: Symbol => - self.info = refineSelfType(self.info) - } - info = bounds - setFlag(Deferred) - typeRef.recomputeDenot() - case _ => - } - } + def opaqueToBounds(info: Type)(given Context): Type = + + def setAlias(tp: Type) = + def recur(self: Type): Unit = self match + case RefinedType(parent, name, rinfo) => rinfo match + case TypeAlias(lzy: LazyRef) if name == this.name => + lzy.update(tp) + case _ => + recur(parent) + recur(owner.asClass.givenSelfType) + end setAlias + + def split(tp: Type): (Type, TypeBounds) = tp match + case AnnotatedType(alias, Annotation.WithBounds(bounds)) => + (alias, bounds) + case tp: HKTypeLambda => + val (alias1, bounds1) = split(tp.resType) + (tp.derivedLambdaType(resType = alias1), + HKTypeLambda.boundsFromParams(tp.typeParams, bounds1)) + case _ => + (tp, HKTypeLambda.boundsFromParams(tp.typeParams, TypeBounds.empty)) + + info match + case TypeAlias(tp) if isOpaqueAlias && owner.isClass => + val (alias, bounds) = split(tp) + setAlias(alias) + bounds + case _ => + info + end opaqueToBounds // ------ Names ---------------------------------------------- @@ -1203,7 +1205,7 @@ object SymDenotations { def opaqueAlias(implicit ctx: Context): Type = { def recur(tp: Type): Type = tp match { case RefinedType(parent, rname, TypeAlias(alias)) => - if (rname == name) alias else recur(parent) + if rname == name then alias.stripLazyRef else recur(parent) case _ => NoType } diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index ea2f009579b6..ae8cf5d15cfa 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1727,10 +1727,14 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w else if tp1.isAny && !tp2.isLambdaSub || tp1.isAnyKind || tp2.isRef(NothingClass) then tp2 else if tp2.isAny && !tp1.isLambdaSub || tp2.isAnyKind || tp1.isRef(NothingClass) then tp1 else tp2 match { // normalize to disjunctive normal form if possible. + case tp2: LazyRef => + glb(tp1, tp2.ref) case OrType(tp21, tp22) => tp1 & tp21 | tp1 & tp22 case _ => tp1 match { + case tp1: LazyRef => + glb(tp1.ref, tp2) case OrType(tp11, tp12) => tp11 & tp2 | tp12 & tp2 case _ => @@ -1777,8 +1781,8 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w else if (!tp2.exists) tp2 else if tp1.isAny && !tp2.isLambdaSub || tp1.isAnyKind || tp2.isRef(NothingClass) then tp1 else if tp2.isAny && !tp1.isLambdaSub || tp2.isAnyKind || tp1.isRef(NothingClass) then tp2 - else { - def mergedLub: Type = { + else + def mergedLub(tp1: Type, tp2: Type): Type = { val atoms1 = tp1.atoms(widenOK = true) if (atoms1.nonEmpty && !widenInUnions) { val atoms2 = tp2.atoms(widenOK = true) @@ -1800,8 +1804,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w if ((tp1 ne tp1w) || (tp2 ne tp2w)) lub(tp1w, tp2w) else orType(tp1w, tp2w) // no need to check subtypes again } - mergedLub - } + mergedLub(tp1.stripLazyRef, tp2.stripLazyRef) } /** The least upper bound of a list of types */ diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 714a6f6503d3..185239e2fae0 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1007,7 +1007,8 @@ object Types { * pos/i536 demonstrates that the infinite loop can also involve lower bounds. */ def safe_& (that: Type)(implicit ctx: Context): Type = (this, that) match { - case (TypeBounds(lo1, hi1), TypeBounds(lo2, hi2)) => TypeBounds(OrType(lo1, lo2), AndType(hi1, hi2)) + case (TypeBounds(lo1, hi1), TypeBounds(lo2, hi2)) => + TypeBounds(OrType(lo1.stripLazyRef, lo2.stripLazyRef), AndType(hi1.stripLazyRef, hi2.stripLazyRef)) case _ => this & that } @@ -1043,12 +1044,17 @@ object Types { case _ => this } - /** Strip PolyType prefix */ + /** Strip PolyType prefixes */ def stripPoly(implicit ctx: Context): Type = this match { case tp: PolyType => tp.resType.stripPoly case _ => this } + /** Strip LazyRef wrappers */ + def stripLazyRef(given Context): Type = this match + case lzy: LazyRef => lzy.ref.stripLazyRef + case _ => this + /** Widen from singleton type to its underlying non-singleton * base type by applying one or more `underlying` dereferences, * Also go from => T to T. @@ -1165,7 +1171,7 @@ object Types { * these types as a set, otherwise the empty set. * Overridden and cached in OrType. * @param widenOK If type proxies that are upperbounded by types with atoms - * have the same atoms. + * have the same atoms. */ def atoms(widenOK: Boolean = false)(implicit ctx: Context): Set[Type] = dealias match { case tp: SingletonType => @@ -2566,25 +2572,34 @@ object Types { } } - case class LazyRef(private var refFn: Context => Type) extends UncachedProxyType with ValueType { + case class LazyRef(private var refFn: Context => Type, reportCycles: Boolean = false) extends UncachedProxyType with ValueType { private var myRef: Type = null private var computed = false - def ref(implicit ctx: Context): Type = { - if (computed) { - if (myRef == null) { + + def ref(implicit ctx: Context): Type = + if computed then + if myRef == null then // if errors were reported previously handle this by throwing a CyclicReference // instead of crashing immediately. A test case is neg/i6057.scala. - assert(ctx.reporter.errorsReported) + assert(reportCycles || ctx.reporter.errorsReported) throw CyclicReference(NoDenotation) - } - } - else { + else computed = true - myRef = refFn(ctx) + val result = refFn(ctx) refFn = null - } + if result != null then myRef = result + else assert(myRef != null) // must have been `update`d myRef - } + + /** Update the value of the lazyref, discarding the compute function `refFn` + * Can be called only as long as the ref is still undefined. + */ + def update(tp: Type) = + assert(myRef == null) + myRef = tp + computed = true + refFn = null + def evaluating: Boolean = computed && myRef == null def completed: Boolean = myRef != null override def underlying(implicit ctx: Context): Type = ref @@ -4300,13 +4315,47 @@ object Types { parentsCache } + protected def newLikeThis(prefix: Type, classParents: List[Type], decls: Scope, selfInfo: TypeOrSymbol)(given Context): ClassInfo = + ClassInfo(prefix, cls, classParents, decls, selfInfo) + def derivedClassInfo(prefix: Type)(implicit ctx: Context): ClassInfo = if (prefix eq this.prefix) this - else ClassInfo(prefix, cls, classParents, decls, selfInfo) + else newLikeThis(prefix, classParents, decls, selfInfo) def derivedClassInfo(prefix: Type = this.prefix, classParents: List[Type] = this.classParents, decls: Scope = this.decls, selfInfo: TypeOrSymbol = this.selfInfo)(implicit ctx: Context): ClassInfo = if ((prefix eq this.prefix) && (classParents eq this.classParents) && (decls eq this.decls) && (selfInfo eq this.selfInfo)) this - else ClassInfo(prefix, cls, classParents, decls, selfInfo) + else newLikeThis(prefix, classParents, decls, selfInfo) + + /** If this class has opaque type alias members, a new class info + * with their aliases added as refinements to the self type of the class. + * Otherwise, this classInfo. + * If there are opaque alias members, updates `cls` to have `Opaque` flag as a side effect. + */ + def integrateOpaqueMembers(given Context): ClassInfo = + decls.toList.foldLeft(this) { (cinfo, sym) => + if sym.isOpaqueAlias then + cls.setFlag(Opaque) + def force = + if sym.isOpaqueAlias then // could have been reset because of a syntax error + sym.infoOrCompleter match + case completer: LazyType => + completer.complete(sym) // will update the LazyRef + null // tells the LazyRef to use the updated value + case info => // can occur under cyclic references, e.g. i6225.scala + defn.AnyType + else defn.AnyType // dummy type in case of errors + def refineSelfType(selfType: Type) = + RefinedType(selfType, sym.name, + TypeAlias(LazyRef(_ => force, reportCycles = true))) + cinfo.selfInfo match + case self: Type => + cinfo.derivedClassInfo( + selfInfo = refineSelfType(self.orElse(defn.AnyType))) + case self: Symbol => + self.info = refineSelfType(self.info) + cinfo + else cinfo + } override def computeHash(bs: Binders): Int = doHash(bs, cls, prefix) override def hashIsStable: Boolean = prefix.hashIsStable && classParents.hashIsStable @@ -4343,13 +4392,12 @@ object Types { final class TempClassInfo(prefix: Type, cls: ClassSymbol, decls: Scope, selfInfo: TypeOrSymbol) extends CachedClassInfo(prefix, cls, Nil, decls, selfInfo) { - /** Install classinfo with known parents in `denot` s */ - def finalize(denot: SymDenotation, parents: List[Type])(implicit ctx: Context): Unit = - denot.info = ClassInfo(prefix, cls, parents, decls, selfInfo) + /** Convert to classinfo with known parents */ + def finalized(parents: List[Type])(implicit ctx: Context): ClassInfo = + ClassInfo(prefix, cls, parents, decls, selfInfo) - override def derivedClassInfo(prefix: Type)(implicit ctx: Context): ClassInfo = - if (prefix eq this.prefix) this - else new TempClassInfo(prefix, cls, decls, selfInfo) + override def newLikeThis(prefix: Type, classParents: List[Type], decls: Scope, selfInfo: TypeOrSymbol)(given Context): ClassInfo = + TempClassInfo(prefix, cls, decls, selfInfo) override def toString: String = s"TempClassInfo($prefix, $cls)" } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index cf4826ba2877..8d02c68d02e2 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -570,6 +570,7 @@ class TreeUnpickler(reader: TastyReader, ctx.newSymbol(ctx.owner, name, flags, completer, privateWithin, coord) } sym.annotations = annotFns.map(_(sym)) + if sym.isOpaqueAlias then sym.setFlag(Deferred) ctx.owner match { case cls: ClassSymbol => cls.enter(sym) case _ => @@ -832,11 +833,12 @@ class TreeUnpickler(reader: TastyReader, override def completerTypeParams(sym: Symbol)(implicit ctx: Context) = rhs.tpe.typeParams } - sym.info = rhs.tpe match { - case _: TypeBounds | _: ClassInfo => checkNonCyclic(sym, rhs.tpe, reportErrors = false) - case _ => rhs.tpe.toBounds + sym.info = sym.opaqueToBounds { + rhs.tpe match + case _: TypeBounds | _: ClassInfo => checkNonCyclic(sym, rhs.tpe, reportErrors = false) + case _ => rhs.tpe.toBounds } - sym.normalizeOpaque() + if sym.isOpaqueAlias then sym.typeRef.recomputeDenot() // make sure we see the new bounds from now on sym.resetFlag(Provisional) TypeDef(rhs) } @@ -905,8 +907,10 @@ class TreeUnpickler(reader: TastyReader, } else EmptyValDef cls.setNoInitsFlags(parentsKind(parents), bodyFlags) - cls.info = ClassInfo(cls.owner.thisType, cls, parentTypes, cls.unforcedDecls, - if (self.isEmpty) NoType else self.tpt.tpe) + cls.info = ClassInfo( + cls.owner.thisType, cls, parentTypes, cls.unforcedDecls, + selfInfo = if (self.isEmpty) NoType else self.tpt.tpe) + .integrateOpaqueMembers val constr = readIndexedDef().asInstanceOf[DefDef] val mappedParents = parents.map(_.changeOwner(localDummy, constr.symbol)) diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index a2dd991c50b6..10def4aa7693 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -135,7 +135,7 @@ object Scala2Unpickler { else registerCompanionPair(scalacCompanion, denot.classSymbol) - tempInfo.finalize(denot, normalizedParents) // install final info, except possibly for typeparams ordering + denot.info = tempInfo.finalized(normalizedParents) denot.ensureTypeParamsInCorrectOrder() } } diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 98d9358f8953..3680501fe9f6 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -197,7 +197,11 @@ class PlainPrinter(_ctx: Context) extends Printer { } case tp: ExprType => changePrec(GlobalPrec) { "=> " ~ toText(tp.resultType) } - case tp: TypeLambda => + case tp: HKTypeLambda => + changePrec(GlobalPrec) { + "[" ~ paramsText(tp) ~ "]" ~ lambdaHash(tp) ~ Str(" =>> ") ~ toTextGlobal(tp.resultType) + } + case tp: PolyType => changePrec(GlobalPrec) { "[" ~ paramsText(tp) ~ "]" ~ lambdaHash(tp) ~ (Str(" => ") provided !tp.resultType.isInstanceOf[MethodType]) ~ diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index fe124b4ede06..22fd2b9f89c6 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1262,7 +1262,8 @@ object messages { case class CyclicReferenceInvolving(denot: SymDenotation)(implicit ctx: Context) extends Message(CyclicReferenceInvolvingID) { val kind: String = "Cyclic" - val msg: String = em"""Cyclic reference involving $denot""" + val where = if denot.exists then s" involving $denot" else "" + val msg: String = em"Cyclic reference $where" val explanation: String = em"""|$denot is declared as part of a cycle which makes it impossible for the |compiler to decide upon ${denot.name}'s type. diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index a57c5b39a244..c8e4f8587167 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -403,7 +403,8 @@ class Namer { typer: Typer => flags |= HigherKinded analyzeRHS(body) case _ => - if tree.rhs.isEmpty then flags |= Deferred else analyzeRHS(tree.rhs) + if rhs.isEmpty || flags.is(Opaque) then flags |= Deferred + analyzeRHS(tree.rhs) // to complete a constructor, move one context further out -- this // is the context enclosing the class. Note that the context in which a @@ -416,13 +417,9 @@ class Namer { typer: Typer => // have no implementation. val cctx = if (tree.name == nme.CONSTRUCTOR && !flags.is(JavaDefined)) ctx.outer else ctx - val completer = tree match { - case tree: TypeDef => - if (flags.is(Opaque) && ctx.owner.isClass) ctx.owner.setFlag(Opaque) - new TypeDefCompleter(tree)(cctx) - case _ => - new Completer(tree)(cctx) - } + val completer = tree match + case tree: TypeDef => TypeDefCompleter(tree)(cctx) + case _ => Completer(tree)(cctx) val info = adjustIfModule(completer, tree) createOrRefine[Symbol](tree, name, flags, ctx.owner, _ => info, (fs, _, pwithin) => ctx.newSymbol(ctx.owner, name, fs, info, pwithin, tree.nameSpan)) @@ -939,7 +936,8 @@ class Namer { typer: Typer => } } - class TypeDefCompleter(original: TypeDef)(ictx: Context) extends Completer(original)(ictx) with TypeParamsCompleter { + class TypeDefCompleter(original: TypeDef)(ictx: Context) + extends Completer(original)(ictx) with TypeParamsCompleter { private var myTypeParams: List[TypeSymbol] = null private var nestedCtx: Context = null assert(!original.isClassDef) @@ -966,8 +964,60 @@ class Namer { typer: Typer => myTypeParams end completerTypeParams - override protected def typeSig(sym: Symbol): Type = - typeDefSig(original, sym, completerTypeParams(sym)(ictx))(nestedCtx) + override final def typeSig(sym: Symbol): Type = + val tparamSyms = completerTypeParams(sym)(ictx) + given ctx as Context = nestedCtx + def abstracted(tp: TypeBounds): TypeBounds = HKTypeLambda.boundsFromParams(tparamSyms, tp) + val dummyInfo1 = abstracted(TypeBounds.empty) + sym.info = dummyInfo1 + sym.setFlag(Provisional) + // Temporarily set info of defined type T to ` >: Nothing <: Any. + // This is done to avoid cyclic reference errors for F-bounds. + // This is subtle: `sym` has now an empty TypeBounds, but is not automatically + // made an abstract type. If it had been made an abstract type, it would count as an + // abstract type of its enclosing class, which might make that class an invalid + // prefix. I verified this would lead to an error when compiling io.ClassPath. + // A distilled version is in pos/prefix.scala. + // + // The scheme critically relies on an implementation detail of isRef, which + // inspects a TypeRef's info, instead of simply dealiasing alias types. + + val isDerived = original.rhs.isInstanceOf[untpd.DerivedTypeTree] + val rhs = original.rhs match { + case LambdaTypeTree(_, body) => body + case rhs => rhs + } + + // For match types: approximate with upper bound while evaluating the rhs. + val dummyInfo2 = rhs match { + case MatchTypeTree(bound, _, _) if !bound.isEmpty => + abstracted(TypeBounds.upper(typedAheadType(bound).tpe)) + case _ => + dummyInfo1 + } + sym.info = dummyInfo2 + + val rhsBodyType: TypeBounds = typedAheadType(rhs).tpe.toBounds + val unsafeInfo = if (isDerived) rhsBodyType else abstracted(rhsBodyType) + if (isDerived) sym.info = unsafeInfo + else { + sym.info = NoCompleter + sym.info = sym.opaqueToBounds(checkNonCyclic(sym, unsafeInfo, reportErrors = true)) + } + if sym.isOpaqueAlias then sym.typeRef.recomputeDenot() // make sure we see the new bounds from now on + sym.resetFlag(Provisional) + + // Here we pay the price for the cavalier setting info to TypeBounds.empty above. + // We need to compensate by reloading the denotation of references that might + // still contain the TypeBounds.empty. If we do not do this, stdlib factories + // fail with a bounds error in PostTyper. + def ensureUpToDate(tref: TypeRef, outdated: Type) = + if (tref.info == outdated && sym.info != outdated) tref.recomputeDenot() + ensureUpToDate(sym.typeRef, dummyInfo1) + if (dummyInfo2 `ne` dummyInfo1) ensureUpToDate(sym.typeRef, dummyInfo2) + + sym.info + end typeSig } class ClassCompleter(cls: ClassSymbol, original: TypeDef)(ictx: Context) extends Completer(original)(ictx) { @@ -976,6 +1026,7 @@ class Namer { typer: Typer => protected implicit val ctx: Context = localContext(cls).setMode(ictx.mode &~ Mode.InSuperCall) private var localCtx: Context = _ + /** info to be used temporarily while completing the class, to avoid cyclic references. */ private var tempInfo: TempClassInfo = _ @@ -1123,13 +1174,13 @@ class Namer { typer: Typer => else createSymbol(self) val savedInfo = denot.infoOrCompleter - tempInfo = new TempClassInfo(cls.owner.thisType, cls, decls, selfInfo) - denot.info = tempInfo + denot.info = new TempClassInfo(cls.owner.thisType, cls, decls, selfInfo) localCtx = ctx.inClassContext(selfInfo) index(constr) index(rest)(localCtx) + symbolOfTree(constr).info.stripPoly match // Completes constr symbol as a side effect case mt: MethodType if cls.is(Case) && mt.isParamDependent => // See issue #8073 for background @@ -1137,6 +1188,8 @@ class Namer { typer: Typer => i"""Implementation restriction: case classes cannot have dependencies between parameters""", cls.sourcePos) case _ => + + tempInfo = denot.asClass.classInfo.integrateOpaqueMembers.asInstanceOf[TempClassInfo] denot.info = savedInfo } @@ -1208,9 +1261,7 @@ class Namer { typer: Typer => } } - if (tempInfo == null) // Constructor has not been completed yet - completeConstructor(denot) - + completeConstructor(denot) denot.info = tempInfo val parentTypes = defn.adjustForTuple(cls, cls.typeParams, @@ -1227,9 +1278,8 @@ class Namer { typer: Typer => original.putAttachment(Deriver, deriver) } - tempInfo.finalize(denot, parentTypes) - // The temporary info can now be garbage-collected - tempInfo = null + denot.info = tempInfo.finalized(parentTypes) + tempInfo = null // The temporary info can now be garbage-collected Checking.checkWellFormed(cls) if (isDerivedValueClass(cls)) cls.setFlag(Final) @@ -1503,57 +1553,4 @@ class Namer { typer: Typer => } else valOrDefDefSig(ddef, sym, typeParams, termParamss, wrapMethType) } - - def typeDefSig(tdef: TypeDef, sym: Symbol, tparamSyms: List[TypeSymbol])(implicit ctx: Context): Type = { - def abstracted(tp: TypeBounds): TypeBounds = HKTypeLambda.boundsFromParams(tparamSyms, tp) - val dummyInfo1 = abstracted(TypeBounds.empty) - sym.info = dummyInfo1 - sym.setFlag(Provisional) - // Temporarily set info of defined type T to ` >: Nothing <: Any. - // This is done to avoid cyclic reference errors for F-bounds. - // This is subtle: `sym` has now an empty TypeBounds, but is not automatically - // made an abstract type. If it had been made an abstract type, it would count as an - // abstract type of its enclosing class, which might make that class an invalid - // prefix. I verified this would lead to an error when compiling io.ClassPath. - // A distilled version is in pos/prefix.scala. - // - // The scheme critically relies on an implementation detail of isRef, which - // inspects a TypeRef's info, instead of simply dealiasing alias types. - - val isDerived = tdef.rhs.isInstanceOf[untpd.DerivedTypeTree] - val rhs = tdef.rhs match { - case LambdaTypeTree(_, body) => body - case rhs => rhs - } - - // For match types: approximate with upper bound while evaluating the rhs. - val dummyInfo2 = rhs match { - case MatchTypeTree(bound, _, _) if !bound.isEmpty => - abstracted(TypeBounds.upper(typedAheadType(bound).tpe)) - case _ => - dummyInfo1 - } - sym.info = dummyInfo2 - - val rhsBodyType: TypeBounds = typedAheadType(rhs).tpe.toBounds - val unsafeInfo = if (isDerived) rhsBodyType else abstracted(rhsBodyType) - if (isDerived) sym.info = unsafeInfo - else { - sym.info = NoCompleter - sym.info = checkNonCyclic(sym, unsafeInfo, reportErrors = true) - } - sym.normalizeOpaque() - sym.resetFlag(Provisional) - - // Here we pay the price for the cavalier setting info to TypeBounds.empty above. - // We need to compensate by reloading the denotation of references that might - // still contain the TypeBounds.empty. If we do not do this, stdlib factories - // fail with a bounds error in PostTyper. - def ensureUpToDate(tref: TypeRef, outdated: Type) = - if (tref.info == outdated && sym.info != outdated) tref.recomputeDenot() - ensureUpToDate(sym.typeRef, dummyInfo1) - if (dummyInfo2 `ne` dummyInfo1) ensureUpToDate(sym.typeRef, dummyInfo2) - - sym.info - } } diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 96d5184c7c04..f6e1407abba0 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -186,7 +186,7 @@ Standard-Section: "ASTs" TopLevelStat* ERASED -- erased LAZY -- lazy OVERRIDE -- override - OPAQUE -- opaque + OPAQUE -- opaque, also used for classes containing opaque aliases INLINE -- inline MACRO -- Inline method containing toplevel splices INLINEPROXY -- Symbol of binding with an argument to an inline method as rhs (TODO: do we still need this?) diff --git a/tests/neg/i6225.scala b/tests/neg/i6225.scala index 6602f8ec928f..d010854490b6 100644 --- a/tests/neg/i6225.scala +++ b/tests/neg/i6225.scala @@ -11,7 +11,7 @@ object O2 { } object O3 { - opaque type R[X] = R[X] // error + opaque type R[X] = R[X] // error: R does not take parameters // error: cyclic } object O4{ @@ -20,4 +20,4 @@ object O4{ object O5{ opaque type T[X] = Nothing -} \ No newline at end of file +} diff --git a/tests/neg/opaque.scala b/tests/neg/opaque.scala index 921544abe5d4..de95d81b0466 100644 --- a/tests/neg/opaque.scala +++ b/tests/neg/opaque.scala @@ -1,5 +1,4 @@ object opaquetypes { - opaque val x: Int = 1 // error opaque class Foo // error @@ -10,7 +9,7 @@ object opaquetypes { opaque type U <: String // error - opaque type Fix[F[_]] = F[Fix[F]] // error: cyclic + opaque type Fix[F[_]] = F[Fix[F]] // error: cyclic // error opaque type O = String diff --git a/tests/pos/i8152.scala b/tests/pos/i8152.scala new file mode 100644 index 000000000000..c612bf93a328 --- /dev/null +++ b/tests/pos/i8152.scala @@ -0,0 +1,12 @@ +object opaque { + opaque type Foo[X] <: String = String +} +object test { + val s: String = ???.asInstanceOf[opaque.Foo[String]] +} +object opaque2 { + opaque type Foo2 <: String = String +} +object test2 { + val s: String = "bla" +} \ No newline at end of file diff --git a/tests/pos/opaque-simple.scala b/tests/pos/opaque-simple.scala new file mode 100644 index 000000000000..9e9a546dfa6a --- /dev/null +++ b/tests/pos/opaque-simple.scala @@ -0,0 +1,4 @@ +object O { + opaque type First[A1] = A1 + opaque type Last[A] = A +}