Skip to content

Commit 6888f09

Browse files
committed
Disallow @targetName on top-level class, trait, and object.
This usage was mostly broken, as it failed with separate compilation.
1 parent f113fc0 commit 6888f09

File tree

13 files changed

+71
-15
lines changed

13 files changed

+71
-15
lines changed

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID]:
176176
MatchableWarningID,
177177
CannotExtendFunctionID,
178178
LossyWideningConstantConversionID,
179-
ImplicitSearchTooLargeID
179+
ImplicitSearchTooLargeID,
180+
TargetNameOnTopLevelClassID
180181

181182
def errorNumber = ordinal - 2
182183

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,3 +2540,26 @@ import transform.SymUtils._
25402540
|
25412541
|${openSearchPairs.reverse.map(showQuery)}%\n%
25422542
"""
2543+
2544+
class TargetNameOnTopLevelClass(symbol: Symbol)(using Context)
2545+
extends SyntaxMsg(TargetNameOnTopLevelClassID):
2546+
def msg = em"${hl("@targetName")} annotation not allowed on top-level $symbol"
2547+
def explain =
2548+
val annot = symbol.getAnnotation(defn.TargetNameAnnot).get
2549+
em"""|The @targetName annotation may be applied to a top-level ${hl("val")} or ${hl("def")}, but not
2550+
|a top-level ${hl("class")}, ${hl("trait")}, or ${hl("object")}.
2551+
|
2552+
|This restriction is due to the naming convention of Java classfiles, whose filenames
2553+
|are based on the name of the class defined within. If @targetName were permitted
2554+
|here, the name of the classfile would be based on the target name, and the compiler
2555+
|could not associate that classfile with the Scala-visible defined name of the class.
2556+
|
2557+
|If your use case requires @targetName, consider wrapping $symbol in an ${hl("object")}
2558+
|(and possibly exporting it), as in the following example:
2559+
|
2560+
|${hl("object Wrapper:")}
2561+
| $annot $symbol { ... }
2562+
|
2563+
|${hl("export")} Wrapper.${symbol.name} ${hl("// optional")}
2564+
|
2565+
|""".stripMargin

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,8 @@ object Checking {
505505
fail(TailrecNotApplicable(sym))
506506
else if sym.is(Inline) then
507507
fail("Inline methods cannot be @tailrec")
508+
if sym.hasAnnotation(defn.TargetNameAnnot) && sym.isClass && sym.isTopLevelClass then
509+
fail(TargetNameOnTopLevelClass(sym))
508510
if (sym.hasAnnotation(defn.NativeAnnot)) {
509511
if (!sym.is(Deferred))
510512
fail(NativeMembersMayNotHaveImplementation(sym))

docs/_docs/reference/other-new-features/targetName.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ The [`@targetName`](https://scala-lang.org/api/3.x/scala/annotation/targetName.h
2929
of type `String`. That string is called the _external name_ of the definition
3030
that's annotated.
3131

32-
2. A `@targetName` annotation can be given for all kinds of definitions.
32+
2. A `@targetName` annotation can be given for all kinds of definitions except a top-level `class`, `trait`, or `object`.
3333

3434
3. The name given in a [`@targetName`](https://scala-lang.org/api/3.x/scala/annotation/targetName.html) annotation must be a legal name
3535
for the defined entities on the host platform.

tests/neg/targetName-toplevel.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import scala.annotation.targetName
2+
3+
@targetName("B1") class A1 // error targetName on top-level class
4+
@targetName("B2") trait A2 // error targetName on top-level trait
5+
@targetName("B3") object A3 // error targetName on top-level object
6+
7+
@targetName("bar") def foo = 42 // OK
8+
9+
object Outer:
10+
@targetName("B1") class A1 // OK
11+
@targetName("B2") trait A2 // OK
12+
@targetName("B3") object A3 // OK
13+
@targetName("D1") class C1 // OK
14+
@targetName("D2") trait C2 // OK
15+
@targetName("D3") object C3 // OK
16+
17+
export Outer.{A1, A2, A3} // error // error // error already defined
18+
export Outer.{C1, C2, C3} // OK

tests/run/targetName-interop/Test_2.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
public class Test_2 {
55

66
public static void main(String[] args) {
7-
Alpha<String> a = new Bar();
7+
Alpha<String> a = new Outer$Bar();
88
assert a.foo() == 1;
99
assert a.bar("a").equals("aa");
1010
Alpha<String> aa = a.append(a);

tests/run/targetName-interop/alpha_1.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ abstract class Alpha[T] {
1111

1212
}
1313

14-
@targetName("Bar") class | extends Alpha[String] {
14+
object Outer {
15+
@targetName("Bar") class | extends Alpha[String] {
1516

16-
@targetName("bar") override def foo(x: String) = x ++ x
17-
18-
@targetName("append") override def ++ (xs: Alpha[String]) = this
17+
@targetName("bar") override def foo(x: String) = x ++ x
1918

19+
@targetName("append") override def ++ (xs: Alpha[String]) = this
20+
}
2021
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package alpha
22

3-
@scala.annotation.targetName("A") object B {
4-
def foo = 23
3+
object Outer {
4+
@scala.annotation.targetName("A") object B {
5+
def foo = 23
6+
}
57
}

tests/run/targetName-modules-1/Test_2.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
public class Test_2 {
44

55
public static void main(String[] args) {
6-
assert A.foo() == 23;
7-
assert A$.MODULE$.foo() == 23;
6+
assert Outer$.A.foo() == 23;
7+
assert Outer$A$.MODULE$.foo() == 23;
88
}
99
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
package alpha
22

3-
@scala.annotation.targetName("A") class B(val i: Int = 1)
3+
object Outer:
4+
@scala.annotation.targetName("A") class B(val i: Int = 1)

tests/run/targetName-modules-2/Test_2.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
public class Test_2 {
44

55
public static void main(String[] args) {
6-
assert new A(101).i() == 101;
7-
assert new A(A.$lessinit$greater$default$1()).i() == 101;
8-
assert new A(A$.MODULE$.$lessinit$greater$default$1()).i() == 101;
6+
assert new Outer$A(101).i() == 101;
7+
assert new Outer$A(Outer$A.$lessinit$greater$default$1()).i() == 101;
8+
assert new Outer$A(Outer$A$.MODULE$.$lessinit$greater$default$1()).i() == 101;
99
}
1010
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object Outer:
2+
@annotation.targetName("Bar") class Foo:
3+
def it: Int = 42
4+
5+
export Outer.Foo
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@main def Test =
2+
assert(new Foo().it == 42)
3+
assert(Foo().it == 42)

0 commit comments

Comments
 (0)