@@ -81,7 +81,7 @@ object CheckCaptures:
8181 end Env
8282
8383 def definesEnv (sym : Symbol )(using Context ): Boolean =
84- sym.is(Method ) || sym.isClass
84+ sym.is(Method ) || sym.isClass || sym.is( Lazy )
8585
8686 /** Similar normal substParams, but this is an approximating type map that
8787 * maps parameters in contravariant capture sets to the empty set.
@@ -225,7 +225,7 @@ object CheckCaptures:
225225 def needsSepCheck : Boolean
226226
227227 /** If a tree is an argument for which needsSepCheck is true,
228- * the type of the formal paremeter corresponding to the argument.
228+ * the type of the formal parameter corresponding to the argument.
229229 */
230230 def formalType : Type
231231
@@ -441,7 +441,7 @@ class CheckCaptures extends Recheck, SymTransformer:
441441 */
442442 def capturedVars (sym : Symbol )(using Context ): CaptureSet =
443443 myCapturedVars.getOrElseUpdate(sym,
444- if sym.isTerm || ! sym.owner.isStaticOwner
444+ if sym.isTerm || ! sym.owner.isStaticOwner || sym.is( Lazy ) // FIXME: are lazy vals in static owners a thing?
445445 then CaptureSet .Var (sym, nestedOK = false )
446446 else CaptureSet .empty)
447447
@@ -655,8 +655,10 @@ 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 ) then
659- // If ident refers to a parameterless method, charge its cv to the environment
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.
660662 includeCallCaptures(sym, sym.info, tree)
661663 else if sym.exists && ! sym.isStatic then
662664 markPathFree(sym.termRef, pt, tree)
@@ -1078,6 +1080,7 @@ class CheckCaptures extends Recheck, SymTransformer:
10781080 * - for externally visible definitions: check that their inferred type
10791081 * does not refine what was known before capture checking.
10801082 * - Interpolate contravariant capture set variables in result type.
1083+ * - for lazy vals: create a nested environment to track captures (similar to methods)
10811084 */
10821085 override def recheckValDef (tree : ValDef , sym : Symbol )(using Context ): Type =
10831086 val savedEnv = curEnv
@@ -1100,8 +1103,16 @@ class CheckCaptures extends Recheck, SymTransformer:
11001103 " "
11011104 disallowBadRootsIn(
11021105 tree.tpt.nuType, NoSymbol , i " Mutable $sym" , " have type" , addendum, sym.srcPos)
1103- if runInConstructor then
1106+
1107+ // Lazy vals need their own environment to track captures from their RHS,
1108+ // similar to how methods work
1109+ if sym.is(Lazy ) then
1110+ val localSet = capturedVars(sym)
1111+ if localSet ne CaptureSet .empty then
1112+ curEnv = Env (sym, EnvKind .Regular , localSet, curEnv, nestedClosure = NoSymbol )
1113+ else if runInConstructor then
11041114 pushConstructorEnv()
1115+
11051116 checkInferredResult(super .recheckValDef(tree, sym), tree)
11061117 finally
11071118 if ! sym.is(Param ) then
@@ -1115,6 +1126,9 @@ class CheckCaptures extends Recheck, SymTransformer:
11151126 if runInConstructor && savedEnv.owner.isClass then
11161127 curEnv = savedEnv
11171128 markFree(declaredCaptures, tree, addUseInfo = false )
1129+ else if sym.is(Lazy ) then
1130+ // Restore environment after checking lazy val
1131+ curEnv = savedEnv
11181132
11191133 if sym.owner.isStaticOwner && ! declaredCaptures.elems.isEmpty && sym != defn.captureRoot then
11201134 def where =
0 commit comments