Skip to content

Commit 0c96231

Browse files
authored
Merge pull request #8170 from dotty-staging/fix-#8152
Fix #8152: Rearchitect some parts of opaque types handling
2 parents 5132664 + 2644e15 commit 0c96231

File tree

14 files changed

+222
-170
lines changed

14 files changed

+222
-170
lines changed

compiler/src/dotty/tools/dotc/core/Denotations.scala

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,30 +1165,8 @@ object Denotations {
11651165
info.asSeenFrom(pre, owner),
11661166
if (symbol.is(Opaque) || this.prefix != NoPrefix) pre else this.prefix)
11671167

1168-
pre match {
1169-
case pre: ThisType if symbol.isOpaqueAlias && pre.cls == owner =>
1170-
// This code is necessary to compensate for a "window of vulnerability" with
1171-
// opaque types. The problematic sequence is as follows.
1172-
// 1. Type a selection `m.this.T` where `T` is an opaque type alias in `m`
1173-
// and this is the first access
1174-
// 2. `T` will normalize to an abstract type on completion.
1175-
// 3. At that time, the default logic in the second case is wrong: `T`'s new info
1176-
// is now an abstract type and running it through an asSeenFrom gives nothing.
1177-
// We fix this as follows:
1178-
// 1. Force opaque normalization as first step
1179-
// 2. Read the info from the enclosing object's refinement
1180-
symbol.normalizeOpaque()
1181-
def findRefined(tp: Type, name: Name): Type = tp match {
1182-
case RefinedType(parent, rname, rinfo) =>
1183-
if (rname == name) rinfo else findRefined(parent, name)
1184-
case _ =>
1185-
symbol.info
1186-
}
1187-
derived(findRefined(pre.underlying, symbol.name))
1188-
case _ =>
1189-
if (!owner.membersNeedAsSeenFrom(pre) || symbol.is(NonMember)) this
1190-
else derived(symbol.info)
1191-
}
1168+
if (!owner.membersNeedAsSeenFrom(pre) || symbol.is(NonMember)) this
1169+
else derived(symbol.info)
11921170
}
11931171

11941172
/** Does this denotation have all the `required` flags but none of the `excluded` flags?

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

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -426,42 +426,44 @@ object SymDenotations {
426426
case _ => unforcedDecls.openForMutations
427427
}
428428

429-
/** If this is a synthetic opaque type alias, mark it as Deferred with bounds
430-
* as given by the right hand side's `WithBounds` annotation, if one is present,
431-
* or with empty bounds of the right kind, otherwise.
432-
* At the same time, integrate the original alias as a refinement of the
429+
/** If this is an opaque alias, replace the right hand side `info`
430+
* by appropriate bounds and store `info` in the refinement of the
433431
* self type of the enclosing class.
432+
* Otherwise return `info`
433+
*
434+
* @param info Is assumed to be a (lambda-abstracted) right hand side TypeAlias
435+
* of the opaque type definition.
434436
*/
435-
final def normalizeOpaque()(implicit ctx: Context) = {
436-
def abstractRHS(tp: Type): Type = tp match {
437-
case tp: HKTypeLambda => tp.derivedLambdaType(resType = abstractRHS(tp.resType))
438-
case _ => defn.AnyType
439-
}
440-
if (isOpaqueAlias)
441-
info match {
442-
case TypeAlias(alias) =>
443-
val (refiningAlias, bounds) = alias match {
444-
case AnnotatedType(alias1, Annotation.WithBounds(bounds)) =>
445-
(alias1, bounds)
446-
case _ =>
447-
(alias, TypeBounds(defn.NothingType, abstractRHS(alias)))
448-
}
449-
def refineSelfType(selfType: Type) =
450-
RefinedType(selfType, name, TypeAlias(refiningAlias))
451-
val enclClassInfo = owner.asClass.classInfo
452-
enclClassInfo.selfInfo match {
453-
case self: Type =>
454-
owner.info = enclClassInfo.derivedClassInfo(
455-
selfInfo = refineSelfType(self.orElse(defn.AnyType)))
456-
case self: Symbol =>
457-
self.info = refineSelfType(self.info)
458-
}
459-
info = bounds
460-
setFlag(Deferred)
461-
typeRef.recomputeDenot()
462-
case _ =>
463-
}
464-
}
437+
def opaqueToBounds(info: Type)(given Context): Type =
438+
439+
def setAlias(tp: Type) =
440+
def recur(self: Type): Unit = self match
441+
case RefinedType(parent, name, rinfo) => rinfo match
442+
case TypeAlias(lzy: LazyRef) if name == this.name =>
443+
lzy.update(tp)
444+
case _ =>
445+
recur(parent)
446+
recur(owner.asClass.givenSelfType)
447+
end setAlias
448+
449+
def split(tp: Type): (Type, TypeBounds) = tp match
450+
case AnnotatedType(alias, Annotation.WithBounds(bounds)) =>
451+
(alias, bounds)
452+
case tp: HKTypeLambda =>
453+
val (alias1, bounds1) = split(tp.resType)
454+
(tp.derivedLambdaType(resType = alias1),
455+
HKTypeLambda.boundsFromParams(tp.typeParams, bounds1))
456+
case _ =>
457+
(tp, HKTypeLambda.boundsFromParams(tp.typeParams, TypeBounds.empty))
458+
459+
info match
460+
case TypeAlias(tp) if isOpaqueAlias && owner.isClass =>
461+
val (alias, bounds) = split(tp)
462+
setAlias(alias)
463+
bounds
464+
case _ =>
465+
info
466+
end opaqueToBounds
465467

466468
// ------ Names ----------------------------------------------
467469

@@ -1203,7 +1205,7 @@ object SymDenotations {
12031205
def opaqueAlias(implicit ctx: Context): Type = {
12041206
def recur(tp: Type): Type = tp match {
12051207
case RefinedType(parent, rname, TypeAlias(alias)) =>
1206-
if (rname == name) alias else recur(parent)
1208+
if rname == name then alias.stripLazyRef else recur(parent)
12071209
case _ =>
12081210
NoType
12091211
}

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1727,10 +1727,14 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
17271727
else if tp1.isAny && !tp2.isLambdaSub || tp1.isAnyKind || tp2.isRef(NothingClass) then tp2
17281728
else if tp2.isAny && !tp1.isLambdaSub || tp2.isAnyKind || tp1.isRef(NothingClass) then tp1
17291729
else tp2 match { // normalize to disjunctive normal form if possible.
1730+
case tp2: LazyRef =>
1731+
glb(tp1, tp2.ref)
17301732
case OrType(tp21, tp22) =>
17311733
tp1 & tp21 | tp1 & tp22
17321734
case _ =>
17331735
tp1 match {
1736+
case tp1: LazyRef =>
1737+
glb(tp1.ref, tp2)
17341738
case OrType(tp11, tp12) =>
17351739
tp11 & tp2 | tp12 & tp2
17361740
case _ =>
@@ -1777,8 +1781,8 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
17771781
else if (!tp2.exists) tp2
17781782
else if tp1.isAny && !tp2.isLambdaSub || tp1.isAnyKind || tp2.isRef(NothingClass) then tp1
17791783
else if tp2.isAny && !tp1.isLambdaSub || tp2.isAnyKind || tp1.isRef(NothingClass) then tp2
1780-
else {
1781-
def mergedLub: Type = {
1784+
else
1785+
def mergedLub(tp1: Type, tp2: Type): Type = {
17821786
val atoms1 = tp1.atoms(widenOK = true)
17831787
if (atoms1.nonEmpty && !widenInUnions) {
17841788
val atoms2 = tp2.atoms(widenOK = true)
@@ -1800,8 +1804,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
18001804
if ((tp1 ne tp1w) || (tp2 ne tp2w)) lub(tp1w, tp2w)
18011805
else orType(tp1w, tp2w) // no need to check subtypes again
18021806
}
1803-
mergedLub
1804-
}
1807+
mergedLub(tp1.stripLazyRef, tp2.stripLazyRef)
18051808
}
18061809

18071810
/** The least upper bound of a list of types */

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,8 @@ object Types {
10071007
* pos/i536 demonstrates that the infinite loop can also involve lower bounds.
10081008
*/
10091009
def safe_& (that: Type)(implicit ctx: Context): Type = (this, that) match {
1010-
case (TypeBounds(lo1, hi1), TypeBounds(lo2, hi2)) => TypeBounds(OrType(lo1, lo2), AndType(hi1, hi2))
1010+
case (TypeBounds(lo1, hi1), TypeBounds(lo2, hi2)) =>
1011+
TypeBounds(OrType(lo1.stripLazyRef, lo2.stripLazyRef), AndType(hi1.stripLazyRef, hi2.stripLazyRef))
10111012
case _ => this & that
10121013
}
10131014

@@ -1043,12 +1044,17 @@ object Types {
10431044
case _ => this
10441045
}
10451046

1046-
/** Strip PolyType prefix */
1047+
/** Strip PolyType prefixes */
10471048
def stripPoly(implicit ctx: Context): Type = this match {
10481049
case tp: PolyType => tp.resType.stripPoly
10491050
case _ => this
10501051
}
10511052

1053+
/** Strip LazyRef wrappers */
1054+
def stripLazyRef(given Context): Type = this match
1055+
case lzy: LazyRef => lzy.ref.stripLazyRef
1056+
case _ => this
1057+
10521058
/** Widen from singleton type to its underlying non-singleton
10531059
* base type by applying one or more `underlying` dereferences,
10541060
* Also go from => T to T.
@@ -2566,25 +2572,34 @@ object Types {
25662572
}
25672573
}
25682574

2569-
case class LazyRef(private var refFn: Context => Type) extends UncachedProxyType with ValueType {
2575+
case class LazyRef(private var refFn: Context => Type, reportCycles: Boolean = false) extends UncachedProxyType with ValueType {
25702576
private var myRef: Type = null
25712577
private var computed = false
2572-
def ref(implicit ctx: Context): Type = {
2573-
if (computed) {
2574-
if (myRef == null) {
2578+
2579+
def ref(implicit ctx: Context): Type =
2580+
if computed then
2581+
if myRef == null then
25752582
// if errors were reported previously handle this by throwing a CyclicReference
25762583
// instead of crashing immediately. A test case is neg/i6057.scala.
2577-
assert(ctx.reporter.errorsReported)
2584+
assert(reportCycles || ctx.reporter.errorsReported)
25782585
throw CyclicReference(NoDenotation)
2579-
}
2580-
}
2581-
else {
2586+
else
25822587
computed = true
2583-
myRef = refFn(ctx)
2588+
val result = refFn(ctx)
25842589
refFn = null
2585-
}
2590+
if result != null then myRef = result
2591+
else assert(myRef != null) // must have been `update`d
25862592
myRef
2587-
}
2593+
2594+
/** Update the value of the lazyref, discarding the compute function `refFn`
2595+
* Can be called only as long as the ref is still undefined.
2596+
*/
2597+
def update(tp: Type) =
2598+
assert(myRef == null)
2599+
myRef = tp
2600+
computed = true
2601+
refFn = null
2602+
25882603
def evaluating: Boolean = computed && myRef == null
25892604
def completed: Boolean = myRef != null
25902605
override def underlying(implicit ctx: Context): Type = ref
@@ -4300,13 +4315,47 @@ object Types {
43004315
parentsCache
43014316
}
43024317

4318+
protected def newLikeThis(prefix: Type, classParents: List[Type], decls: Scope, selfInfo: TypeOrSymbol)(given Context): ClassInfo =
4319+
ClassInfo(prefix, cls, classParents, decls, selfInfo)
4320+
43034321
def derivedClassInfo(prefix: Type)(implicit ctx: Context): ClassInfo =
43044322
if (prefix eq this.prefix) this
4305-
else ClassInfo(prefix, cls, classParents, decls, selfInfo)
4323+
else newLikeThis(prefix, classParents, decls, selfInfo)
43064324

43074325
def derivedClassInfo(prefix: Type = this.prefix, classParents: List[Type] = this.classParents, decls: Scope = this.decls, selfInfo: TypeOrSymbol = this.selfInfo)(implicit ctx: Context): ClassInfo =
43084326
if ((prefix eq this.prefix) && (classParents eq this.classParents) && (decls eq this.decls) && (selfInfo eq this.selfInfo)) this
4309-
else ClassInfo(prefix, cls, classParents, decls, selfInfo)
4327+
else newLikeThis(prefix, classParents, decls, selfInfo)
4328+
4329+
/** If this class has opaque type alias members, a new class info
4330+
* with their aliases added as refinements to the self type of the class.
4331+
* Otherwise, this classInfo.
4332+
* If there are opaque alias members, updates `cls` to have `Opaque` flag as a side effect.
4333+
*/
4334+
def integrateOpaqueMembers(given Context): ClassInfo =
4335+
decls.toList.foldLeft(this) { (cinfo, sym) =>
4336+
if sym.isOpaqueAlias then
4337+
cls.setFlag(Opaque)
4338+
def force =
4339+
if sym.isOpaqueAlias then // could have been reset because of a syntax error
4340+
sym.infoOrCompleter match
4341+
case completer: LazyType =>
4342+
completer.complete(sym) // will update the LazyRef
4343+
null // tells the LazyRef to use the updated value
4344+
case info => // can occur under cyclic references, e.g. i6225.scala
4345+
defn.AnyType
4346+
else defn.AnyType // dummy type in case of errors
4347+
def refineSelfType(selfType: Type) =
4348+
RefinedType(selfType, sym.name,
4349+
TypeAlias(LazyRef(_ => force, reportCycles = true)))
4350+
cinfo.selfInfo match
4351+
case self: Type =>
4352+
cinfo.derivedClassInfo(
4353+
selfInfo = refineSelfType(self.orElse(defn.AnyType)))
4354+
case self: Symbol =>
4355+
self.info = refineSelfType(self.info)
4356+
cinfo
4357+
else cinfo
4358+
}
43104359

43114360
override def computeHash(bs: Binders): Int = doHash(bs, cls, prefix)
43124361
override def hashIsStable: Boolean = prefix.hashIsStable && classParents.hashIsStable
@@ -4343,13 +4392,12 @@ object Types {
43434392
final class TempClassInfo(prefix: Type, cls: ClassSymbol, decls: Scope, selfInfo: TypeOrSymbol)
43444393
extends CachedClassInfo(prefix, cls, Nil, decls, selfInfo) {
43454394

4346-
/** Install classinfo with known parents in `denot` s */
4347-
def finalize(denot: SymDenotation, parents: List[Type])(implicit ctx: Context): Unit =
4348-
denot.info = ClassInfo(prefix, cls, parents, decls, selfInfo)
4395+
/** Convert to classinfo with known parents */
4396+
def finalized(parents: List[Type])(implicit ctx: Context): ClassInfo =
4397+
ClassInfo(prefix, cls, parents, decls, selfInfo)
43494398

4350-
override def derivedClassInfo(prefix: Type)(implicit ctx: Context): ClassInfo =
4351-
if (prefix eq this.prefix) this
4352-
else new TempClassInfo(prefix, cls, decls, selfInfo)
4399+
override def newLikeThis(prefix: Type, classParents: List[Type], decls: Scope, selfInfo: TypeOrSymbol)(given Context): ClassInfo =
4400+
TempClassInfo(prefix, cls, decls, selfInfo)
43534401

43544402
override def toString: String = s"TempClassInfo($prefix, $cls)"
43554403
}

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ class TreeUnpickler(reader: TastyReader,
570570
ctx.newSymbol(ctx.owner, name, flags, completer, privateWithin, coord)
571571
}
572572
sym.annotations = annotFns.map(_(sym))
573+
if sym.isOpaqueAlias then sym.setFlag(Deferred)
573574
ctx.owner match {
574575
case cls: ClassSymbol => cls.enter(sym)
575576
case _ =>
@@ -832,11 +833,12 @@ class TreeUnpickler(reader: TastyReader,
832833
override def completerTypeParams(sym: Symbol)(implicit ctx: Context) =
833834
rhs.tpe.typeParams
834835
}
835-
sym.info = rhs.tpe match {
836-
case _: TypeBounds | _: ClassInfo => checkNonCyclic(sym, rhs.tpe, reportErrors = false)
837-
case _ => rhs.tpe.toBounds
836+
sym.info = sym.opaqueToBounds {
837+
rhs.tpe match
838+
case _: TypeBounds | _: ClassInfo => checkNonCyclic(sym, rhs.tpe, reportErrors = false)
839+
case _ => rhs.tpe.toBounds
838840
}
839-
sym.normalizeOpaque()
841+
if sym.isOpaqueAlias then sym.typeRef.recomputeDenot() // make sure we see the new bounds from now on
840842
sym.resetFlag(Provisional)
841843
TypeDef(rhs)
842844
}
@@ -905,8 +907,10 @@ class TreeUnpickler(reader: TastyReader,
905907
}
906908
else EmptyValDef
907909
cls.setNoInitsFlags(parentsKind(parents), bodyFlags)
908-
cls.info = ClassInfo(cls.owner.thisType, cls, parentTypes, cls.unforcedDecls,
909-
if (self.isEmpty) NoType else self.tpt.tpe)
910+
cls.info = ClassInfo(
911+
cls.owner.thisType, cls, parentTypes, cls.unforcedDecls,
912+
selfInfo = if (self.isEmpty) NoType else self.tpt.tpe)
913+
.integrateOpaqueMembers
910914
val constr = readIndexedDef().asInstanceOf[DefDef]
911915
val mappedParents = parents.map(_.changeOwner(localDummy, constr.symbol))
912916

compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ object Scala2Unpickler {
135135
else
136136
registerCompanionPair(scalacCompanion, denot.classSymbol)
137137

138-
tempInfo.finalize(denot, normalizedParents) // install final info, except possibly for typeparams ordering
138+
denot.info = tempInfo.finalized(normalizedParents)
139139
denot.ensureTypeParamsInCorrectOrder()
140140
}
141141
}

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,11 @@ class PlainPrinter(_ctx: Context) extends Printer {
197197
}
198198
case tp: ExprType =>
199199
changePrec(GlobalPrec) { "=> " ~ toText(tp.resultType) }
200-
case tp: TypeLambda =>
200+
case tp: HKTypeLambda =>
201+
changePrec(GlobalPrec) {
202+
"[" ~ paramsText(tp) ~ "]" ~ lambdaHash(tp) ~ Str(" =>> ") ~ toTextGlobal(tp.resultType)
203+
}
204+
case tp: PolyType =>
201205
changePrec(GlobalPrec) {
202206
"[" ~ paramsText(tp) ~ "]" ~ lambdaHash(tp) ~
203207
(Str(" => ") provided !tp.resultType.isInstanceOf[MethodType]) ~

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1262,7 +1262,8 @@ object messages {
12621262
case class CyclicReferenceInvolving(denot: SymDenotation)(implicit ctx: Context)
12631263
extends Message(CyclicReferenceInvolvingID) {
12641264
val kind: String = "Cyclic"
1265-
val msg: String = em"""Cyclic reference involving $denot"""
1265+
val where = if denot.exists then s" involving $denot" else ""
1266+
val msg: String = em"Cyclic reference $where"
12661267
val explanation: String =
12671268
em"""|$denot is declared as part of a cycle which makes it impossible for the
12681269
|compiler to decide upon ${denot.name}'s type.

0 commit comments

Comments
 (0)