@@ -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