Skip to content

Commit 9865ef6

Browse files
authored
Merge pull request #15068 from griggt/disallow-targetname-on-toplevel-classes
Disallow `@targetName` on top-level `class`, `trait`, and `object`
2 parents 244317f + 47189c7 commit 9865ef6

File tree

32 files changed

+98
-44
lines changed

32 files changed

+98
-44
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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,3 +2540,24 @@ 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")}"""

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))

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ class CompilationTests {
171171
defaultOptions),
172172
compileFile("tests/neg-custom-args/i6300.scala", allowDeepSubtypes),
173173
compileFile("tests/neg-custom-args/infix.scala", defaultOptions.and("-source", "future", "-deprecation", "-Xfatal-warnings")),
174-
compileFile("tests/neg-custom-args/missing-alpha.scala", defaultOptions.and("-Yrequire-targetName", "-Xfatal-warnings")),
174+
compileFile("tests/neg-custom-args/missing-targetName.scala", defaultOptions.and("-Yrequire-targetName", "-Xfatal-warnings")),
175175
compileFile("tests/neg-custom-args/wildcards.scala", defaultOptions.and("-source", "future", "-deprecation", "-Xfatal-warnings")),
176176
compileFile("tests/neg-custom-args/indentRight.scala", defaultOptions.and("-no-indent", "-Xfatal-warnings")),
177177
compileDir("tests/neg-custom-args/adhoc-extension", defaultOptions.and("-source", "future", "-feature", "-Xfatal-warnings")),

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.
File renamed without changes.
File renamed without changes.

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
File renamed without changes.

tests/run/alpha-interop/alpha_1.scala

Lines changed: 0 additions & 20 deletions
This file was deleted.

tests/run/alpha-modules-1/7721_1.scala

Lines changed: 0 additions & 5 deletions
This file was deleted.

tests/run/alpha-modules-2/7723_1.scala

Lines changed: 0 additions & 3 deletions
This file was deleted.

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

Lines changed: 0 additions & 10 deletions
This file was deleted.

tests/run/alpha-interop/Test_2.java renamed to 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);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package alpha
2+
import annotation.targetName
3+
4+
abstract class Alpha[T] {
5+
6+
def foo() = 1
7+
8+
@targetName("bar") def foo(x: T): T
9+
10+
@targetName("append") def ++ (xs: Alpha[T]): Alpha[T] = this
11+
12+
}
13+
14+
object Outer {
15+
@targetName("Bar") class | extends Alpha[String] {
16+
17+
@targetName("bar") override def foo(x: String) = x ++ x
18+
19+
@targetName("append") override def ++ (xs: Alpha[String]) = this
20+
}
21+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package alpha
2+
3+
object Outer {
4+
@scala.annotation.targetName("A") object B {
5+
def foo = 23
6+
}
7+
}

tests/run/alpha-modules-1/Test_2.java renamed to 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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package alpha
2+
3+
object Outer:
4+
@scala.annotation.targetName("A") class B(val i: Int = 1)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package alpha;
2+
3+
public class Test_2 {
4+
5+
public static void main(String[] args) {
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;
9+
}
10+
}
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)
File renamed without changes.

0 commit comments

Comments
 (0)