Как добавить конструктор без аргументов в класс case Scala с аннотацией макроса?

Я пытаюсь ответить этот вопрос.

Вместо того, чтобы писать:

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

Я думал, что буду использовать аннотации макросов, чтобы расширить его:

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

Поэтому я попытался добавить новый конструктор как старый добрый 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

Есть ли способ исправить это? Что я упустил?

Спасибо, что заглянули, - Джулиан.


person Julian Peeters    schedule 31.03.2014    source источник


Ответы (1)


Оказывается, вполне естественное намерение создать вторичный конструктор в аннотации макроса выявило две разные проблемы.

1) Первый выпуск (https://issues.scala-lang.org/browse/SI-8451) относится к квазикавычкам, испускающим неправильные древовидные формы для вторичных конструкторов. Это было исправлено в 2.11.0-RC4 (еще не выпущено, в настоящее время доступно как 2.11.0-SNAPSHOT) и в раю 2.0.0-M6 для 2.10.x (выпущено вчера).

2) Вторая проблема связана с неназначенными позициями, вызывающими хаос во время проверки типов. Любопытно, что при проверке типов вызовов конструкторов 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)
person Eugene Burmako    schedule 31.03.2014
comment
Увы, я использую рай 2.0.0-М6 для 2.10.3. Вот минимальный исполняемый пример. И вот ошибка в M4. - person Julian Peeters; 31.03.2014
comment
Ага, понятно. Итак, у нас было две ошибки. Один починил М6, а другой остался. - person Eugene Burmako; 31.03.2014
comment
Похоже, обходной путь успешно работает и для классов с двойными аннотациями, если @NewCtorAnnotation непосредственно предшествует аннотируемому. Однако та же ошибка возвращается, если между ними находится @AnotherAnnotation (для меня это не проблема, просто к вашему сведению). - person Julian Peeters; 07.04.2014