@@ -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 */
@@ -598,7 +595,10 @@ 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
598+ if nextEnv.owner != env.owner
599+ && env.owner.isReadOnlyMember
600+ && env.owner.owner.derivesFrom(defn.Caps_Mutable )
601+ then
602602 checkReadOnlyMethod(included, env.owner)
603603 recur(included, nextEnv, env)
604604 // Under deferredReaches, don't propagate out of methods inside terms.
@@ -705,29 +705,23 @@ class CheckCaptures extends Recheck, SymTransformer:
705705 * where `b` is a read-only method, we charge `x.a.b.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 *
@@ -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 sepChecksEnabled && false
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
@@ -1793,7 +1796,10 @@ class CheckCaptures extends Recheck, SymTransformer:
17931796
17941797 if needsAdaptation && ! insertBox then // we are unboxing
17951798 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
1799+ if covariant then
1800+ if expected.expectsReadOnly && actual.derivesFromMutable
1801+ then captures.readOnly
1802+ else captures
17971803 else expected.captureSet // contravarant: we unbox with captures of epected type
17981804 // debugShowEnvs()
17991805 markFree(criticalSet, tree)
0 commit comments