Skip to content

Commit

Permalink
Disallow @targetName on top-level class, trait, and object.
Browse files Browse the repository at this point in the history
This usage was mostly broken, as it failed with separate compilation.
  • Loading branch information
griggt committed Apr 29, 2022
1 parent f113fc0 commit 47189c7
Show file tree
Hide file tree
Showing 13 changed files with 69 additions and 15 deletions.
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID]:
MatchableWarningID,
CannotExtendFunctionID,
LossyWideningConstantConversionID,
ImplicitSearchTooLargeID
ImplicitSearchTooLargeID,
TargetNameOnTopLevelClassID

def errorNumber = ordinal - 2

Expand Down
21 changes: 21 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")}"""
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion docs/_docs/reference/other-new-features/targetName.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 18 additions & 0 deletions tests/neg/targetName-toplevel.scala
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion tests/run/targetName-interop/Test_2.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
public class Test_2 {

public static void main(String[] args) {
Alpha<String> a = new Bar();
Alpha<String> a = new Outer$Bar();
assert a.foo() == 1;
assert a.bar("a").equals("aa");
Alpha<String> aa = a.append(a);
Expand Down
9 changes: 5 additions & 4 deletions tests/run/targetName-interop/alpha_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ abstract class Alpha[T] {

}

@targetName("Bar") class | extends Alpha[String] {
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
@targetName("bar") override def foo(x: String) = x ++ x

@targetName("append") override def ++ (xs: Alpha[String]) = this
}
}
6 changes: 4 additions & 2 deletions tests/run/targetName-modules-1/7721_1.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package alpha

@scala.annotation.targetName("A") object B {
def foo = 23
object Outer {
@scala.annotation.targetName("A") object B {
def foo = 23
}
}
4 changes: 2 additions & 2 deletions tests/run/targetName-modules-1/Test_2.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
3 changes: 2 additions & 1 deletion tests/run/targetName-modules-2/7723_1.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
package alpha

@scala.annotation.targetName("A") class B(val i: Int = 1)
object Outer:
@scala.annotation.targetName("A") class B(val i: Int = 1)
6 changes: 3 additions & 3 deletions tests/run/targetName-modules-2/Test_2.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
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;
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;
}
}
5 changes: 5 additions & 0 deletions tests/run/targetName-separate/Foo_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object Outer:
@annotation.targetName("Bar") class Foo:
def it: Int = 42

export Outer.Foo
3 changes: 3 additions & 0 deletions tests/run/targetName-separate/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@main def Test =
assert(new Foo().it == 42)
assert(Foo().it == 42)

0 comments on commit 47189c7

Please sign in to comment.