From 4b0f18c2370fcbe087129f287aeca5347f5fad33 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Fri, 6 Jun 2025 14:31:21 +0200 Subject: [PATCH 1/9] Add use annotations in parameter info type for use parameters --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 1 + compiler/src/dotty/tools/dotc/core/Types.scala | 2 ++ 2 files changed, 3 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 3dd847f19b56..74e09948106f 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -495,6 +495,7 @@ extension (sym: Symbol) */ def isUseParam(using Context): Boolean = sym.hasAnnotation(defn.UseAnnot) + || sym.info.hasAnnotation(defn.UseAnnot) || sym.is(TypeParam) && sym.owner.rawParamss.nestedExists: param => param.is(TermParam) && param.hasAnnotation(defn.UseAnnot) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b06bd5c00a28..0a97593eef89 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4234,6 +4234,8 @@ object Types extends TypeUtils { paramType = addAnnotation(paramType, defn.InlineParamAnnot, param) if param.is(Erased) then paramType = addAnnotation(paramType, defn.ErasedParamAnnot, param) + if param.isUseParam then + paramType = addAnnotation(paramType, defn.UseAnnot, param) paramType def adaptParamInfo(param: Symbol)(using Context): Type = From 921a930d148a16b942815ede75cd664a42e0a160 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Fri, 6 Jun 2025 14:48:42 +0200 Subject: [PATCH 2/9] Add testcases for use annotations and value classes --- .../captures/cc-annot-value-classes.scala | 18 ++++++++++++++++++ .../captures/cc-use-iterable.scala | 10 ++++++++++ 2 files changed, 28 insertions(+) create mode 100644 tests/neg-custom-args/captures/cc-annot-value-classes.scala create mode 100644 tests/pos-custom-args/captures/cc-use-iterable.scala diff --git a/tests/neg-custom-args/captures/cc-annot-value-classes.scala b/tests/neg-custom-args/captures/cc-annot-value-classes.scala new file mode 100644 index 000000000000..745b1c85b8b1 --- /dev/null +++ b/tests/neg-custom-args/captures/cc-annot-value-classes.scala @@ -0,0 +1,18 @@ +import language.experimental.captureChecking +import caps.* + +class Runner(val x: Int) extends AnyVal: + def runOps(@use ops: List[() => Unit]): Unit = + ops.foreach(_()) // ok + +class RunnerAlt(val x: Int): + def runOps(@use ops: List[() => Unit]): Unit = + ops.foreach(_()) // ok, of course + +class RunnerAltAlt(val x: Int) extends AnyVal: + def runOps(ops: List[() => Unit]): Unit = + ops.foreach(_()) // error, as expected + +class RunnerAltAltAlt(val x: Int): + def runOps(ops: List[() => Unit]): Unit = + ops.foreach(_()) // error, as expected diff --git a/tests/pos-custom-args/captures/cc-use-iterable.scala b/tests/pos-custom-args/captures/cc-use-iterable.scala new file mode 100644 index 000000000000..84c497c0f6ce --- /dev/null +++ b/tests/pos-custom-args/captures/cc-use-iterable.scala @@ -0,0 +1,10 @@ +import language.experimental.captureChecking +trait IterableOnce[+T] +trait Iterable[+T] extends IterableOnce[T]: + def flatMap[U](@caps.use f: T => IterableOnce[U]^): Iterable[U]^{this, f*} + + +class IterableOnceExtensionMethods[T](val it: IterableOnce[T]) extends AnyVal: + def flatMap[U](@caps.use f: T => IterableOnce[U]^): IterableOnce[U]^{f*} = it match + case it: Iterable[T] => it.flatMap(f) + From b867b3fee761ec866acf70a226ba032e267328dc Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Fri, 6 Jun 2025 17:07:44 +0200 Subject: [PATCH 3/9] Copy `@consume` annotations to the type --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 10 +++++++++- .../src/dotty/tools/dotc/cc/CheckCaptures.scala | 7 +++++-- compiler/src/dotty/tools/dotc/cc/SepCheck.scala | 6 +++--- compiler/src/dotty/tools/dotc/core/Types.scala | 5 ++++- .../captures/cc-annot-value-classes2.scala | 16 ++++++++++++++++ 5 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 tests/neg-custom-args/captures/cc-annot-value-classes2.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 74e09948106f..feae2cc9fa4f 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -395,6 +395,9 @@ extension (tp: Type) RefinedType(tp, name, AnnotatedType(rinfo, Annotation(defn.RefineOverrideAnnot, util.Spans.NoSpan))) + def dropUseAndConsumeAnnots(using Context): Type = + tp.dropAnnot(defn.UseAnnot).dropAnnot(defn.ConsumeAnnot) + extension (tp: MethodType) /** A method marks an existential scope unless it is the prefix of a curried method */ def marksExistentialScope(using Context): Boolean = @@ -490,7 +493,7 @@ extension (sym: Symbol) def hasTrackedParts(using Context): Boolean = !CaptureSet.ofTypeDeeply(sym.info).isAlwaysEmpty - /** `sym` is annotated @use or it is a type parameter with a matching + /** `sym` itself or its info is annotated @use or it is a type parameter with a matching * @use-annotated term parameter that contains `sym` in its deep capture set. */ def isUseParam(using Context): Boolean = @@ -503,6 +506,11 @@ extension (sym: Symbol) case c: TypeRef => c.symbol == sym case _ => false + /** `sym` or its info is annotated with `@consume`. */ + def isConsumeParam(using Context): Boolean = + sym.hasAnnotation(defn.ConsumeAnnot) + || sym.info.hasAnnotation(defn.ConsumeAnnot) + def isUpdateMethod(using Context): Boolean = sym.isAllOf(Mutable | Method, butNot = Accessor) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 1bdd7ce92129..44082346e3cc 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -716,7 +716,7 @@ class CheckCaptures extends Recheck, SymTransformer: funtpe.paramInfos.zipWithConserve(funtpe.paramNames): (formal, pname) => val param = meth.paramNamed(pname) def copyAnnot(tp: Type, cls: ClassSymbol) = param.getAnnotation(cls) match - case Some(ann) => AnnotatedType(tp, ann) + case Some(ann) if !tp.hasAnnotation(cls) => AnnotatedType(tp, ann) case _ => tp copyAnnot(copyAnnot(formal, defn.UseAnnot), defn.ConsumeAnnot) funtpe.derivedLambdaType(paramInfos = paramInfosWithUses) @@ -1616,7 +1616,10 @@ class CheckCaptures extends Recheck, SymTransformer: if noWiden(actual, expected) then actual else - val improvedVAR = improveCaptures(actual.widen.dealiasKeepAnnots, actual) + // Compute the widened type. Drop `@use` and `@consume` annotations from the type, + // since they obscures the capturing type. + val widened = actual.widen.dealiasKeepAnnots.dropUseAndConsumeAnnots + val improvedVAR = improveCaptures(widened, actual) val improved = improveReadOnly(improvedVAR, expected) val adapted = adaptBoxed( improved.withReachCaptures(actual), expected, tree, diff --git a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala index 6dad0e9a2ff7..a402e58624f2 100644 --- a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala +++ b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala @@ -620,7 +620,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: if currentOwner.enclosingMethodOrClass.isProperlyContainedIn(refSym.maybeOwner.enclosingMethodOrClass) then report.error(em"""Separation failure: $descr non-local $refSym""", pos) else if refSym.is(TermParam) - && !refSym.hasAnnotation(defn.ConsumeAnnot) + && !refSym.isConsumeParam && currentOwner.isContainedIn(refSym.owner) then badParams += refSym @@ -899,7 +899,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: if !isUnsafeAssumeSeparate(tree) then trace(i"checking separate $tree"): checkUse(tree) tree match - case tree @ Select(qual, _) if tree.symbol.is(Method) && tree.symbol.hasAnnotation(defn.ConsumeAnnot) => + case tree @ Select(qual, _) if tree.symbol.is(Method) && tree.symbol.isConsumeParam => traverseChildren(tree) checkConsumedRefs( captures(qual).footprint(), qual.nuType, @@ -962,4 +962,4 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: consumeInLoopError(ref, pos) case _ => traverseChildren(tree) -end SepCheck \ No newline at end of file +end SepCheck diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 0a97593eef89..e10a5221e8e7 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4234,8 +4234,11 @@ object Types extends TypeUtils { paramType = addAnnotation(paramType, defn.InlineParamAnnot, param) if param.is(Erased) then paramType = addAnnotation(paramType, defn.ErasedParamAnnot, param) - if param.isUseParam then + // Copy `@use` and `@consume` annotations from parameter symbols to the type. + if param.hasAnnotation(defn.UseAnnot) then paramType = addAnnotation(paramType, defn.UseAnnot, param) + if param.hasAnnotation(defn.ConsumeAnnot) then + paramType = addAnnotation(paramType, defn.ConsumeAnnot, param) paramType def adaptParamInfo(param: Symbol)(using Context): Type = diff --git a/tests/neg-custom-args/captures/cc-annot-value-classes2.scala b/tests/neg-custom-args/captures/cc-annot-value-classes2.scala new file mode 100644 index 000000000000..5821f9664f6b --- /dev/null +++ b/tests/neg-custom-args/captures/cc-annot-value-classes2.scala @@ -0,0 +1,16 @@ +import language.experimental.captureChecking +import caps.* +trait Ref extends Mutable +def kill(@consume x: Ref^): Unit = () + +class C1: + def myKill(@consume x: Ref^): Unit = kill(x) // ok + +class C2(val dummy: Int) extends AnyVal: + def myKill(@consume x: Ref^): Unit = kill(x) // ok, too + +class C3: + def myKill(x: Ref^): Unit = kill(x) // error + +class C4(val dummy: Int) extends AnyVal: + def myKill(x: Ref^): Unit = kill(x) // error, too From 531d165fb494f6595db469792f1fa50654a36370 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Sat, 7 Jun 2025 13:55:29 +0200 Subject: [PATCH 4/9] Properly print `@use` and `@consume` parameters --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 2 +- compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala | 6 +++++- tests/neg-custom-args/captures/unbox-overrides.check | 6 +++--- tests/neg-custom-args/captures/unsound-reach-4.check | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 83c85adb0f43..9626e5ccb7c5 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1117,7 +1117,7 @@ class Definitions { // Set of annotations that are not printed in types except under -Yprint-debug @tu lazy val SilentAnnots: Set[Symbol] = - Set(InlineParamAnnot, ErasedParamAnnot, RefineOverrideAnnot, SilentIntoAnnot) + Set(InlineParamAnnot, ErasedParamAnnot, RefineOverrideAnnot, SilentIntoAnnot, UseAnnot, ConsumeAnnot) // A list of annotations that are commonly used to indicate that a field/method argument or return // type is not null. These annotations are used by the nullification logic in JavaNullInterop to diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 750e4b646e0d..a7f0f59aba3b 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -379,7 +379,11 @@ class PlainPrinter(_ctx: Context) extends Printer { protected def paramsText(lam: LambdaType): Text = { def paramText(ref: ParamRef) = val erased = ref.underlying.hasAnnotation(defn.ErasedParamAnnot) - keywordText("erased ").provided(erased) ~ ParamRefNameString(ref) ~ hashStr(lam) ~ toTextRHS(ref.underlying, isParameter = true) + def maybeAnnotsText(sym: ClassSymbol): Text = + Str(s"@${sym.name} ").provided(ref.underlying.hasAnnotation(sym)) + keywordText("erased ").provided(erased) + ~ maybeAnnotsText(defn.UseAnnot) ~ maybeAnnotsText(defn.ConsumeAnnot) + ~ ParamRefNameString(ref) ~ hashStr(lam) ~ toTextRHS(ref.underlying, isParameter = true) Text(lam.paramRefs.map(paramText), ", ") } diff --git a/tests/neg-custom-args/captures/unbox-overrides.check b/tests/neg-custom-args/captures/unbox-overrides.check index dbffc164b5c5..a531b546a62a 100644 --- a/tests/neg-custom-args/captures/unbox-overrides.check +++ b/tests/neg-custom-args/captures/unbox-overrides.check @@ -1,7 +1,7 @@ -- [E164] Declaration Error: tests/neg-custom-args/captures/unbox-overrides.scala:8:6 ---------------------------------- 8 | def foo(x: C): C // error | ^ - |error overriding method foo in trait A of type (x: C): C; + |error overriding method foo in trait A of type (@use x: C): C; | method foo of type (x: C): C has a parameter x with different @use status than the corresponding parameter in the overridden definition | | longer explanation available when compiling with `-explain` @@ -9,13 +9,13 @@ 9 | def bar(@use x: C): C // error | ^ |error overriding method bar in trait A of type (x: C): C; - | method bar of type (x: C): C has a parameter x with different @use status than the corresponding parameter in the overridden definition + | method bar of type (@use x: C): C has a parameter x with different @use status than the corresponding parameter in the overridden definition | | longer explanation available when compiling with `-explain` -- [E164] Declaration Error: tests/neg-custom-args/captures/unbox-overrides.scala:15:15 -------------------------------- 15 |abstract class C extends A[C], B2 // error | ^ - |error overriding method foo in trait A of type (x: C): C; + |error overriding method foo in trait A of type (@use x: C): C; | method foo in trait B2 of type (x: C): C has a parameter x with different @use status than the corresponding parameter in the overridden definition | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/unsound-reach-4.check b/tests/neg-custom-args/captures/unsound-reach-4.check index 0e9acbea1afa..361b1158305d 100644 --- a/tests/neg-custom-args/captures/unsound-reach-4.check +++ b/tests/neg-custom-args/captures/unsound-reach-4.check @@ -18,9 +18,9 @@ 17 | def use(@consume x: F): File^ = x // error @consume override | ^ | error overriding method use in trait Foo of type (x: File^): box File^; - | method use of type (x: File^): File^² has incompatible type + | method use of type (@consume x: File^): File^² has incompatible type | | where: ^ refers to the universal root capability - | ^² refers to a root capability associated with the result type of (x: File^): File^² + | ^² refers to a root capability associated with the result type of (@consume x: File^): File^² | | longer explanation available when compiling with `-explain` From 0dd57f21de060a4efe9b368356cf7c63a21db3ac Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Sat, 7 Jun 2025 14:11:44 +0200 Subject: [PATCH 5/9] Improve printing `@use` and `@consume` --- .../src/dotty/tools/dotc/printing/PlainPrinter.scala | 12 +++++++++--- .../captures/leak-problem-unboxed.scala | 10 +++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index a7f0f59aba3b..e1ae2ec32de2 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -134,6 +134,8 @@ class PlainPrinter(_ctx: Context) extends Printer { protected def argText(arg: Type, isErased: Boolean = false): Text = keywordText("erased ").provided(isErased) + ~ specialAnnotText(defn.UseAnnot, arg) + ~ specialAnnotText(defn.ConsumeAnnot, arg) ~ homogenizeArg(arg).match case arg: TypeBounds => "?" ~ toText(arg) case arg => toText(arg) @@ -376,13 +378,17 @@ class PlainPrinter(_ctx: Context) extends Printer { try "(" ~ toTextRef(tp) ~ " : " ~ toTextGlobal(tp.underlying) ~ ")" finally elideCapabilityCaps = saved + /** Print the annotation that are meant to be on the parameter symbol but was moved + * to parameter types. Examples are `@use` and `@consume`. */ + protected def specialAnnotText(sym: ClassSymbol, tp: Type): Text = + Str(s"@${sym.name} ").provided(tp.hasAnnotation(sym)) + protected def paramsText(lam: LambdaType): Text = { def paramText(ref: ParamRef) = val erased = ref.underlying.hasAnnotation(defn.ErasedParamAnnot) - def maybeAnnotsText(sym: ClassSymbol): Text = - Str(s"@${sym.name} ").provided(ref.underlying.hasAnnotation(sym)) keywordText("erased ").provided(erased) - ~ maybeAnnotsText(defn.UseAnnot) ~ maybeAnnotsText(defn.ConsumeAnnot) + ~ specialAnnotText(defn.UseAnnot, ref.underlying) + ~ specialAnnotText(defn.ConsumeAnnot, ref.underlying) ~ ParamRefNameString(ref) ~ hashStr(lam) ~ toTextRHS(ref.underlying, isParameter = true) Text(lam.paramRefs.map(paramText), ", ") } diff --git a/tests/neg-custom-args/captures/leak-problem-unboxed.scala b/tests/neg-custom-args/captures/leak-problem-unboxed.scala index aedd7c889112..6d4c4b4c94aa 100644 --- a/tests/neg-custom-args/captures/leak-problem-unboxed.scala +++ b/tests/neg-custom-args/captures/leak-problem-unboxed.scala @@ -19,14 +19,14 @@ def useBoxedAsync1(@use x: Box[Async^]): Unit = x.get.read() // ok def test(): Unit = val f: Box[Async^] => Unit = (x: Box[Async^]) => useBoxedAsync(x) // error - val _: Box[Async^] => Unit = useBoxedAsync(_) // error - val _: Box[Async^] => Unit = useBoxedAsync // error - val _ = useBoxedAsync(_) // error - val _ = useBoxedAsync // error + val t1: Box[Async^] => Unit = useBoxedAsync(_) // error + val t2: Box[Async^] => Unit = useBoxedAsync // error + val t3 = useBoxedAsync(_) // was error, now ok + val t4 = useBoxedAsync // was error, now ok def boom(x: Async^): () ->{f} Unit = () => f(Box(x)) val leaked = usingAsync[() ->{f} Unit](boom) - leaked() // scope violation \ No newline at end of file + leaked() // scope violation From 1cf238f209dbc7bcedfd9c3e86d82b4a16e4995b Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Sun, 8 Jun 2025 14:15:55 +0200 Subject: [PATCH 6/9] Drop redundant copying logic --- .../src/dotty/tools/dotc/cc/CheckCaptures.scala | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 44082346e3cc..29107037f441 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -708,19 +708,6 @@ class CheckCaptures extends Recheck, SymTransformer: selType }//.showing(i"recheck sel $tree, $qualType = $result") - /** Hook for massaging a function before it is applied. Copies all @use and @consume - * annotations on method parameter symbols to the corresponding paramInfo types. - */ - override def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = - val paramInfosWithUses = - funtpe.paramInfos.zipWithConserve(funtpe.paramNames): (formal, pname) => - val param = meth.paramNamed(pname) - def copyAnnot(tp: Type, cls: ClassSymbol) = param.getAnnotation(cls) match - case Some(ann) if !tp.hasAnnotation(cls) => AnnotatedType(tp, ann) - case _ => tp - copyAnnot(copyAnnot(formal, defn.UseAnnot), defn.ConsumeAnnot) - funtpe.derivedLambdaType(paramInfos = paramInfosWithUses) - /** Recheck applications, with special handling of unsafeAssumePure. * More work is done in `recheckApplication`, `recheckArg` and `instantiate` below. */ From 9bdf3a7f8889d1a43367f3c0c946f7fb9be41d01 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Sun, 8 Jun 2025 14:19:12 +0200 Subject: [PATCH 7/9] Documenting where the annotations were added --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 29107037f441..d92ed29b8a6f 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -735,7 +735,8 @@ class CheckCaptures extends Recheck, SymTransformer: val argType = recheck(arg, freshenedFormal) .showing(i"recheck arg $arg vs $freshenedFormal = $result", capt) if formal.hasAnnotation(defn.UseAnnot) || formal.hasAnnotation(defn.ConsumeAnnot) then - // The @use and/or @consume annotation is added to `formal` by `prepareFunction` + // The @use and/or @consume annotation is added to `formal` when creating methods types. + // See [[MethodTypeCompanion.adaptParamInfo]]. capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}") markFree(argType.deepCaptureSet, arg) if formal.containsCap then From 3dcaf5932de430f1f40fbfdadaffb18c0c793610 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Mon, 9 Jun 2025 17:44:32 +0200 Subject: [PATCH 8/9] Drop `hasAnnotation` check on symbols for `@use` and `@consume` --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index feae2cc9fa4f..5c5d5f777047 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -497,8 +497,7 @@ extension (sym: Symbol) * @use-annotated term parameter that contains `sym` in its deep capture set. */ def isUseParam(using Context): Boolean = - sym.hasAnnotation(defn.UseAnnot) - || sym.info.hasAnnotation(defn.UseAnnot) + sym.info.hasAnnotation(defn.UseAnnot) || sym.is(TypeParam) && sym.owner.rawParamss.nestedExists: param => param.is(TermParam) && param.hasAnnotation(defn.UseAnnot) @@ -508,8 +507,7 @@ extension (sym: Symbol) /** `sym` or its info is annotated with `@consume`. */ def isConsumeParam(using Context): Boolean = - sym.hasAnnotation(defn.ConsumeAnnot) - || sym.info.hasAnnotation(defn.ConsumeAnnot) + sym.info.hasAnnotation(defn.ConsumeAnnot) def isUpdateMethod(using Context): Boolean = sym.isAllOf(Mutable | Method, butNot = Accessor) From 1d3c55a1c5729a8043396a45b06084c96884c044 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Mon, 9 Jun 2025 21:15:41 +0200 Subject: [PATCH 9/9] Revert "Drop `hasAnnotation` check on symbols for `@use` and `@consume`" This reverts commit 3dcaf5932de430f1f40fbfdadaffb18c0c793610. --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 5c5d5f777047..feae2cc9fa4f 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -497,7 +497,8 @@ extension (sym: Symbol) * @use-annotated term parameter that contains `sym` in its deep capture set. */ def isUseParam(using Context): Boolean = - sym.info.hasAnnotation(defn.UseAnnot) + sym.hasAnnotation(defn.UseAnnot) + || sym.info.hasAnnotation(defn.UseAnnot) || sym.is(TypeParam) && sym.owner.rawParamss.nestedExists: param => param.is(TermParam) && param.hasAnnotation(defn.UseAnnot) @@ -507,7 +508,8 @@ extension (sym: Symbol) /** `sym` or its info is annotated with `@consume`. */ def isConsumeParam(using Context): Boolean = - sym.info.hasAnnotation(defn.ConsumeAnnot) + sym.hasAnnotation(defn.ConsumeAnnot) + || sym.info.hasAnnotation(defn.ConsumeAnnot) def isUpdateMethod(using Context): Boolean = sym.isAllOf(Mutable | Method, butNot = Accessor)