Scala常量表达式和计算字符串字面量

11

我在处理Java注解时遇到了一些棘手的问题,因为它们的参数必须是"常量"。Scala 2.8语言规范的第6.24节指出,“常量表达式”可以是以下任何一种(重点在于):

  • 值类别(如整数)的文字
  • 字符串字面量
  • 使用Predef.classOf(§12.5)构造的类
  • 来自底层平台的枚举元素
  • 形式为Array(c1,...,cn)的文字数组,其中所有ci本身都是常量表达式
  • 由常量值定义(§4.1)定义的标识符。

现在,“字符串字面量”的定义似乎是以一种将其定义为只是一个用“”或“”“”“”括起来的字符块的方式进行的,这是非常明确的。那么我的问题是,为什么考虑到

object MyObject {
  final val MY_CONSTANT1="foo"
  final val MY_CONSTANT2="bar" + "baz"
  final val MY_CONSTANT3="qux" + "quux" + "frobozz"
}

// ...

@MyAnnotation( ??? )
def Foo(): Unit {
...

@MyAnnotation 编译和 scaladoc 可以使用 MY_CONSTANT1 和 MY_CONSTANT2,但不能使用 MY_CONSTANT3(我收到“注释参数需要是常量”的消息)。为什么 MY_CONSTANT2 能正常工作?是因为有一个未指定的最多两个字符串字面值可以组合成一个更大字符串的规则在起作用,还是说我疯了?

编辑 我正在使用Scala 2.10版本,该版本似乎已经修复了之前Scala版本中与注释相关的编译器错误。


请关闭问题 - 它适用于Scala 2.10+。 - Andrzej Jozwik
4
如果它“工作正常”(在这种情况下意味着什么?),我就不会遇到导致我提出问题的行为,所以不,我不会关闭这个问题。 - cbmanica
你能详细说明一下如何重现这个问题吗?这在2.10.2版本中对我有效:object MyObject { final val MY_CONSTANT1="foo" final val MY_CONSTANT2="bar" + "baz" final val MY_CONSTANT3="qux" + "quux" + "frobozz" }case class NoopAnnotation(val param: String) extends scala.annotation.StaticAnnotation@NoopAnnotation("ABC") def abc1() = 1@NoopAnnotation(MyObject.MY_CONSTANT1) def abc2() = 1@NoopAnnotation(MyObject.MY_CONSTANT2) def abc3() = 1@NoopAnnotation(MyObject.MY_CONSTANT3) def abc4() = 1 - Matt Malone
我会尽量找时间 - 还没有发生。可能在这个周末发布更新信息。 - cbmanica
2个回答

6
你说“编译和scaladocs”,所以我猜你的错误是在运行scaladoc时,这也是我的情况。
使用scaladoc时,你会得到一个只运行到typer阶段的专门编译器。
它在其typer中定制的其中一件事情是:
override def canAdaptConstantTypeToLiteral = false

将其更改为true,你的简单示例将会生成scaladoc。
adapt的开头有一个很大的注释,它表示这是它要做的第一件事情,或者更确切地说,在scaladocking时不做的第零件事情。
*  (0) Convert expressions with constant types to literals (unless in interactive/scaladoc mode)

仅仅是为了好玩,我会尝试翻转这个标志,以查看哪些内容会出错。(编辑:Scala文档构建正常。该标志源于演示编译器的行为,但我不确定它如何适用于Scaladoc。)


哦,我想它大概是那样的,但是我不知道从哪里开始查找。 - cbmanica
当使用SBT生成scaladoc时,如何更改此设置? - Alessandro Vermeulen
@AlessandroVermeulen 我不认为有一个标志可以做到这一点。我必须触碰源代码,重新构建,在build.sbt中您可以指定本地scala发行版。我总是忘记如何做到这一点。scalaHome:= Some(file(“mydistro”))。所有这些年过去了,我仍然不喜欢sbt。 - som-snytt

4
所有这些字符串都以相同的方式编译 - 它们都被折叠成单个字面量。您可以在字节码中看到它们。
0: ldc           #19                 // String foo
0: ldc           #22                 // String barbaz
0: ldc           #24                 // String quxquuxfrobozz

对于StaticAnnotation,您不会看到“注释参数必须是常量”的警告,因为它并不强制执行这样的规定。这种代码可以编译通过,没有警告。
class MyAnnotation(val s: String) extends scala.annotation.StaticAnnotation
object MyObject {
  def inconstant(): String = scala.util.Random.nextString(10)
  @MyAnnotation(s = inconstant) def f = null
}

引发“注释参数需要常量”错误的方法是首先扩展ClassfileAnnotation,然后执行某些操作,使val无法具有常量类型。类似于这样。类型指定导致MY_CONSTANT1具有类型String,而不是常量类型String(“foo”)。在scala中不能直接表达常量类型,但它在内部表示的方式如此。

class MyAnnotation(val s: String) extends scala.annotation.ClassfileAnnotation
object MyObject {
  final val MY_CONSTANT1: String = "foo" 
  final val MY_CONSTANT2 = "bar" + "baz"
  final val MY_CONSTANT3 = "qux" + "quux" + "frobozz"
}
object Bippy {
  @MyAnnotation(s = MyObject.MY_CONSTANT1) def f1 = null
  @MyAnnotation(s = MyObject.MY_CONSTANT2) def f2 = null
  @MyAnnotation(s = MyObject.MY_CONSTANT3) def f3 = null
}

/***
a.scala:8: error: annotation argument needs to be a constant; found: MyObject.MY_CONSTANT1
  @MyAnnotation(s = MyObject.MY_CONSTANT1) def f1 = null
                             ^
one warning found
one error found
***/

如果My_CONSTANTX是一个数组,我甚至无法使用没有类型注释的final val使其工作:但是通过直接传递表达式,我能够让它工作...:@MyAnnotation(s = Array("someString")) - bbarker
对我上面的评论进行了轻微改进:final val someString = "asdf1234",然后执行 @MyAnnotation(s = Array(someString)) - bbarker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接