@@ -103,9 +103,6 @@ object CheckCaptures:
103103 override def toString = " SubstParamsMap"
104104 end SubstParamsMap
105105
106- /** A prototype that indicates selection with an immutable value */
107- class PathSelectionProto (val select : Select , val pt : Type )(using Context ) extends WildcardSelectionProto
108-
109106 /** Check that a @retains annotation only mentions references that can be tracked.
110107 * This check is performed at Typer.
111108 */
@@ -573,12 +570,12 @@ class CheckCaptures extends Recheck, SymTransformer:
573570 // fresh capabilities. We do check that they hide no parameter reach caps in checkEscapingUses
574571 case _ =>
575572
576- def checkReadOnlyMethod (included : CaptureSet , env : Env ): Unit =
573+ def checkReadOnlyMethod (included : CaptureSet , meth : Symbol ): Unit =
577574 included.checkAddedElems: elem =>
578575 if elem.isExclusive then
579576 report.error(
580- em """ Read-only ${env.owner} accesses exclusive capability $elem;
581- | ${env.owner} should be declared an update method to allow this. """ ,
577+ em """ Read-only $meth accesses exclusive capability $elem;
578+ | $meth should be declared an update method to allow this. """ ,
582579 tree.srcPos)
583580
584581 def recur (cs : CaptureSet , env : Env , lastEnv : Env | Null ): Unit =
@@ -598,8 +595,11 @@ class CheckCaptures extends Recheck, SymTransformer:
598595 if ! isOfNestedMethod(env) then
599596 val nextEnv = nextEnvToCharge(env)
600597 if nextEnv != null && ! nextEnv.owner.isStaticOwner then
601- if env.owner.isReadOnlyMethodOrLazyVal && nextEnv.owner != env.owner then
602- checkReadOnlyMethod(included, env)
598+ if nextEnv.owner != env.owner
599+ && env.owner.isReadOnlyMember
600+ && env.owner.owner.derivesFrom(defn.Caps_Mutable )
601+ then
602+ checkReadOnlyMethod(included, env.owner)
603603 recur(included, nextEnv, env)
604604 // Under deferredReaches, don't propagate out of methods inside terms.
605605 // The use set of these methods will be charged when that method is called.
@@ -702,32 +702,26 @@ class CheckCaptures extends Recheck, SymTransformer:
702702 * type `pt` to `ref`. Expand the marked tree accordingly to take account of
703703 * the added path. Example:
704704 * If we have `x` and the expected type says we select that with `.a.b`
705- * where `b` is a read-only method, we charge `x.a.b. rd` for tree `x.a.b`
705+ * where `b` is a read-only method, we charge `x.a.rd` for tree `x.a.b`
706706 * instead of just charging `x`.
707707 */
708- private def markPathFree (ref : TermRef | ThisType , pt : Type , tree : Tree )(using Context ): Unit =
709- pt match
710- case pt : PathSelectionProto if ref.isTracked =>
711- // if `ref` is not tracked then the selection could not give anything new
712- // class SerializationProxy in stdlib-cc/../LazyListIterable.scala has an example where this matters.
713- if pt.select.symbol.isReadOnlyMethodOrLazyVal then
714- markFree(ref.readOnly, tree)
715- else
716- val sel = ref.select(pt.select.symbol).asInstanceOf [TermRef ]
717- markPathFree(sel, pt.pt, pt.select)
718- case _ =>
719- markFree(ref.adjustReadOnly(pt), tree)
708+ private def markPathFree (ref : TermRef | ThisType , pt : Type , tree : Tree )(using Context ): Unit = pt match
709+ case pt : PathSelectionProto
710+ if ref.isTracked && ! pt.selector.isOneOf(MethodOrLazyOrMutable ) =>
711+ // if `ref` is not tracked then the selection could not give anything new
712+ // class SerializationProxy in stdlib-cc/../LazyListIterable.scala has an example where this matters.
713+ val sel = ref.select(pt.selector).asInstanceOf [TermRef ]
714+ markPathFree(sel, pt.pt, pt.select)
715+ case _ =>
716+ markFree(ref.adjustReadOnly(pt), tree)
720717
721718 /** The expected type for the qualifier of a selection. If the selection
722719 * could be part of a capability path or is a a read-only method, we return
723720 * a PathSelectionProto.
724721 */
725722 override def selectionProto (tree : Select , pt : Type )(using Context ): Type =
726- val sym = tree.symbol
727- if ! sym.isOneOf(MethodOrLazyOrMutable ) && ! sym.isStatic
728- || sym.isReadOnlyMethodOrLazyVal
729- then PathSelectionProto (tree, pt)
730- else super .selectionProto(tree, pt)
723+ if tree.symbol.isStatic then super .selectionProto(tree, pt)
724+ else PathSelectionProto (tree, pt)
731725
732726 /** A specialized implementation of the selection rule.
733727 *
@@ -977,7 +971,7 @@ class CheckCaptures extends Recheck, SymTransformer:
977971 .getOrElse(cls, cls.info.decls.toList) // pick all symbols in class scope for other classes
978972 .flatMap(classifiersOfFreshInType)
979973 if cls.typeRef.isMutableType then
980- fieldClassifiers = defn. Caps_Mutable :: fieldClassifiers
974+ fieldClassifiers = cls.classifier :: fieldClassifiers
981975 val parentClassifiers =
982976 cls.parentSyms.map(impliedClassifiers).filter(_.nonEmpty)
983977 if fieldClassifiers.isEmpty && parentClassifiers.isEmpty
@@ -1039,7 +1033,7 @@ class CheckCaptures extends Recheck, SymTransformer:
10391033 recheck(tree.rhs, lhsType.widen)
10401034 lhsType match
10411035 case lhsType @ TermRef (qualType, _)
1042- if (qualType ne NoPrefix ) && ! lhsType.symbol.is( Transparent ) =>
1036+ if (qualType ne NoPrefix ) && ! lhsType.symbol.hasAnnotation(defn. UntrackedCapturesAnnot ) =>
10431037 checkUpdate(qualType, tree.srcPos)(i " Cannot assign to field ${lhsType.name} of ${qualType.showRef}" )
10441038 case _ =>
10451039 defn.UnitType
@@ -1131,21 +1125,30 @@ class CheckCaptures extends Recheck, SymTransformer:
11311125 try
11321126 if sym.is(Module ) then sym.info // Modules are checked by checking the module class
11331127 else
1134- if sym.is(Mutable ) && ! sym.hasAnnotation(defn.UncheckedCapturesAnnot ) then
1135- val addendum = setup.capturedBy.get(sym) match
1136- case Some (encl) =>
1137- val enclStr =
1138- if encl.isAnonymousFunction then
1139- val location = setup.anonFunCallee.get(encl) match
1140- case Some (meth) if meth.exists => i " argument in a call to $meth"
1141- case _ => " "
1142- s " an anonymous function $location"
1143- else encl.show
1144- i " \n\n Note that $sym does not count as local since it is captured by $enclStr"
1145- case _ =>
1146- " "
1147- disallowBadRootsIn(
1148- tree.tpt.nuType, NoSymbol , i " Mutable $sym" , " have type" , addendum, sym.srcPos)
1128+ if sym.is(Mutable ) then
1129+ if ! sym.hasAnnotation(defn.UncheckedCapturesAnnot ) then
1130+ val addendum = setup.capturedBy.get(sym) match
1131+ case Some (encl) =>
1132+ val enclStr =
1133+ if encl.isAnonymousFunction then
1134+ val location = setup.anonFunCallee.get(encl) match
1135+ case Some (meth) if meth.exists => i " argument in a call to $meth"
1136+ case _ => " "
1137+ s " an anonymous function $location"
1138+ else encl.show
1139+ i " \n\n Note that $sym does not count as local since it is captured by $enclStr"
1140+ case _ =>
1141+ " "
1142+ disallowBadRootsIn(
1143+ tree.tpt.nuType, NoSymbol , i " Mutable $sym" , " have type" , addendum, sym.srcPos)
1144+ if ccConfig.noUnsafeMutableFields
1145+ && sym.owner.isClass
1146+ && ! sym.owner.derivesFrom(defn.Caps_Mutable )
1147+ && ! sym.hasAnnotation(defn.UntrackedCapturesAnnot ) then
1148+ report.error(
1149+ em """ Mutable $sym is defined in a class that does not extend `Mutable`.
1150+ |The variable needs to be annotated with `untrackedCaptures` to allow this. """ ,
1151+ tree.namePos)
11491152
11501153 // Lazy vals need their own environment to track captures from their RHS,
11511154 // similar to how methods work
@@ -1481,6 +1484,9 @@ class CheckCaptures extends Recheck, SymTransformer:
14811484 else
14821485 trace.force(i " rechecking $tree with pt = $pt" , recheckr, show = true ):
14831486 super .recheck(tree, pt)
1487+ catch case ex : AssertionError =>
1488+ println(i " error while rechecking $tree against $pt" )
1489+ throw ex
14841490 finally curEnv = saved
14851491 if tree.isTerm && ! pt.isBoxedCapturing && pt != LhsProto then
14861492 markFree(res.boxedCaptureSet, tree)
@@ -1793,7 +1799,10 @@ class CheckCaptures extends Recheck, SymTransformer:
17931799
17941800 if needsAdaptation && ! insertBox then // we are unboxing
17951801 val criticalSet = // the set with which we unbox
1796- if covariant then captures // covariant: we box with captures of actual type plus captures leaked by inner adapation
1802+ if covariant then
1803+ if expected.expectsReadOnly && actual.derivesFromMutable
1804+ then captures.readOnly
1805+ else captures
17971806 else expected.captureSet // contravarant: we unbox with captures of epected type
17981807 // debugShowEnvs()
17991808 markFree(criticalSet, tree)
0 commit comments