Skip to content

Commit 043c237

Browse files
committed
Switch to new scheme for handling opaques in frontend
1 parent d0e0c4e commit 043c237

File tree

8 files changed

+174
-104
lines changed

8 files changed

+174
-104
lines changed

compiler/src/dotty/tools/dotc/config/Config.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package dotty.tools.dotc.config
22

33
object Config {
44

5+
val newScheme = true
6+
57
final val cacheMembersNamed = true
68
final val cacheAsSeenFrom = true
79
final val cacheMemberNames = true

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: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,40 @@ object SymDenotations {
463463
}
464464
}
465465

466+
/** If this is an opaque alias, replace the right hand side `info`
467+
* by appropriate bounds and store `info` in the refinement of the
468+
* self type of the enclosing class.
469+
* Otherwise return `info`
470+
*
471+
* @param info Is assumed to be a (lambda-abstracted) right hand side TypeAlias
472+
* of the opaque type definition.
473+
*/
474+
def opaqueToBounds(info: Type)(given Context): Type =
475+
476+
def setAlias(tp: Type) =
477+
def recur(self: Type): Unit = self match
478+
case RefinedType(parent, name, rinfo) => rinfo match
479+
case TypeAlias(lzy: LazyRef) if name == this.name =>
480+
lzy.update(tp)
481+
case _ =>
482+
recur(parent)
483+
recur(owner.asClass.givenSelfType)
484+
end setAlias
485+
486+
info match
487+
case TypeAlias(alias) if isOpaqueAlias && owner.isClass =>
488+
val bounds = alias match
489+
case AnnotatedType(alias1, Annotation.WithBounds(bounds)) =>
490+
setAlias(alias1)
491+
bounds
492+
case _ =>
493+
setAlias(alias)
494+
TypeBounds.empty
495+
HKTypeLambda.boundsFromParams(alias.typeParams, bounds)
496+
case _ =>
497+
info
498+
end opaqueToBounds
499+
466500
// ------ Names ----------------------------------------------
467501

468502
/** The expanded name of this denotation. */
@@ -1203,10 +1237,10 @@ object SymDenotations {
12031237
def opaqueAlias(implicit ctx: Context): Type = {
12041238
def recur(tp: Type): Type = tp match {
12051239
case RefinedType(parent, rname, TypeAlias(alias)) =>
1206-
def alias1 = alias match
1240+
if rname == name then alias match
12071241
case alias: LazyRef => alias.ref
12081242
case _ => alias
1209-
if (rname == name) alias1 else recur(parent)
1243+
else recur(parent)
12101244
case _ =>
12111245
NoType
12121246
}

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

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2571,22 +2571,31 @@ object Types {
25712571
case class LazyRef(private var refFn: Context => Type, reportCycles: Boolean = false) extends UncachedProxyType with ValueType {
25722572
private var myRef: Type = null
25732573
private var computed = false
2574-
def ref(implicit ctx: Context): Type = {
2575-
if (computed) {
2576-
if (myRef == null) {
2574+
2575+
def ref(implicit ctx: Context): Type =
2576+
if computed then
2577+
if myRef == null then
25772578
// if errors were reported previously handle this by throwing a CyclicReference
25782579
// instead of crashing immediately. A test case is neg/i6057.scala.
25792580
assert(reportCycles || ctx.reporter.errorsReported)
25802581
throw CyclicReference(NoDenotation)
2581-
}
2582-
}
2583-
else {
2582+
else
25842583
computed = true
2585-
myRef = refFn(ctx)
2584+
val result = refFn(ctx)
25862585
refFn = null
2587-
}
2586+
if result != null then myRef = result
2587+
else assert(myRef != null) // must have been `update`d
25882588
myRef
2589-
}
2589+
2590+
/** Update the value of the lazyref, discarding the compute function `refFn`
2591+
* Can be called only as long as the ref is still undefined.
2592+
*/
2593+
def update(tp: Type) =
2594+
assert(myRef == null)
2595+
myRef = tp
2596+
computed = true
2597+
refFn = null
2598+
25902599
def evaluating: Boolean = computed && myRef == null
25912600
def completed: Boolean = myRef != null
25922601
override def underlying(implicit ctx: Context): Type = ref
@@ -4309,6 +4318,37 @@ object Types {
43094318
if ((prefix eq this.prefix) && (classParents eq this.classParents) && (decls eq this.decls) && (selfInfo eq this.selfInfo)) this
43104319
else newLikeThis(prefix, classParents, decls, selfInfo)
43114320

4321+
/** If this class has opqque type alias members, a new class info
4322+
* with their aliases added as refinements to the self type of the class.
4323+
* Otherwise, this classInfo.
4324+
* If there are opaque alias members, updates `cls` to have `Opaque` flag as a side effect.
4325+
*/
4326+
def integrateOpaqueMembers(given Context): ClassInfo =
4327+
decls.toList.foldLeft(this) { (cinfo, sym) =>
4328+
if sym.isOpaqueAlias then
4329+
cls.setFlag(Opaque)
4330+
def force =
4331+
if sym.isOpaqueAlias then // could have been reset because of a syntax error
4332+
sym.infoOrCompleter match
4333+
case completer: LazyType =>
4334+
completer.complete(sym) // will update the LazyRef
4335+
null // tells the LazyRef to use the updated value
4336+
case info => // can occur under cyclic references, e.g. i6225.scala
4337+
defn.AnyType
4338+
else defn.AnyType // dummy type in case of errors
4339+
def refineSelfType(selfType: Type) =
4340+
RefinedType(selfType, sym.name,
4341+
TypeAlias(LazyRef(_ => force, reportCycles = true)))
4342+
cinfo.selfInfo match
4343+
case self: Type =>
4344+
cinfo.derivedClassInfo(
4345+
selfInfo = refineSelfType(self.orElse(defn.AnyType)))
4346+
case self: Symbol =>
4347+
self.info = refineSelfType(self.info)
4348+
cinfo
4349+
else cinfo
4350+
}
4351+
43124352
override def computeHash(bs: Binders): Int = doHash(bs, cls, prefix)
43134353
override def hashIsStable: Boolean = prefix.hashIsStable && classParents.hashIsStable
43144354

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 65 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,8 @@ class Namer { typer: Typer =>
403403
flags |= HigherKinded
404404
analyzeRHS(body)
405405
case _ =>
406-
if tree.rhs.isEmpty then flags |= Deferred else analyzeRHS(tree.rhs)
406+
if rhs.isEmpty || flags.is(Opaque) then flags |= Deferred
407+
analyzeRHS(tree.rhs)
407408

408409
// to complete a constructor, move one context further out -- this
409410
// is the context enclosing the class. Note that the context in which a
@@ -416,13 +417,9 @@ class Namer { typer: Typer =>
416417
// have no implementation.
417418
val cctx = if (tree.name == nme.CONSTRUCTOR && !flags.is(JavaDefined)) ctx.outer else ctx
418419

419-
val completer = tree match {
420-
case tree: TypeDef =>
421-
if (flags.is(Opaque) && ctx.owner.isClass) ctx.owner.setFlag(Opaque)
422-
new TypeDefCompleter(tree)(cctx)
423-
case _ =>
424-
new Completer(tree)(cctx)
425-
}
420+
val completer = tree match
421+
case tree: TypeDef => TypeDefCompleter(tree)(cctx)
422+
case _ => Completer(tree)(cctx)
426423
val info = adjustIfModule(completer, tree)
427424
createOrRefine[Symbol](tree, name, flags, ctx.owner, _ => info,
428425
(fs, _, pwithin) => ctx.newSymbol(ctx.owner, name, fs, info, pwithin, tree.nameSpan))
@@ -939,7 +936,8 @@ class Namer { typer: Typer =>
939936
}
940937
}
941938

942-
class TypeDefCompleter(original: TypeDef)(ictx: Context) extends Completer(original)(ictx) with TypeParamsCompleter {
939+
class TypeDefCompleter(original: TypeDef)(ictx: Context)
940+
extends Completer(original)(ictx) with TypeParamsCompleter {
943941
private var myTypeParams: List[TypeSymbol] = null
944942
private var nestedCtx: Context = null
945943
assert(!original.isClassDef)
@@ -966,8 +964,61 @@ class Namer { typer: Typer =>
966964
myTypeParams
967965
end completerTypeParams
968966

969-
override protected def typeSig(sym: Symbol): Type =
970-
typeDefSig(original, sym, completerTypeParams(sym)(ictx))(nestedCtx)
967+
override final def typeSig(sym: Symbol): Type =
968+
val tparamSyms = completerTypeParams(sym)(ictx)
969+
given ctx as Context = nestedCtx
970+
def abstracted(tp: TypeBounds): TypeBounds = HKTypeLambda.boundsFromParams(tparamSyms, tp)
971+
val dummyInfo1 = abstracted(TypeBounds.empty)
972+
sym.info = dummyInfo1
973+
sym.setFlag(Provisional)
974+
// Temporarily set info of defined type T to ` >: Nothing <: Any.
975+
// This is done to avoid cyclic reference errors for F-bounds.
976+
// This is subtle: `sym` has now an empty TypeBounds, but is not automatically
977+
// made an abstract type. If it had been made an abstract type, it would count as an
978+
// abstract type of its enclosing class, which might make that class an invalid
979+
// prefix. I verified this would lead to an error when compiling io.ClassPath.
980+
// A distilled version is in pos/prefix.scala.
981+
//
982+
// The scheme critically relies on an implementation detail of isRef, which
983+
// inspects a TypeRef's info, instead of simply dealiasing alias types.
984+
985+
val isDerived = original.rhs.isInstanceOf[untpd.DerivedTypeTree]
986+
val rhs = original.rhs match {
987+
case LambdaTypeTree(_, body) => body
988+
case rhs => rhs
989+
}
990+
991+
// For match types: approximate with upper bound while evaluating the rhs.
992+
val dummyInfo2 = rhs match {
993+
case MatchTypeTree(bound, _, _) if !bound.isEmpty =>
994+
abstracted(TypeBounds.upper(typedAheadType(bound).tpe))
995+
case _ =>
996+
dummyInfo1
997+
}
998+
sym.info = dummyInfo2
999+
1000+
val rhsBodyType: TypeBounds = typedAheadType(rhs).tpe.toBounds
1001+
val unsafeInfo = if (isDerived) rhsBodyType else abstracted(rhsBodyType)
1002+
if (isDerived) sym.info = unsafeInfo
1003+
else {
1004+
sym.info = NoCompleter
1005+
sym.info = sym.opaqueToBounds(checkNonCyclic(sym, unsafeInfo, reportErrors = true))
1006+
}
1007+
if !Config.newScheme then sym.normalizeOpaque()
1008+
else if sym.isOpaqueAlias then sym.typeRef.recomputeDenot() // make sure we see the new bounds from now on
1009+
sym.resetFlag(Provisional)
1010+
1011+
// Here we pay the price for the cavalier setting info to TypeBounds.empty above.
1012+
// We need to compensate by reloading the denotation of references that might
1013+
// still contain the TypeBounds.empty. If we do not do this, stdlib factories
1014+
// fail with a bounds error in PostTyper.
1015+
def ensureUpToDate(tref: TypeRef, outdated: Type) =
1016+
if (tref.info == outdated && sym.info != outdated) tref.recomputeDenot()
1017+
ensureUpToDate(sym.typeRef, dummyInfo1)
1018+
if (dummyInfo2 `ne` dummyInfo1) ensureUpToDate(sym.typeRef, dummyInfo2)
1019+
1020+
sym.info
1021+
end typeSig
9711022
}
9721023

9731024
class ClassCompleter(cls: ClassSymbol, original: TypeDef)(ictx: Context) extends Completer(original)(ictx) {
@@ -1130,14 +1181,16 @@ class Namer { typer: Typer =>
11301181

11311182
index(constr)
11321183
index(rest)(localCtx)
1184+
11331185
symbolOfTree(constr).info.stripPoly match // Completes constr symbol as a side effect
11341186
case mt: MethodType if cls.is(Case) && mt.isParamDependent =>
11351187
// See issue #8073 for background
11361188
ctx.error(
11371189
i"""Implementation restriction: case classes cannot have dependencies between parameters""",
11381190
cls.sourcePos)
11391191
case _ =>
1140-
tempInfo = denot.info.asInstanceOf[TempClassInfo]
1192+
1193+
tempInfo = denot.asClass.classInfo.integrateOpaqueMembers.asInstanceOf[TempClassInfo]
11411194
denot.info = savedInfo
11421195
tempInfo
11431196
}
@@ -1502,57 +1555,4 @@ class Namer { typer: Typer =>
15021555
}
15031556
else valOrDefDefSig(ddef, sym, typeParams, termParamss, wrapMethType)
15041557
}
1505-
1506-
def typeDefSig(tdef: TypeDef, sym: Symbol, tparamSyms: List[TypeSymbol])(implicit ctx: Context): Type = {
1507-
def abstracted(tp: TypeBounds): TypeBounds = HKTypeLambda.boundsFromParams(tparamSyms, tp)
1508-
val dummyInfo1 = abstracted(TypeBounds.empty)
1509-
sym.info = dummyInfo1
1510-
sym.setFlag(Provisional)
1511-
// Temporarily set info of defined type T to ` >: Nothing <: Any.
1512-
// This is done to avoid cyclic reference errors for F-bounds.
1513-
// This is subtle: `sym` has now an empty TypeBounds, but is not automatically
1514-
// made an abstract type. If it had been made an abstract type, it would count as an
1515-
// abstract type of its enclosing class, which might make that class an invalid
1516-
// prefix. I verified this would lead to an error when compiling io.ClassPath.
1517-
// A distilled version is in pos/prefix.scala.
1518-
//
1519-
// The scheme critically relies on an implementation detail of isRef, which
1520-
// inspects a TypeRef's info, instead of simply dealiasing alias types.
1521-
1522-
val isDerived = tdef.rhs.isInstanceOf[untpd.DerivedTypeTree]
1523-
val rhs = tdef.rhs match {
1524-
case LambdaTypeTree(_, body) => body
1525-
case rhs => rhs
1526-
}
1527-
1528-
// For match types: approximate with upper bound while evaluating the rhs.
1529-
val dummyInfo2 = rhs match {
1530-
case MatchTypeTree(bound, _, _) if !bound.isEmpty =>
1531-
abstracted(TypeBounds.upper(typedAheadType(bound).tpe))
1532-
case _ =>
1533-
dummyInfo1
1534-
}
1535-
sym.info = dummyInfo2
1536-
1537-
val rhsBodyType: TypeBounds = typedAheadType(rhs).tpe.toBounds
1538-
val unsafeInfo = if (isDerived) rhsBodyType else abstracted(rhsBodyType)
1539-
if (isDerived) sym.info = unsafeInfo
1540-
else {
1541-
sym.info = NoCompleter
1542-
sym.info = checkNonCyclic(sym, unsafeInfo, reportErrors = true)
1543-
}
1544-
sym.normalizeOpaque()
1545-
sym.resetFlag(Provisional)
1546-
1547-
// Here we pay the price for the cavalier setting info to TypeBounds.empty above.
1548-
// We need to compensate by reloading the denotation of references that might
1549-
// still contain the TypeBounds.empty. If we do not do this, stdlib factories
1550-
// fail with a bounds error in PostTyper.
1551-
def ensureUpToDate(tref: TypeRef, outdated: Type) =
1552-
if (tref.info == outdated && sym.info != outdated) tref.recomputeDenot()
1553-
ensureUpToDate(sym.typeRef, dummyInfo1)
1554-
if (dummyInfo2 `ne` dummyInfo1) ensureUpToDate(sym.typeRef, dummyInfo2)
1555-
1556-
sym.info
1557-
}
15581558
}

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1832,6 +1832,23 @@ class Typer extends Namer
18321832
ctx.featureWarning(nme.dynamics.toString, "extension of type scala.Dynamic", cls, isRequired, cdef.sourcePos)
18331833
}
18341834

1835+
// TODO: Drop or use
1836+
def simplifyOpaques(tp: Type): Type = tp match
1837+
case tp @ RefinedType(parent, name, alias @ TypeAlias(lzy: LazyRef)) =>
1838+
tp.derivedRefinedType(simplifyOpaques(parent), name,
1839+
alias.derivedAlias(lzy.ref))
1840+
case _ =>
1841+
tp
1842+
1843+
if false && cls.containsOpaques then
1844+
val cinfo = cls.classInfo
1845+
cinfo.selfInfo match
1846+
case self: Type =>
1847+
cls.info = cinfo.derivedClassInfo(selfInfo = simplifyOpaques(self))
1848+
case self: Symbol =>
1849+
self.info = simplifyOpaques(self.info)
1850+
//println(i"simplified: $cls with ${cls.classInfo.selfInfo}")
1851+
18351852
checkNonCyclicInherited(cls.thisType, cls.classParents, cls.info.decls, cdef.posd)
18361853

18371854
// check value class constraints

tests/neg/i6225.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ object O2 {
1111
}
1212

1313
object O3 {
14-
opaque type R[X] = R[X] // error
14+
opaque type R[X] = R[X] // error: R does not take parameters // error: cyclic
1515
}
1616

1717
object O4{
@@ -20,4 +20,4 @@ object O4{
2020

2121
object O5{
2222
opaque type T[X] = Nothing
23-
}
23+
}

0 commit comments

Comments
 (0)