@@ -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)
@@ -1083,6 +1085,7 @@ class CheckCaptures extends Recheck, SymTransformer:
10831085 * - for externally visible definitions: check that their inferred type
10841086 * does not refine what was known before capture checking.
10851087 * - Interpolate contravariant capture set variables in result type.
1088+ * - for lazy vals: create a nested environment to track captures (similar to methods)
10861089 */
10871090 override def recheckValDef (tree : ValDef , sym : Symbol )(using Context ): Type =
10881091 val savedEnv = curEnv
@@ -1105,8 +1108,16 @@ class CheckCaptures extends Recheck, SymTransformer:
11051108 " "
11061109 disallowBadRootsIn(
11071110 tree.tpt.nuType, NoSymbol , i " Mutable $sym" , " have type" , addendum, sym.srcPos)
1108- if runInConstructor then
1111+
1112+ // Lazy vals need their own environment to track captures from their RHS,
1113+ // similar to how methods work
1114+ if sym.is(Lazy ) then
1115+ val localSet = capturedVars(sym)
1116+ if localSet ne CaptureSet .empty then
1117+ curEnv = Env (sym, EnvKind .Regular , localSet, curEnv, nestedClosure = NoSymbol )
1118+ else if runInConstructor then
11091119 pushConstructorEnv()
1120+
11101121 checkInferredResult(super .recheckValDef(tree, sym), tree)
11111122 finally
11121123 if ! sym.is(Param ) then
@@ -1120,6 +1131,9 @@ class CheckCaptures extends Recheck, SymTransformer:
11201131 if runInConstructor && savedEnv.owner.isClass then
11211132 curEnv = savedEnv
11221133 markFree(declaredCaptures, tree, addUseInfo = false )
1134+ else if sym.is(Lazy ) then
1135+ // Restore environment after checking lazy val
1136+ curEnv = savedEnv
11231137
11241138 if sym.owner.isStaticOwner && ! declaredCaptures.elems.isEmpty && sym != defn.captureRoot then
11251139 def where =
0 commit comments