在 GitHub 上编辑此页面

@targetName 注解

在定义上使用 @targetName 注解可以为该定义的实现定义一个备用名称。示例

import scala.annotation.targetName

object VecOps:
  extension [T](xs: Vec[T])
    @targetName("append")
    def ++= [T] (ys: Vec[T]): Vec[T] = ...

在这里,++= 操作在字节码或原生代码中以 append 的名称实现。实现名称会影响生成的代码,并且是其他语言代码调用该方法时使用的名称。例如,++= 可以从 Java 中这样调用

VecOps.append(vec1, vec2)

@targetName 注解对 Scala 使用没有影响。在 Scala 中使用该方法时,必须使用 ++=,而不是 append

详情

  1. @targetName 定义在 scala.annotation 包中。它接受一个类型为 String 的参数。该字符串被称为被注释定义的 *外部名称*。

  2. 除了顶层的 classtraitobject 之外,所有类型的定义都可以使用 @targetName 注解。

  3. @targetName 注解中给出的名称必须是宿主平台上定义的实体的合法名称。

  4. 建议使用符号名称的定义使用 @targetName 注解。这将建立一个更容易搜索的备用名称,并避免在运行时诊断中出现神秘的编码。

  5. 使用反引号括起来的名称不是合法宿主平台名称的定义也应该使用 @targetName 注解。

与重写的关系

@targetName 注解对于匹配两个方法定义以确定它们是否冲突或重写彼此非常重要。如果两个方法定义具有相同的名称、签名和擦除名称,则它们匹配。这里,

  • 定义的 *签名* 包括所有(值)参数的擦除类型的名称和方法的结果类型。
  • 方法定义的 *擦除名称* 是其目标名称(如果给出了 @targetName 注解),否则是其定义的名称。

这意味着 @targetName 注解可以用来消除两个原本会发生冲突的方法定义。例如。

def f(x: => String): Int = x.length
def f(x: => Int): Int = x + 1  // error: double definition

上面的两个定义发生冲突,因为它们的擦除参数类型都是 Function0,它是按名称参数转换的类型。因此它们具有相同的名称和签名。但是,我们可以通过在任一方法或两个方法中添加 @targetName 注解来避免冲突。示例

@targetName("f_string")
def f(x: => String): Int = x.length
def f(x: => Int): Int = x + 1  // OK

这将生成名为f_stringf的方法,并将其包含在生成的代码中。

然而,@targetName注解不允许破坏两个具有相同名称和类型的定义之间的覆盖关系。因此,以下代码将导致错误

import annotation.targetName
class A:
  def f(): Int = 1
class B extends A:
  @targetName("g") def f(): Int = 2

编译器在这里报告错误

-- Error: test.scala:6:23 ------------------------------------------------------
6 |  @targetName("g") def f(): Int = 2
  |                       ^
  |error overriding method f in class A of type (): Int;
  |  method f of type (): Int should not have a @targetName
  |  annotation since the overridden member hasn't one either

相关的覆盖规则可以总结如下

  • 两个成员可以相互覆盖,如果它们的名称和签名相同,并且它们要么具有相同的擦除名称,要么具有相同的类型。
  • 如果两个成员相互覆盖,那么它们的擦除名称和类型必须相同。

与往常一样,生成的代码中的任何覆盖关系也必须存在于原始代码中。因此,以下示例也将导致错误

import annotation.targetName
class A:
  def f(): Int = 1
class B extends A:
  @targetName("f") def g(): Int = 2

在这里,原始方法gf不会相互覆盖,因为它们具有不同的名称。但是,一旦我们切换到目标名称,就会发生冲突,编译器会报告该冲突

-- [E120] Naming Error: test.scala:4:6 -----------------------------------------
4 |class B extends A:
  |      ^
  |      Name clash between defined and inherited member:
  |      def f(): Int in class A at line 3 and
  |      def g(): Int in class B at line 5
  |      have the same name and type after erasure.
1 error found