Skip to content

Commit 35e3793

Browse files
authored
Merge branch 'main' into modify-nullable-union-erasure-sjs
2 parents 57c72cf + 71fcd1a commit 35e3793

31 files changed

+445
-301
lines changed

compiler/src/dotty/tools/dotc/cc/Capability.scala

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import CCState.*
1212
import Periods.{NoRunId, RunWidth}
1313
import compiletime.uninitialized
1414
import StdNames.nme
15-
import CaptureSet.VarState
15+
import CaptureSet.{Refs, emptyRefs, VarState}
1616
import Annotations.Annotation
1717
import Flags.*
1818
import config.Printers.capt
@@ -147,7 +147,7 @@ object Capabilities:
147147
* @param origin an indication where and why the FreshCap was created, used
148148
* for diagnostics
149149
*/
150-
case class FreshCap private (owner: Symbol, origin: Origin)(using @constructorOnly ctx: Context) extends RootCapability:
150+
case class FreshCap (owner: Symbol, origin: Origin)(using @constructorOnly ctx: Context) extends RootCapability:
151151
val hiddenSet = CaptureSet.HiddenSet(owner, this: @unchecked)
152152
// fails initialization check without the @unchecked
153153

@@ -547,6 +547,23 @@ object Capabilities:
547547
captureSetValid = currentId
548548
computed
549549

550+
/** The elements hidden by this capability, if this is a FreshCap
551+
* or a derived version of one. Read-only status and restrictions
552+
* are transferred from the capability to its hidden set.
553+
*/
554+
def hiddenSet(using Context): Refs = computeHiddenSet(identity)
555+
556+
/** Compute result based on hidden set of this capability.
557+
* Restrictions and read-only status transfer from the capability to its
558+
* hidden set.
559+
* @param f a function that gets applied to all detected hidden sets
560+
*/
561+
def computeHiddenSet(f: Refs => Refs)(using Context): Refs = this match
562+
case self: FreshCap => f(self.hiddenSet.elems)
563+
case Restricted(elem1, cls) => elem1.computeHiddenSet(f).map(_.restrict(cls))
564+
case ReadOnly(elem1) => elem1.computeHiddenSet(f).map(_.readOnly)
565+
case _ => emptyRefs
566+
550567
/** The transitive classifiers of this capability. */
551568
def transClassifiers(using Context): Classifiers =
552569
def toClassifiers(cls: ClassSymbol): Classifiers =
@@ -753,25 +770,44 @@ object Capabilities:
753770
* x covers x
754771
* x covers y ==> x covers y.f
755772
* x covers y ==> x* covers y*, x? covers y?
773+
* x covers y ==> <fresh hiding x> covers y
774+
* x covers y ==> x.only[C] covers y, x covers y.only[C]
775+
*
756776
* TODO what other clauses from subsumes do we need to port here?
777+
* The last clause is a conservative over-approximation: basically, we can't achieve
778+
* separation by having different classifiers for now. It would be good to
779+
* have a test that would expect such separation, then we can try to refine
780+
* the clause to make the test pass.
757781
*/
758782
final def covers(y: Capability)(using Context): Boolean =
759-
(this eq y)
760-
|| y.match
761-
case y @ TermRef(ypre: Capability, _) =>
762-
this.covers(ypre)
763-
case Reach(y1) =>
764-
this match
765-
case Reach(x1) => x1.covers(y1)
766-
case _ => false
767-
case Maybe(y1) =>
768-
this match
769-
case Maybe(x1) => x1.covers(y1)
770-
case _ => false
771-
case y: FreshCap =>
772-
y.hiddenSet.superCaps.exists(this.covers(_))
773-
case _ =>
774-
false
783+
val seen: util.EqHashSet[FreshCap] = new util.EqHashSet
784+
785+
def recur(x: Capability, y: Capability): Boolean =
786+
(x eq y)
787+
|| y.match
788+
case y @ TermRef(ypre: Capability, _) =>
789+
recur(x, ypre)
790+
case Reach(y1) =>
791+
x match
792+
case Reach(x1) => recur(x1, y1)
793+
case _ => false
794+
case Maybe(y1) =>
795+
x match
796+
case Maybe(x1) => recur(x1, y1)
797+
case _ => false
798+
case Restricted(y1, _) =>
799+
recur(x, y1)
800+
case _ =>
801+
false
802+
|| x.match
803+
case x: FreshCap if !seen.contains(x) =>
804+
seen.add(x)
805+
x.hiddenSet.exists(recur(_, y))
806+
case Restricted(x1, _) => recur(x1, y)
807+
case _ => false
808+
809+
recur(this, y)
810+
end covers
775811

776812
def assumedContainsOf(x: TypeRef)(using Context): SimpleIdentitySet[Capability] =
777813
CaptureSet.assumedContains.getOrElse(x, SimpleIdentitySet.empty)

compiler/src/dotty/tools/dotc/cc/CaptureOps.scala

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,21 +151,37 @@ extension (tp: Type)
151151
case tp: ObjectCapability => tp.captureSetOfInfo
152152
case _ => CaptureSet.ofType(tp, followResult = false)
153153

154-
/** The deep capture set of a type. This is by default the union of all
154+
/** Compute a captureset by traversing parts of this type. This is by default the union of all
155155
* covariant capture sets embedded in the widened type, as computed by
156156
* `CaptureSet.ofTypeDeeply`. If that set is nonempty, and the type is
157157
* a singleton capability `x` or a reach capability `x*`, the deep capture
158158
* set can be narrowed to`{x*}`.
159+
* @param includeTypevars if true, return a new FreshCap for every type parameter
160+
* or abstract type with an Any upper bound. Types with
161+
* defined upper bound are always mapped to the dcs of their bound
162+
* @param includeBoxed if true, include capture sets found in boxed parts of this type
159163
*/
160-
def deepCaptureSet(includeTypevars: Boolean)(using Context): CaptureSet =
161-
val dcs = CaptureSet.ofTypeDeeply(tp.widen.stripCapturing, includeTypevars)
164+
def computeDeepCaptureSet(includeTypevars: Boolean, includeBoxed: Boolean = true)(using Context): CaptureSet =
165+
val dcs = CaptureSet.ofTypeDeeply(tp.widen.stripCapturing, includeTypevars, includeBoxed)
162166
if dcs.isAlwaysEmpty then tp.captureSet
163167
else tp match
164168
case tp: ObjectCapability if tp.isTrackableRef => tp.reach.singletonCaptureSet
165169
case _ => tp.captureSet ++ dcs
166170

171+
/** The deep capture set of a type. This is by default the union of all
172+
* covariant capture sets embedded in the widened type, as computed by
173+
* `CaptureSet.ofTypeDeeply`. If that set is nonempty, and the type is
174+
* a singleton capability `x` or a reach capability `x*`, the deep capture
175+
* set can be narrowed to`{x*}`.
176+
*/
167177
def deepCaptureSet(using Context): CaptureSet =
168-
deepCaptureSet(includeTypevars = false)
178+
computeDeepCaptureSet(includeTypevars = false)
179+
180+
/** The span capture set of a type. This is analogous to deepCaptureSet but ignoring
181+
* capture sets in boxed parts.
182+
*/
183+
def spanCaptureSet(using Context): CaptureSet =
184+
computeDeepCaptureSet(includeTypevars = false, includeBoxed = false)
169185

170186
/** A type capturing `ref` */
171187
def capturing(ref: Capability)(using Context): Type =
@@ -362,7 +378,7 @@ extension (tp: Type)
362378
*/
363379
def derivesFromCapTraitDeeply(cls: ClassSymbol)(using Context): Boolean =
364380
val accumulate = new DeepTypeAccumulator[Boolean]:
365-
def capturingCase(acc: Boolean, parent: Type, refs: CaptureSet) =
381+
def capturingCase(acc: Boolean, parent: Type, refs: CaptureSet, boxed: Boolean) =
366382
this(acc, parent)
367383
&& (parent.derivesFromCapTrait(cls)
368384
|| refs.isConst && refs.elems.forall(_.derivesFromCapTrait(cls)))
@@ -734,15 +750,15 @@ object ContainsParam:
734750
abstract class DeepTypeAccumulator[T](using Context) extends TypeAccumulator[T]:
735751
val seen = util.HashSet[Symbol]()
736752

737-
protected def capturingCase(acc: T, parent: Type, refs: CaptureSet): T
753+
protected def capturingCase(acc: T, parent: Type, refs: CaptureSet, boxed: Boolean): T
738754

739755
protected def abstractTypeCase(acc: T, t: TypeRef, upperBound: Type): T
740756

741757
def apply(acc: T, t: Type) =
742758
if variance < 0 then acc
743759
else t.dealias match
744760
case t @ CapturingType(parent, cs) =>
745-
capturingCase(acc, parent, cs)
761+
capturingCase(acc, parent, cs, t.isBoxed)
746762
case t: TypeRef if t.symbol.isAbstractOrParamType && !seen.contains(t.symbol) =>
747763
seen += t.symbol
748764
abstractTypeCase(acc, t, t.info.bounds.hi)

compiler/src/dotty/tools/dotc/cc/CaptureSet.scala

Lines changed: 76 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,7 @@ object CaptureSet:
777777
assert(elem.subsumes(elem1),
778778
i"Skipped map ${tm.getClass} maps newly added $elem to $elem1 in $this")
779779

780-
protected def includeElem(elem: Capability)(using Context): Unit =
780+
protected def includeElem(elem: Capability)(using Context, VarState): Unit =
781781
if !elems.contains(elem) then
782782
if debugVars && id == debugTarget then
783783
println(i"###INCLUDE $elem in $this")
@@ -803,7 +803,10 @@ object CaptureSet:
803803
// id == 108 then assert(false, i"trying to add $elem to $this")
804804
assert(elem.isWellformed, elem)
805805
assert(!this.isInstanceOf[HiddenSet] || summon[VarState].isSeparating, summon[VarState])
806-
includeElem(elem)
806+
try includeElem(elem)
807+
catch case ex: AssertionError =>
808+
println(i"error for incl $elem in $this, ${summon[VarState].toString}")
809+
throw ex
807810
if isBadRoot(elem) then
808811
rootAddedHandler()
809812
val normElem = if isMaybeSet then elem else elem.stripMaybe
@@ -947,12 +950,64 @@ object CaptureSet:
947950
override def toString = s"Var$id$elems"
948951
end Var
949952

950-
/** Variables that represent refinements of class parameters can have the universal
951-
* capture set, since they represent only what is the result of the constructor.
952-
* Test case: Without that tweak, logger.scala would not compile.
953-
*/
954-
class RefiningVar(owner: Symbol)(using Context) extends Var(owner):
955-
override def disallowBadRoots(upto: Symbol)(handler: () => Context ?=> Unit)(using Context) = this
953+
/** Variables created in types of inferred type trees */
954+
class ProperVar(override val owner: Symbol, initialElems: Refs = emptyRefs, nestedOK: Boolean = true, isRefining: Boolean)(using /*@constructorOnly*/ ictx: Context)
955+
extends Var(owner, initialElems, nestedOK):
956+
957+
/** Make sure that capset variables in types of vals and result types of
958+
* non-anonymous functions contain only a single FreshCap, and furthermore
959+
* that that FreshCap has as origin InDecl(owner), where owner is the val
960+
* or def for which the type is defined.
961+
* Note: This currently does not apply to classified or read-only fresh caps.
962+
*/
963+
override def includeElem(elem: Capability)(using ctx: Context, vs: VarState): Unit = elem match
964+
case elem: FreshCap
965+
if !nestedOK
966+
&& !elems.contains(elem)
967+
&& !owner.isAnonymousFunction
968+
&& ccConfig.newScheme =>
969+
def fail = i"attempting to add $elem to $this"
970+
def hideIn(fc: FreshCap): Unit =
971+
assert(elem.tryClassifyAs(fc.hiddenSet.classifier), fail)
972+
if !isRefining then
973+
// If a variable is added by addCaptureRefinements in a synthetic
974+
// refinement of a class type, don't do level checking. The problem is
975+
// that the variable might be matched against a type that does not have
976+
// a refinement, in which case FreshCaps of the class definition would
977+
// leak out in the corresponding places. This will fail level checking.
978+
// The disallowBadRoots override below has a similar reason.
979+
// TODO: We should instead mark the variable as impossible to instantiate
980+
// and drop the refinement later in the inferred type.
981+
// Test case is drop-refinement.scala.
982+
assert(fc.acceptsLevelOf(elem),
983+
i"level failure, cannot add $elem with ${elem.levelOwner} to $owner / $getClass / $fail")
984+
fc.hiddenSet.add(elem)
985+
val isSubsumed = (false /: elems): (isSubsumed, prev) =>
986+
prev match
987+
case prev: FreshCap =>
988+
hideIn(prev)
989+
true
990+
case _ => isSubsumed
991+
if !isSubsumed then
992+
if elem.origin != Origin.InDecl(owner) || elem.hiddenSet.isConst then
993+
val fc = new FreshCap(owner, Origin.InDecl(owner))
994+
assert(fc.tryClassifyAs(elem.hiddenSet.classifier), fail)
995+
hideIn(fc)
996+
super.includeElem(fc)
997+
else
998+
super.includeElem(elem)
999+
case _ =>
1000+
super.includeElem(elem)
1001+
1002+
/** Variables that represent refinements of class parameters can have the universal
1003+
* capture set, since they represent only what is the result of the constructor.
1004+
* Test case: Without that tweak, logger.scala would not compile.
1005+
*/
1006+
override def disallowBadRoots(upto: Symbol)(handler: () => Context ?=> Unit)(using Context) =
1007+
if isRefining then this
1008+
else super.disallowBadRoots(upto)(handler)
1009+
1010+
end ProperVar
9561011

9571012
/** A variable that is derived from some other variable via a map or filter. */
9581013
abstract class DerivedVar(owner: Symbol, initialElems: Refs)(using @constructorOnly ctx: Context)
@@ -1163,6 +1218,9 @@ object CaptureSet:
11631218
*/
11641219
class HiddenSet(initialOwner: Symbol, val owningCap: FreshCap)(using @constructorOnly ictx: Context)
11651220
extends Var(initialOwner):
1221+
1222+
// Updated by anchorCaps in CheckCaptures, but owner can be changed only
1223+
// if it was NoSymbol before.
11661224
var givenOwner: Symbol = initialOwner
11671225

11681226
override def owner = givenOwner
@@ -1171,62 +1229,9 @@ object CaptureSet:
11711229

11721230
description = i"of elements subsumed by a fresh cap in $initialOwner"
11731231

1174-
private def aliasRef: FreshCap | Null =
1175-
if myElems.size == 1 then
1176-
myElems.nth(0) match
1177-
case alias: FreshCap if deps.contains(alias.hiddenSet) => alias
1178-
case _ => null
1179-
else null
1180-
1181-
private def aliasSet: HiddenSet =
1182-
if myElems.size == 1 then
1183-
myElems.nth(0) match
1184-
case alias: FreshCap if deps.contains(alias.hiddenSet) => alias.hiddenSet
1185-
case _ => this
1186-
else this
1187-
1188-
def superCaps: List[FreshCap] =
1189-
deps.toList.map(_.asInstanceOf[HiddenSet].owningCap)
1190-
1191-
override def elems: Refs =
1192-
val al = aliasSet
1193-
if al eq this then super.elems else al.elems
1194-
1195-
override def elems_=(refs: Refs) =
1196-
val al = aliasSet
1197-
if al eq this then super.elems_=(refs) else al.elems_=(refs)
1198-
1199-
/** Add element to hidden set. Also add it to all supersets (as indicated by
1200-
* deps of this set). Follow aliases on both hidden set and added element
1201-
* before adding. If the added element is also a Fresh instance with
1202-
* hidden set H which is a superset of this set, then make this set an
1203-
* alias of H.
1204-
*/
1232+
/** Add element to hidden set. */
12051233
def add(elem: Capability)(using ctx: Context, vs: VarState): Unit =
1206-
val alias = aliasSet
1207-
if alias ne this then alias.add(elem)
1208-
else
1209-
def addToElems() =
1210-
assert(!isConst)
1211-
includeElem(elem)
1212-
deps.foreach: dep =>
1213-
assert(dep != this)
1214-
vs.addHidden(dep.asInstanceOf[HiddenSet], elem)
1215-
elem match
1216-
case elem: FreshCap =>
1217-
if this ne elem.hiddenSet then
1218-
val alias = elem.hiddenSet.aliasRef
1219-
if alias != null then
1220-
add(alias)
1221-
else if deps.contains(elem.hiddenSet) then // make this an alias of elem
1222-
capt.println(i"Alias $this to ${elem.hiddenSet}")
1223-
elems = SimpleIdentitySet(elem)
1224-
deps = SimpleIdentitySet(elem.hiddenSet)
1225-
else
1226-
addToElems()
1227-
elem.hiddenSet.includeDep(this)
1228-
case _ =>
1229-
addToElems()
1234+
includeElem(elem)
12301235

12311236
/** Apply function `f` to `elems` while setting `elems` to empty for the
12321237
* duration. This is used to escape infinite recursions if two Freshs
@@ -1553,7 +1558,7 @@ object CaptureSet:
15531558
/** The capture set of the type underlying the capability `c` */
15541559
def ofInfo(c: Capability)(using Context): CaptureSet = c match
15551560
case Reach(c1) =>
1556-
c1.widen.deepCaptureSet(includeTypevars = true)
1561+
c1.widen.computeDeepCaptureSet(includeTypevars = true)
15571562
.showing(i"Deep capture set of $c: ${c1.widen} = ${result}", capt)
15581563
case Restricted(c1, cls) =>
15591564
if cls == defn.NothingClass then CaptureSet.empty
@@ -1615,13 +1620,17 @@ object CaptureSet:
16151620
/** The deep capture set of a type is the union of all covariant occurrences of
16161621
* capture sets. Nested existential sets are approximated with `cap`.
16171622
*/
1618-
def ofTypeDeeply(tp: Type, includeTypevars: Boolean = false)(using Context): CaptureSet =
1623+
def ofTypeDeeply(tp: Type, includeTypevars: Boolean = false, includeBoxed: Boolean = true)(using Context): CaptureSet =
16191624
val collect = new DeepTypeAccumulator[CaptureSet]:
1620-
def capturingCase(acc: CaptureSet, parent: Type, refs: CaptureSet) =
1621-
this(acc, parent) ++ refs
1625+
1626+
def capturingCase(acc: CaptureSet, parent: Type, refs: CaptureSet, boxed: Boolean) =
1627+
if includeBoxed || !boxed then this(acc, parent) ++ refs
1628+
else this(acc, parent)
1629+
16221630
def abstractTypeCase(acc: CaptureSet, t: TypeRef, upperBound: Type) =
16231631
if includeTypevars && upperBound.isExactlyAny then fresh(Origin.DeepCS(t))
16241632
else this(acc, upperBound)
1633+
16251634
collect(CaptureSet.empty, tp)
16261635

16271636
type AssumedContains = immutable.Map[TypeRef, SimpleIdentitySet[Capability]]

compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1376,7 +1376,10 @@ class CheckCaptures extends Recheck, SymTransformer:
13761376
* where local capture roots are instantiated to root variables.
13771377
*/
13781378
override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Type =
1379-
testAdapted(actual, expected, tree, addenda)(err.typeMismatch)
1379+
try testAdapted(actual, expected, tree, addenda)(err.typeMismatch)
1380+
catch case ex: AssertionError =>
1381+
println(i"error while checking $tree: $actual against $expected")
1382+
throw ex
13801383

13811384
@annotation.tailrec
13821385
private def findImpureUpperBound(tp: Type)(using Context): Type = tp match

0 commit comments

Comments
 (0)