@@ -16,7 +16,7 @@ import typer.ErrorReporting.{Addenda, err}
16
16
import typer .ProtoTypes .{AnySelectionProto , LhsProto }
17
17
import util .{SimpleIdentitySet , EqHashMap , EqHashSet , SrcPos , Property }
18
18
import transform .SymUtils .*
19
- import transform .{Recheck , PreRecheck }
19
+ import transform .{Recheck , PreRecheck , CapturedVars }
20
20
import Recheck .*
21
21
import scala .collection .mutable
22
22
import CaptureSet .{withCaptureSetsExplained , IdempotentCaptRefMap , CompareResult }
@@ -149,15 +149,25 @@ object CheckCaptures:
149
149
150
150
private val seen = new EqHashSet [TypeRef ]
151
151
152
+ /** Check that there is at least one method containing carrier and defined
153
+ * in the scope of tparam. E.g. this is OK:
154
+ * def f[T] = { ... var x: T ... }
155
+ * So is this:
156
+ * class C[T] { def f() = { class D { var x: T }}}
157
+ * But this is not OK:
158
+ * class C[T] { object o { var x: T }}
159
+ */
152
160
extension (tparam : Symbol ) def isParametricIn (carrier : Symbol ): Boolean =
153
- val encl = carrier.maybeOwner.enclosingMethodOrClass
154
- if encl.isClass then tparam.isParametricIn(encl)
155
- else
156
- def recur (encl : Symbol ): Boolean =
157
- if tparam.owner == encl then true
158
- else if encl.isStatic || ! encl.exists then false
159
- else recur(encl.owner.enclosingMethodOrClass)
160
- recur(encl)
161
+ carrier.exists && {
162
+ val encl = carrier.owner.enclosingMethodOrClass
163
+ if encl.isClass then tparam.isParametricIn(encl)
164
+ else
165
+ def recur (encl : Symbol ): Boolean =
166
+ if tparam.owner == encl then true
167
+ else if encl.isStatic || ! encl.exists then false
168
+ else recur(encl.owner.enclosingMethodOrClass)
169
+ recur(encl)
170
+ }
161
171
162
172
def traverse (t : Type ) =
163
173
t.dealiasKeepAnnots match
@@ -168,9 +178,12 @@ object CheckCaptures:
168
178
t.info match
169
179
case TypeBounds (_, hi) if ! t.isSealed && ! t.symbol.isParametricIn(carrier) =>
170
180
if hi.isAny then
181
+ val detailStr =
182
+ if t eq tp then " variable"
183
+ else i " refers to the type variable $t, which "
171
184
report.error(
172
185
em """ $what cannot $have $tp since
173
- |that type refers to the type variable $t , which is not sealed.
186
+ |that type $detailStr is not sealed.
174
187
| $addendum""" ,
175
188
pos)
176
189
else
@@ -549,7 +562,7 @@ class CheckCaptures extends Recheck, SymTransformer:
549
562
for case (arg : TypeTree , formal, pname) <- args.lazyZip(polyType.paramRefs).lazyZip((polyType.paramNames)) do
550
563
if formal.isSealed then
551
564
def where = if fn.symbol.exists then i " in an argument of ${fn.symbol}" else " "
552
- disallowRootCapabilitiesIn(arg.knownType, fn.symbol ,
565
+ disallowRootCapabilitiesIn(arg.knownType, NoSymbol ,
553
566
i " Sealed type variable $pname" , " be instantiated to" ,
554
567
i " This is often caused by a local capability $where\n leaking as part of its result. " ,
555
568
tree.srcPos)
@@ -590,13 +603,58 @@ class CheckCaptures extends Recheck, SymTransformer:
590
603
openClosures = openClosures.tail
591
604
end recheckClosureBlock
592
605
606
+ /** Maps mutable variables to the symbols that capture them (in the
607
+ * CheckCaptures sense, i.e. symbol is referred to from a different method
608
+ * than the one it is defined in).
609
+ */
610
+ private val capturedBy = util.HashMap [Symbol , Symbol ]()
611
+
612
+ /** Maps anonymous functions appearing as function arguments to
613
+ * the function that is called.
614
+ */
615
+ private val anonFunCallee = util.HashMap [Symbol , Symbol ]()
616
+
617
+ /** Populates `capturedBy` and `anonFunCallee`. Called by `checkUnit`.
618
+ */
619
+ private def collectCapturedMutVars (using Context ) = new TreeTraverser :
620
+ def traverse (tree : Tree )(using Context ) = tree match
621
+ case id : Ident =>
622
+ val sym = id.symbol
623
+ if sym.is(Mutable , butNot = Method ) && sym.owner.isTerm then
624
+ val enclMeth = ctx.owner.enclosingMethod
625
+ if sym.enclosingMethod != enclMeth then
626
+ capturedBy(sym) = enclMeth
627
+ case Apply (fn, args) =>
628
+ for case closureDef(mdef) <- args do
629
+ anonFunCallee(mdef.symbol) = fn.symbol
630
+ traverseChildren(tree)
631
+ case Inlined (_, bindings, expansion) =>
632
+ traverse(bindings)
633
+ traverse(expansion)
634
+ case mdef : DefDef =>
635
+ if ! mdef.symbol.isInlineMethod then traverseChildren(tree)
636
+ case _ =>
637
+ traverseChildren(tree)
638
+
593
639
override def recheckValDef (tree : ValDef , sym : Symbol )(using Context ): Type =
594
640
try
595
641
if sym.is(Module ) then sym.info // Modules are checked by checking the module class
596
642
else
597
643
if sym.is(Mutable ) && ! sym.hasAnnotation(defn.UncheckedCapturesAnnot ) then
598
- disallowRootCapabilitiesIn(tree.tpt.knownType, sym,
599
- i " mutable $sym" , " have type" , " " , sym.srcPos)
644
+ val (carrier, addendum) = capturedBy.get(sym) match
645
+ case Some (encl) =>
646
+ val enclStr =
647
+ if encl.isAnonymousFunction then
648
+ val location = anonFunCallee.get(encl) match
649
+ case Some (meth) if meth.exists => i " argument in a call to $meth"
650
+ case _ => " "
651
+ s " an anonymous function $location"
652
+ else encl.show
653
+ (NoSymbol , i " \n Note that $sym does not count as local since it is captured by $enclStr" )
654
+ case _ =>
655
+ (sym, " " )
656
+ disallowRootCapabilitiesIn(
657
+ tree.tpt.knownType, carrier, i " Mutable $sym" , " have type" , addendum, sym.srcPos)
600
658
checkInferredResult(super .recheckValDef(tree, sym), tree)
601
659
finally
602
660
if ! sym.is(Param ) then
@@ -1168,11 +1226,12 @@ class CheckCaptures extends Recheck, SymTransformer:
1168
1226
private val setup : SetupAPI = thisPhase.prev.asInstanceOf [Setup ]
1169
1227
1170
1228
override def checkUnit (unit : CompilationUnit )(using Context ): Unit =
1171
- setup.setupUnit(ctx.compilationUnit.tpdTree, completeDef)
1229
+ setup.setupUnit(unit.tpdTree, completeDef)
1230
+ collectCapturedMutVars.traverse(unit.tpdTree)
1172
1231
1173
1232
if ctx.settings.YccPrintSetup .value then
1174
1233
val echoHeader = " [[syntax tree at end of cc setup]]"
1175
- val treeString = show(ctx.compilationUnit .tpdTree)
1234
+ val treeString = show(unit .tpdTree)
1176
1235
report.echo(s " $echoHeader\n $treeString\n " )
1177
1236
1178
1237
withCaptureSetsExplained :
0 commit comments