Skip to content

Commit 6ed8caa

Browse files
committed
Charge a lazy val if its initialization is impure
1 parent 076d5a7 commit 6ed8caa

File tree

1 file changed

+43
-5
lines changed

1 file changed

+43
-5
lines changed

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

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -655,11 +655,26 @@ class CheckCaptures extends Recheck, SymTransformer:
655655
*/
656656
override def recheckIdent(tree: Ident, pt: Type)(using Context): Type =
657657
val sym = tree.symbol
658-
if sym.is(Method) || sym.is(Lazy) then
659-
// If ident refers to a parameterless method or lazy val, charge its cv to the environment.
660-
// Lazy vals are like parameterless methods: accessing them may trigger initialization
661-
// that uses captured references.
658+
if sym.is(Method) then
659+
// If ident refers to a parameterless method, charge its cv to the environment.
662660
includeCallCaptures(sym, sym.info, tree)
661+
else if sym.is(Lazy) then
662+
// For lazy vals: when the declared type is pure but captures capabilities,
663+
// charge the captured vars instead of the lazy val itself. This makes
664+
// {x} work through its captured vars rather than being independent.
665+
val declaredCs = sym.info.captureSet
666+
val capturedCs = capturedVars(sym)
667+
if declaredCs.isAlwaysEmpty && !capturedCs.isAlwaysEmpty then
668+
// Pure type but captures: charge the captured vars directly
669+
def isRetained(ref: Capability): Boolean = ref.pathRoot match
670+
case root: ThisType => ctx.owner.isContainedIn(root.cls)
671+
case _ => true
672+
if curEnv.kind != EnvKind.Boxed then
673+
markFree(capturedCs.filter(isRetained), tree)
674+
else
675+
// Otherwise treat like a regular val
676+
if !sym.isStatic then
677+
markPathFree(sym.termRef, pt, tree)
663678
else if sym.exists && !sym.isStatic then
664679
markPathFree(sym.termRef, pt, tree)
665680
mapResultRoots(super.recheckIdent(tree, pt), tree.symbol)
@@ -1113,7 +1128,30 @@ class CheckCaptures extends Recheck, SymTransformer:
11131128
else if runInConstructor then
11141129
pushConstructorEnv()
11151130

1116-
checkInferredResult(super.recheckValDef(tree, sym), tree)
1131+
val resType = checkInferredResult(super.recheckValDef(tree, sym), tree)
1132+
1133+
// For lazy vals, if the declared type is pure but the initialization captures
1134+
// capabilities, we need to make the lazy val trackable by adding its captured
1135+
// vars to the result type. This allows subcapturing to work: {x} can subcapture
1136+
// {console} if x's type includes console in its capture set.
1137+
if sym.is(Lazy) then
1138+
val declaredCs = tree.tpt.nuType.captureSet
1139+
val capturedCs = capturedVars(sym)
1140+
if declaredCs.isAlwaysEmpty && !capturedCs.isAlwaysEmpty then
1141+
// The declared type is pure, but we captured capabilities.
1142+
// Augment the type to make it trackable.
1143+
val augmentedType = resType.capturing(capturedCs)
1144+
capt.println(i"lazy val $sym: augmenting pure type $resType with $capturedCs -> $augmentedType")
1145+
tree.setNuType(augmentedType)
1146+
// Also update the symbol's info so that TermRefs to this lazy val
1147+
// will have the augmented type
1148+
sym.info = augmentedType
1149+
capt.println(i"lazy val $sym: sym.info = ${sym.info}, termRef.captureSet = ${sym.termRef.captureSet}")
1150+
augmentedType
1151+
else
1152+
resType
1153+
else
1154+
resType
11171155
finally
11181156
if !sym.is(Param) then
11191157
// Parameters with inferred types belong to anonymous methods. We need to wait

0 commit comments

Comments
 (0)