From e1eaf28a0ba8fd546a1feeafb0cb1fdc6e53c368 Mon Sep 17 00:00:00 2001 From: Ondrej Lhotak Date: Mon, 7 Apr 2025 12:41:39 -0400 Subject: [PATCH] fix #22833: allow unroll annotation in methods of final class Co-authored-by: Lukas Rytz --- compiler/src/dotty/tools/dotc/reporting/messages.scala | 8 ++++---- compiler/src/dotty/tools/dotc/transform/PostTyper.scala | 4 ++-- .../dotty/tools/dotc/transform/UnrollDefinitions.scala | 3 ++- tests/neg/unroll-abstractMethod.check | 4 ++-- tests/neg/unroll-illegal3.check | 6 +++--- tests/pos/i22833-unroll-final-class.scala | 5 +++++ 6 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 tests/pos/i22833-unroll-final-class.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index dcd7ed10987b..10ad4fadf950 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3430,12 +3430,12 @@ extends DeclarationMsg(IllegalUnrollPlacementID): val isCtor = method.isConstructor def what = if isCtor then i"a ${if method.owner.is(Trait) then "trait" else "class"} constructor" else i"method ${method.name}" val prefix = s"Cannot unroll parameters of $what" - if method.is(Deferred) then - i"$prefix: it must not be abstract" + if method.isLocal then + i"$prefix because it is a local method" + else if !method.isEffectivelyFinal then + i"$prefix because it can be overridden" else if isCtor && method.owner.is(Trait) then i"implementation restriction: $prefix" - else if !(isCtor || method.is(Final) || method.owner.is(ModuleClass)) then - i"$prefix: it is not final" else if method.owner.companionClass.is(CaseClass) then i"$prefix of a case class companion object: please annotate the class constructor instead" else diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index df74e102f693..3ca67c5798fe 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -132,9 +132,9 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => then false // not an error, but not an expandable unrolled method else if - method.is(Deferred) + method.isLocal + || !method.isEffectivelyFinal || isCtor && method.owner.is(Trait) - || !(isCtor || method.is(Final) || method.owner.is(ModuleClass)) || method.owner.companionClass.is(CaseClass) && (method.name == nme.apply || method.name == nme.fromProduct) || method.owner.is(CaseClass) && method.name == nme.copy diff --git a/compiler/src/dotty/tools/dotc/transform/UnrollDefinitions.scala b/compiler/src/dotty/tools/dotc/transform/UnrollDefinitions.scala index 44379b88bf16..bf9e20e68930 100644 --- a/compiler/src/dotty/tools/dotc/transform/UnrollDefinitions.scala +++ b/compiler/src/dotty/tools/dotc/transform/UnrollDefinitions.scala @@ -79,7 +79,8 @@ class UnrollDefinitions extends MacroTransform, IdentityDenotTransformer { else Some((paramClauseIndex, annotationIndices)) if indices.nonEmpty then // pre-validation should have occurred in posttyper - assert(annotated.is(Final, butNot = Deferred) || annotated.isConstructor || annotated.owner.is(ModuleClass) || annotated.name.is(DefaultGetterName), + assert(!annotated.isLocal, i"$annotated is local") + assert(annotated.isEffectivelyFinal || annotated.name.is(DefaultGetterName), i"$annotated is not final&concrete, or a constructor") indices }) diff --git a/tests/neg/unroll-abstractMethod.check b/tests/neg/unroll-abstractMethod.check index d0874c8a44d8..7593cf62c8a1 100644 --- a/tests/neg/unroll-abstractMethod.check +++ b/tests/neg/unroll-abstractMethod.check @@ -1,8 +1,8 @@ -- [E207] Declaration Error: tests/neg/unroll-abstractMethod.scala:6:41 ------------------------------------------------ 6 | def foo(s: String, n: Int = 1, @unroll b: Boolean = true): String // error | ^ - | Cannot unroll parameters of method foo: it must not be abstract + | Cannot unroll parameters of method foo because it can be overridden -- [E207] Declaration Error: tests/neg/unroll-abstractMethod.scala:10:41 ----------------------------------------------- 10 | def foo(s: String, n: Int = 1, @unroll b: Boolean = true): String // error | ^ - | Cannot unroll parameters of method foo: it must not be abstract + | Cannot unroll parameters of method foo because it can be overridden diff --git a/tests/neg/unroll-illegal3.check b/tests/neg/unroll-illegal3.check index 6201a7d815cd..65a523d0b94d 100644 --- a/tests/neg/unroll-illegal3.check +++ b/tests/neg/unroll-illegal3.check @@ -1,12 +1,12 @@ -- [E207] Declaration Error: tests/neg/unroll-illegal3.scala:7:31 ------------------------------------------------------ 7 | def foo(s: String, @unroll y: Boolean) = s + y // error | ^ - | Cannot unroll parameters of method foo: it is not final + | Cannot unroll parameters of method foo because it is a local method -- [E207] Declaration Error: tests/neg/unroll-illegal3.scala:12:29 ----------------------------------------------------- 12 | def foo(s: String, @unroll y: Boolean) = s + y // error | ^ - | Cannot unroll parameters of method foo: it is not final + | Cannot unroll parameters of method foo because it can be overridden -- [E207] Declaration Error: tests/neg/unroll-illegal3.scala:16:29 ----------------------------------------------------- 16 | def foo(s: String, @unroll y: Boolean): String // error | ^ - | Cannot unroll parameters of method foo: it must not be abstract + | Cannot unroll parameters of method foo because it can be overridden diff --git a/tests/pos/i22833-unroll-final-class.scala b/tests/pos/i22833-unroll-final-class.scala new file mode 100644 index 000000000000..1c5ef8cf0673 --- /dev/null +++ b/tests/pos/i22833-unroll-final-class.scala @@ -0,0 +1,5 @@ +import scala.annotation.{experimental,unroll} + +@experimental final class Foo { + def bar(@unroll x: Int = 0) = x + 1 +} \ No newline at end of file