如何使用宏注释向Scala案例类添加无参数构造函数?

时间:2014-03-31 08:10:04

标签: scala constructor scala-macros case-class scala-quasiquotes

我试图回答this question

而不是写作:

case class Person(name: String, age: Int) {
  def this() = this("",1)
}

我以为我会使用宏注释来扩展它:

@Annotation
case class Person(name: String, age: Int)

所以我尝试在宏注释的impl中使用quasiquotes将新构造函数添加为普通的DefDef,如:

val newCtor = q"""def this() = this("", 1)"""
val newBody = body :+ newCtor
q"$mods class $name[..$tparams](..$first)(...$rest) extends ..$parents { $self => ..$newBody }"

但是会返回错误:called constructor's definition must precede calling constructor's definition

有没有办法解决这个问题?我错过了什么?

谢谢你看看, -Julian

1 个答案:

答案 0 :(得分:5)

事实证明,在宏注释中生成辅助构造函数的一个非常自然的意图暴露了两个不同的问题。

1)第一个问题(https://issues.scala-lang.org/browse/SI-8451)是关于为二级构造函数发出错误树形状的quasiquotes。这在2.11.0-RC4(尚未发布,目前可用作2.11.0-SNAPSHOT)和天堂2.0.0-M6(2.10.x(昨天发布))中修复。

2)第二个问题是关于在typechecker期间造成严重破坏的未分配职位。奇怪的是,当类型检查调用构造函数时,typer使用位置来判断这些调用是否合法。这不能轻易修补,我们必须解决这个问题:

         val newCtor = q"""def this() = this(List(Some("")))"""
-        val newBody = body :+ newCtor
+
+        // It looks like typer sometimes uses positions to decide whether stuff
+        // (secondary constructors in this case) typechecks or not (?!!):
+        // https://github.com/xeno-by/scala/blob/c74e1325ff1514b1042c959b0b268b3c6bf8d349/src/compiler/scala/tools/nsc/typechecker/Typers.scala#L2932
+        //
+        // In general, positions are important in getting error messages and debug
+        // information right, but maintaining positions is too hard, so macro writers typically don't care.
+        //
+        // This has never been a problem up until now, but here we're forced to work around
+        // by manually setting an artificial position for the secondary constructor to be greater
+        // than the position that the default constructor is going to get after macro expansion.
+        //
+        // We have a few ideas how to fix positions in a principled way in Palladium,
+        // but we'll have to see how it goes.
+        val defaultCtorPos = c.enclosingPosition
+        val newCtorPos = defaultCtorPos.withEnd(defaultCtorPos.endOrPoint + 1).withStart(defaultCtorPos.startOrPoint + 1).withPoint(defaultCtorPos.    point + 1)
+        val newBody = body :+ atPos(newCtorPos)(newCtor)