Skip to content

Commit dc0dd7f

Browse files
committed
Refactor addenda generation
Avoid globally visible vars in CCState. Make sure notes don't leak from one test to another.
1 parent d781b3e commit dc0dd7f

File tree

4 files changed

+77
-71
lines changed

4 files changed

+77
-71
lines changed

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

+24-11
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import config.Feature
1616
import collection.mutable
1717
import CCState.*
1818
import reporting.Message
19-
import CaptureSet.VarState
19+
import CaptureSet.{VarState, CompareResult}
2020

2121
/** Attachment key for capturing type trees */
2222
private val Captures: Key[CaptureSet] = Key()
@@ -76,19 +76,32 @@ def depFun(args: List[Type], resultType: Type, isContextual: Boolean, paramNames
7676
/** An exception thrown if a @retains argument is not syntactically a CaptureRef */
7777
class IllegalCaptureRef(tpe: Type)(using Context) extends Exception(tpe.show)
7878

79+
/** A base trait for data producing addenda to error messages */
80+
trait ErrorNote
81+
7982
/** Capture checking state, which is known to other capture checking components */
8083
class CCState:
8184

82-
/** The last pair of capture reference and capture set where
83-
* the reference could not be added to the set due to a level conflict.
84-
*/
85-
var levelError: Option[CaptureSet.CompareResult.LevelError] = None
86-
87-
/** Optionally, a pair of an existential variable and another capability.
88-
* Set when a subsumes check decides that an existential variable cannot be
89-
* instantiated to the other capability.
90-
*/
91-
var existentialSubsumesFailure: Option[(CaptureRef, CaptureRef)] = None
85+
/** Error reprting notes produces since the last call to `test` */
86+
var notes: List[ErrorNote] = Nil
87+
88+
def addNote(note: ErrorNote): Unit =
89+
if !notes.exists(_.getClass == note.getClass) then
90+
notes = note :: notes
91+
92+
def test(op: => CompareResult): CompareResult =
93+
val saved = notes
94+
notes = Nil
95+
try op.withNotes(notes)
96+
finally notes = saved
97+
98+
def testOK(op: => Boolean): CompareResult =
99+
val saved = notes
100+
notes = Nil
101+
try
102+
if op then CompareResult.OK
103+
else CompareResult.Fail(Nil).withNotes(notes)
104+
finally notes = saved
92105

93106
/** Warnings relating to upper approximations of capture sets with
94107
* existentially bound variables.

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,10 @@ trait CaptureRef extends TypeProxy, ValueType:
253253
case root.Fresh(hidden) =>
254254
vs.ifNotSeen(this)(hidden.elems.exists(_.subsumes(y)))
255255
|| !y.stripReadOnly.isCap && !yIsExistential && canAddHidden && vs.addHidden(hidden, y)
256-
case root.Result(binder) =>
256+
case x @ root.Result(binder) =>
257257
if y.derivesFromSharedCapability then true
258258
else
259-
ccState.existentialSubsumesFailure =
260-
ccState.existentialSubsumesFailure.orElse(Some(this, y))
259+
ccState.addNote(CaptureSet.ExistentialSubsumesFailure(x, y))
261260
false
262261
case _ =>
263262
y match

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

+18-22
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ sealed abstract class CaptureSet extends Showable:
233233
if result.isOK then
234234
addDependent(that)
235235
else
236-
ccState.levelError = ccState.levelError.orElse(result.levelError)
236+
result.levelError.foreach(ccState.addNote)
237237
varState.rollBack()
238238
result
239239
//.showing(i"subcaptures $this <:< $that = ${result.show}", capt)
@@ -1077,10 +1077,16 @@ object CaptureSet:
10771077
/** A TypeMap that is the identity on capture references */
10781078
trait IdentityCaptRefMap extends TypeMap
10791079

1080+
/** A value of this class is produced and added as a note to ccState
1081+
* when a subsumes check decides that an existential variable `ex` cannot be
1082+
* instantiated to the other capability `other`.
1083+
*/
1084+
case class ExistentialSubsumesFailure(val ex: root.Result, val other: CaptureRef) extends ErrorNote
1085+
10801086
enum CompareResult extends Showable:
10811087
case OK
10821088
case Fail(trace: List[CaptureSet])
1083-
case LevelError(cs: CaptureSet, elem: CaptureRef)
1089+
case LevelError(cs: CaptureSet, elem: CaptureRef) extends CompareResult, ErrorNote
10841090

10851091
override def toText(printer: Printer): Text =
10861092
inContext(printer.printerContext):
@@ -1105,6 +1111,16 @@ object CaptureSet:
11051111
case result: LevelError => Some(result)
11061112
case _ => None
11071113

1114+
private var myErrorNotes: List[ErrorNote] = Nil
1115+
1116+
def errorNotes: List[ErrorNote] = myErrorNotes
1117+
1118+
def withNotes(notes: List[ErrorNote]): CompareResult = this match
1119+
case OK => OK
1120+
case _ =>
1121+
myErrorNotes = notes
1122+
this
1123+
11081124
inline def andAlso(op: Context ?=> CompareResult)(using Context): CompareResult =
11091125
if isOK then op else this
11101126

@@ -1412,24 +1428,4 @@ object CaptureSet:
14121428
println(i" ${cv.show.padTo(20, ' ')} :: ${cv.deps.toList}%, %")
14131429
}
14141430
else op
1415-
1416-
def levelErrors: Addenda = new Addenda:
1417-
override def toAdd(using Context) =
1418-
for CompareResult.LevelError(cs, ref) <- ccState.levelError.toList yield
1419-
ccState.levelError = None
1420-
if ref.stripReadOnly.isCapOrFresh then
1421-
def capStr = if ref.isReadOnly then "cap.rd" else "cap"
1422-
i"""
1423-
|
1424-
|Note that the universal capability `$capStr`
1425-
|cannot be included in capture set $cs"""
1426-
else
1427-
val levelStr = ref match
1428-
case ref: TermRef => i", defined in ${ref.symbol.maybeOwner}"
1429-
case _ => ""
1430-
i"""
1431-
|
1432-
|Note that reference ${ref}$levelStr
1433-
|cannot be included in outer capture set $cs"""
1434-
14351431
end CaptureSet

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

+33-35
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import util.{SimpleIdentitySet, EqHashMap, EqHashSet, SrcPos, Property}
1818
import transform.{Recheck, PreRecheck, CapturedVars}
1919
import Recheck.*
2020
import scala.collection.mutable
21-
import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult}
21+
import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult, ExistentialSubsumesFailure}
2222
import CCState.*
2323
import StdNames.nme
2424
import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind}
@@ -354,7 +354,7 @@ class CheckCaptures extends Recheck, SymTransformer:
354354
def checkOK(res: CompareResult, prefix: => String, added: CaptureRef | CaptureSet, pos: SrcPos, provenance: => String = "")(using Context): Unit =
355355
if !res.isOK then
356356
inContext(root.printContext(added, res.blocking)):
357-
def toAdd: String = CaptureSet.levelErrors.toAdd.mkString
357+
def toAdd: String = errorNotes(res.errorNotes).toAdd.mkString
358358
def descr: String =
359359
val d = res.blocking.description
360360
if d.isEmpty then provenance else ""
@@ -363,15 +363,15 @@ class CheckCaptures extends Recheck, SymTransformer:
363363
/** Check subcapturing `{elem} <: cs`, report error on failure */
364364
def checkElem(elem: CaptureRef, cs: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context) =
365365
checkOK(
366-
elem.singletonCaptureSet.subCaptures(cs),
366+
ccState.test(elem.singletonCaptureSet.subCaptures(cs)),
367367
i"$elem cannot be referenced here; it is not",
368368
elem, pos, provenance)
369369

370370
/** Check subcapturing `cs1 <: cs2`, report error on failure */
371371
def checkSubset(cs1: CaptureSet, cs2: CaptureSet, pos: SrcPos,
372372
provenance: => String = "", cs1description: String = "")(using Context) =
373373
checkOK(
374-
cs1.subCaptures(cs2),
374+
ccState.test(cs1.subCaptures(cs2)),
375375
if cs1.elems.size == 1 then i"reference ${cs1.elems.toList.head}$cs1description is not"
376376
else i"references $cs1$cs1description are not all",
377377
cs1, pos, provenance)
@@ -1210,27 +1210,29 @@ class CheckCaptures extends Recheck, SymTransformer:
12101210

12111211
type BoxErrors = mutable.ListBuffer[Message] | Null
12121212

1213-
private def boxErrorAddenda(boxErrors: BoxErrors) =
1214-
if boxErrors == null then NothingToAdd
1213+
private def errorNotes(notes: List[ErrorNote])(using Context): Addenda =
1214+
if notes.isEmpty then NothingToAdd
12151215
else new Addenda:
1216-
override def toAdd(using Context): List[String] =
1217-
boxErrors.toList.map: msg =>
1218-
i"""
1219-
|
1220-
|Note that ${msg.toString}"""
1221-
1222-
private def existentialSubsumesFailureAddenda(using Context): Addenda =
1223-
ccState.existentialSubsumesFailure match
1224-
case Some((ex @ root.Result(binder), other)) =>
1225-
new Addenda:
1226-
override def toAdd(using Context): List[String] =
1227-
val ann = ex.rootAnnot
1228-
i"""
1229-
|
1230-
|Note that the existential capture root in ${ex.rootAnnot.originalBinder.resType}
1216+
override def toAdd(using Context) = notes.map: note =>
1217+
val msg = note match
1218+
case CompareResult.LevelError(cs, ref) =>
1219+
if ref.stripReadOnly.isCapOrFresh then
1220+
def capStr = if ref.isReadOnly then "cap.rd" else "cap"
1221+
i"""the universal capability `$capStr`
1222+
|cannot be included in capture set $cs"""
1223+
else
1224+
val levelStr = ref match
1225+
case ref: TermRef => i", defined in ${ref.symbol.maybeOwner}"
1226+
case _ => ""
1227+
i"""reference ${ref}$levelStr
1228+
|cannot be included in outer capture set $cs"""
1229+
case ExistentialSubsumesFailure(ex, other) =>
1230+
i"""the existential capture root in ${ex.rootAnnot.originalBinder.resType}
12311231
|cannot subsume the capability $other"""
1232-
:: Nil
1233-
case _ => NothingToAdd
1232+
i"""
1233+
|
1234+
|Note that ${msg.toString}"""
1235+
12341236

12351237
/** Addendas for error messages that show where we have under-approximated by
12361238
* mapping a a capture ref in contravariant position to the empty set because
@@ -1264,15 +1266,14 @@ class CheckCaptures extends Recheck, SymTransformer:
12641266
*/
12651267
override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Type =
12661268
var expected1 = alignDependentFunction(expected, actual.stripCapturing)
1267-
val boxErrors = new mutable.ListBuffer[Message]
1268-
val actualBoxed = adapt(actual, expected1, tree, boxErrors)
1269+
val actualBoxed = adapt(actual, expected1, tree)
12691270
//println(i"check conforms $actualBoxed <<< $expected1")
12701271

12711272
if actualBoxed eq actual then
12721273
// Only `addOuterRefs` when there is no box adaptation
12731274
expected1 = addOuterRefs(expected1, actual, tree.srcPos)
1274-
ccState.existentialSubsumesFailure = None
1275-
if isCompatible(actualBoxed, expected1) then
1275+
val result = ccState.testOK(isCompatible(actualBoxed, expected1))
1276+
if result.isOK then
12761277
if debugSuccesses then tree match
12771278
case Ident(_) =>
12781279
println(i"SUCCESS $tree for $actual <:< $expected:\n${TypeComparer.explained(_.isSubType(actualBoxed, expected1))}")
@@ -1283,10 +1284,7 @@ class CheckCaptures extends Recheck, SymTransformer:
12831284
inContext(root.printContext(actualBoxed, expected1)):
12841285
err.typeMismatch(tree.withType(actualBoxed), expected1,
12851286
addApproxAddenda(
1286-
addenda
1287-
++ CaptureSet.levelErrors
1288-
++ boxErrorAddenda(boxErrors)
1289-
++ existentialSubsumesFailureAddenda,
1287+
addenda ++ errorNotes(result.errorNotes),
12901288
expected1))
12911289
actual
12921290
end checkConformsExpr
@@ -1397,7 +1395,7 @@ class CheckCaptures extends Recheck, SymTransformer:
13971395
*
13981396
* @param alwaysConst always make capture set variables constant after adaptation
13991397
*/
1400-
def adaptBoxed(actual: Type, expected: Type, tree: Tree, covariant: Boolean, alwaysConst: Boolean, boxErrors: BoxErrors)(using Context): Type =
1398+
def adaptBoxed(actual: Type, expected: Type, tree: Tree, covariant: Boolean, alwaysConst: Boolean)(using Context): Type =
14011399

14021400
def recur(actual: Type, expected: Type, covariant: Boolean): Type =
14031401

@@ -1551,15 +1549,15 @@ class CheckCaptures extends Recheck, SymTransformer:
15511549
* - narrow nested captures of `x`'s underlying type to `{x*}`
15521550
* - do box adaptation
15531551
*/
1554-
def adapt(actual: Type, expected: Type, tree: Tree, boxErrors: BoxErrors)(using Context): Type =
1552+
def adapt(actual: Type, expected: Type, tree: Tree)(using Context): Type =
15551553
if noWiden(actual, expected) then
15561554
actual
15571555
else
15581556
val improvedVAR = improveCaptures(actual.widen.dealiasKeepAnnots, actual)
15591557
val improved = improveReadOnly(improvedVAR, expected)
15601558
val adapted = adaptBoxed(
15611559
improved.withReachCaptures(actual), expected, tree,
1562-
covariant = true, alwaysConst = false, boxErrors)
1560+
covariant = true, alwaysConst = false)
15631561
if adapted eq improvedVAR // no .rd improvement, no box-adaptation
15641562
then actual // might as well use actual instead of improved widened
15651563
else adapted.showing(i"adapt $actual vs $expected = $adapted", capt)
@@ -1585,7 +1583,7 @@ class CheckCaptures extends Recheck, SymTransformer:
15851583
try
15861584
curEnv = Env(clazz, EnvKind.NestedInOwner, capturedVars(clazz), outer0 = curEnv)
15871585
val adapted =
1588-
adaptBoxed(actual, expected1, tree, covariant = true, alwaysConst = true, null)
1586+
adaptBoxed(actual, expected1, tree, covariant = true, alwaysConst = true)
15891587
actual match
15901588
case _: MethodType =>
15911589
// We remove the capture set resulted from box adaptation for method types,

0 commit comments

Comments
 (0)