@@ -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 *
@@ -1127,21 +1121,30 @@ class CheckCaptures extends Recheck, SymTransformer:
11271121 try
11281122 if sym.is(Module ) then sym.info // Modules are checked by checking the module class
11291123 else
1130- if sym.is(Mutable ) && ! sym.hasAnnotation(defn.UncheckedCapturesAnnot ) then
1131- val addendum = setup.capturedBy.get(sym) match
1132- case Some (encl) =>
1133- val enclStr =
1134- if encl.isAnonymousFunction then
1135- val location = setup.anonFunCallee.get(encl) match
1136- case Some (meth) if meth.exists => i " argument in a call to $meth"
1137- case _ => " "
1138- s " an anonymous function $location"
1139- else encl.show
1140- i " \n\n Note that $sym does not count as local since it is captured by $enclStr"
1141- case _ =>
1142- " "
1143- disallowBadRootsIn(
1144- tree.tpt.nuType, NoSymbol , i " Mutable $sym" , " have type" , addendum, sym.srcPos)
1124+ if sym.is(Mutable ) then
1125+ if ! sym.hasAnnotation(defn.UncheckedCapturesAnnot ) then
1126+ val addendum = setup.capturedBy.get(sym) match
1127+ case Some (encl) =>
1128+ val enclStr =
1129+ if encl.isAnonymousFunction then
1130+ val location = setup.anonFunCallee.get(encl) match
1131+ case Some (meth) if meth.exists => i " argument in a call to $meth"
1132+ case _ => " "
1133+ s " an anonymous function $location"
1134+ else encl.show
1135+ i " \n\n Note that $sym does not count as local since it is captured by $enclStr"
1136+ case _ =>
1137+ " "
1138+ disallowBadRootsIn(
1139+ tree.tpt.nuType, NoSymbol , i " Mutable $sym" , " have type" , addendum, sym.srcPos)
1140+ if sepChecksEnabled && false
1141+ && sym.owner.isClass
1142+ && ! sym.owner.derivesFrom(defn.Caps_Mutable )
1143+ && ! sym.hasAnnotation(defn.UntrackedCapturesAnnot ) then
1144+ report.error(
1145+ em """ Mutable $sym is defined in a class that does not extend `Mutable`.
1146+ |The variable needs to be annotated with `untrackedCaptures` to allow this. """ ,
1147+ tree.namePos)
11451148
11461149 // Lazy vals need their own environment to track captures from their RHS,
11471150 // similar to how methods work
@@ -1771,7 +1774,10 @@ class CheckCaptures extends Recheck, SymTransformer:
17711774
17721775 if needsAdaptation && ! insertBox then // we are unboxing
17731776 val criticalSet = // the set with which we unbox
1774- if covariant then captures // covariant: we box with captures of actual type plus captures leaked by inner adapation
1777+ if covariant then
1778+ if expected.expectsReadOnly && actual.derivesFromMutable
1779+ then captures.readOnly
1780+ else captures
17751781 else expected.captureSet // contravarant: we unbox with captures of epected type
17761782 // debugShowEnvs()
17771783 markFree(criticalSet, tree)
0 commit comments