diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 2f1fe0e1c058..69b70e0a1720 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -176,7 +176,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID]: MatchableWarningID, CannotExtendFunctionID, LossyWideningConstantConversionID, - ImplicitSearchTooLargeID + ImplicitSearchTooLargeID, + TargetNameOnTopLevelClassID def errorNumber = ordinal - 2 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 797073115bb5..48135f9aa35d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2540,3 +2540,24 @@ import transform.SymUtils._ | |${openSearchPairs.reverse.map(showQuery)}%\n% """ + + class TargetNameOnTopLevelClass(symbol: Symbol)(using Context) + extends SyntaxMsg(TargetNameOnTopLevelClassID): + def msg = em"${hl("@targetName")} annotation not allowed on top-level $symbol" + def explain = + val annot = symbol.getAnnotation(defn.TargetNameAnnot).get + em"""The @targetName annotation may be applied to a top-level ${hl("val")} or ${hl("def")}, but not + |a top-level ${hl("class")}, ${hl("trait")}, or ${hl("object")}. + | + |This restriction is due to the naming convention of Java classfiles, whose filenames + |are based on the name of the class defined within. If @targetName were permitted + |here, the name of the classfile would be based on the target name, and the compiler + |could not associate that classfile with the Scala-visible defined name of the class. + | + |If your use case requires @targetName, consider wrapping $symbol in an ${hl("object")} + |(and possibly exporting it), as in the following example: + | + |${hl("object Wrapper:")} + | $annot $symbol { ... } + | + |${hl("export")} Wrapper.${symbol.name} ${hl("// optional")}""" diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 1e597e42d2a5..7b7bdd5945be 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -505,6 +505,8 @@ object Checking { fail(TailrecNotApplicable(sym)) else if sym.is(Inline) then fail("Inline methods cannot be @tailrec") + if sym.hasAnnotation(defn.TargetNameAnnot) && sym.isClass && sym.isTopLevelClass then + fail(TargetNameOnTopLevelClass(sym)) if (sym.hasAnnotation(defn.NativeAnnot)) { if (!sym.is(Deferred)) fail(NativeMembersMayNotHaveImplementation(sym)) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 5681bd480511..c8082069c28f 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -171,7 +171,7 @@ class CompilationTests { defaultOptions), compileFile("tests/neg-custom-args/i6300.scala", allowDeepSubtypes), compileFile("tests/neg-custom-args/infix.scala", defaultOptions.and("-source", "future", "-deprecation", "-Xfatal-warnings")), - compileFile("tests/neg-custom-args/missing-alpha.scala", defaultOptions.and("-Yrequire-targetName", "-Xfatal-warnings")), + compileFile("tests/neg-custom-args/missing-targetName.scala", defaultOptions.and("-Yrequire-targetName", "-Xfatal-warnings")), compileFile("tests/neg-custom-args/wildcards.scala", defaultOptions.and("-source", "future", "-deprecation", "-Xfatal-warnings")), compileFile("tests/neg-custom-args/indentRight.scala", defaultOptions.and("-no-indent", "-Xfatal-warnings")), compileDir("tests/neg-custom-args/adhoc-extension", defaultOptions.and("-source", "future", "-feature", "-Xfatal-warnings")), diff --git a/docs/_docs/reference/other-new-features/targetName.md b/docs/_docs/reference/other-new-features/targetName.md index d2a654697d15..09886968a232 100644 --- a/docs/_docs/reference/other-new-features/targetName.md +++ b/docs/_docs/reference/other-new-features/targetName.md @@ -29,7 +29,7 @@ The [`@targetName`](https://scala-lang.org/api/3.x/scala/annotation/targetName.h of type `String`. That string is called the _external name_ of the definition that's annotated. - 2. A `@targetName` annotation can be given for all kinds of definitions. + 2. A `@targetName` annotation can be given for all kinds of definitions except a top-level `class`, `trait`, or `object`. 3. The name given in a [`@targetName`](https://scala-lang.org/api/3.x/scala/annotation/targetName.html) annotation must be a legal name for the defined entities on the host platform. diff --git a/tests/neg-custom-args/missing-alpha.scala b/tests/neg-custom-args/missing-targetName.scala similarity index 100% rename from tests/neg-custom-args/missing-alpha.scala rename to tests/neg-custom-args/missing-targetName.scala diff --git a/tests/neg/alpha-early.scala b/tests/neg/targetName-early.scala similarity index 100% rename from tests/neg/alpha-early.scala rename to tests/neg/targetName-early.scala diff --git a/tests/neg/alpha-late.scala b/tests/neg/targetName-late.scala similarity index 100% rename from tests/neg/alpha-late.scala rename to tests/neg/targetName-late.scala diff --git a/tests/neg/alpha-override/B_1.java b/tests/neg/targetName-override0/B_1.java similarity index 100% rename from tests/neg/alpha-override/B_1.java rename to tests/neg/targetName-override0/B_1.java diff --git a/tests/neg/alpha-override/C_2.scala b/tests/neg/targetName-override0/C_2.scala similarity index 100% rename from tests/neg/alpha-override/C_2.scala rename to tests/neg/targetName-override0/C_2.scala diff --git a/tests/neg/alpha-override1/B_1.java b/tests/neg/targetName-override1/B_1.java similarity index 100% rename from tests/neg/alpha-override1/B_1.java rename to tests/neg/targetName-override1/B_1.java diff --git a/tests/neg/alpha-override1/D_2.scala b/tests/neg/targetName-override1/D_2.scala similarity index 100% rename from tests/neg/alpha-override1/D_2.scala rename to tests/neg/targetName-override1/D_2.scala diff --git a/tests/neg/targetName-toplevel.scala b/tests/neg/targetName-toplevel.scala new file mode 100644 index 000000000000..28e8b0e84ca3 --- /dev/null +++ b/tests/neg/targetName-toplevel.scala @@ -0,0 +1,18 @@ +import scala.annotation.targetName + +@targetName("B1") class A1 // error targetName on top-level class +@targetName("B2") trait A2 // error targetName on top-level trait +@targetName("B3") object A3 // error targetName on top-level object + +@targetName("bar") def foo = 42 // OK + +object Outer: + @targetName("B1") class A1 // OK + @targetName("B2") trait A2 // OK + @targetName("B3") object A3 // OK + @targetName("D1") class C1 // OK + @targetName("D2") trait C2 // OK + @targetName("D3") object C3 // OK + +export Outer.{A1, A2, A3} // error // error // error already defined +export Outer.{C1, C2, C3} // OK diff --git a/tests/pos/alpha-override/A_1.scala b/tests/pos/targetName-override/A_1.scala similarity index 100% rename from tests/pos/alpha-override/A_1.scala rename to tests/pos/targetName-override/A_1.scala diff --git a/tests/pos/alpha-override/B_2.java b/tests/pos/targetName-override/B_2.java similarity index 100% rename from tests/pos/alpha-override/B_2.java rename to tests/pos/targetName-override/B_2.java diff --git a/tests/pos/alpha.scala b/tests/pos/targetName.scala similarity index 100% rename from tests/pos/alpha.scala rename to tests/pos/targetName.scala diff --git a/tests/run/alpha-interop/alpha_1.scala b/tests/run/alpha-interop/alpha_1.scala deleted file mode 100644 index 41fb26c3f019..000000000000 --- a/tests/run/alpha-interop/alpha_1.scala +++ /dev/null @@ -1,20 +0,0 @@ -package alpha -import annotation.targetName - -abstract class Alpha[T] { - - def foo() = 1 - - @targetName("bar") def foo(x: T): T - - @targetName("append") def ++ (xs: Alpha[T]): Alpha[T] = this - -} - -@targetName("Bar") class | extends Alpha[String] { - - @targetName("bar") override def foo(x: String) = x ++ x - - @targetName("append") override def ++ (xs: Alpha[String]) = this - -} \ No newline at end of file diff --git a/tests/run/alpha-modules-1/7721_1.scala b/tests/run/alpha-modules-1/7721_1.scala deleted file mode 100644 index 5e59433c6beb..000000000000 --- a/tests/run/alpha-modules-1/7721_1.scala +++ /dev/null @@ -1,5 +0,0 @@ -package alpha - -@scala.annotation.targetName("A") object B { - def foo = 23 -} diff --git a/tests/run/alpha-modules-2/7723_1.scala b/tests/run/alpha-modules-2/7723_1.scala deleted file mode 100644 index 128dac508a14..000000000000 --- a/tests/run/alpha-modules-2/7723_1.scala +++ /dev/null @@ -1,3 +0,0 @@ -package alpha - -@scala.annotation.targetName("A") class B(val i: Int = 1) diff --git a/tests/run/alpha-modules-2/Test_2.java b/tests/run/alpha-modules-2/Test_2.java deleted file mode 100644 index 39bdf269a98a..000000000000 --- a/tests/run/alpha-modules-2/Test_2.java +++ /dev/null @@ -1,10 +0,0 @@ -package alpha; - -public class Test_2 { - - public static void main(String[] args) { - assert new A(101).i() == 101; - assert new A(A.$lessinit$greater$default$1()).i() == 101; - assert new A(A$.MODULE$.$lessinit$greater$default$1()).i() == 101; - } -} diff --git a/tests/run/alpha-interop/Test_2.java b/tests/run/targetName-interop/Test_2.java similarity index 82% rename from tests/run/alpha-interop/Test_2.java rename to tests/run/targetName-interop/Test_2.java index 6e93f3cd3533..a1c710174c68 100644 --- a/tests/run/alpha-interop/Test_2.java +++ b/tests/run/targetName-interop/Test_2.java @@ -4,7 +4,7 @@ public class Test_2 { public static void main(String[] args) { - Alpha a = new Bar(); + Alpha a = new Outer$Bar(); assert a.foo() == 1; assert a.bar("a").equals("aa"); Alpha aa = a.append(a); diff --git a/tests/run/alpha-interop/Test_3.scala b/tests/run/targetName-interop/Test_3.scala similarity index 100% rename from tests/run/alpha-interop/Test_3.scala rename to tests/run/targetName-interop/Test_3.scala diff --git a/tests/run/targetName-interop/alpha_1.scala b/tests/run/targetName-interop/alpha_1.scala new file mode 100644 index 000000000000..6408fccb48e5 --- /dev/null +++ b/tests/run/targetName-interop/alpha_1.scala @@ -0,0 +1,21 @@ +package alpha +import annotation.targetName + +abstract class Alpha[T] { + + def foo() = 1 + + @targetName("bar") def foo(x: T): T + + @targetName("append") def ++ (xs: Alpha[T]): Alpha[T] = this + +} + +object Outer { + @targetName("Bar") class | extends Alpha[String] { + + @targetName("bar") override def foo(x: String) = x ++ x + + @targetName("append") override def ++ (xs: Alpha[String]) = this + } +} \ No newline at end of file diff --git a/tests/run/targetName-modules-1/7721_1.scala b/tests/run/targetName-modules-1/7721_1.scala new file mode 100644 index 000000000000..8420c14107ab --- /dev/null +++ b/tests/run/targetName-modules-1/7721_1.scala @@ -0,0 +1,7 @@ +package alpha + +object Outer { + @scala.annotation.targetName("A") object B { + def foo = 23 + } +} diff --git a/tests/run/alpha-modules-1/Test_2.java b/tests/run/targetName-modules-1/Test_2.java similarity index 54% rename from tests/run/alpha-modules-1/Test_2.java rename to tests/run/targetName-modules-1/Test_2.java index 2dbed46aa644..e92e602362c6 100644 --- a/tests/run/alpha-modules-1/Test_2.java +++ b/tests/run/targetName-modules-1/Test_2.java @@ -3,7 +3,7 @@ public class Test_2 { public static void main(String[] args) { - assert A.foo() == 23; - assert A$.MODULE$.foo() == 23; + assert Outer$.A.foo() == 23; + assert Outer$A$.MODULE$.foo() == 23; } } diff --git a/tests/run/alpha-modules-1/Test_3.scala b/tests/run/targetName-modules-1/Test_3.scala similarity index 100% rename from tests/run/alpha-modules-1/Test_3.scala rename to tests/run/targetName-modules-1/Test_3.scala diff --git a/tests/run/targetName-modules-2/7723_1.scala b/tests/run/targetName-modules-2/7723_1.scala new file mode 100644 index 000000000000..8865c5ada577 --- /dev/null +++ b/tests/run/targetName-modules-2/7723_1.scala @@ -0,0 +1,4 @@ +package alpha + +object Outer: + @scala.annotation.targetName("A") class B(val i: Int = 1) diff --git a/tests/run/targetName-modules-2/Test_2.java b/tests/run/targetName-modules-2/Test_2.java new file mode 100644 index 000000000000..7d48407a6ded --- /dev/null +++ b/tests/run/targetName-modules-2/Test_2.java @@ -0,0 +1,10 @@ +package alpha; + +public class Test_2 { + + public static void main(String[] args) { + assert new Outer$A(101).i() == 101; + assert new Outer$A(Outer$A.$lessinit$greater$default$1()).i() == 101; + assert new Outer$A(Outer$A$.MODULE$.$lessinit$greater$default$1()).i() == 101; + } +} diff --git a/tests/run/alpha-modules-2/Test_3.scala b/tests/run/targetName-modules-2/Test_3.scala similarity index 100% rename from tests/run/alpha-modules-2/Test_3.scala rename to tests/run/targetName-modules-2/Test_3.scala diff --git a/tests/run/targetName-separate/Foo_1.scala b/tests/run/targetName-separate/Foo_1.scala new file mode 100644 index 000000000000..dca3c888470c --- /dev/null +++ b/tests/run/targetName-separate/Foo_1.scala @@ -0,0 +1,5 @@ +object Outer: + @annotation.targetName("Bar") class Foo: + def it: Int = 42 + +export Outer.Foo diff --git a/tests/run/targetName-separate/Test_2.scala b/tests/run/targetName-separate/Test_2.scala new file mode 100644 index 000000000000..f051adde1531 --- /dev/null +++ b/tests/run/targetName-separate/Test_2.scala @@ -0,0 +1,3 @@ +@main def Test = + assert(new Foo().it == 42) + assert(Foo().it == 42) diff --git a/tests/run/alpha.scala b/tests/run/targetName1.scala similarity index 100% rename from tests/run/alpha.scala rename to tests/run/targetName1.scala