From c8d4ba4b55eadb8e34660d2a1629b690fc0b911d Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Fri, 15 Aug 2025 16:09:27 +0200 Subject: [PATCH 1/6] chore: render `@consume` at the `consume` modifier [Cherry-picked 42a55e2da3326e33e7617278a7e349544bb531b4] --- scaladoc/src/dotty/tools/scaladoc/api.scala | 1 + .../dotty/tools/scaladoc/tasty/BasicSupport.scala | 2 +- .../tools/scaladoc/tasty/ClassLikeSupport.scala | 14 ++++++++------ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index 41ccd8fb2280..8106bd349e34 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -45,6 +45,7 @@ enum Modifier(val name: String, val prefix: Boolean): case Infix extends Modifier("infix", true) case AbsOverride extends Modifier("abstract override", true) case Update extends Modifier("update", true) + case Consume extends Modifier("consume", true) case class ExtensionTarget(name: String, typeParams: Seq[TypeParameter], argsLists: Seq[TermParameterList], signature: Signature, dri: DRI, position: Long) case class ImplicitConversion(from: DRI, to: DRI) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala index 06fe658b850e..c96b6899501d 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala @@ -57,7 +57,7 @@ trait BasicSupport: ) val fqNameAllowlist = if ccEnabled then - fqNameAllowlist0 + CaptureDefs.useAnnotFullName + CaptureDefs.consumeAnnotFullName + fqNameAllowlist0 + CaptureDefs.useAnnotFullName else fqNameAllowlist0 val documentedSymbol = summon[Quotes].reflect.Symbol.requiredClass("java.lang.annotation.Documented") val annotations = sym.annotations.filter { a => diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index 690a19c6e78b..baebea484aa8 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -23,11 +23,12 @@ trait ClassLikeSupport: extension (symbol: Symbol) { def getExtraModifiers(): Seq[Modifier] = - val mods = SymOps.getExtraModifiers(symbol)() - if ccEnabled && symbol.flags.is(Flags.Mutable)then - mods :+ Modifier.Update - else - mods + var mods = SymOps.getExtraModifiers(symbol)() + if ccEnabled && symbol.flags.is(Flags.Mutable) then + mods :+= Modifier.Update + if ccEnabled && symbol.hasAnnotation(cc.CaptureDefs.ConsumeAnnot) then + mods :+= Modifier.Consume + mods } private def bareClasslikeKind(using Quotes)(symbol: reflect.Symbol): Kind = @@ -452,11 +453,12 @@ trait ClassLikeSupport: ) = val symbol = argument.symbol val inlinePrefix = if symbol.flags.is(Flags.Inline) then "inline " else "" + val comsumePrefix = if self.ccEnabled && symbol.hasAnnotation(cc.CaptureDefs.ConsumeAnnot) then "consume " else "" val name = symbol.normalizedName val nameIfNotSynthetic = Option.when(!symbol.flags.is(Flags.Synthetic))(name) api.TermParameter( symbol.getAnnotations(), - inlinePrefix + prefix(symbol), + comsumePrefix + inlinePrefix + prefix(symbol), nameIfNotSynthetic, symbol.dri, argument.tpt.asSignature(classDef, symbol.owner), From b62952d11076eaa14d018318f411ca66e43e135a Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 14 Jul 2025 16:24:26 +0200 Subject: [PATCH 2/6] Enhance pattern matching with capturing types; fix stdlib-cc [Cherry-picked e0256bdc0d8cb473b670a350d8263c8ee9cafe32] --- .../tools/dotc/transform/PatternMatcher.scala | 7 ++++++- .../src/scala/collection/IndexedSeqView.scala | 2 +- .../mutable/CheckedIndexedSeqView.scala | 2 +- tests/neg-custom-args/captures/match.scala | 21 +++++++++++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 tests/neg-custom-args/captures/match.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 8bf88a0027c4..b4d766a1bd24 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -14,9 +14,11 @@ import Flags.*, Constants.* import Decorators.* import NameKinds.{PatMatStdBinderName, PatMatAltsName, PatMatResultName} import config.Printers.patmatch +import config.Feature import reporting.* import ast.* import util.Property.* +import cc.{CapturingType, Capabilities} import scala.annotation.tailrec import scala.collection.mutable @@ -427,8 +429,11 @@ object PatternMatcher { && !hasExplicitTypeArgs(extractor) case _ => false } + val castTp = if Feature.ccEnabled + then CapturingType(tpt.tpe, scrutinee.termRef.singletonCaptureSet) + else tpt.tpe TestPlan(TypeTest(tpt, isTrusted), scrutinee, tree.span, - letAbstract(ref(scrutinee).cast(tpt.tpe)) { casted => + letAbstract(ref(scrutinee).cast(castTp)) { casted => nonNull += casted patternPlan(casted, pat, onSuccess) }) diff --git a/scala2-library-cc/src/scala/collection/IndexedSeqView.scala b/scala2-library-cc/src/scala/collection/IndexedSeqView.scala index 78f8abb8e327..07698bc09951 100644 --- a/scala2-library-cc/src/scala/collection/IndexedSeqView.scala +++ b/scala2-library-cc/src/scala/collection/IndexedSeqView.scala @@ -160,7 +160,7 @@ object IndexedSeqView { @SerialVersionUID(3L) class Reverse[A](underlying: SomeIndexedSeqOps[A]^) extends SeqView.Reverse[A](underlying) with IndexedSeqView[A] { - override def reverse: IndexedSeqView[A] = underlying match { + override def reverse: IndexedSeqView[A]^{underlying} = underlying match { case x: IndexedSeqView[A] => x case _ => super.reverse } diff --git a/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala b/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala index 1c3f669f5358..89e3dfb78d8e 100644 --- a/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala +++ b/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala @@ -101,7 +101,7 @@ private[mutable] object CheckedIndexedSeqView { @SerialVersionUID(3L) class Reverse[A](underlying: SomeIndexedSeqOps[A]^)(protected val mutationCount: () ->{cap.rd} Int) extends IndexedSeqView.Reverse[A](underlying) with CheckedIndexedSeqView[A] { - override def reverse: IndexedSeqView[A] = underlying match { + override def reverse: IndexedSeqView[A]^{underlying} = underlying match { case x: IndexedSeqView[A] => x case _ => super.reverse } diff --git a/tests/neg-custom-args/captures/match.scala b/tests/neg-custom-args/captures/match.scala new file mode 100644 index 000000000000..9f7c9c6d8e74 --- /dev/null +++ b/tests/neg-custom-args/captures/match.scala @@ -0,0 +1,21 @@ +import language.experimental.captureChecking + +trait A + +case class B(x: AnyRef^) extends A + +def test = + val x: AnyRef^ = new AnyRef + val a: A^{x} = B(x) + + val y1: A = a match + case b: B => b // error: (b: B) becomes B^{x} implicitly + + val y2: A^{x} = a match + case b: B => b // ok + + val x3: AnyRef = a match + case B(x2: AnyRef) => x2 // error: we lose some information about field x, but it still cannot be pure + + val x4: AnyRef = a match + case b: B => b.x // error From f5491e7d4a2a2d742c7e156f1856b189b6b40a8d Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 15 Aug 2025 13:57:17 +0200 Subject: [PATCH 3/6] Try to bypass separation check in case body [Cherry-picked 484e97a68c778b10e260cfbf4797ddca9b77dc58] --- scala2-library-cc/src/scala/collection/View.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scala2-library-cc/src/scala/collection/View.scala b/scala2-library-cc/src/scala/collection/View.scala index 72a073836e77..d93ccaa89f20 100644 --- a/scala2-library-cc/src/scala/collection/View.scala +++ b/scala2-library-cc/src/scala/collection/View.scala @@ -152,8 +152,9 @@ object View extends IterableFactory[View] { def apply[A](underlying: Iterable[A]^, p: A => Boolean, isFlipped: Boolean): Filter[A]^{underlying, p} = underlying match { case filter: Filter[A] if filter.isFlipped == isFlipped => - new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) - .asInstanceOf[Filter[A]^{underlying, p}] + unsafeAssumeSeparate: + new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) + .asInstanceOf[Filter[A]^{underlying, p}] // !!! asInstanceOf needed once paths were added, see path-patmat-should-be-pos.scala for minimization //case filter: Filter[A]^{underlying} if filter.isFlipped == isFlipped => // unsafeAssumeSeparate: From d832045fa0ab961a8e512f28b5429d94d996a4c0 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 15 Aug 2025 15:02:28 +0200 Subject: [PATCH 4/6] Update test [Cherry-picked d140083bb8ba3605499aa8fbd3215d7dba4c0184] --- scala2-library-cc/src/scala/collection/View.scala | 3 --- tests/neg-custom-args/captures/match.scala | 5 ++++- .../captures/colltest5/CollectionStrawManCC5_1.scala | 7 +++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/scala2-library-cc/src/scala/collection/View.scala b/scala2-library-cc/src/scala/collection/View.scala index d93ccaa89f20..b8de97159f06 100644 --- a/scala2-library-cc/src/scala/collection/View.scala +++ b/scala2-library-cc/src/scala/collection/View.scala @@ -156,12 +156,9 @@ object View extends IterableFactory[View] { new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) .asInstanceOf[Filter[A]^{underlying, p}] // !!! asInstanceOf needed once paths were added, see path-patmat-should-be-pos.scala for minimization - //case filter: Filter[A]^{underlying} if filter.isFlipped == isFlipped => - // unsafeAssumeSeparate: // See filter-iterable.scala for a test where a variant of Filter // works without the unsafeAssumeSeparate. But it requires significant // changes compared to the version here. See also Filter in colltest5.CollectionStrawManCC5_1. - // new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) case _ => new Filter(underlying, p, isFlipped) } } diff --git a/tests/neg-custom-args/captures/match.scala b/tests/neg-custom-args/captures/match.scala index 9f7c9c6d8e74..cf0c0f1a3377 100644 --- a/tests/neg-custom-args/captures/match.scala +++ b/tests/neg-custom-args/captures/match.scala @@ -12,7 +12,10 @@ def test = case b: B => b // error: (b: B) becomes B^{x} implicitly val y2: A^{x} = a match - case b: B => b // ok + case b: B => + val bb: B^{b} = b + val aa: A^{a} = bb + b // ok val x3: AnyRef = a match case B(x2: AnyRef) => x2 // error: we lose some information about field x, but it still cannot be pure diff --git a/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala b/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala index ffb68c8d0d60..ea0bdc240e0c 100644 --- a/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala +++ b/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala @@ -457,13 +457,12 @@ object CollectionStrawMan5 { def apply[A](underlying: Iterable[A]^, pp: A => Boolean, isFlipped: Boolean): Filter[A]^{underlying, pp} = underlying match case filter: Filter[A] => - new Filter(filter.underlying, a => filter.p(a) && pp(a)) - .asInstanceOf[Filter[A]^{underlying, pp}] - //unsafeAssumeSeparate: + unsafeAssumeSeparate: + new Filter(filter.underlying, a => filter.p(a) && pp(a)) + .asInstanceOf[Filter[A]^{underlying, pp}] // See filter-iterable.scala for a test where a variant of Filter // works without the unsafeAssumeSeparate. But it requires significant // changes compared to the version here. - //new Filter(filter.underlying, a => filter.p(a) && pp(a)) case _ => new Filter(underlying, pp) case class Partition[A](val underlying: Iterable[A]^, p: A => Boolean) { From d4fa8e4f93d2738ee6e8d360c759c1d7ec0b3241 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sat, 16 Aug 2025 02:12:34 +0200 Subject: [PATCH 5/6] Scaladoc fix: don't drop caps on parameters [Cherry-picked 705fe5f21ec6ece4f06fc322cc815e3ca84088c7] --- scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index f3b6f32b6638..482807010d40 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -533,6 +533,7 @@ trait TypesSupport: private def isCapturedInContext(using Quotes)(ref: reflect.TypeRepr)(using elideThis: reflect.ClassDef): Boolean = import reflect._ ref match + case t if t.isCaptureRoot => true case ReachCapability(c) => isCapturedInContext(c) case ReadOnlyCapability(c) => isCapturedInContext(c) case ThisType(tr) => !elideThis.symbol.typeRef.isPureClass(elideThis) /* is the current class pure? */ From dd4721c074e7f220fb935fba4336835e98055bfe Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Sat, 16 Aug 2025 12:59:13 +0200 Subject: [PATCH 6/6] chore: do not render `consume update` in `scaladoc` [Cherry-picked fe8937d1501822b338fd4f955b3d7a4f22a2e662] --- .../src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index baebea484aa8..a42849fbd6fb 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -25,9 +25,10 @@ trait ClassLikeSupport: def getExtraModifiers(): Seq[Modifier] = var mods = SymOps.getExtraModifiers(symbol)() if ccEnabled && symbol.flags.is(Flags.Mutable) then - mods :+= Modifier.Update - if ccEnabled && symbol.hasAnnotation(cc.CaptureDefs.ConsumeAnnot) then - mods :+= Modifier.Consume + if symbol.hasAnnotation(cc.CaptureDefs.ConsumeAnnot) then + mods :+= Modifier.Consume + else + mods :+= Modifier.Update mods }