From e8fb67b09714251fa0b6b0ff9d4bb4c91e09534b Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Sun, 7 Sep 2025 19:15:53 +0200 Subject: [PATCH 1/3] Skip capture sets in checkNoPrivateLeaks when cc is not enabled --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 5 +++-- tests/pos/i23885/S_1.scala | 4 ++++ tests/pos/i23885/S_2.scala | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i23885/S_1.scala create mode 100644 tests/pos/i23885/S_2.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 990eb01b999e..493a4207717d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -771,8 +771,9 @@ object Checking { !(symBoundary.isContainedIn(otherBoundary) || otherLinkedBoundary.exists && symBoundary.isContainedIn(otherLinkedBoundary)) } - && !(inCaptureSet && other.isAllOf(LocalParamAccessor)) - // class parameters in capture sets are not treated as leaked since in + && !(inCaptureSet && (!Feature.ccEnabled || other.isAllOf(LocalParamAccessor))) + // All references are skipped in capture sets when CC is not enabled. + // Class parameters in capture sets are not treated as leaked since in // phase CheckCaptures these are treated as normal vals. def apply(tp: Type): Type = tp match { diff --git a/tests/pos/i23885/S_1.scala b/tests/pos/i23885/S_1.scala new file mode 100644 index 000000000000..85b5dad758c4 --- /dev/null +++ b/tests/pos/i23885/S_1.scala @@ -0,0 +1,4 @@ +import language.experimental.captureChecking + +class A: + def f(x: A^): A^{this, x} = ??? diff --git a/tests/pos/i23885/S_2.scala b/tests/pos/i23885/S_2.scala new file mode 100644 index 000000000000..651f8f7f5330 --- /dev/null +++ b/tests/pos/i23885/S_2.scala @@ -0,0 +1,3 @@ +class B: + private val a: A = ??? + def g(b: A) = a.f(b) From 5b587b575ea476ccbc4a4627b6c341d49720163c Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Sun, 7 Sep 2025 23:39:12 +0200 Subject: [PATCH 2/3] Refactor CleanupRetains to handle retains annotations based on CC feature status --- .../src/dotty/tools/dotc/cc/Capability.scala | 2 ++ .../src/dotty/tools/dotc/cc/CaptureOps.scala | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Capability.scala b/compiler/src/dotty/tools/dotc/cc/Capability.scala index 41a084a5d87e..2b82e7d7414d 100644 --- a/compiler/src/dotty/tools/dotc/cc/Capability.scala +++ b/compiler/src/dotty/tools/dotc/cc/Capability.scala @@ -898,6 +898,8 @@ object Capabilities: case t @ AnnotatedType(parent, ann) => val parent1 = this(parent) if ann.symbol.isRetains && ann.tree.toCaptureSet.containsCap then + // TODO: this can cause infinite recursion in some cases during printing + // scalac -Xprint:all tests/pos/i23885/S_1.scala tests/pos/i23885/S_2.scala this(CapturingType(parent1, ann.tree.toCaptureSet)) else t.derivedAnnotatedType(parent1, ann) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index d5f8b635fa58..420e6e1ab253 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -14,6 +14,7 @@ import Annotations.Annotation import CaptureSet.VarState import Capabilities.* import StdNames.nme +import config.Feature /** Attachment key for capturing type trees */ private val Captures: Key[CaptureSet] = Key() @@ -634,13 +635,18 @@ extension (tp: AnnotatedType) case ann: CaptureAnnotation => ann.boxed case _ => false -/** Drop retains annotations in the type. */ +/** Drop retains annotations in the inferred type if CC is not enabled + * or transform them into RetainingTypes if CC is enabled. + */ class CleanupRetains(using Context) extends TypeMap: - def apply(tp: Type): Type = - tp match - case AnnotatedType(tp, annot) if annot.symbol == defn.RetainsAnnot || annot.symbol == defn.RetainsByNameAnnot => - RetainingType(tp, defn.NothingType, byName = annot.symbol == defn.RetainsByNameAnnot) - case _ => mapOver(tp) + def apply(tp: Type): Type = tp match + case AnnotatedType(parent, annot) if annot.symbol.isRetainsLike => + if Feature.ccEnabled then + if annot.symbol == defn.RetainsAnnot || annot.symbol == defn.RetainsByNameAnnot then + RetainingType(parent, defn.NothingType, byName = annot.symbol == defn.RetainsByNameAnnot) + else mapOver(tp) + else apply(parent) + case _ => mapOver(tp) /** A base class for extractors that match annotated types with a specific * Capability annotation. From d1fe1fda095db190efc17fa4221728004048a171 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 8 Sep 2025 13:17:31 +0200 Subject: [PATCH 3/3] Fix infinite recursion in CapToFresh --- compiler/src/dotty/tools/dotc/cc/Capability.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Capability.scala b/compiler/src/dotty/tools/dotc/cc/Capability.scala index 2b82e7d7414d..77c67dff5a00 100644 --- a/compiler/src/dotty/tools/dotc/cc/Capability.scala +++ b/compiler/src/dotty/tools/dotc/cc/Capability.scala @@ -898,9 +898,9 @@ object Capabilities: case t @ AnnotatedType(parent, ann) => val parent1 = this(parent) if ann.symbol.isRetains && ann.tree.toCaptureSet.containsCap then - // TODO: this can cause infinite recursion in some cases during printing + // Applying `this` can cause infinite recursion in some cases during printing. // scalac -Xprint:all tests/pos/i23885/S_1.scala tests/pos/i23885/S_2.scala - this(CapturingType(parent1, ann.tree.toCaptureSet)) + mapOver(CapturingType(parent1, ann.tree.toCaptureSet)) else t.derivedAnnotatedType(parent1, ann) case defn.RefinedFunctionOf(_) =>