@@ -14,14 +14,14 @@ import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents, OverridingPa
14
14
import typer .Checking .{checkBounds , checkAppliedTypesIn }
15
15
import typer .ErrorReporting .{Addenda , err }
16
16
import typer .ProtoTypes .{AnySelectionProto , LhsProto }
17
- import util .{SimpleIdentitySet , EqHashMap , SrcPos , Property }
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 }
23
23
import StdNames .nme
24
- import NameKinds .DefaultGetterName
24
+ import NameKinds .{ DefaultGetterName , WildcardParamName }
25
25
import reporting .trace
26
26
27
27
/** The capture checker */
@@ -147,33 +147,49 @@ object CheckCaptures:
147
147
private def disallowRootCapabilitiesIn (tp : Type , carrier : Symbol , what : String , have : String , addendum : String , pos : SrcPos )(using Context ) =
148
148
val check = new TypeTraverser :
149
149
150
+ private val seen = new EqHashSet [TypeRef ]
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
+ */
150
160
extension (tparam : Symbol ) def isParametricIn (carrier : Symbol ): Boolean =
151
- val encl = carrier.owner.enclosingMethodOrClass
152
- if encl.isClass then tparam.isParametricIn(encl)
153
- else
154
- def recur (encl : Symbol ): Boolean =
155
- if tparam.owner == encl then true
156
- else if encl.isStatic || ! encl.exists then false
157
- else recur(encl.owner.enclosingMethodOrClass)
158
- 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
+ }
159
171
160
172
def traverse (t : Type ) =
161
173
t.dealiasKeepAnnots match
162
174
case t : TypeRef =>
163
- capt.println(i " disallow $t, $tp, $what, ${t.symbol.is(Sealed )}" )
164
- t.info match
165
- case TypeBounds (_, hi)
166
- if ! t.symbol.is(Sealed ) && ! t.symbol.isParametricIn(carrier) =>
167
- if hi.isAny then
168
- report.error(
169
- em """ $what cannot $have $tp since
170
- |that type refers to the type variable $t, which is not sealed.
171
- | $addendum""" ,
172
- pos)
173
- else
174
- traverse(hi)
175
- case _ =>
176
- traverseChildren(t)
175
+ if ! seen.contains(t) then
176
+ capt.println(i " disallow $t, $tp, $what, ${t.isSealed}" )
177
+ seen += t
178
+ t.info match
179
+ case TypeBounds (_, hi) if ! t.isSealed && ! t.symbol.isParametricIn(carrier) =>
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 "
184
+ report.error(
185
+ em """ $what cannot $have $tp since
186
+ |that type $detailStr is not sealed.
187
+ | $addendum""" ,
188
+ pos)
189
+ else
190
+ traverse(hi)
191
+ case _ =>
192
+ traverseChildren(t)
177
193
case AnnotatedType (_, ann) if ann.symbol == defn.UncheckedCapturesAnnot =>
178
194
()
179
195
case t =>
@@ -260,11 +276,12 @@ class CheckCaptures extends Recheck, SymTransformer:
260
276
pos, provenance)
261
277
262
278
/** Check subcapturing `cs1 <: cs2`, report error on failure */
263
- def checkSubset (cs1 : CaptureSet , cs2 : CaptureSet , pos : SrcPos , provenance : => String = " " )(using Context ) =
279
+ def checkSubset (cs1 : CaptureSet , cs2 : CaptureSet , pos : SrcPos ,
280
+ provenance : => String = " " , cs1description : String = " " )(using Context ) =
264
281
checkOK(
265
282
cs1.subCaptures(cs2, frozen = false ),
266
- if cs1.elems.size == 1 then i " reference ${cs1.elems.toList.head} is not "
267
- else i " references $cs1 are not all " ,
283
+ if cs1.elems.size == 1 then i " reference ${cs1.elems.toList.head}$cs1description is not "
284
+ else i " references $cs1$cs1description are not all " ,
268
285
pos, provenance)
269
286
270
287
/** The current environment */
@@ -542,10 +559,10 @@ class CheckCaptures extends Recheck, SymTransformer:
542
559
val TypeApply (fn, args) = tree
543
560
val polyType = atPhase(thisPhase.prev):
544
561
fn.tpe.widen.asInstanceOf [TypeLambda ]
545
- for case (arg : TypeTree , pinfo , pname) <- args.lazyZip(polyType.paramInfos ).lazyZip((polyType.paramNames)) do
546
- if pinfo.bounds.hi.hasAnnotation(defn. Caps_SealedAnnot ) then
562
+ for case (arg : TypeTree , formal , pname) <- args.lazyZip(polyType.paramRefs ).lazyZip((polyType.paramNames)) do
563
+ if formal.isSealed then
547
564
def where = if fn.symbol.exists then i " in an argument of ${fn.symbol}" else " "
548
- disallowRootCapabilitiesIn(arg.knownType, fn.symbol ,
565
+ disallowRootCapabilitiesIn(arg.knownType, NoSymbol ,
549
566
i " Sealed type variable $pname" , " be instantiated to" ,
550
567
i " This is often caused by a local capability $where\n leaking as part of its result. " ,
551
568
tree.srcPos)
@@ -586,13 +603,58 @@ class CheckCaptures extends Recheck, SymTransformer:
586
603
openClosures = openClosures.tail
587
604
end recheckClosureBlock
588
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
+
589
639
override def recheckValDef (tree : ValDef , sym : Symbol )(using Context ): Type =
590
640
try
591
641
if sym.is(Module ) then sym.info // Modules are checked by checking the module class
592
642
else
593
643
if sym.is(Mutable ) && ! sym.hasAnnotation(defn.UncheckedCapturesAnnot ) then
594
- disallowRootCapabilitiesIn(tree.tpt.knownType, sym,
595
- 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)
596
658
checkInferredResult(super .recheckValDef(tree, sym), tree)
597
659
finally
598
660
if ! sym.is(Param ) then
@@ -680,9 +742,15 @@ class CheckCaptures extends Recheck, SymTransformer:
680
742
if ! param.hasAnnotation(defn.ConstructorOnlyAnnot ) then
681
743
checkSubset(param.termRef.captureSet, thisSet, param.srcPos) // (3)
682
744
for pureBase <- cls.pureBaseClass do // (4)
745
+ def selfType = impl.body
746
+ .collect:
747
+ case TypeDef (tpnme.SELF , rhs) => rhs
748
+ .headOption
749
+ .getOrElse(tree)
750
+ .orElse(tree)
683
751
checkSubset(thisSet,
684
752
CaptureSet .empty.withDescription(i " of pure base class $pureBase" ),
685
- tree .srcPos)
753
+ selfType .srcPos, cs1description = " captured by this self type " )
686
754
super .recheckClassDef(tree, impl, cls)
687
755
finally
688
756
curEnv = saved
@@ -1122,6 +1190,8 @@ class CheckCaptures extends Recheck, SymTransformer:
1122
1190
1123
1191
override def needsCheck (overriding : Symbol , overridden : Symbol )(using Context ): Boolean =
1124
1192
! setup.isPreCC(overriding) && ! setup.isPreCC(overridden)
1193
+
1194
+ override def checkInheritedTraitParameters : Boolean = false
1125
1195
end OverridingPairsCheckerCC
1126
1196
1127
1197
def traverse (t : Tree )(using Context ) =
@@ -1158,11 +1228,12 @@ class CheckCaptures extends Recheck, SymTransformer:
1158
1228
private val setup : SetupAPI = thisPhase.prev.asInstanceOf [Setup ]
1159
1229
1160
1230
override def checkUnit (unit : CompilationUnit )(using Context ): Unit =
1161
- setup.setupUnit(ctx.compilationUnit.tpdTree, completeDef)
1231
+ setup.setupUnit(unit.tpdTree, completeDef)
1232
+ collectCapturedMutVars.traverse(unit.tpdTree)
1162
1233
1163
1234
if ctx.settings.YccPrintSetup .value then
1164
1235
val echoHeader = " [[syntax tree at end of cc setup]]"
1165
- val treeString = show(ctx.compilationUnit .tpdTree)
1236
+ val treeString = show(unit .tpdTree)
1166
1237
report.echo(s " $echoHeader\n $treeString\n " )
1167
1238
1168
1239
withCaptureSetsExplained :
@@ -1298,6 +1369,39 @@ class CheckCaptures extends Recheck, SymTransformer:
1298
1369
checker.traverse(tree.knownType)
1299
1370
end healTypeParam
1300
1371
1372
+ def checkNoLocalRootIn (sym : Symbol , info : Type , pos : SrcPos )(using Context ): Unit =
1373
+ val check = new TypeTraverser :
1374
+ def traverse (tp : Type ) = tp match
1375
+ case tp : TermRef if tp.isLocalRootCapability =>
1376
+ if tp.localRootOwner == sym then
1377
+ report.error(i " local root $tp cannot appear in type of $sym" , pos)
1378
+ case tp : ClassInfo =>
1379
+ traverseChildren(tp)
1380
+ for mbr <- tp.decls do
1381
+ if ! mbr.is(Private ) then checkNoLocalRootIn(sym, mbr.info, mbr.srcPos)
1382
+ case _ =>
1383
+ traverseChildren(tp)
1384
+ check.traverse(info)
1385
+
1386
+ def checkArraysAreSealedIn (tp : Type , pos : SrcPos )(using Context ): Unit =
1387
+ val check = new TypeTraverser :
1388
+ def traverse (t : Type ): Unit =
1389
+ t match
1390
+ case AppliedType (tycon, arg :: Nil ) if tycon.typeSymbol == defn.ArrayClass =>
1391
+ if ! (pos.span.isSynthetic && ctx.reporter.errorsReported)
1392
+ && ! arg.typeSymbol.name.is(WildcardParamName )
1393
+ then
1394
+ CheckCaptures .disallowRootCapabilitiesIn(arg, NoSymbol ,
1395
+ " Array" , " have element type" ,
1396
+ " Since arrays are mutable, they have to be treated like variables,\n so their element type must be sealed." ,
1397
+ pos)
1398
+ traverseChildren(t)
1399
+ case defn.RefinedFunctionOf (rinfo : MethodType ) =>
1400
+ traverse(rinfo)
1401
+ case _ =>
1402
+ traverseChildren(t)
1403
+ check.traverse(tp)
1404
+
1301
1405
/** Perform the following kinds of checks
1302
1406
* - Check all explicitly written capturing types for well-formedness using `checkWellFormedPost`.
1303
1407
* - Check that arguments of TypeApplys and AppliedTypes conform to their bounds.
@@ -1309,10 +1413,11 @@ class CheckCaptures extends Recheck, SymTransformer:
1309
1413
val lctx = tree match
1310
1414
case _ : DefTree | _ : TypeDef if tree.symbol.exists => ctx.withOwner(tree.symbol)
1311
1415
case _ => ctx
1312
- traverseChildren(tree)(using lctx)
1313
- check(tree)
1416
+ trace(i " post check $tree" ):
1417
+ traverseChildren(tree)(using lctx)
1418
+ check(tree)
1314
1419
def check (tree : Tree )(using Context ) = tree match
1315
- case t @ TypeApply (fun, args) =>
1420
+ case TypeApply (fun, args) =>
1316
1421
fun.knownType.widen match
1317
1422
case tl : PolyType =>
1318
1423
val normArgs = args.lazyZip(tl.paramInfos).map: (arg, bounds) =>
@@ -1321,6 +1426,10 @@ class CheckCaptures extends Recheck, SymTransformer:
1321
1426
checkBounds(normArgs, tl)
1322
1427
args.lazyZip(tl.paramNames).foreach(healTypeParam(_, _, fun.symbol))
1323
1428
case _ =>
1429
+ case _ : ValOrDefDef | _ : TypeDef =>
1430
+ checkNoLocalRootIn(tree.symbol, tree.symbol.info, tree.symbol.srcPos)
1431
+ case tree : TypeTree =>
1432
+ checkArraysAreSealedIn(tree.tpe, tree.srcPos)
1324
1433
case _ =>
1325
1434
end check
1326
1435
end checker
0 commit comments