Skip to content

Commit bb51ba0

Browse files
committed
Redo capture checks if necessary
Schedule another capture checking run when a provisionally solved set is later extended.
1 parent bd0533a commit bb51ba0

File tree

6 files changed

+42
-22
lines changed

6 files changed

+42
-22
lines changed

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

+8
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ object ccConfig:
5151
*/
5252
inline val checkSkippedMaps = false
5353

54+
/** Always repeat a capture checking run at least once if there are no errors
55+
* yet. Used for stress-testing the logic for when a new capture checking run needs
56+
* to be scheduled because a provisionally solved capture set was later extended.
57+
* So far this happens only in very few tests. With the flag on, the logic is
58+
* tested for all tests except neg tests.
59+
*/
60+
inline val alwaysRepeatRun = false
61+
5462
/** If true, turn on separation checking */
5563
def useSepChecks(using Context): Boolean =
5664
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`)

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

+16-12
Original file line numberDiff line numberDiff line change
@@ -259,18 +259,15 @@ class CheckCaptures extends Recheck, SymTransformer:
259259
/** The references used at identifier or application trees */
260260
private val usedSet = util.EqHashMap[Tree, CaptureSet]()
261261

262-
/** The set of symbols that were rechecked via a completer, mapped to the completer. */
263-
private val completed = new mutable.HashMap[Symbol, Type]
262+
/** The set of symbols that were rechecked via a completer */
263+
private val completed = new mutable.HashSet[Symbol]
264264

265265
var needAnotherRun = false
266266

267267
def resetIteration()(using Context): Unit =
268268
needAnotherRun = false
269269
resetNuTypes()
270270
todoAtPostCheck.clear()
271-
for (sym, completer) <- completed do
272-
sym.info = completer
273-
sym.resetFlag(Touched)
274271
completed.clear()
275272

276273
extension [T <: Tree](tree: T)
@@ -357,23 +354,27 @@ class CheckCaptures extends Recheck, SymTransformer:
357354
def checkOK(res: CompareResult, prefix: => String, added: CaptureRef | CaptureSet, target: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context): Unit =
358355
res match
359356
case res: CompareFailure =>
360-
def msg =
357+
def msg(provisional: Boolean) =
361358
def toAdd: String = errorNotes(res.errorNotes).toAdd.mkString
362359
def descr: String =
363360
val d = res.blocking.description
364361
if d.isEmpty then provenance else ""
365-
em"$prefix included in the allowed capture set ${res.blocking}$descr$toAdd"
362+
def kind = if provisional then "previously estimated\n" else "allowed "
363+
em"$prefix included in the ${kind}capture set ${res.blocking}$descr$toAdd"
366364
target match
367365
case target: CaptureSet.Var
368366
if res.blocking.isProvisionallySolved =>
369-
report.error(msg.prepend(i"Another capture checking run needs to be scheduled because\n"), pos)
367+
report.warning(
368+
msg(provisional = true)
369+
.prepend(i"Another capture checking run needs to be scheduled because\n"),
370+
pos)
370371
needAnotherRun = true
371372
added match
372373
case added: CaptureRef => target.elems += added
373374
case added: CaptureSet => target.elems ++= added.elems
374375
case _ =>
375376
inContext(root.printContext(added, res.blocking)):
376-
report.error(msg, pos)
377+
report.error(msg(provisional = false), pos)
377378
case _ =>
378379

379380
/** Check subcapturing `{elem} <: cs`, report error on failure */
@@ -1107,7 +1108,7 @@ class CheckCaptures extends Recheck, SymTransformer:
11071108
curEnv = restoreEnvFor(sym.owner)
11081109
capt.println(i"Complete $sym in ${curEnv.outersIterator.toList.map(_.owner)}")
11091110
try recheckDef(tree, sym)
1110-
finally completed(sym) = completer
1111+
finally completed += sym
11111112
finally
11121113
curEnv = saved
11131114

@@ -1737,11 +1738,14 @@ class CheckCaptures extends Recheck, SymTransformer:
17371738
withCaptureSetsExplained:
17381739
while
17391740
super.checkUnit(unit)
1740-
!ctx.reporter.errorsReported && needAnotherRun
1741+
!ctx.reporter.errorsReported
1742+
&& (needAnotherRun || ccConfig.alwaysRepeatRun && ccState.iterCount == 1)
17411743
do
17421744
resetIteration()
1745+
setup.setupUnit(unit.tpdTree, this)
17431746
ccState.iterCount += 1
1744-
println(s"**** capture checking run ${ccState.iterCount} started on ${ctx.source}")
1747+
capt.println(s"**** capture checking run ${ccState.iterCount} started on ${ctx.source}")
1748+
17451749
checkOverrides.traverse(unit.tpdTree)
17461750
postCheck(unit.tpdTree)
17471751
checkSelfTypes(unit.tpdTree)

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,14 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
705705
if cls.is(ModuleClass) then
706706
// if it's a module, the capture set of the module reference is the capture set of the self type
707707
val modul = cls.sourceModule
708-
updateInfo(modul, CapturingType(modul.info, selfInfo1.asInstanceOf[Type].captureSet))
708+
val selfCaptures = selfInfo1 match
709+
case CapturingType(_, refs) => refs
710+
case _ => CaptureSet.empty
711+
// Note: Can't do val selfCaptures = selfInfo1.captureSet here.
712+
// This would potentially give stackoverflows when setup is run repeatedly.
713+
// One test case is pos-custom-args/captures/checkbounds.scala under
714+
// ccConfig.alwaysRepeatRun = true.
715+
updateInfo(modul, CapturingType(modul.info, selfCaptures))
709716
modul.termRef.invalidateCaches()
710717
case _ =>
711718
case _ =>

tests/neg-custom-args/captures/byname.check

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
-- Error: tests/neg-custom-args/captures/byname.scala:5:21 -------------------------------------------------------------
2-
5 | def g(x: Int) = if cap2 == cap2 then 1 else x // error
1+
-- Warning: tests/neg-custom-args/captures/byname.scala:5:21 -----------------------------------------------------------
2+
5 | def g(x: Int) = if cap2 == cap2 then 1 else x
33
| ^^^^
44
| Another capture checking run needs to be scheduled because
5-
| reference (cap2 : Cap) is not included in the allowed capture set {} of method f
5+
| reference (cap2 : Cap) is not included in the previously estimated
6+
| capture set {} of method f
67
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:10:6 ----------------------------------------
78
10 | h(f2()) // error
89
| ^^^^

tests/neg-custom-args/captures/byname.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ class Cap extends caps.Capability
22

33
def test(cap1: Cap, cap2: Cap) =
44
def f() = if cap1 == cap1 then g else g
5-
def g(x: Int) = if cap2 == cap2 then 1 else x // error
5+
def g(x: Int) = if cap2 == cap2 then 1 else x
66
def g2(x: Int) = if cap1 == cap1 then 1 else x
77
def f2() = if cap1 == cap1 then g2 else g2
88
def h(ff: => Int ->{cap2} Int) = ff

tests/neg-custom-args/captures/i22808.scala

+5-5
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ def test1(io: Object^): Unit =
66
val x = () =>
77
foo()
88
val y = Box(io)
9-
println(y.m) // error: another run needs to be scheduled
10-
val _: () -> Unit = x // was error
9+
println(y.m) // warning: another run needs to be scheduled
10+
val _: () -> Unit = x // error
1111

1212
def test2(io: Object^): Unit =
1313
def foo(): Unit = bar()
1414
def bar(): Unit =
1515
val x = () =>
1616
foo()
17-
val _: () -> Unit = x
17+
val _: () -> Unit = x // error
1818
val y = Box(io)
19-
println(y.m) // error: another run needs to be scheduled
20-
val _: () -> Unit = x
19+
println(y.m) // warning: another run needs to be scheduled
20+
val _: () -> Unit = x // error

0 commit comments

Comments
 (0)