@@ -407,25 +407,22 @@ class CheckCaptures extends Recheck, SymTransformer:
407407 else i " references $cs1$cs1description are not all " ,
408408 cs1, cs2, pos, provenance)
409409
410- /** If `sym` is a class or method nested inside a term , a capture set variable representing
411- * the captured variables of the environment associated with `sym`.
410+ /** If `sym` is a method or a non-static inner class , a capture set variable
411+ * representing the captured variables of the environment associated with `sym`.
412412 */
413413 def capturedVars (sym : Symbol )(using Context ): CaptureSet =
414414 myCapturedVars.getOrElseUpdate(sym,
415- if sym.ownersIterator.exists(_.isTerm)
415+ if sym.isTerm || ! sym.owner.isStaticOwner
416416 then CaptureSet .Var (sym.owner, level = ccState.symLevel(sym))
417417 else CaptureSet .empty)
418418
419419// ---- Record Uses with MarkFree ----------------------------------------------------
420420
421421 /** The next environment enclosing `env` that needs to be charged
422422 * with free references.
423- * @param included Whether an environment is included in the range of
424- * environments to charge. Once `included` is false, no
425- * more environments need to be charged.
426423 */
427- def nextEnvToCharge (env : Env , included : Env => Boolean )(using Context ): Env =
428- if env.owner.isConstructor && included(env.outer) then env.outer.outer
424+ def nextEnvToCharge (env : Env )(using Context ): Env | Null =
425+ if env.owner.isConstructor then env.outer.outer0
429426 else env.outer
430427
431428 /** A description where this environment comes from */
@@ -458,21 +455,27 @@ class CheckCaptures extends Recheck, SymTransformer:
458455 markFree(sym, sym.termRef, tree)
459456
460457 def markFree (sym : Symbol , ref : Capability , tree : Tree )(using Context ): Unit =
461- if sym.exists && ref.isTracked then markFree(ref.singletonCaptureSet, tree)
458+ if sym.exists then markFree(ref, tree)
459+
460+ def markFree (ref : Capability , tree : Tree )(using Context ): Unit =
461+ if ref.isTracked then markFree(ref.singletonCaptureSet, tree)
462462
463463 /** Make sure the (projected) `cs` is a subset of the capture sets of all enclosing
464464 * environments. At each stage, only include references from `cs` that are outside
465465 * the environment's owner
466466 */
467- def markFree (cs : CaptureSet , tree : Tree )(using Context ): Unit =
467+ def markFree (cs : CaptureSet , tree : Tree , addUseInfo : Boolean = true )(using Context ): Unit =
468468 // A captured reference with the symbol `sym` is visible from the environment
469469 // if `sym` is not defined inside the owner of the environment.
470470 inline def isVisibleFromEnv (sym : Symbol , env : Env ) =
471471 sym.exists && {
472+ val effectiveOwner =
473+ if env.owner.isConstructor then env.owner.owner
474+ else env.owner
472475 if env.kind == EnvKind .NestedInOwner then
473- ! sym.isProperlyContainedIn(env.owner )
476+ ! sym.isProperlyContainedIn(effectiveOwner )
474477 else
475- ! sym.isContainedIn(env.owner )
478+ ! sym.isContainedIn(effectiveOwner )
476479 }
477480
478481 /** Avoid locally defined capability by charging the underlying type
@@ -535,13 +538,15 @@ class CheckCaptures extends Recheck, SymTransformer:
535538 checkSubset(included, env.captured, tree.srcPos, provenance(env))
536539 capt.println(i " Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}" )
537540 if ! isOfNestedMethod(env) then
538- recur(included, nextEnvToCharge(env, ! _.owner.isStaticOwner), env)
541+ val nextEnv = nextEnvToCharge(env)
542+ if nextEnv != null && ! nextEnv.owner.isStaticOwner then
543+ recur(included, nextEnv, env)
539544 // Under deferredReaches, don't propagate out of methods inside terms.
540545 // The use set of these methods will be charged when that method is called.
541546
542547 if ! cs.isAlwaysEmpty then
543548 recur(cs, curEnv, null )
544- useInfos += ((tree, cs, curEnv))
549+ if addUseInfo then useInfos += ((tree, cs, curEnv))
545550 end markFree
546551
547552 /** If capability `c` refers to a parameter that is not implicitly or explicitly
@@ -626,25 +631,33 @@ class CheckCaptures extends Recheck, SymTransformer:
626631 // If ident refers to a parameterless method, charge its cv to the environment
627632 includeCallCaptures(sym, sym.info, tree)
628633 else if ! sym.isStatic then
629- // Otherwise charge its symbol, but add all selections and also any `.rd`
630- // modifier implied by the expected type `pt`.
631- // Example: If we have `x` and the expected type says we select that with `.a.b`
632- // where `b` is a read-only method, we charge `x.a.b.rd` instead of `x`.
633- def addSelects (ref : TermRef , pt : Type ): Capability = pt match
634- case pt : PathSelectionProto if ref.isTracked =>
635- if pt.sym.isReadOnlyMethod then
636- ref.readOnly
637- else
638- // if `ref` is not tracked then the selection could not give anything new
639- // class SerializationProxy in stdlib-cc/../LazyListIterable.scala has an example where this matters.
640- addSelects(ref.select(pt.sym).asInstanceOf [TermRef ], pt.pt)
641- case _ => ref
642- var pathRef : Capability = addSelects(sym.termRef, pt)
643- if pathRef.derivesFromMutable && pt.isValueType && ! pt.isMutableType then
644- pathRef = pathRef.readOnly
645- markFree(sym, pathRef, tree)
634+ markFree(sym, pathRef(sym.termRef, pt), tree)
646635 mapResultRoots(super .recheckIdent(tree, pt), tree.symbol)
647636
637+ override def recheckThis (tree : This , pt : Type )(using Context ): Type =
638+ markFree(pathRef(tree.tpe.asInstanceOf [ThisType ], pt), tree)
639+ super .recheckThis(tree, pt)
640+
641+ /** Add all selections and also any `.rd modifier implied by the expected
642+ * type `pt` to `base`. Example:
643+ * If we have `x` and the expected type says we select that with `.a.b`
644+ * where `b` is a read-only method, we charge `x.a.b.rd` instead of `x`.
645+ */
646+ private def pathRef (base : TermRef | ThisType , pt : Type )(using Context ): Capability =
647+ def addSelects (ref : TermRef | ThisType , pt : Type ): Capability = pt match
648+ case pt : PathSelectionProto if ref.isTracked =>
649+ if pt.sym.isReadOnlyMethod then
650+ ref.readOnly
651+ else
652+ // if `ref` is not tracked then the selection could not give anything new
653+ // class SerializationProxy in stdlib-cc/../LazyListIterable.scala has an example where this matters.
654+ addSelects(ref.select(pt.sym).asInstanceOf [TermRef ], pt.pt)
655+ case _ => ref
656+ val ref : Capability = addSelects(base, pt)
657+ if ref.derivesFromMutable && pt.isValueType && ! pt.isMutableType
658+ then ref.readOnly
659+ else ref
660+
648661 /** The expected type for the qualifier of a selection. If the selection
649662 * could be part of a capability path or is a a read-only method, we return
650663 * a PathSelectionProto.
@@ -866,7 +879,7 @@ class CheckCaptures extends Recheck, SymTransformer:
866879 val (refined, cs) = addParamArgRefinements(core, initCs)
867880 refined.capturing(cs)
868881
869- augmentConstructorType(resType, capturedVars(cls) ++ capturedVars(constr) )
882+ augmentConstructorType(resType, capturedVars(cls))
870883 .showing(i " constr type $mt with $argTypes%, % in $constr = $result" , capt)
871884 end refineConstructorInstance
872885
@@ -975,6 +988,8 @@ class CheckCaptures extends Recheck, SymTransformer:
975988 * - Interpolate contravariant capture set variables in result type.
976989 */
977990 override def recheckValDef (tree : ValDef , sym : Symbol )(using Context ): Type =
991+ val savedEnv = curEnv
992+ val runInConstructor = ! sym.isOneOf(Param | ParamAccessor | Lazy | NonMember )
978993 try
979994 if sym.is(Module ) then sym.info // Modules are checked by checking the module class
980995 else
@@ -993,6 +1008,8 @@ class CheckCaptures extends Recheck, SymTransformer:
9931008 " "
9941009 disallowBadRootsIn(
9951010 tree.tpt.nuType, NoSymbol , i " Mutable $sym" , " have type" , addendum, sym.srcPos)
1011+ if runInConstructor then
1012+ pushConstructorEnv()
9961013 checkInferredResult(super .recheckValDef(tree, sym), tree)
9971014 finally
9981015 if ! sym.is(Param ) then
@@ -1002,6 +1019,22 @@ class CheckCaptures extends Recheck, SymTransformer:
10021019 // function is compiled since we do not propagate expected types into blocks.
10031020 interpolateIfInferred(tree.tpt, sym)
10041021
1022+ def declaredCaptures = tree.tpt.nuType.captureSet
1023+ if runInConstructor && savedEnv.owner.isClass then
1024+ curEnv = savedEnv
1025+ markFree(declaredCaptures, tree, addUseInfo = false )
1026+
1027+ if sym.owner.isStaticOwner && ! declaredCaptures.elems.isEmpty && sym != defn.captureRoot then
1028+ def where =
1029+ if sym.effectiveOwner.is(Package ) then " top-level definition"
1030+ else i " member of static ${sym.owner}"
1031+ report.warning(
1032+ em """ $sym has a non-empty capture set but will not be added as
1033+ |a capability to computed capture sets since it is globally accessible
1034+ |as a $where. Global values cannot be capabilities. """ ,
1035+ tree.namePos)
1036+ end recheckValDef
1037+
10051038 /** Recheck method definitions:
10061039 * - check body in a nested environment that tracks uses, in a nested level,
10071040 * and in a nested context that knows abaout Contains parameters so that we
@@ -1228,6 +1261,24 @@ class CheckCaptures extends Recheck, SymTransformer:
12281261 recheckFinish(result, arg, pt)
12291262 */
12301263
1264+ /** If environment is owned by a class, run in a new environment owned by
1265+ * its primary constructor instead.
1266+ */
1267+ def pushConstructorEnv ()(using Context ): Unit =
1268+ if curEnv.owner.isClass then
1269+ val constr = curEnv.owner.primaryConstructor
1270+ if constr.exists then
1271+ val constrSet = capturedVars(constr)
1272+ if capturedVars(constr) ne CaptureSet .empty then
1273+ curEnv = Env (constr, EnvKind .Regular , constrSet, curEnv)
1274+
1275+ override def recheckStat (stat : Tree )(using Context ): Unit =
1276+ val saved = curEnv
1277+ if ! stat.isInstanceOf [MemberDef ] then
1278+ pushConstructorEnv()
1279+ try recheck(stat)
1280+ finally curEnv = saved
1281+
12311282 /** The main recheck method does some box adapation for all nodes:
12321283 * - If expected type `pt` is boxed and the tree is a lambda or a reference,
12331284 * don't propagate free variables.
@@ -2021,7 +2072,9 @@ class CheckCaptures extends Recheck, SymTransformer:
20212072 if env.kind == EnvKind .Boxed then env.owner
20222073 else if isOfNestedMethod(env) then env.owner.owner
20232074 else if env.owner.isStaticOwner then NoSymbol
2024- else boxedOwner(nextEnvToCharge(env, alwaysTrue))
2075+ else
2076+ val nextEnv = nextEnvToCharge(env)
2077+ if nextEnv == null then NoSymbol else boxedOwner(nextEnv)
20252078
20262079 def checkUseUnlessBoxed (c : Capability , croot : NamedType ) =
20272080 if ! boxedOwner(env).isContainedIn(croot.symbol.owner) then
0 commit comments