@@ -407,25 +407,22 @@ class CheckCaptures extends Recheck, SymTransformer:
407
407
else i " references $cs1$cs1description are not all " ,
408
408
cs1, cs2, pos, provenance)
409
409
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`.
412
412
*/
413
413
def capturedVars (sym : Symbol )(using Context ): CaptureSet =
414
414
myCapturedVars.getOrElseUpdate(sym,
415
- if sym.ownersIterator.exists(_.isTerm)
415
+ if sym.isTerm || ! sym.owner.isStaticOwner
416
416
then CaptureSet .Var (sym.owner, level = ccState.symLevel(sym))
417
417
else CaptureSet .empty)
418
418
419
419
// ---- Record Uses with MarkFree ----------------------------------------------------
420
420
421
421
/** The next environment enclosing `env` that needs to be charged
422
422
* 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.
426
423
*/
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
429
426
else env.outer
430
427
431
428
/** A description where this environment comes from */
@@ -458,21 +455,27 @@ class CheckCaptures extends Recheck, SymTransformer:
458
455
markFree(sym, sym.termRef, tree)
459
456
460
457
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)
462
462
463
463
/** Make sure the (projected) `cs` is a subset of the capture sets of all enclosing
464
464
* environments. At each stage, only include references from `cs` that are outside
465
465
* the environment's owner
466
466
*/
467
- def markFree (cs : CaptureSet , tree : Tree )(using Context ): Unit =
467
+ def markFree (cs : CaptureSet , tree : Tree , addUseInfo : Boolean = true )(using Context ): Unit =
468
468
// A captured reference with the symbol `sym` is visible from the environment
469
469
// if `sym` is not defined inside the owner of the environment.
470
470
inline def isVisibleFromEnv (sym : Symbol , env : Env ) =
471
471
sym.exists && {
472
+ val effectiveOwner =
473
+ if env.owner.isConstructor then env.owner.owner
474
+ else env.owner
472
475
if env.kind == EnvKind .NestedInOwner then
473
- ! sym.isProperlyContainedIn(env.owner )
476
+ ! sym.isProperlyContainedIn(effectiveOwner )
474
477
else
475
- ! sym.isContainedIn(env.owner )
478
+ ! sym.isContainedIn(effectiveOwner )
476
479
}
477
480
478
481
/** Avoid locally defined capability by charging the underlying type
@@ -493,7 +496,7 @@ class CheckCaptures extends Recheck, SymTransformer:
493
496
case _ => c.core match
494
497
case c1 : RootCapability => c1.singletonCaptureSet
495
498
case c1 : CoreCapability =>
496
- CaptureSet .ofType(c1.widen, followResult = true )
499
+ CaptureSet .ofType(c1.widen, followResult = ccConfig.useSpanCapset )
497
500
capt.println(i " Widen reach $c to $underlying in ${env.owner}" )
498
501
underlying.disallowBadRoots(NoSymbol ): () =>
499
502
report.error(em " Local capability $c${env.owner.qualString(" in" )} cannot have `cap` as underlying capture set " , tree.srcPos)
@@ -535,13 +538,15 @@ class CheckCaptures extends Recheck, SymTransformer:
535
538
checkSubset(included, env.captured, tree.srcPos, provenance(env))
536
539
capt.println(i " Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}" )
537
540
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)
539
544
// Under deferredReaches, don't propagate out of methods inside terms.
540
545
// The use set of these methods will be charged when that method is called.
541
546
542
547
if ! cs.isAlwaysEmpty then
543
548
recur(cs, curEnv, null )
544
- useInfos += ((tree, cs, curEnv))
549
+ if addUseInfo then useInfos += ((tree, cs, curEnv))
545
550
end markFree
546
551
547
552
/** If capability `c` refers to a parameter that is not implicitly or explicitly
@@ -626,25 +631,33 @@ class CheckCaptures extends Recheck, SymTransformer:
626
631
// If ident refers to a parameterless method, charge its cv to the environment
627
632
includeCallCaptures(sym, sym.info, tree)
628
633
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)
646
635
mapResultRoots(super .recheckIdent(tree, pt), tree.symbol)
647
636
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
+
648
661
/** The expected type for the qualifier of a selection. If the selection
649
662
* could be part of a capability path or is a a read-only method, we return
650
663
* a PathSelectionProto.
@@ -866,7 +879,7 @@ class CheckCaptures extends Recheck, SymTransformer:
866
879
val (refined, cs) = addParamArgRefinements(core, initCs)
867
880
refined.capturing(cs)
868
881
869
- augmentConstructorType(resType, capturedVars(cls) ++ capturedVars(constr) )
882
+ augmentConstructorType(resType, capturedVars(cls))
870
883
.showing(i " constr type $mt with $argTypes%, % in $constr = $result" , capt)
871
884
end refineConstructorInstance
872
885
@@ -975,6 +988,8 @@ class CheckCaptures extends Recheck, SymTransformer:
975
988
* - Interpolate contravariant capture set variables in result type.
976
989
*/
977
990
override def recheckValDef (tree : ValDef , sym : Symbol )(using Context ): Type =
991
+ val savedEnv = curEnv
992
+ val runInConstructor = ! sym.isOneOf(Param | ParamAccessor | Lazy | NonMember )
978
993
try
979
994
if sym.is(Module ) then sym.info // Modules are checked by checking the module class
980
995
else
@@ -993,6 +1008,8 @@ class CheckCaptures extends Recheck, SymTransformer:
993
1008
" "
994
1009
disallowBadRootsIn(
995
1010
tree.tpt.nuType, NoSymbol , i " Mutable $sym" , " have type" , addendum, sym.srcPos)
1011
+ if runInConstructor then
1012
+ pushConstructorEnv()
996
1013
checkInferredResult(super .recheckValDef(tree, sym), tree)
997
1014
finally
998
1015
if ! sym.is(Param ) then
@@ -1002,6 +1019,22 @@ class CheckCaptures extends Recheck, SymTransformer:
1002
1019
// function is compiled since we do not propagate expected types into blocks.
1003
1020
interpolateIfInferred(tree.tpt, sym)
1004
1021
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
+
1005
1038
/** Recheck method definitions:
1006
1039
* - check body in a nested environment that tracks uses, in a nested level,
1007
1040
* and in a nested context that knows abaout Contains parameters so that we
@@ -1228,6 +1261,24 @@ class CheckCaptures extends Recheck, SymTransformer:
1228
1261
recheckFinish(result, arg, pt)
1229
1262
*/
1230
1263
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
+
1231
1282
/** The main recheck method does some box adapation for all nodes:
1232
1283
* - If expected type `pt` is boxed and the tree is a lambda or a reference,
1233
1284
* don't propagate free variables.
@@ -2021,7 +2072,9 @@ class CheckCaptures extends Recheck, SymTransformer:
2021
2072
if env.kind == EnvKind .Boxed then env.owner
2022
2073
else if isOfNestedMethod(env) then env.owner.owner
2023
2074
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)
2025
2078
2026
2079
def checkUseUnlessBoxed (c : Capability , croot : NamedType ) =
2027
2080
if ! boxedOwner(env).isContainedIn(croot.symbol.owner) then
0 commit comments