I'm trying to answer this question.
Instead of writing:
case class Person(name: String, age: Int) {
def this() = this("",1)
}
I thought I'd use macro annotations to expand it from:
@Annotation
case class Person(name: String, age: Int)
So I tried adding the new constructor as a plain-old DefDef
using quasiquotes in a macro annotation's impl, like:
val newCtor = q"""def this() = this("", 1)"""
val newBody = body :+ newCtor
q"$mods class $name[..$tparams](..$first)(...$rest) extends ..$parents { $self => ..$newBody }"
But that returns an error: called constructor's definition must precede calling constructor's definition
Is there a way to fix that? What did I miss?
Thanks for taking a look,
-Julian
It turns out that a very natural intent to generate a secondary constructor in a macro annotation has exposed two different issues.
1) The first issue (https://issues.scala-lang.org/browse/SI-8451) is about quasiquotes emitting wrong tree shapes for secondary constructors. That was fixed in 2.11.0-RC4 (yet to be released, currently available as 2.11.0-SNAPSHOT) and in paradise 2.0.0-M6 for 2.10.x (released yesterday).
2) The second issue is about unassigned positions wreaking havoc during typechecker. Curiously enough, when typechecking calls to constructors, typer uses positions in order to decide whether these calls are legal or not. That can't be patched that easily, and we have to work around:
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)