From de4e56ff3c8e2dd7fc223a37599d6e757c953b1d Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 21 Jun 2024 19:36:52 +0200 Subject: [PATCH] Refine parameter accessors that have nonempty deep capture sets 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. --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 3 +++ compiler/src/dotty/tools/dotc/cc/CaptureSet.scala | 2 +- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 2 +- compiler/src/dotty/tools/dotc/cc/Setup.scala | 2 +- compiler/src/dotty/tools/dotc/core/Symbols.scala | 3 ++- tests/pos/reach-problem.scala | 9 +++++++++ 6 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 tests/pos/reach-problem.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 633aaae57a5d..4dda8f1803e0 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 80e563e108a0..6e9343629388 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -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) => diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 6d6222374944..3981dcbb34a2 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index ee2cf4c2858d..f588094fbdf3 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index da0ecac47b7d..1c5d1941c524 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -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 => diff --git a/tests/pos/reach-problem.scala b/tests/pos/reach-problem.scala new file mode 100644 index 000000000000..60dd1d4667a7 --- /dev/null +++ b/tests/pos/reach-problem.scala @@ -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 \ No newline at end of file