Skip to content

Commit 682693b

Browse files
committed
Under 3.8 solve all capture sets in types of vals and defs
1 parent f127be0 commit 682693b

File tree

8 files changed

+157
-82
lines changed

8 files changed

+157
-82
lines changed

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

Lines changed: 65 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,15 @@ sealed abstract class CaptureSet extends Showable:
5151
/** Is this capture set constant (i.e. not an unsolved capture variable)?
5252
* Solved capture variables count as constant.
5353
*/
54-
def isConst: Boolean
54+
def isConst(using Context): Boolean
5555

5656
/** Is this capture set always empty? For unsolved capture veriables, returns
5757
* always false.
5858
*/
59-
def isAlwaysEmpty: Boolean
59+
def isAlwaysEmpty(using Context): Boolean
60+
61+
/** Is this set provisionally solved, so that another cc run might unfreeze it? */
62+
def isProvisionallySolved(using Context): Boolean
6063

6164
/** An optional level limit, or undefinedLevel if none exists. All elements of the set
6265
* must be at levels equal or smaller than the level of the set, if it is defined.
@@ -71,14 +74,14 @@ sealed abstract class CaptureSet extends Showable:
7174
final def isNotEmpty: Boolean = !elems.isEmpty
7275

7376
/** Convert to Const. @pre: isConst */
74-
def asConst: Const = this match
77+
def asConst(using Context): Const = this match
7578
case c: Const => c
7679
case v: Var =>
7780
assert(v.isConst)
7881
Const(v.elems)
7982

8083
/** Cast to variable. @pre: !isConst */
81-
def asVar: Var =
84+
def asVar(using Context): Var =
8285
assert(!isConst)
8386
asInstanceOf[Var]
8487

@@ -316,23 +319,29 @@ sealed abstract class CaptureSet extends Showable:
316319
* `OtherMapped` provides some approximation to a solution, but it is neither
317320
* sound nor complete.
318321
*/
319-
def map(tm: TypeMap)(using Context): CaptureSet = tm match
320-
case tm: BiTypeMap =>
321-
val mappedElems = elems.map(tm.forward)
322-
if isConst then
323-
if mappedElems == elems then this
324-
else Const(mappedElems)
325-
else BiMapped(asVar, tm, mappedElems)
326-
case tm: IdentityCaptRefMap =>
327-
this
328-
case tm: AvoidMap if this.isInstanceOf[HiddenSet] =>
329-
this
330-
case _ =>
331-
val mapped = mapRefs(elems, tm, tm.variance)
332-
if isConst then
333-
if mapped.isConst && mapped.elems == elems && !mapped.keepAlways then this
334-
else mapped
335-
else Mapped(asVar, tm, tm.variance, mapped)
322+
def map(tm: TypeMap)(using Context): CaptureSet =
323+
def freeze() = this match
324+
case self: Var if !isConst && ccConfig.newScheme =>
325+
if tm.variance < 0 then self.solve()
326+
else self.markSolved(provisional = true)
327+
case _ =>
328+
tm match
329+
case tm: BiTypeMap =>
330+
val mappedElems = elems.map(tm.forward)
331+
if isConst then
332+
if mappedElems == elems then this
333+
else Const(mappedElems)
334+
else BiMapped(asVar, tm, mappedElems)
335+
case tm: IdentityCaptRefMap =>
336+
this
337+
case tm: AvoidMap if this.isInstanceOf[HiddenSet] =>
338+
this
339+
case _ =>
340+
val mapped = mapRefs(elems, tm, tm.variance)
341+
if isConst then
342+
if mapped.isConst && mapped.elems == elems && !mapped.keepAlways then this
343+
else mapped
344+
else Mapped(asVar, tm, tm.variance, mapped)
336345

337346
/** A mapping resulting from substituting parameters of a BindingType to a list of types */
338347
def substParams(tl: BindingType, to: List[Type])(using Context) =
@@ -368,7 +377,7 @@ sealed abstract class CaptureSet extends Showable:
368377
* to this set. This might result in the set being solved to be constant
369378
* itself.
370379
*/
371-
protected def propagateSolved()(using Context): Unit = ()
380+
protected def propagateSolved(provisional: Boolean)(using Context): Unit = ()
372381

373382
/** This capture set with a description that tells where it comes from */
374383
def withDescription(description: String): CaptureSet
@@ -438,8 +447,9 @@ object CaptureSet:
438447

439448
/** The subclass of constant capture sets with given elements `elems` */
440449
class Const private[CaptureSet] (val elems: Refs, val description: String = "") extends CaptureSet:
441-
def isConst = true
442-
def isAlwaysEmpty = elems.isEmpty
450+
def isConst(using Context) = true
451+
def isAlwaysEmpty(using Context) = elems.isEmpty
452+
def isProvisionallySolved(using Context) = false
443453

444454
def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult =
445455
addIfHiddenOrFail(elem)
@@ -470,7 +480,7 @@ object CaptureSet:
470480
* were not yet compiled with capture checking on.
471481
*/
472482
object Fluid extends Const(emptyRefs):
473-
override def isAlwaysEmpty = false
483+
override def isAlwaysEmpty(using Context) = false
474484
override def addThisElem(elem: CaptureRef)(using Context, VarState) = CompareResult.OK
475485
override def accountsFor(x: CaptureRef)(using Context, VarState): Boolean = true
476486
override def mightAccountFor(x: CaptureRef)(using Context): Boolean = true
@@ -489,8 +499,13 @@ object CaptureSet:
489499

490500
//assert(id != 40)
491501

492-
/** A variable is solved if it is aproximated to a from-then-on constant set. */
493-
private var isSolved: Boolean = false
502+
/** A variable is solved if it is aproximated to a from-then-on constant set.
503+
* Interpretation:
504+
* 0 not solved
505+
* Int.MaxValue definitively solved
506+
* n > 0 provisionally solved in iteration n
507+
*/
508+
private var solved: Int = 0
494509

495510
/** The elements currently known to be in the set */
496511
protected var myElems: Refs = initialElems
@@ -503,8 +518,9 @@ object CaptureSet:
503518
*/
504519
var deps: Deps = SimpleIdentitySet.empty
505520

506-
def isConst = isSolved
507-
def isAlwaysEmpty = isSolved && elems.isEmpty
521+
def isConst(using Context) = solved >= ccState.iterCount
522+
def isAlwaysEmpty(using Context) = isConst && elems.isEmpty
523+
def isProvisionallySolved(using Context): Boolean = solved > 0 && solved != Int.MaxValue
508524

509525
def isMaybeSet = false // overridden in BiMapped
510526

@@ -656,21 +672,20 @@ object CaptureSet:
656672
* in the results of defs and vals.
657673
*/
658674
def solve()(using Context): Unit =
659-
if !isConst then
660-
CCState.withCapAsRoot: // // OK here since we infer parameter types that get checked later
661-
val approx = upperApprox(empty)
662-
.map(root.CapToFresh(NoSymbol).inverse) // Fresh --> cap
663-
.showing(i"solve $this = $result", capt)
664-
//println(i"solving var $this $approx ${approx.isConst} deps = ${deps.toList}")
665-
val newElems = approx.elems -- elems
666-
given VarState()
667-
if tryInclude(newElems, empty).isOK then
668-
markSolved()
675+
CCState.withCapAsRoot: // // OK here since we infer parameter types that get checked later
676+
val approx = upperApprox(empty)
677+
.map(root.CapToFresh(NoSymbol).inverse) // Fresh --> cap
678+
.showing(i"solve $this = $result", capt)
679+
//println(i"solving var $this $approx ${approx.isConst} deps = ${deps.toList}")
680+
val newElems = approx.elems -- elems
681+
given VarState()
682+
if tryInclude(newElems, empty).isOK then
683+
markSolved(provisional = false)
669684

670685
/** Mark set as solved and propagate this info to all dependent sets */
671-
def markSolved()(using Context): Unit =
672-
isSolved = true
673-
deps.foreach(_.propagateSolved())
686+
def markSolved(provisional: Boolean)(using Context): Unit =
687+
solved = if provisional then ccState.iterCount else Int.MaxValue
688+
deps.foreach(_.propagateSolved(provisional))
674689

675690
def withDescription(description: String): this.type =
676691
this.description = this.description.join(" and ", description)
@@ -728,8 +743,8 @@ object CaptureSet:
728743

729744
addAsDependentTo(source)
730745

731-
override def propagateSolved()(using Context) =
732-
if source.isConst && !isConst then markSolved()
746+
override def propagateSolved(provisional: Boolean)(using Context) =
747+
if source.isConst && !isConst then markSolved(provisional)
733748
end DerivedVar
734749

735750
/** A variable that changes when `source` changes, where all additional new elements are mapped
@@ -823,8 +838,8 @@ object CaptureSet:
823838
else
824839
source.upperApprox(this).map(tm)
825840

826-
override def propagateSolved()(using Context) =
827-
if initial.isConst then super.propagateSolved()
841+
override def propagateSolved(provisional: Boolean)(using Context) =
842+
if initial.isConst then super.propagateSolved(provisional)
828843

829844
override def toString = s"Mapped$id($source, elems = $elems)"
830845
end Mapped
@@ -914,8 +929,8 @@ object CaptureSet:
914929
else res
915930
else res
916931

917-
override def propagateSolved()(using Context) =
918-
if cs1.isConst && cs2.isConst && !isConst then markSolved()
932+
override def propagateSolved(provisional: Boolean)(using Context) =
933+
if cs1.isConst && cs2.isConst && !isConst then markSolved(provisional)
919934
end Union
920935

921936
class Intersection(cs1: CaptureSet, cs2: CaptureSet)(using Context)
@@ -941,8 +956,8 @@ object CaptureSet:
941956
else
942957
CaptureSet(elemIntersection(cs1.upperApprox(this), cs2.upperApprox(this)))
943958

944-
override def propagateSolved()(using Context) =
945-
if cs1.isConst && cs2.isConst && !isConst then markSolved()
959+
override def propagateSolved(provisional: Boolean)(using Context) =
960+
if cs1.isConst && cs2.isConst && !isConst then markSolved(provisional)
946961
end Intersection
947962

948963
def elemIntersection(cs1: CaptureSet, cs2: CaptureSet)(using Context): Refs =

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

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ object CheckCaptures:
5959
def isOutermost = outer0 == null
6060

6161
/** If an environment is open it tracks free references */
62-
def isOpen = !captured.isAlwaysEmpty && kind != EnvKind.Boxed
62+
def isOpen(using Context) = !captured.isAlwaysEmpty && kind != EnvKind.Boxed
6363

6464
def outersIterator: Iterator[Env] = new:
6565
private var cur = Env.this
@@ -220,7 +220,7 @@ object CheckCaptures:
220220

221221
trait CheckerAPI:
222222
/** Complete symbol info of a val or a def */
223-
def completeDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type
223+
def completeDef(tree: ValOrDefDef, sym: Symbol, newInfo: Type)(using Context): Type
224224

225225
extension [T <: Tree](tree: T)
226226

@@ -271,6 +271,8 @@ class CheckCaptures extends Recheck, SymTransformer:
271271

272272
class CaptureChecker(ictx: Context) extends Rechecker(ictx), CheckerAPI:
273273

274+
// println(i"checking ${ictx.source}"(using ictx))
275+
274276
/** The current environment */
275277
private val rootEnv: Env = inContext(ictx):
276278
Env(defn.RootClass, EnvKind.Regular, CaptureSet.empty, null)
@@ -292,8 +294,21 @@ class CheckCaptures extends Recheck, SymTransformer:
292294
*/
293295
private val sepCheckFormals = util.EqHashMap[Tree, Type]()
294296

297+
/** The references used at identifier or application trees */
295298
private val usedSet = util.EqHashMap[Tree, CaptureSet]()
296299

300+
/** The set of symbols that were rechecked via a completer, mapped to the completer. */
301+
private val completed = new mutable.HashMap[Symbol, Type]
302+
303+
var needAnotherRun = false
304+
305+
def resetIteration()(using Context): Unit =
306+
needAnotherRun = false
307+
resetNuTypes()
308+
todoAtPostCheck.clear()
309+
for (sym, completer) <- completed do sym.info = completer
310+
completed.clear()
311+
297312
extension [T <: Tree](tree: T)
298313
def needsSepCheck: Boolean = sepCheckFormals.contains(tree)
299314
def formalType: Type = sepCheckFormals.getOrElse(tree, NoType)
@@ -307,7 +322,9 @@ class CheckCaptures extends Recheck, SymTransformer:
307322
override def traverse(t: Type) = t match
308323
case t @ CapturingType(parent, refs) =>
309324
refs match
310-
case refs: CaptureSet.Var if variance < 0 => refs.solve()
325+
case refs: CaptureSet.Var if !refs.isConst =>
326+
if variance < 0 then refs.solve()
327+
else if ccConfig.newScheme then refs.markSolved(provisional = true)
311328
case _ =>
312329
traverse(parent)
313330
case t @ defn.RefinedFunctionOf(rinfo) =>
@@ -340,7 +357,7 @@ class CheckCaptures extends Recheck, SymTransformer:
340357
private def interpolateVarsIn(tpt: Tree, sym: Symbol)(using Context): Unit =
341358
if tpt.isInstanceOf[InferredTypeTree] then
342359
interpolator().traverse(tpt.nuType)
343-
.showing(i"solved vars in ${tpt.nuType}", capt)
360+
.showing(i"solved vars for $sym in ${tpt.nuType}", capt)
344361
anchorCaps(sym).traverse(tpt.nuType)
345362
for msg <- ccState.approxWarnings do
346363
report.warning(msg, tpt.srcPos)
@@ -351,23 +368,33 @@ class CheckCaptures extends Recheck, SymTransformer:
351368
assert(cs1.subCaptures(cs2).isOK, i"$cs1 is not a subset of $cs2")
352369

353370
/** If `res` is not CompareResult.OK, report an error */
354-
def checkOK(res: CompareResult, prefix: => String, added: CaptureRef | CaptureSet, pos: SrcPos, provenance: => String = "")(using Context): Unit =
371+
def checkOK(res: CompareResult, prefix: => String, added: CaptureRef | CaptureSet, target: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context): Unit =
355372
res match
356373
case res: CompareFailure =>
357-
inContext(root.printContext(added, res.blocking)):
374+
def msg =
358375
def toAdd: String = errorNotes(res.errorNotes).toAdd.mkString
359376
def descr: String =
360377
val d = res.blocking.description
361378
if d.isEmpty then provenance else ""
362-
report.error(em"$prefix included in the allowed capture set ${res.blocking}$descr$toAdd", pos)
379+
em"$prefix included in the allowed capture set ${res.blocking}$descr$toAdd"
380+
target match
381+
case target: CaptureSet.Var if res.blocking.isProvisionallySolved =>
382+
report.warning(msg.prepend(i"Another capture checking run needs to be scheduled because:"), pos)
383+
needAnotherRun = true
384+
added match
385+
case added: CaptureRef => target.elems += added
386+
case added: CaptureSet => target.elems ++= added.elems
387+
case _ =>
388+
inContext(root.printContext(added, res.blocking)):
389+
report.error(msg, pos)
363390
case _ =>
364391

365392
/** Check subcapturing `{elem} <: cs`, report error on failure */
366393
def checkElem(elem: CaptureRef, cs: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context) =
367394
checkOK(
368395
ccState.test(elem.singletonCaptureSet.subCaptures(cs)),
369396
i"$elem cannot be referenced here; it is not",
370-
elem, pos, provenance)
397+
elem, cs, pos, provenance)
371398

372399
/** Check subcapturing `cs1 <: cs2`, report error on failure */
373400
def checkSubset(cs1: CaptureSet, cs2: CaptureSet, pos: SrcPos,
@@ -376,7 +403,7 @@ class CheckCaptures extends Recheck, SymTransformer:
376403
ccState.test(cs1.subCaptures(cs2)),
377404
if cs1.elems.size == 1 then i"reference ${cs1.elems.toList.head}$cs1description is not"
378405
else i"references $cs1$cs1description are not all",
379-
cs1, pos, provenance)
406+
cs1, cs2, pos, provenance)
380407

381408
/** If `sym` is a class or method nested inside a term, a capture set variable representing
382409
* the captured variables of the environment associated with `sym`.
@@ -1051,9 +1078,6 @@ class CheckCaptures extends Recheck, SymTransformer:
10511078
tp
10521079
end checkInferredResult
10531080

1054-
/** The set of symbols that were rechecked via a completer */
1055-
private val completed = new mutable.HashSet[Symbol]
1056-
10571081
/** The normal rechecking if `sym` was already completed before */
10581082
override def skipRecheck(sym: Symbol)(using Context): Boolean =
10591083
completed.contains(sym)
@@ -1062,7 +1086,9 @@ class CheckCaptures extends Recheck, SymTransformer:
10621086
* these checks can appear out of order, we need to first create the correct
10631087
* environment for checking the definition.
10641088
*/
1065-
def completeDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type =
1089+
def completeDef(tree: ValOrDefDef, sym: Symbol, newInfo: Type)(using Context): Type =
1090+
val completer = sym.infoOrCompleter
1091+
sym.info = newInfo
10661092
val saved = curEnv
10671093
try
10681094
// Setup environment to reflect the new owner.
@@ -1079,7 +1105,7 @@ class CheckCaptures extends Recheck, SymTransformer:
10791105
curEnv = restoreEnvFor(sym.owner)
10801106
capt.println(i"Complete $sym in ${curEnv.outersIterator.toList.map(_.owner)}")
10811107
try recheckDef(tree, sym)
1082-
finally completed += sym
1108+
finally completed(sym) = completer
10831109
finally
10841110
curEnv = saved
10851111

@@ -1707,7 +1733,13 @@ class CheckCaptures extends Recheck, SymTransformer:
17071733
report.echo(s"$echoHeader\n$treeString\n")
17081734

17091735
withCaptureSetsExplained:
1710-
super.checkUnit(unit)
1736+
while
1737+
super.checkUnit(unit)
1738+
!ctx.reporter.errorsReported && needAnotherRun
1739+
do
1740+
resetIteration()
1741+
ccState.iterCount += 1
1742+
println(s"**** capture checking run ${ccState.iterCount} started on ${ctx.source}")
17111743
checkOverrides.traverse(unit.tpdTree)
17121744
postCheck(unit.tpdTree)
17131745
checkSelfTypes(unit.tpdTree)

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -728,8 +728,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
728728
assert(ctx.phase == thisPhase.next, i"$sym")
729729
capt.println(i"forcing $sym, printing = ${ctx.mode.is(Mode.Printing)}")
730730
//if ctx.mode.is(Mode.Printing) then new Error().printStackTrace()
731-
denot.info = newInfo
732-
completeDef(tree, sym)
731+
completeDef(tree, sym, newInfo)
733732
updateInfo(sym, updatedInfo)
734733

735734
case tree: Bind =>

compiler/src/dotty/tools/dotc/transform/Recheck.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ abstract class Recheck extends Phase, SymTransformer:
189189
def keepNuTypes(using Context): Boolean =
190190
ctx.settings.Xprint.value.containsPhase(thisPhase)
191191

192+
def resetNuTypes()(using Context): Unit =
193+
nuTypes.clear(resetToInitial = false)
194+
192195
/** A map from NamedTypes to the denotations they had before this phase.
193196
* Needed so that we can `reset` them after this phase.
194197
*/

0 commit comments

Comments
 (0)