Skip to content

Commit 0019bc2

Browse files
committed
humane reporting of macro impl binding version errors
Macro defs are linked to macro impls by the virtue of MacroImplBinding structures that are persisted between compilation runs serialized within instances of macroImpl annotations. Along with the evolution of our macro engine, we sometimes have to evolve the format of MacroImplBinding, which means that it has to be versioned. Version mismatches are checked upon every macro expansion, ensuring that macros that we expand were compiled with exactly the same version of the macro engine that we’re running. That’s all really cool apart from the fact that version mismatches result in aborting the entire compilation with an obscure message without giving a hint about the culprits. This commit improves the situation by providing pretty per-expansion compilation errors that tell the programmer what macro expansions are at fault and what macro engines were used to compile them.
1 parent 68b8e23 commit 0019bc2

File tree

8 files changed

+73
-11
lines changed

8 files changed

+73
-11
lines changed

src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,11 @@ trait ContextErrors {
730730
issueNormalTypeError(expandee, s"macro in $role role can only expand into $allowedExpansions")
731731
}
732732

733+
def MacroIncompatibleEngineError(macroEngine: String) = {
734+
val message = s"macro cannot be expanded, because it was compiled by an incompatible macro engine $macroEngine"
735+
issueNormalTypeError(lastTreeToTyper, message)
736+
}
737+
733738
case object MacroExpansionException extends Exception with scala.util.control.ControlThrowable
734739

735740
protected def macroExpansionError(expandee: Tree, msg: String, pos: Position = NoPosition) = {

src/compiler/scala/tools/nsc/typechecker/Macros.scala

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -123,16 +123,15 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
123123
*
124124
* @scala.reflect.macros.internal.macroImpl(
125125
* `macro`(
126+
* "macroEngine" = <current macro engine>,
126127
* "isBundle" = false,
127128
* "isBlackbox" = true,
128129
* "signature" = List(Other),
129130
* "methodName" = "impl",
130-
* "versionFormat" = <current version format>,
131131
* "className" = "Macros$"))
132132
*/
133+
def macroEngine = "v7.0 (implemented in Scala 2.11.0-M8)"
133134
object MacroImplBinding {
134-
val versionFormat = 6.0
135-
136135
def pickleAtom(obj: Any): Tree =
137136
obj match {
138137
case list: List[_] => Apply(Ident(ListModule), list map pickleAtom)
@@ -183,12 +182,12 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
183182
}
184183

185184
val payload = List[(String, Any)](
186-
"versionFormat" -> versionFormat,
187-
"isBundle" -> isBundle,
188-
"isBlackbox" -> isBlackbox,
189-
"className" -> className,
190-
"methodName" -> macroImpl.name.toString,
191-
"signature" -> signature
185+
"macroEngine" -> macroEngine,
186+
"isBundle" -> isBundle,
187+
"isBlackbox" -> isBlackbox,
188+
"className" -> className,
189+
"methodName" -> macroImpl.name.toString,
190+
"signature" -> signature
192191
)
193192

194193
// the shape of the nucleus is chosen arbitrarily. it doesn't carry any payload.
@@ -237,8 +236,8 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
237236
raw.asInstanceOf[T]
238237
}
239238

240-
val pickleVersionFormat = unpickle("versionFormat", classOf[Double])
241-
if (versionFormat != pickleVersionFormat) fail(s"expected version format $versionFormat, actual $pickleVersionFormat")
239+
val macroEngine = unpickle("macroEngine", classOf[String])
240+
if (self.macroEngine != macroEngine) typer.TyperErrorGen.MacroIncompatibleEngineError(macroEngine)
242241

243242
val isBundle = unpickle("isBundle", classOf[Boolean])
244243
val isBlackbox = unpickle("isBlackbox", classOf[Boolean])
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Test_3.scala:2: error: macro cannot be expanded, because it was compiled by an incompatible macro engine vxxx (implemented in the incompatibleMacroEngine plugin)
2+
Macros.foo
3+
^
4+
Test_3.scala:3: error: macro cannot be expanded, because it was compiled by an incompatible macro engine vxxx (implemented in the incompatibleMacroEngine plugin)
5+
Macros.foo
6+
^
7+
two errors found
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xplugin:.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.language.experimental.macros
2+
import scala.reflect.macros.BlackboxContext
3+
4+
object Macros {
5+
def impl(c: BlackboxContext) = c.universe.Literal(c.universe.Constant(()))
6+
def foo: Unit = macro impl
7+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package incompatibleMacroEngine
2+
3+
import scala.tools.nsc.Global
4+
import scala.tools.nsc.plugins.{Plugin => NscPlugin}
5+
6+
class Plugin(val global: Global) extends NscPlugin {
7+
import global._
8+
import analyzer._
9+
10+
val name = "incompatibleMacroEngine"
11+
val description = "A sample analyzer plugin that crafts a macro impl binding with a non-standard macro engine."
12+
val components = Nil
13+
addMacroPlugin(MacroPlugin)
14+
15+
object MacroPlugin extends MacroPlugin {
16+
def fixupBinding(tree: Tree) = new Transformer {
17+
override def transform(tree: Tree) = {
18+
tree match {
19+
case Literal(const @ Constant(x)) if tree.tpe == null => tree setType ConstantType(const)
20+
case _ if tree.tpe == null => tree setType NoType
21+
case _ => ;
22+
}
23+
super.transform(tree)
24+
}
25+
}.transform(tree)
26+
27+
override def pluginsTypedMacroBody(typer: Typer, ddef: DefDef): Option[Tree] = {
28+
val result = typedMacroBody(typer, ddef)
29+
val List(AnnotationInfo(atp, List(Apply(nucleus, _ :: others)), Nil)) = ddef.symbol.annotations
30+
val updatedBinding = Apply(nucleus, Assign(Literal(Constant("macroEngine")), Literal(Constant("vxxx (implemented in the incompatibleMacroEngine plugin)"))) :: others)
31+
ddef.symbol.setAnnotations(List(AnnotationInfo(atp, List(fixupBinding(updatedBinding)), Nil)))
32+
Some(result)
33+
}
34+
}
35+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object Test extends App {
2+
Macros.foo
3+
Macros.foo
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<plugin>
2+
<name>incompatible-macro-engine</name>
3+
<classname>incompatibleMacroEngine.Plugin</classname>
4+
</plugin>

0 commit comments

Comments
 (0)