Skip to content

Commit

Permalink
Refine parameter accessors that have nonempty deep capture sets
Browse files Browse the repository at this point in the history
A parameter accessor with a nonempty deep capture set needs to be tracked in refinements
even if it is pure, as long as it might contain captures that can be referenced using
a reach capability.
  • Loading branch information
odersky committed Jun 26, 2024
1 parent ec1e66d commit de4e56f
Show file tree
Hide file tree
Showing 6 changed files with 17 additions and 4 deletions.
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/cc/CaptureOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,9 @@ extension (sym: Symbol)
&& !param.hasAnnotation(defn.UntrackedCapturesAnnot)
}

def hasTrackedParts(using Context): Boolean =
!CaptureSet.deepCaptureSet(sym.info).isAlwaysEmpty

extension (tp: AnnotatedType)
/** Is this a boxed capturing type? */
def isBoxed(using Context): Boolean = tp.annot match
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/cc/CaptureSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,7 @@ object CaptureSet:
recur(tp)
//.showing(i"capture set of $tp = $result", captDebug)

private def deepCaptureSet(tp: Type)(using Context): CaptureSet =
def deepCaptureSet(tp: Type)(using Context): CaptureSet =
val collect = new TypeAccumulator[CaptureSet]:
def apply(cs: CaptureSet, t: Type) = t.dealias match
case t @ CapturingType(p, cs1) =>
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ class CheckCaptures extends Recheck, SymTransformer:
if core.derivesFromCapability then CaptureSet.universal else initCs
for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do
val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol
if !getter.is(Private) && getter.termRef.isTracked then
if !getter.is(Private) && getter.hasTrackedParts then
refined = RefinedType(refined, getterName, argType)
allCaptures ++= argType.captureSet
(refined, allCaptures)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/cc/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
case cls: ClassSymbol
if !defn.isFunctionClass(cls) && cls.is(CaptureChecked) =>
cls.paramGetters.foldLeft(tp) { (core, getter) =>
if atPhase(thisPhase.next)(getter.termRef.isTracked)
if atPhase(thisPhase.next)(getter.hasTrackedParts)
&& getter.isRefiningParamAccessor
&& !getter.is(Tracked)
then
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -846,7 +846,8 @@ object Symbols extends SymUtils {
/** Map given symbols, subjecting their attributes to the mappings
* defined in the given TreeTypeMap `ttmap`.
* Cross symbol references are brought over from originals to copies.
* Do not copy any symbols if all attributes of all symbols stay the same.
* Do not copy any symbols if all attributes of all symbols stay the same
* and mapAlways is false.
*/
def mapSymbols(originals: List[Symbol], ttmap: TreeTypeMap, mapAlways: Boolean = false)(using Context): List[Symbol] =
if (originals.forall(sym =>
Expand Down
9 changes: 9 additions & 0 deletions tests/pos/reach-problem.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import language.experimental.captureChecking

class Box[T](items: Seq[T^]):
def getOne: T^{items*} = ???

object Box:
def getOne[T](items: Seq[T^]): T^{items*} =
val bx = Box(items)
bx.getOne

0 comments on commit de4e56f

Please sign in to comment.