@@ -10,7 +10,9 @@ import config.SourceVersion
10
10
import config .Printers .capt
11
11
import util .Property .Key
12
12
import tpd .*
13
+ import StdNames .nme
13
14
import config .Feature
15
+ import collection .mutable
14
16
15
17
private val Captures : Key [CaptureSet ] = Key ()
16
18
private val BoxedType : Key [BoxedTypeCache ] = Key ()
@@ -21,6 +23,11 @@ private val BoxedType: Key[BoxedTypeCache] = Key()
21
23
*/
22
24
private val adaptUnpickledFunctionTypes = false
23
25
26
+ /** Switch whether we constrain a root var that includes the source of a
27
+ * root map to be an alias of that source (so that it can be mapped)
28
+ */
29
+ private val constrainRootsWhenMapping = true
30
+
24
31
/** The arguments of a @retains or @retainsByName annotation */
25
32
private [cc] def retainedElems (tree : Tree )(using Context ): List [Tree ] = tree match
26
33
case Apply (_, Typed (SeqLiteral (elems, _), _) :: Nil ) => elems
@@ -32,12 +39,82 @@ def allowUniversalInBoxed(using Context) =
32
39
/** An exception thrown if a @retains argument is not syntactically a CaptureRef */
33
40
class IllegalCaptureRef (tpe : Type ) extends Exception
34
41
42
+ /** Capture checking state, which is stored in a context property */
43
+ class CCState :
44
+
45
+ val rhsClosure : mutable.HashSet [Symbol ] = new mutable.HashSet
46
+
47
+ val levelOwners : mutable.HashSet [Symbol ] = new mutable.HashSet
48
+
49
+ /** Associates certain symbols (the nesting level owners) with their ccNestingLevel */
50
+ val nestingLevels : mutable.HashMap [Symbol , Int ] = new mutable.HashMap
51
+
52
+ /** Associates nesting level owners with the local roots valid in their scopes. */
53
+ val localRoots : mutable.HashMap [Symbol , Symbol ] = new mutable.HashMap
54
+
55
+ /** The last pair of capture reference and capture set where
56
+ * the reference could not be added to the set due to a level conflict.
57
+ */
58
+ var levelError : Option [(CaptureRef , CaptureSet )] = None
59
+
60
+ /** Under saferExceptions: The <try block> symbol generated for a try.
61
+ * Installed by Setup, removed by CheckCaptures.
62
+ */
63
+ val tryBlockOwner : mutable.HashMap [Try , Symbol ] = new mutable.HashMap
64
+ end CCState
65
+
66
+ /** Property key for capture checking state */
67
+ val ccStateKey : Key [CCState ] = Key ()
68
+
69
+ /** The currently valid CCState */
70
+ def ccState (using Context ) = ctx.property(ccStateKey).get
71
+
72
+ trait FollowAliases extends TypeMap :
73
+ def mapOverFollowingAliases (t : Type ): Type = t match
74
+ case t : LazyRef =>
75
+ val t1 = this (t.ref)
76
+ if t1 ne t.ref then t1 else t
77
+ case _ =>
78
+ val t1 = t.dealiasKeepAnnots
79
+ if t1 ne t then
80
+ val t2 = this (t1)
81
+ if t2 ne t1 then return t2
82
+ mapOver(t)
83
+
84
+ class mapRoots (from : CaptureRoot , to : CaptureRoot )(using Context ) extends BiTypeMap , FollowAliases :
85
+ thisMap =>
86
+
87
+ def apply (t : Type ): Type =
88
+ if t eq from then to
89
+ else t match
90
+ case t : CaptureRoot .Var =>
91
+ val ta = t.followAlias
92
+ if ta ne t then apply(ta)
93
+ else from match
94
+ case from : TermRef
95
+ if t.upperLevel >= from.symbol.ccNestingLevel
96
+ && constrainRootsWhenMapping // next two lines do the constraining
97
+ && CaptureRoot .isEnclosingRoot(from, t)
98
+ && CaptureRoot .isEnclosingRoot(t, from) => to
99
+ case from : CaptureRoot .Var if from.followAlias eq t => to
100
+ case _ => t
101
+ case _ =>
102
+ mapOverFollowingAliases(t)
103
+
104
+ def inverse = mapRoots(to, from)
105
+ end mapRoots
106
+
35
107
extension (tree : Tree )
36
108
37
109
/** Map tree with CaptureRef type to its type, throw IllegalCaptureRef otherwise */
38
- def toCaptureRef (using Context ): CaptureRef = tree.tpe match
39
- case ref : CaptureRef => ref
40
- case tpe => throw IllegalCaptureRef (tpe)
110
+ def toCaptureRef (using Context ): CaptureRef = tree match
111
+ case QualifiedRoot (outer) =>
112
+ ctx.owner.levelOwnerNamed(outer)
113
+ .orElse(defn.captureRoot) // non-existing outer roots are reported in Setup's checkQualifiedRoots
114
+ .localRoot.termRef
115
+ case _ => tree.tpe match
116
+ case ref : CaptureRef => ref
117
+ case tpe => throw IllegalCaptureRef (tpe) // if this was compiled from cc syntax, problem should have been reported at Typer
41
118
42
119
/** Convert a @retains or @retainsByName annotation tree to the capture set it represents.
43
120
* For efficience, the result is cached as an Attachment on the tree.
@@ -164,7 +241,7 @@ extension (tp: Type)
164
241
* a by name parameter type, turning the latter into an impure by name parameter type.
165
242
*/
166
243
def adaptByNameArgUnderPureFuns (using Context ): Type =
167
- if Feature .pureFunsEnabledSomewhere then
244
+ if adaptUnpickledFunctionTypes && Feature .pureFunsEnabledSomewhere then
168
245
AnnotatedType (tp,
169
246
CaptureAnnotation (CaptureSet .universal, boxed = false )(defn.RetainsByNameAnnot ))
170
247
else
@@ -253,6 +330,91 @@ extension (sym: Symbol)
253
330
&& sym != defn.Caps_unsafeBox
254
331
&& sym != defn.Caps_unsafeUnbox
255
332
333
+ def isLevelOwner (using Context ): Boolean = ccState.levelOwners.contains(sym)
334
+
335
+ /** The owner of the current level. Qualifying owners are
336
+ * - methods other than constructors and anonymous functions
337
+ * - anonymous functions, provided they either define a local
338
+ * root of type caps.Cap, or they are the rhs of a val definition.
339
+ * - classes, if they are not staticOwners
340
+ * - _root_
341
+ */
342
+ def levelOwner (using Context ): Symbol =
343
+ if ! sym.exists || sym.isRoot || sym.isStaticOwner then defn.RootClass
344
+ else if sym.isLevelOwner then sym
345
+ else sym.owner.levelOwner
346
+
347
+ /** The nesting level of `sym` for the purposes of `cc`,
348
+ * -1 for NoSymbol
349
+ */
350
+ def ccNestingLevel (using Context ): Int =
351
+ if sym.exists then
352
+ val lowner = sym.levelOwner
353
+ ccState.nestingLevels.getOrElseUpdate(lowner,
354
+ if lowner.isRoot then 0 else lowner.owner.ccNestingLevel + 1 )
355
+ else - 1
356
+
357
+ /** Optionally, the nesting level of `sym` for the purposes of `cc`, provided
358
+ * a capture checker is running.
359
+ */
360
+ def ccNestingLevelOpt (using Context ): Option [Int ] =
361
+ if ctx.property(ccStateKey).isDefined then Some (ccNestingLevel) else None
362
+
363
+ /** The parameter with type caps.Cap in the leading term parameter section,
364
+ * or NoSymbol, if none exists.
365
+ */
366
+ def definedLocalRoot (using Context ): Symbol =
367
+ sym.paramSymss.dropWhile(psyms => psyms.nonEmpty && psyms.head.isType) match
368
+ case psyms :: _ => psyms.find(_.info.typeSymbol == defn.Caps_Cap ).getOrElse(NoSymbol )
369
+ case _ => NoSymbol
370
+
371
+ /** The local root corresponding to sym's level owner */
372
+ def localRoot (using Context ): Symbol =
373
+ val owner = sym.levelOwner
374
+ assert(owner.exists)
375
+ def newRoot = newSymbol(if owner.isClass then newLocalDummy(owner) else owner,
376
+ nme.LOCAL_CAPTURE_ROOT , Synthetic , defn.Caps_Cap .typeRef, nestingLevel = owner.ccNestingLevel)
377
+ def lclRoot =
378
+ if owner.isTerm then owner.definedLocalRoot.orElse(newRoot)
379
+ else newRoot
380
+ ccState.localRoots.getOrElseUpdate(owner, lclRoot)
381
+
382
+ /** The level owner enclosing `sym` which has the given name, or NoSymbol if none exists.
383
+ * If name refers to a val that has a closure as rhs, we return the closure as level
384
+ * owner.
385
+ */
386
+ def levelOwnerNamed (name : String )(using Context ): Symbol =
387
+ def recur (owner : Symbol , prev : Symbol ): Symbol =
388
+ if owner.name.toString == name then
389
+ if owner.isLevelOwner then owner
390
+ else if owner.isTerm && ! owner.isOneOf(Method | Module ) && prev.exists then prev
391
+ else NoSymbol
392
+ else if owner == defn.RootClass then
393
+ NoSymbol
394
+ else
395
+ val prev1 = if owner.isAnonymousFunction && owner.isLevelOwner then owner else NoSymbol
396
+ recur(owner.owner, prev1)
397
+ recur(sym, NoSymbol )
398
+ .showing(i " find outer $sym [ $name ] = $result" , capt)
399
+
400
+ def maxNested (other : Symbol )(using Context ): Symbol =
401
+ if sym.ccNestingLevel < other.ccNestingLevel then other else sym
402
+ /* does not work yet, we do mix sets with different levels, for instance in cc-this.scala.
403
+ else if sym.ccNestingLevel > other.ccNestingLevel then sym
404
+ else
405
+ assert(sym == other, i"conflicting symbols at same nesting level: $sym, $other")
406
+ sym
407
+ */
408
+
409
+ def minNested (other : Symbol )(using Context ): Symbol =
410
+ if sym.ccNestingLevel > other.ccNestingLevel then other else sym
411
+
412
+ extension (tp : TermRef | ThisType )
413
+ /** The nesting level of this reference as defined by capture checking */
414
+ def ccNestingLevel (using Context ): Int = tp match
415
+ case tp : TermRef => tp.symbol.ccNestingLevel
416
+ case tp : ThisType => tp.cls.ccNestingLevel
417
+
256
418
extension (tp : AnnotatedType )
257
419
/** Is this a boxed capturing type? */
258
420
def isBoxed (using Context ): Boolean = tp.annot match
0 commit comments