From d17e3fc29cd48a3d4cbbbfc1fc9eb021d787d4d7 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sat, 5 Jan 2013 00:25:28 +0300 Subject: adds c.macroRole Currently there's only one flavor of macros - def macros, and the plan was to gradually introduce additional flavors, such as type macros and macro annotations. However as shown by the experience with type macros, it makes sense to distinguish subflavors of macros that tell us in which context the macro gets expanded. For def macros we have the only role - expansion of an application. But for type macros there are multiple. --- src/compiler/scala/reflect/macros/runtime/Enclosures.scala | 4 ++++ src/compiler/scala/tools/nsc/typechecker/Macros.scala | 7 +++++++ src/reflect/scala/reflect/macros/Enclosures.scala | 13 +++++++++++++ 3 files changed, 24 insertions(+) diff --git a/src/compiler/scala/reflect/macros/runtime/Enclosures.scala b/src/compiler/scala/reflect/macros/runtime/Enclosures.scala index e8b2961611..8fe0b09700 100644 --- a/src/compiler/scala/reflect/macros/runtime/Enclosures.scala +++ b/src/compiler/scala/reflect/macros/runtime/Enclosures.scala @@ -8,6 +8,10 @@ trait Enclosures { import universe._ + type MacroRole = analyzer.MacroRole + def APPLY_ROLE = analyzer.APPLY_ROLE + def macroRole: MacroRole + private lazy val site = callsiteTyper.context private lazy val enclTrees = site.enclosingContextChain map (_.tree) private lazy val enclPoses = enclosingMacros map (_.macroApplication.pos) filterNot (_ eq NoPosition) diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 768d739b4b..23e00960d4 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -538,6 +538,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { val universe: self.global.type = self.global val callsiteTyper: universe.analyzer.Typer = typer.asInstanceOf[global.analyzer.Typer] val expandee = expandeeTree + val macroRole = APPLY_ROLE } with UnaffiliatedMacroContext { val prefix = Expr[Nothing](prefixTree)(TypeTag.Nothing) override def toString = "MacroContext(%s@%s +%d)".format(expandee.symbol.name, expandee.pos, enclosingMacros.length - 1 /* exclude myself */) @@ -645,6 +646,12 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { private def popMacroContext() = _openMacros = _openMacros.tail def enclosingMacroPosition = openMacros map (_.macroApplication.pos) find (_ ne NoPosition) getOrElse NoPosition + /** Describes the role that the macro expandee is performing. + */ + type MacroRole = String + final def APPLY_ROLE: MacroRole = "APPLY_ROLE" + private val roleNames = Map(APPLY_ROLE -> "apply") + private sealed abstract class MacroExpansionResult private case class Success(expanded: Tree) extends MacroExpansionResult private case class Fallback(fallback: Tree) extends MacroExpansionResult { currentRun.seenMacroExpansionsFallingBack = true } diff --git a/src/reflect/scala/reflect/macros/Enclosures.scala b/src/reflect/scala/reflect/macros/Enclosures.scala index 1e366ccbc3..723b94016d 100644 --- a/src/reflect/scala/reflect/macros/Enclosures.scala +++ b/src/reflect/scala/reflect/macros/Enclosures.scala @@ -17,6 +17,19 @@ trait Enclosures { */ def macroApplication: Tree + /** The semantic role that `macroApplication` plays in the code. + */ + type MacroRole + + /** The role that represents an application of a term macro, + * e.g. `M(2)(3)` in `val x = M(2)(3)` or `M(a, b)` in `x match { case x @ M(a, b) => }`. + */ + def APPLY_ROLE: MacroRole + + /** The semantic role that `macroApplication` plays in the code. + */ + def macroRole: MacroRole + /** Contexts that represent macros in-flight, including the current one. Very much like a stack trace, but for macros only. * Can be useful for interoperating with other macros and for imposing compiler-friendly limits on macro expansion. * -- cgit v1.2.3 From 66acf364ae3003dd1abb4eeb8759afc6e12aa1a1 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Mon, 7 Jan 2013 07:42:38 +0300 Subject: SI-5903 extractor macros do work Apparently it is already possible to use macros to customize pattern matching as described in the comments to the aforementioned JIRA issue. What's even better - with the incoming addition of c.introduceTopLevel it becomes possible to generate arbitrarily complex unappliers, even with heterogeneous types of arguments varying from expansion to expansion --- test/files/run/macro-expand-unapply-a.check | 2 ++ test/files/run/macro-expand-unapply-a.flags | 1 + .../macro-expand-unapply-a/Impls_Macros_1.scala | 15 +++++++++ test/files/run/macro-expand-unapply-a/Test_2.scala | 6 ++++ test/files/run/macro-expand-unapply-b.check | 2 ++ test/files/run/macro-expand-unapply-b.flags | 1 + .../macro-expand-unapply-b/Impls_Macros_1.scala | 37 ++++++++++++++++++++++ test/files/run/macro-expand-unapply-b/Test_2.scala | 8 +++++ 8 files changed, 72 insertions(+) create mode 100644 test/files/run/macro-expand-unapply-a.check create mode 100644 test/files/run/macro-expand-unapply-a.flags create mode 100644 test/files/run/macro-expand-unapply-a/Impls_Macros_1.scala create mode 100644 test/files/run/macro-expand-unapply-a/Test_2.scala create mode 100644 test/files/run/macro-expand-unapply-b.check create mode 100644 test/files/run/macro-expand-unapply-b.flags create mode 100644 test/files/run/macro-expand-unapply-b/Impls_Macros_1.scala create mode 100644 test/files/run/macro-expand-unapply-b/Test_2.scala diff --git a/test/files/run/macro-expand-unapply-a.check b/test/files/run/macro-expand-unapply-a.check new file mode 100644 index 0000000000..7c2976e51e --- /dev/null +++ b/test/files/run/macro-expand-unapply-a.check @@ -0,0 +1,2 @@ +(1,2) +(1,2,3) diff --git a/test/files/run/macro-expand-unapply-a.flags b/test/files/run/macro-expand-unapply-a.flags new file mode 100644 index 0000000000..cd66464f2f --- /dev/null +++ b/test/files/run/macro-expand-unapply-a.flags @@ -0,0 +1 @@ +-language:experimental.macros \ No newline at end of file diff --git a/test/files/run/macro-expand-unapply-a/Impls_Macros_1.scala b/test/files/run/macro-expand-unapply-a/Impls_Macros_1.scala new file mode 100644 index 0000000000..61d6345f16 --- /dev/null +++ b/test/files/run/macro-expand-unapply-a/Impls_Macros_1.scala @@ -0,0 +1,15 @@ +import scala.reflect.macros.Context + +object Helper { + def unapplySeq[T](x: List[T]): Option[Seq[T]] = List.unapplySeq[T](x) +} + +object Macros { + def impl[T: c.WeakTypeTag](c: Context)(x: c.Expr[List[T]]) = { + c.universe.reify(Helper.unapplySeq(x.splice)) + } + + object UnapplyMacro { + def unapplySeq[T](x: List[T]): Option[Seq[T]] = macro impl[T] + } +} \ No newline at end of file diff --git a/test/files/run/macro-expand-unapply-a/Test_2.scala b/test/files/run/macro-expand-unapply-a/Test_2.scala new file mode 100644 index 0000000000..6169d86b19 --- /dev/null +++ b/test/files/run/macro-expand-unapply-a/Test_2.scala @@ -0,0 +1,6 @@ +import Macros._ + +object Test extends App { + List(1, 2) match { case UnapplyMacro(x, y) => println((x, y)) } + List(1, 2, 3) match { case UnapplyMacro(x, y, z) => println((x, y, z)) } +} \ No newline at end of file diff --git a/test/files/run/macro-expand-unapply-b.check b/test/files/run/macro-expand-unapply-b.check new file mode 100644 index 0000000000..5272f0d00a --- /dev/null +++ b/test/files/run/macro-expand-unapply-b.check @@ -0,0 +1,2 @@ +(1,List(2)) +List(1) diff --git a/test/files/run/macro-expand-unapply-b.flags b/test/files/run/macro-expand-unapply-b.flags new file mode 100644 index 0000000000..cd66464f2f --- /dev/null +++ b/test/files/run/macro-expand-unapply-b.flags @@ -0,0 +1 @@ +-language:experimental.macros \ No newline at end of file diff --git a/test/files/run/macro-expand-unapply-b/Impls_Macros_1.scala b/test/files/run/macro-expand-unapply-b/Impls_Macros_1.scala new file mode 100644 index 0000000000..d0300bdf7e --- /dev/null +++ b/test/files/run/macro-expand-unapply-b/Impls_Macros_1.scala @@ -0,0 +1,37 @@ +import language.experimental.macros +import scala.reflect.macros.Context + +object Macros { + implicit class ContextExtensions(c: StringContext) { + object q { + def unapply(x: Any): Option[Any] = macro impl + } + } + + def impl(c: Context)(x: c.Expr[Any]): c.Expr[Option[Any]] = { + import c.universe._ + import Flag._ + + // parts here will be string literals - static parts of the string interpolation + // e.g. for q"$x, $y" parts will be Literal(Constant("")), Literal(Constant(", ")) and Literal(Constant("")) + val Apply(Select(Select(Apply(_, List(Apply(_, parts))), _), _), _) = c.macroApplication + val nresults = parts.length - 1 + + def results() = + ((1 to (nresults - 1)).toList map (i => Literal(Constant(i)))) :+ // (n - 1) results of type Int + Apply(Ident(TermName("List")), List(Literal(Constant(nresults)))) // and also one result of a different type + def extractorBody() = + if (nresults == 0) Literal(Constant(true)) + else if (nresults == 1) Apply(Ident(TermName("Some")), results()) + else Apply(Ident(TermName("Some")), List(Apply(Ident(TermName("Tuple" + nresults)), results()))) + + val name = TermName(java.util.UUID.randomUUID().toString.replace("-", "")) + val mdef = ModuleDef(NoMods, name, Template(List(Select(Ident(TermName("scala")), TypeName("AnyRef"))), emptyValDef, List( + DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), + Block(List(pendingSuperCall), Literal(Constant(())))), + DefDef(Modifiers(), TermName("unapply"), List(), List(List(ValDef(Modifiers(PARAM), TermName("x"), Ident(TypeName("Any")), EmptyTree))), TypeTree(), + extractorBody())))) + c.introduceTopLevel(nme.EMPTY_PACKAGE_NAME.toString, mdef) + c.Expr[Option[Any]](Apply(Select(Ident(name), TermName("unapply")), List(x.tree))) + } +} \ No newline at end of file diff --git a/test/files/run/macro-expand-unapply-b/Test_2.scala b/test/files/run/macro-expand-unapply-b/Test_2.scala new file mode 100644 index 0000000000..5352160dfe --- /dev/null +++ b/test/files/run/macro-expand-unapply-b/Test_2.scala @@ -0,0 +1,8 @@ +object Test extends App { + import Macros._ + def whatever() = null + val q"$x1, $y1" = whatever() + println(x1, y1) + val q"$x2" = whatever() + println(x2) +} -- cgit v1.2.3 From fa4531e9f28a0e49fdb63406307826c57b19f4bb Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sat, 5 Jan 2013 00:31:58 +0300 Subject: refactors handling of macros in repl Macros now have a dedicated member handler, so that the logic of their processing doesn't get mixed up with vanilla DefHandler. I've also factored out an abstract MacroHandler to provides a basis to build the upcoming type macro handler upon. --- .../tools/nsc/interpreter/MemberHandlers.scala | 38 ++++++++++++++-------- test/files/run/macro-repl-dontexpand.check | 2 +- test/files/run/t6381.check | 2 +- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala b/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala index 5b0705a39e..84a47311e2 100644 --- a/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala +++ b/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala @@ -48,16 +48,19 @@ trait MemberHandlers { } } + private def isTermMacro(ddef: DefDef): Boolean = ddef.mods.isMacro + def chooseHandler(member: Tree): MemberHandler = member match { - case member: DefDef => new DefHandler(member) - case member: ValDef => new ValHandler(member) - case member: ModuleDef => new ModuleHandler(member) - case member: ClassDef => new ClassHandler(member) - case member: TypeDef => new TypeAliasHandler(member) - case member: Assign => new AssignHandler(member) - case member: Import => new ImportHandler(member) - case DocDef(_, documented) => chooseHandler(documented) - case member => new GenericHandler(member) + case member: DefDef if isTermMacro(member) => new TermMacroHandler(member) + case member: DefDef => new DefHandler(member) + case member: ValDef => new ValHandler(member) + case member: ModuleDef => new ModuleHandler(member) + case member: ClassDef => new ClassHandler(member) + case member: TypeDef => new TypeAliasHandler(member) + case member: Assign => new AssignHandler(member) + case member: Import => new ImportHandler(member) + case DocDef(_, documented) => chooseHandler(documented) + case member => new GenericHandler(member) } sealed abstract class MemberDefHandler(override val member: MemberDef) extends MemberHandler(member) { @@ -122,14 +125,23 @@ trait MemberHandlers { } class DefHandler(member: DefDef) extends MemberDefHandler(member) { - private def vparamss = member.vparamss - private def isMacro = member.symbol hasFlag MACRO - // true if not a macro and 0-arity - override def definesValue = !isMacro && flattensToEmpty(vparamss) + override def definesValue = flattensToEmpty(member.vparamss) // true if 0-arity override def resultExtractionCode(req: Request) = if (mods.isPublic) codegenln(name, ": ", req.typeOf(name)) else "" } + abstract class MacroHandler(member: DefDef) extends MemberDefHandler(member) { + override def definesValue = false + override def definesTerm: Option[TermName] = Some(name.toTermName) + override def definesType: Option[TypeName] = None + override def resultExtractionCode(req: Request) = if (mods.isPublic) codegenln(notification(req)) else "" + def notification(req: Request): String + } + + class TermMacroHandler(member: DefDef) extends MacroHandler(member) { + def notification(req: Request) = s"defined term macro $name: ${req.typeOf(name)}" + } + class AssignHandler(member: Assign) extends MemberHandler(member) { val Assign(lhs, rhs) = member override lazy val name = newTermName(freshInternalVarName()) diff --git a/test/files/run/macro-repl-dontexpand.check b/test/files/run/macro-repl-dontexpand.check index 628a9146c4..99b70ea3dc 100644 --- a/test/files/run/macro-repl-dontexpand.check +++ b/test/files/run/macro-repl-dontexpand.check @@ -7,6 +7,6 @@ scala> def bar(c: scala.reflect.macros.Context) = ??? bar: (c: scala.reflect.macros.Context)Nothing scala> def foo = macro bar -foo: Any +defined term macro foo: Any scala> diff --git a/test/files/run/t6381.check b/test/files/run/t6381.check index b51cfd0398..5070b67e46 100644 --- a/test/files/run/t6381.check +++ b/test/files/run/t6381.check @@ -9,7 +9,7 @@ scala> def pos_impl(c: reflect.macros.Context): c.Expr[String] = pos_impl: (c: scala.reflect.macros.Context)c.Expr[String] scala> def pos = macro pos_impl -pos: String +defined term macro pos: String scala> pos res0: String = class scala.reflect.internal.util.RangePosition -- cgit v1.2.3 From 75507997c79e46018e3e77a8b89d7bd72a5fac15 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sat, 5 Jan 2013 00:32:47 +0300 Subject: makes macro override error more consistent --- src/compiler/scala/tools/nsc/typechecker/RefChecks.scala | 4 ++-- test/files/neg/macro-override-macro-overrides-abstract-method-a.check | 2 +- test/files/neg/macro-override-macro-overrides-abstract-method-b.check | 2 +- test/files/neg/macro-override-method-overrides-macro.check | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 12562fecf8..a31df6579b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -424,9 +424,9 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans member.isValue && !member.isLazy) { overrideError("must be declared lazy to override a concrete lazy value") } else if (other.isDeferred && member.isTermMacro) { // (1.9) - overrideError("cannot override an abstract method") + overrideError("cannot be used here - term macros cannot override abstract methods") } else if (other.isTermMacro && !member.isTermMacro) { // (1.10) - overrideError("cannot override a macro") + overrideError("cannot be used here - only term macros can override term macros") } else { checkOverrideTypes() checkOverrideDeprecated() diff --git a/test/files/neg/macro-override-macro-overrides-abstract-method-a.check b/test/files/neg/macro-override-macro-overrides-abstract-method-a.check index 895e0dca50..8c8f039225 100644 --- a/test/files/neg/macro-override-macro-overrides-abstract-method-a.check +++ b/test/files/neg/macro-override-macro-overrides-abstract-method-a.check @@ -1,5 +1,5 @@ Impls_Macros_1.scala:12: error: overriding method foo in trait Foo of type (x: Int)Int; - macro method foo cannot override an abstract method + macro method foo cannot be used here - term macros cannot override abstract methods def foo(x: Int) = macro Impls.impl ^ one error found diff --git a/test/files/neg/macro-override-macro-overrides-abstract-method-b.check b/test/files/neg/macro-override-macro-overrides-abstract-method-b.check index 895e0dca50..8c8f039225 100644 --- a/test/files/neg/macro-override-macro-overrides-abstract-method-b.check +++ b/test/files/neg/macro-override-macro-overrides-abstract-method-b.check @@ -1,5 +1,5 @@ Impls_Macros_1.scala:12: error: overriding method foo in trait Foo of type (x: Int)Int; - macro method foo cannot override an abstract method + macro method foo cannot be used here - term macros cannot override abstract methods def foo(x: Int) = macro Impls.impl ^ one error found diff --git a/test/files/neg/macro-override-method-overrides-macro.check b/test/files/neg/macro-override-method-overrides-macro.check index 66dc11be96..e8cba5d029 100644 --- a/test/files/neg/macro-override-method-overrides-macro.check +++ b/test/files/neg/macro-override-method-overrides-macro.check @@ -1,5 +1,5 @@ Macros_Test_2.scala:8: error: overriding macro method foo in class B of type (x: String)Unit; - method foo cannot override a macro + method foo cannot be used here - only term macros can override term macros override def foo(x: String) = println("fooDString") ^ one error found -- cgit v1.2.3 From 5660b7af199ae02a014fb200f199b58d42addde9 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sat, 5 Jan 2013 00:34:39 +0300 Subject: adds Trees.replace(Tree, Tree) Currently dead code. Has proven to be useful to implement type macros, therefore I'm moving it to scala.reflect.internal.Trees. --- src/compiler/scala/tools/nsc/interactive/Global.scala | 9 --------- src/reflect/scala/reflect/internal/Trees.scala | 13 +++++++++++++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala index 07ffe9e437..e3d59d83ea 100644 --- a/src/compiler/scala/tools/nsc/interactive/Global.scala +++ b/src/compiler/scala/tools/nsc/interactive/Global.scala @@ -1031,15 +1031,6 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") // ---------------- Helper classes --------------------------- - /** A transformer that replaces tree `from` with tree `to` in a given tree */ - class TreeReplacer(from: Tree, to: Tree) extends Transformer { - override def transform(t: Tree): Tree = { - if (t == from) to - else if ((t.pos includes from.pos) || t.pos.isTransparent) super.transform(t) - else t - } - } - /** The typer run */ class TyperRun extends Run { // units is always empty diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index 308ab93c07..3c2249bfb5 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -164,6 +164,9 @@ trait Trees extends api.Trees { self: SymbolTable => override def substituteThis(clazz: Symbol, to: Tree): Tree = new ThisSubstituter(clazz, to) transform this + def replace(from: Tree, to: Tree): Tree = + new TreeReplacer(from, to, positionAware = false) transform this + def hasSymbolWhich(f: Symbol => Boolean) = (symbol ne null) && (symbol ne NoSymbol) && f(symbol) @@ -1381,6 +1384,16 @@ trait Trees extends api.Trees { self: SymbolTable => if (tree eq orig) super.transform(tree) else tree } + + /** A transformer that replaces tree `from` with tree `to` in a given tree */ + class TreeReplacer(from: Tree, to: Tree, positionAware: Boolean) extends Transformer { + override def transform(t: Tree): Tree = { + if (t == from) to + else if (!positionAware || (t.pos includes from.pos) || t.pos.isTransparent) super.transform(t) + else t + } + } + // Create a readable string describing a substitution. private def substituterString(fromStr: String, toStr: String, from: List[Any], to: List[Any]): String = { "subst[%s, %s](%s)".format(fromStr, toStr, (from, to).zipped map (_ + " -> " + _) mkString ", ") -- cgit v1.2.3 From 1077c928f9961783a9efc16dde201efe8504c092 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sat, 5 Jan 2013 00:35:50 +0300 Subject: fixes printing of AppliedTypeTree --- src/compiler/scala/tools/nsc/ast/NodePrinters.scala | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/NodePrinters.scala b/src/compiler/scala/tools/nsc/ast/NodePrinters.scala index deea4de707..602366a201 100644 --- a/src/compiler/scala/tools/nsc/ast/NodePrinters.scala +++ b/src/compiler/scala/tools/nsc/ast/NodePrinters.scala @@ -168,6 +168,13 @@ abstract class NodePrinters { } } + def typeApplyCommon(tree: Tree, fun: Tree, args: List[Tree]) { + printMultiline(tree) { + traverse(fun) + traverseList("[]", "type argument")(args) + } + } + def treePrefix(tree: Tree) = showPosition(tree) + tree.productPrefix def printMultiline(tree: Tree)(body: => Unit) { printMultiline(treePrefix(tree), showAttributes(tree))(body) @@ -203,9 +210,11 @@ abstract class NodePrinters { showPosition(tree) tree match { - case AppliedTypeTree(tpt, args) => applyCommon(tree, tpt, args) - case ApplyDynamic(fun, args) => applyCommon(tree, fun, args) - case Apply(fun, args) => applyCommon(tree, fun, args) + case ApplyDynamic(fun, args) => applyCommon(tree, fun, args) + case Apply(fun, args) => applyCommon(tree, fun, args) + + case TypeApply(fun, args) => typeApplyCommon(tree, fun, args) + case AppliedTypeTree(tpt, args) => typeApplyCommon(tree, tpt, args) case Throw(Ident(name)) => printSingle(tree, name) @@ -312,11 +321,6 @@ abstract class NodePrinters { } case This(qual) => printSingle(tree, qual) - case TypeApply(fun, args) => - printMultiline(tree) { - traverse(fun) - traverseList("[]", "type argument")(args) - } case tt @ TypeTree() => println(showTypeTree(tt)) -- cgit v1.2.3 From baef45632a2b48c0f881f11c339fb14d1c33f6c3 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sat, 5 Jan 2013 00:43:20 +0300 Subject: changes isTermMacro checks to something more universal Previously I wanted to be as precise as possible, but as the experience with type macros shows, a lot of isTermMacro checks should also extend to include type macros. Therefore I'm changing the checks to isMacro. This doesn't make any difference for existing code, but will reduce the amount of changes in the upcoming type macro pull request. --- src/compiler/scala/tools/nsc/transform/Mixin.scala | 2 +- src/compiler/scala/tools/nsc/typechecker/Macros.scala | 4 ++-- src/compiler/scala/tools/nsc/typechecker/Namers.scala | 2 +- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 16 ++++++++-------- src/reflect/scala/reflect/internal/Symbols.scala | 2 +- src/reflect/scala/reflect/internal/TreeInfo.scala | 11 +++++++++++ src/reflect/scala/reflect/runtime/JavaMirrors.scala | 4 ++-- 7 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 0769b67282..6a253a98b1 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -1089,7 +1089,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // add forwarders assert(sym.alias != NoSymbol, sym) // debuglog("New forwarder: " + sym.defString + " => " + sym.alias.defString) - if (!sym.isTermMacro) addDefDef(sym, Apply(staticRef(sym.alias), gen.mkAttributedThis(clazz) :: sym.paramss.head.map(Ident))) + if (!sym.isMacro) addDefDef(sym, Apply(staticRef(sym.alias), gen.mkAttributedThis(clazz) :: sym.paramss.head.map(Ident))) } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 23e00960d4..f7d51de10d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -357,7 +357,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { // Phase I: sanity checks val macroDef = macroDdef.symbol macroLogVerbose("typechecking macro def %s at %s".format(macroDef, macroDdef.pos)) - assert(macroDef.isTermMacro, macroDdef) + assert(macroDef.isMacro, macroDdef) if (fastTrack contains macroDef) MacroDefIsFastTrack() if (!typer.checkFeature(macroDdef.pos, MacrosFeature, immediate = true)) MacroFeatureNotEnabled() @@ -372,7 +372,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { // doesn't manifest itself as an error in the resulting tree val prevNumErrors = reporter.ERROR.count var rhs1 = typer.typed1(rhs, EXPRmode, WildcardType) - def rhsNeedsMacroExpansion = rhs1.symbol != null && rhs1.symbol.isTermMacro && !rhs1.symbol.isErroneous + def rhsNeedsMacroExpansion = rhs1.symbol != null && rhs1.symbol.isMacro && !rhs1.symbol.isErroneous while (rhsNeedsMacroExpansion) { rhs1 = macroExpand1(typer, rhs1) match { case Success(expanded) => diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index b5160c0519..af8858bd60 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -1054,7 +1054,7 @@ trait Namers extends MethodSynthesis { // because @macroImpl annotation only gets assigned during typechecking // otherwise macro defs wouldn't be able to robustly coexist with their clients // because a client could be typechecked before a macro def that it uses - if (ddef.symbol.isTermMacro) { + if (ddef.symbol.isMacro) { val pt = resultPt.substSym(tparamSyms, tparams map (_.symbol)) typer.computeMacroDefType(ddef, pt) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index b853f687a7..7851261e9c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -829,7 +829,7 @@ trait Typers extends Modes with Adaptations with Tags { case Block(_, tree1) => tree1.symbol case _ => tree.symbol } - if (!meth.isConstructor && !meth.isTermMacro && isFunctionType(pt)) { // (4.2) + if (!meth.isConstructor && isFunctionType(pt)) { // (4.2) debuglog("eta-expanding " + tree + ":" + tree.tpe + " to " + pt) checkParamsConvertible(tree, tree.tpe) val tree0 = etaExpand(context.unit, tree, this) @@ -1057,7 +1057,7 @@ trait Typers extends Modes with Adaptations with Tags { adaptToImplicitMethod(mt) case mt: MethodType if (((mode & (EXPRmode | FUNmode | LHSmode)) == EXPRmode) && - (context.undetparams.isEmpty || inPolyMode(mode))) && !(tree.symbol != null && tree.symbol.isTermMacro) => + (context.undetparams.isEmpty || inPolyMode(mode))) && !treeInfo.isMacroApplicationOrBlock(tree) => instantiateToMethodType(mt) case _ => @@ -2182,7 +2182,7 @@ trait Typers extends Modes with Adaptations with Tags { meth.owner.isAnonOrRefinementClass)) InvalidConstructorDefError(ddef) typed(ddef.rhs) - } else if (meth.isTermMacro) { + } else if (meth.isMacro) { // typechecking macro bodies is sort of unconventional // that's why we employ our custom typing scheme orchestrated outside of the typer transformedOr(ddef.rhs, typedMacroBody(this, ddef)) @@ -3055,7 +3055,7 @@ trait Typers extends Modes with Adaptations with Tags { val lencmp = compareLengths(args, formals) def checkNotMacro() = { - if (fun.symbol != null && fun.symbol.filter(sym => sym != null && sym.isTermMacro && !sym.isErroneous) != NoSymbol) + if (treeInfo.isMacroApplication(fun)) tryTupleApply getOrElse duplErrorTree(NamedAndDefaultArgumentsNotSupportedForMacros(tree, fun)) } @@ -4951,7 +4951,7 @@ trait Typers extends Modes with Adaptations with Tags { // because `expr` might contain nested macro calls (see SI-6673) val exprTyped = typed1(expr updateAttachment SuppressMacroExpansionAttachment, mode, pt) exprTyped match { - case macroDef if macroDef.symbol != null && macroDef.symbol.isTermMacro && !macroDef.symbol.isErroneous => + case macroDef if treeInfo.isMacroApplication(macroDef) => MacroEtaError(exprTyped) case _ => typedEta(checkDead(exprTyped)) @@ -5337,7 +5337,7 @@ trait Typers extends Modes with Adaptations with Tags { def computeType(tree: Tree, pt: Type): Type = { // macros employ different logic of `computeType` - assert(!context.owner.isTermMacro, context.owner) + assert(!context.owner.isMacro, context.owner) val tree1 = typed(tree, pt) transformed(tree) = tree1 val tpe = packedType(tree1, context.owner) @@ -5346,8 +5346,8 @@ trait Typers extends Modes with Adaptations with Tags { } def computeMacroDefType(tree: Tree, pt: Type): Type = { - assert(context.owner.isTermMacro, context.owner) - assert(tree.symbol.isTermMacro, tree.symbol) + assert(context.owner.isMacro, context.owner) + assert(tree.symbol.isMacro, tree.symbol) assert(tree.isInstanceOf[DefDef], tree.getClass) val ddef = tree.asInstanceOf[DefDef] diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index ae68c1bcfd..7761ace50e 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -645,7 +645,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => info.firstParent.typeSymbol == AnyValClass && !isPrimitiveValueClass final def isMethodWithExtension = - isMethod && owner.isDerivedValueClass && !isParamAccessor && !isConstructor && !hasFlag(SUPERACCESSOR) && !isTermMacro + isMethod && owner.isDerivedValueClass && !isParamAccessor && !isConstructor && !hasFlag(SUPERACCESSOR) && !isMacro final def isAnonymousFunction = isSynthetic && (name containsName tpnme.ANON_FUN_NAME) final def isDefinedInPackage = effectiveOwner.isPackageClass diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index 9614513458..032a4aebef 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -735,4 +735,15 @@ abstract class TreeInfo { case tree: RefTree => true case _ => false }) + + def isMacroApplication(tree: Tree): Boolean = + !tree.isDef && tree.symbol != null && tree.symbol.isMacro && !tree.symbol.isErroneous + + def isMacroApplicationOrBlock(tree: Tree): Boolean = tree match { + case Block(_, expr) => isMacroApplicationOrBlock(expr) + case tree => isMacroApplication(tree) + } + + def isNonTrivialMacroApplication(tree: Tree): Boolean = + isMacroApplication(tree) && dissectApplied(tree).core != tree } diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 67b24cbdea..cdb9e7159c 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -319,7 +319,7 @@ private[reflect] trait JavaMirrors extends internal.SymbolTable with api.JavaUni lazy val bytecodefulObjectMethods = Set[Symbol](Object_clone, Object_equals, Object_finalize, Object_hashCode, Object_toString, Object_notify, Object_notifyAll) ++ ObjectClass.info.member(nme.wait_).asTerm.alternatives.map(_.asMethod) private def isBytecodelessMethod(meth: MethodSymbol): Boolean = { - if (isGetClass(meth) || isStringConcat(meth) || meth.owner.isPrimitiveValueClass || meth == Predef_classOf || meth.isTermMacro) return true + if (isGetClass(meth) || isStringConcat(meth) || meth.owner.isPrimitiveValueClass || meth == Predef_classOf || meth.isMacro) return true bytecodelessMethodOwners(meth.owner) && !bytecodefulObjectMethods(meth) } @@ -457,7 +457,7 @@ private[reflect] trait JavaMirrors extends internal.SymbolTable with api.JavaUni case sym if isStringConcat(sym) => receiver.toString + objArg0 case sym if sym.owner.isPrimitiveValueClass => invokePrimitiveMethod case sym if sym == Predef_classOf => fail("Predef.classOf is a compile-time function") - case sym if sym.isTermMacro => fail(s"${symbol.fullName} is a macro, i.e. a compile-time function") + case sym if sym.isMacro => fail(s"${symbol.fullName} is a macro, i.e. a compile-time function") case _ => abort(s"unsupported symbol $symbol when invoking $this") } } -- cgit v1.2.3 From 055b07e4db30742e8282772d08f1c42811bab4fa Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sat, 5 Jan 2013 00:45:50 +0300 Subject: parentTypes => typedParentTypes I felt really uncomfortable having to stare at `parentTypes`, when everyone else in Typers.scala is named typedSomething. --- src/compiler/scala/tools/nsc/typechecker/Namers.scala | 2 +- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 8 ++++---- src/reflect/scala/reflect/internal/Symbols.scala | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index af8858bd60..352090892e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -878,7 +878,7 @@ trait Namers extends MethodSynthesis { else tpt.tpe } - val parents = typer.parentTypes(templ) map checkParent + val parents = typer.typedParentTypes(templ) map checkParent enterSelf(templ.self) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 7851261e9c..95d7f9475d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1630,7 +1630,7 @@ trait Typers extends Modes with Adaptations with Tags { ) } - def parentTypes(templ: Template): List[Tree] = templ.parents match { + def typedParentTypes(templ: Template): List[Tree] = templ.parents match { case Nil => List(atPos(templ.pos)(TypeTree(AnyRefClass.tpe))) case first :: rest => try { @@ -1652,7 +1652,7 @@ trait Typers extends Modes with Adaptations with Tags { case ex: TypeError => // fallback in case of cyclic errors // @H none of the tests enter here but I couldn't rule it out - // upd. @E when a definitions inherits itself, we end up here + // upd. @E when a definition inherits itself, we end up here // because `typedParentType` triggers `initialize` for parent types symbols log("Type error calculating parents in template " + templ) log("Error: " + ex) @@ -1763,7 +1763,7 @@ trait Typers extends Modes with Adaptations with Tags { reenterTypeParams(cdef.tparams) val tparams1 = cdef.tparams mapConserve (typedTypeDef) val impl1 = typerReportAnyContextErrors(context.make(cdef.impl, clazz, newScope)) { - _.typedTemplate(cdef.impl, parentTypes(cdef.impl)) + _.typedTemplate(cdef.impl, typedParentTypes(cdef.impl)) } val impl2 = finishMethodSynthesis(impl1, clazz, context) if (clazz.isTrait && clazz.info.parents.nonEmpty && clazz.info.firstParent.typeSymbol == AnyClass) @@ -1803,7 +1803,7 @@ trait Typers extends Modes with Adaptations with Tags { ) val impl1 = typerReportAnyContextErrors(context.make(mdef.impl, clazz, newScope)) { _.typedTemplate(mdef.impl, { - parentTypes(mdef.impl) ++ ( + typedParentTypes(mdef.impl) ++ ( if (noSerializable) Nil else { clazz.makeSerializable() diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 7761ace50e..41971338fa 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -2232,7 +2232,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => private case class SymbolKind(accurate: String, sanitized: String, abbreviation: String) private def symbolKind: SymbolKind = { var kind = - if (isTermMacro) ("macro method", "macro method", "MAC") + if (isTermMacro) ("term macro", "macro method", "MACM") else if (isInstanceOf[FreeTermSymbol]) ("free term", "free term", "FTE") else if (isInstanceOf[FreeTypeSymbol]) ("free type", "free type", "FTY") else if (isPackage) ("package", "package", "PK") -- cgit v1.2.3 From 3d397aa5e6381fa7514f3b5e160d170dad9d8f1c Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sat, 5 Jan 2013 00:48:01 +0300 Subject: more precise errors for macros --- .../scala/tools/nsc/typechecker/ContextErrors.scala | 15 +++++++++++---- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 3 ++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index ea03aca8c4..df78f8a05d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -562,11 +562,13 @@ trait ContextErrors { //adapt def MissingArgsForMethodTpeError(tree: Tree, meth: Symbol) = { - issueNormalTypeError(tree, - "missing arguments for " + meth.fullLocationString + ( + val message = + if (meth.isMacro) MacroPartialApplicationErrorMessage + else "missing arguments for " + meth.fullLocationString + ( if (meth.isConstructor) "" else ";\nfollow this method with `_' if you want to treat it as a partially applied function" - )) + ) + issueNormalTypeError(tree, message) setError(tree) } @@ -670,6 +672,10 @@ trait ContextErrors { setError(tree) } + def MacroTooManyArgumentListsError(expandee: Tree, fun: Symbol) = { + NormalTypeError(expandee, "too many argument lists for " + fun) + } + // same reason as for MacroBodyTypecheckException case object MacroExpansionException extends Exception with scala.util.control.ControlThrowable @@ -681,10 +687,11 @@ trait ContextErrors { throw MacroExpansionException } + def MacroPartialApplicationErrorMessage = "macros cannot be partially applied" def MacroPartialApplicationError(expandee: Tree) = { // macroExpansionError won't work => swallows positions, hence needed to do issueTypeError // kinda contradictory to the comment in `macroExpansionError`, but this is how it works - issueNormalTypeError(expandee, "macros cannot be partially applied") + issueNormalTypeError(expandee, MacroPartialApplicationErrorMessage) setError(expandee) throw MacroExpansionException } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 95d7f9475d..f0bb425884 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3226,7 +3226,8 @@ trait Typers extends Modes with Adaptations with Tags { doTypedUnapply(tree, fun0, fun, args, mode, pt) case _ => - duplErrorTree(ApplyWithoutArgsError(tree, fun)) + if (treeInfo.isMacroApplication(tree)) duplErrorTree(MacroTooManyArgumentListsError(tree, fun.symbol)) + else duplErrorTree(ApplyWithoutArgsError(tree, fun)) } } -- cgit v1.2.3 From 94de3c87edf420b4d1d8b495fd7976f4b515483d Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sat, 5 Jan 2013 00:52:48 +0300 Subject: typedPrimaryConstrBody now returns supercall Previously this method returned the entire block with supercall being potentially wrapped in early defs. However when implementing type macros, I found it more convenient to have typedPrimaryConstrBody to only return the supercall itself. This doesn't make any difference for our current codebase, since the only time anyone cares about the return value, it's only to inspect its tpe. Therefore I'm submitting this patch in a preparatory pull request to reduce the surface of changes in the upcoming type macros pull request. --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index f0bb425884..0a653a3e15 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1587,7 +1587,11 @@ trait Typers extends Modes with Adaptations with Tags { else map2(preSuperStats, preSuperVals)((ldef, gdef) => gdef.tpt setType ldef.symbol.tpe) - if (superCall1 == cunit) EmptyTree else cbody2 + if (superCall1 == cunit) EmptyTree + else cbody2 match { + case Block(_, expr) => expr + case tree => tree + } case _ => EmptyTree } -- cgit v1.2.3 From 30e2e3a78f9061ea93352427cb0ca205203041f0 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sat, 5 Jan 2013 00:59:13 +0300 Subject: generalizes macroExpand Changes macroExpand to accommodate expansion schemes different from the currently supported one. As a bonus, which follows clarification of the macro expansion logic, this refactoring fixes suppression of macro expansions, which previously didn't work with delayed expansion. --- .../scala/reflect/reify/phases/Reshape.scala | 4 +- .../tools/nsc/typechecker/ContextErrors.scala | 13 +- .../scala/tools/nsc/typechecker/Macros.scala | 217 +++++++++++++++------ .../tools/nsc/typechecker/StdAttachments.scala | 96 +++++++++ .../scala/tools/nsc/typechecker/Typers.scala | 9 +- .../scala/reflect/internal/StdAttachments.scala | 13 -- 6 files changed, 263 insertions(+), 89 deletions(-) diff --git a/src/compiler/scala/reflect/reify/phases/Reshape.scala b/src/compiler/scala/reflect/reify/phases/Reshape.scala index 75384ddce1..5dd5f08b45 100644 --- a/src/compiler/scala/reflect/reify/phases/Reshape.scala +++ b/src/compiler/scala/reflect/reify/phases/Reshape.scala @@ -89,8 +89,8 @@ trait Reshape { } private def undoMacroExpansion(tree: Tree): Tree = - tree.attachments.get[MacroExpansionAttachment] match { - case Some(MacroExpansionAttachment(original)) => + tree.attachments.get[analyzer.MacroExpansionAttachment] match { + case Some(analyzer.MacroExpansionAttachment(original, _)) => original match { // this hack is necessary until I fix implicit macros // so far tag materialization is implemented by sneaky macros hidden in scala-compiler.jar diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index df78f8a05d..2d4054e93b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -676,6 +676,10 @@ trait ContextErrors { NormalTypeError(expandee, "too many argument lists for " + fun) } + def MacroInvalidExpansionError(expandee: Tree, role: String, allowedExpansions: String) = { + issueNormalTypeError(expandee, s"macro in $role role can only expand into $allowedExpansions") + } + // same reason as for MacroBodyTypecheckException case object MacroExpansionException extends Exception with scala.util.control.ControlThrowable @@ -757,13 +761,16 @@ trait ContextErrors { macroExpansionError(expandee, template(sym.name.nameKind).format(sym.name + " " + sym.origin, forgotten)) } - def MacroExpansionIsNotExprError(expandee: Tree, expanded: Any) = + def MacroExpansionHasInvalidTypeError(expandee: Tree, expanded: Any) = { + val expected = "expr" + val isPathMismatch = expanded != null && expanded.isInstanceOf[scala.reflect.api.Exprs#Expr[_]] macroExpansionError(expandee, - "macro must return a compiler-specific expr; returned value is " + ( + s"macro must return a compiler-specific $expected; returned value is " + ( if (expanded == null) "null" - else if (expanded.isInstanceOf[Expr[_]]) " Expr, but it doesn't belong to this compiler's universe" + else if (isPathMismatch) s" $expected, but it doesn't belong to this compiler" else " of " + expanded.getClass )) + } def MacroImplementationNotFoundError(expandee: Tree) = macroExpansionError(expandee, diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index f7d51de10d..fa90b4963a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -5,6 +5,7 @@ import symtab.Flags._ import scala.tools.nsc.util._ import scala.reflect.runtime.ReflectionUtils import scala.collection.mutable.ListBuffer +import scala.reflect.ClassTag import scala.reflect.internal.util.Statistics import scala.reflect.macros.util._ import scala.util.control.ControlThrowable @@ -385,8 +386,12 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } case Fallback(fallback) => typer.typed1(fallback, EXPRmode, WildcardType) - case Other(result) => - result + case Delayed(delayed) => + delayed + case Skipped(skipped) => + skipped + case Failure(failure) => + failure } } val typecheckedWithErrors = (rhs1 exists (_.isErroneous)) || reporter.ERROR.count != prevNumErrors @@ -533,16 +538,17 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } } - private def macroContext(typer: Typer, prefixTree: Tree, expandeeTree: Tree): MacroContext = + private def macroContext(typer: Typer, prefixTree: Tree, expandeeTree: Tree): MacroContext = { new { val universe: self.global.type = self.global val callsiteTyper: universe.analyzer.Typer = typer.asInstanceOf[global.analyzer.Typer] - val expandee = expandeeTree - val macroRole = APPLY_ROLE + val expandee = universe.analyzer.macroExpanderAttachment(expandeeTree).original orElse expandeeTree + val macroRole = universe.analyzer.macroExpanderAttachment(expandeeTree).role } with UnaffiliatedMacroContext { val prefix = Expr[Nothing](prefixTree)(TypeTag.Nothing) override def toString = "MacroContext(%s@%s +%d)".format(expandee.symbol.name, expandee.pos, enclosingMacros.length - 1 /* exclude myself */) } + } /** Calculate the arguments to pass to a macro implementation when expanding the provided tree. */ @@ -652,21 +658,31 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { final def APPLY_ROLE: MacroRole = "APPLY_ROLE" private val roleNames = Map(APPLY_ROLE -> "apply") - private sealed abstract class MacroExpansionResult - private case class Success(expanded: Tree) extends MacroExpansionResult - private case class Fallback(fallback: Tree) extends MacroExpansionResult { currentRun.seenMacroExpansionsFallingBack = true } - private case class Other(result: Tree) extends MacroExpansionResult - private def Delay(expanded: Tree) = Other(expanded) - private def Skip(expanded: Tree) = Other(expanded) - private def Cancel(expandee: Tree) = Other(expandee) - private def Failure(expandee: Tree) = Other(expandee) - - /** Performs macro expansion: - * 1) Checks whether the expansion needs to be delayed (see `mustDelayMacroExpansion`) - * 2) Loads macro implementation using `macroMirror` - * 3) Synthesizes invocation arguments for the macro implementation - * 4) Checks that the result is a tree bound to this universe - * 5) Typechecks the result against the return type of the macro definition + /** Performs macro expansion. + * + * ========= Expandable trees ========= + * + * A term of one of the following shapes: + * + * Ident() + * Select(, ) + * TypeApply(, ) + * Apply(...Apply(, )...) + * + * ========= Macro expansion ========= + * + * First of all `macroExpandXXX`: + * 1) If necessary desugars the `expandee` to fit into `macroExpand1` + * + * Then `macroExpand1`: + * 2) Checks whether the expansion needs to be delayed (see `mustDelayMacroExpansion`) + * 3) Loads macro implementation using `macroMirror` + * 4) Synthesizes invocation arguments for the macro implementation + * 5) Checks that the result is a tree or an expr bound to this universe + * + * Finally `macroExpandXXX`: + * 6) Validates the expansion against the white list of supported tree shapes + * 7) Typechecks the result as required by the circumstances of the macro application * * If -Ymacro-debug-lite is enabled, you will get basic notifications about macro expansion * along with macro expansions logged in the form that can be copy/pasted verbatim into REPL. @@ -677,52 +693,118 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { * * @return * the expansion result if the expansion has been successful, - * the fallback method invocation if the expansion has been unsuccessful, but there is a fallback, + * the fallback tree if the expansion has been unsuccessful, but there is a fallback, * the expandee unchanged if the expansion has been delayed, * the expandee fully expanded if the expansion has been delayed before and has been expanded now, * the expandee with an error marker set if the expansion has been cancelled due malformed arguments or implementation * the expandee with an error marker set if there has been an error */ - def macroExpand(typer: Typer, expandee: Tree, mode: Int = EXPRmode, pt: Type = WildcardType): Tree = { - val start = if (Statistics.canEnable) Statistics.startTimer(macroExpandNanos) else null - if (Statistics.canEnable) Statistics.incCounter(macroExpandCount) - try { - macroExpand1(typer, expandee) match { - case Success(expanded) => - try { - def typecheck(phase: String, tree: Tree, pt: Type): Tree = { - if (tree.isErroneous) return tree - macroLogVerbose(s"typechecking against $phase $pt: $expanded") - val numErrors = reporter.ERROR.count - def hasNewErrors = reporter.ERROR.count > numErrors - val result = typer.context.withImplicitsEnabled(typer.typed(tree, EXPRmode, pt)) - macroTraceVerbose(s"""${if (hasNewErrors) "failed to typecheck" else "successfully typechecked"} against $phase $pt:\n$result\n""")(result) + private abstract class MacroExpander[Result: ClassTag](val role: MacroRole, val typer: Typer, val expandee: Tree) { + def allowExpandee(expandee: Tree): Boolean = true + def allowExpanded(expanded: Tree): Boolean = true + def allowedExpansions: String = "anything" + def allowResult(result: Result): Boolean = true + + def onSuccess(expanded: Tree): Result + def onFallback(expanded: Tree): Result + def onSuppressed(expandee: Tree): Result = expandee match { case expandee: Result => expandee } + def onDelayed(expanded: Tree): Result = expanded match { case expanded: Result => expanded } + def onSkipped(expanded: Tree): Result = expanded match { case expanded: Result => expanded } + def onFailure(expanded: Tree): Result = { typer.infer.setError(expandee); expandee match { case expandee: Result => expandee } } + + def apply(desugared: Tree): Result = { + if (isMacroExpansionSuppressed(desugared)) onSuppressed(expandee) + else expand(desugared) + } + + protected def expand(desugared: Tree): Result = { + def showDetailed(tree: Tree) = showRaw(tree, printIds = true, printTypes = true) + def summary() = s"expander = $this, expandee = ${showDetailed(expandee)}, desugared = ${if (expandee == desugared) () else showDetailed(desugared)}" + if (macroDebugVerbose) println(s"macroExpand: ${summary()}") + assert(allowExpandee(expandee), summary()) + + val start = if (Statistics.canEnable) Statistics.startTimer(macroExpandNanos) else null + if (Statistics.canEnable) Statistics.incCounter(macroExpandCount) + try { + linkExpandeeAndDesugared(expandee, desugared, role) + macroExpand1(typer, desugared) match { + case Success(expanded) => + if (allowExpanded(expanded)) { + // also see http://groups.google.com/group/scala-internals/browse_thread/thread/492560d941b315cc + val expanded1 = try onSuccess(duplicateAndKeepPositions(expanded)) finally popMacroContext() + if (!hasMacroExpansionAttachment(expanded1)) linkExpandeeAndExpanded(expandee, expanded1) + if (allowResult(expanded1)) expanded1 else onFailure(expanded) + } else { + typer.TyperErrorGen.MacroInvalidExpansionError(expandee, roleNames(role), allowedExpansions) + onFailure(expanded) } + case Fallback(fallback) => onFallback(fallback) + case Delayed(delayed) => onDelayed(delayed) + case Skipped(skipped) => onSkipped(skipped) + case Failure(failure) => onFailure(failure) + } + } finally { + if (Statistics.canEnable) Statistics.stopTimer(macroExpandNanos, start) + } + } + } - var expectedTpe = expandee.tpe - if (isNullaryInvocation(expandee)) expectedTpe = expectedTpe.finalResultType - // also see http://groups.google.com/group/scala-internals/browse_thread/thread/492560d941b315cc - val expanded0 = duplicateAndKeepPositions(expanded) - val expanded1 = typecheck("macro def return type", expanded0, expectedTpe) - val expanded2 = typecheck("expected type", expanded1, pt) - expanded2 - } finally { - popMacroContext() - } - case Fallback(fallback) => - typer.context.withImplicitsEnabled(typer.typed(fallback, EXPRmode, pt)) - case Other(result) => - result + /** Expands a tree that carries a term, which happens to be a term macro. + * @see MacroExpander + */ + private abstract class TermMacroExpander(role: MacroRole, typer: Typer, expandee: Tree, mode: Int, pt: Type) + extends MacroExpander[Tree](role, typer, expandee) { + override def allowedExpansions: String = "term trees" + override def allowExpandee(expandee: Tree) = expandee.isTerm + override def onSuccess(expanded: Tree) = typer.typed(expanded, mode, pt) + override def onFallback(fallback: Tree) = typer.typed(fallback, mode, pt) + } + + /** Expands a term macro used in apply role as `M(2)(3)` in `val x = M(2)(3)`. + * @see MacroExpander + */ + def macroExpandApply(typer: Typer, expandee: Tree, mode: Int, pt: Type) = { + object expander extends TermMacroExpander(APPLY_ROLE, typer, expandee, mode, pt) { + override def onSuccess(expanded: Tree) = { + // prematurely annotate the tree with a macro expansion attachment + // so that adapt called indirectly by typer.typed knows that it needs to apply the existential fixup + linkExpandeeAndExpanded(expandee, expanded) + var expectedTpe = expandee.tpe + if (isNullaryInvocation(expandee)) expectedTpe = expectedTpe.finalResultType + // `macroExpandApply` is called from `adapt`, where implicit conversions are disabled + // therefore we need to re-enable the conversions back temporarily + if (macroDebugVerbose) println(s"typecheck #1 (against expectedTpe = $expectedTpe): $expanded") + val expanded1 = typer.context.withImplicitsEnabled(typer.typed(expanded, mode, expectedTpe)) + if (expanded1.isErrorTyped) { + if (macroDebugVerbose) println(s"typecheck #1 has failed: ${typer.context.errBuffer}") + expanded1 + } else { + if (macroDebugVerbose) println(s"typecheck #2 (against pt = $pt): $expanded1") + val expanded2 = typer.context.withImplicitsEnabled(super.onSuccess(expanded1)) + if (macroDebugVerbose && expanded2.isErrorTyped) println(s"typecheck #2 has failed: ${typer.context.errBuffer}") + expanded2 + } } - } finally { - if (Statistics.canEnable) Statistics.stopTimer(macroExpandNanos, start) } + expander(expandee) } + /** Captures statuses of macro expansions performed by `macroExpand1'. + */ + private sealed abstract class MacroStatus(val result: Tree) + private case class Success(expanded: Tree) extends MacroStatus(expanded) + private case class Fallback(fallback: Tree) extends MacroStatus(fallback) { currentRun.seenMacroExpansionsFallingBack = true } + private case class Delayed(delayed: Tree) extends MacroStatus(delayed) + private case class Skipped(skipped: Tree) extends MacroStatus(skipped) + private case class Failure(failure: Tree) extends MacroStatus(failure) + private def Delay(expanded: Tree) = Delayed(expanded) + private def Skip(expanded: Tree) = Skipped(expanded) + private def Cancel(expandee: Tree) = Failure(expandee) + /** Does the same as `macroExpand`, but without typechecking the expansion * Meant for internal use within the macro infrastructure, don't use it elsewhere. */ - private def macroExpand1(typer: Typer, expandee: Tree): MacroExpansionResult = { + private def macroExpand1(typer: Typer, expandee: Tree): MacroStatus = { // verbose printing might cause recursive macro expansions, so I'm shutting it down here withInfoLevel(nodePrinters.InfoLevel.Quiet) { if (expandee.symbol.isErroneous || (expandee exists (_.isErroneous))) { @@ -743,14 +825,18 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { /** Expands a macro when a runtime (i.e. the macro implementation) can be successfully loaded * Meant for internal use within the macro infrastructure, don't use it elsewhere. */ - private def macroExpandWithRuntime(typer: Typer, expandee: Tree, runtime: MacroRuntime): MacroExpansionResult = { + private def macroExpandWithRuntime(typer: Typer, expandee: Tree, runtime: MacroRuntime): MacroStatus = { val wasDelayed = isDelayed(expandee) val undetparams = calculateUndetparams(expandee) val nowDelayed = !typer.context.macrosEnabled || undetparams.nonEmpty (wasDelayed, nowDelayed) match { - case (true, true) => Delay(expandee) - case (true, false) => Skip(macroExpandAll(typer, expandee)) + case (true, true) => + Delay(expandee) + case (true, false) => + val expanded = macroExpandAll(typer, expandee) + if (expanded exists (_.isErroneous)) Failure(expandee) + else Skip(expanded) case (false, true) => macroLogLite("macro expansion is delayed: %s".format(expandee)) delayed += expandee -> undetparams @@ -765,15 +851,16 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { def hasNewErrors = reporter.ERROR.count > numErrors val expanded = { pushMacroContext(args.c); runtime(args) } if (hasNewErrors) MacroGeneratedTypeError(expandee) + def validateResultingTree(expanded: Tree) = { + macroLogVerbose("original:") + macroLogLite("" + expanded + "\n" + showRaw(expanded)) + val freeSyms = expanded.freeTerms ++ expanded.freeTypes + freeSyms foreach (sym => MacroFreeSymbolError(expandee, sym)) + Success(atPos(enclosingMacroPosition.focus)(expanded)) + } expanded match { - case expanded: Expr[_] => - macroLogVerbose("original:") - macroLogLite("" + expanded.tree + "\n" + showRaw(expanded.tree)) - val freeSyms = expanded.tree.freeTerms ++ expanded.tree.freeTypes - freeSyms foreach (sym => MacroFreeSymbolError(expandee, sym)) - Success(atPos(enclosingMacroPosition.focus)(expanded.tree updateAttachment MacroExpansionAttachment(expandee))) - case _ => - MacroExpansionIsNotExprError(expandee, expanded) + case expanded: Expr[_] if expandee.symbol.isTermMacro => validateResultingTree(expanded.tree) + case _ => MacroExpansionHasInvalidTypeError(expandee, expanded) } } catch { case ex: Throwable => @@ -794,7 +881,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { /** Expands a macro when a runtime (i.e. the macro implementation) cannot be loaded * Meant for internal use within the macro infrastructure, don't use it elsewhere. */ - private def macroExpandWithoutRuntime(typer: Typer, expandee: Tree): MacroExpansionResult = { + private def macroExpandWithoutRuntime(typer: Typer, expandee: Tree): MacroStatus = { import typer.TyperErrorGen._ val fallbackSym = expandee.symbol.nextOverriddenSymbol orElse MacroImplementationNotFoundError(expandee) macroTraceLite("falling back to: ")(fallbackSym) @@ -870,7 +957,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { context.implicitsEnabled = typer.context.implicitsEnabled context.enrichmentEnabled = typer.context.enrichmentEnabled context.macrosEnabled = typer.context.macrosEnabled - macroExpand(newTyper(context), tree, EXPRmode, WildcardType) + macroExpandApply(newTyper(context), tree, EXPRmode, WildcardType) case _ => tree }) diff --git a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala index 20db479463..64fcda3b80 100644 --- a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala +++ b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala @@ -14,6 +14,102 @@ trait StdAttachments { type MacroContext = UnaffiliatedMacroContext { val universe: self.global.type } case class MacroRuntimeAttachment(delayed: Boolean, typerContext: Context, macroContext: Option[MacroContext]) + /** Scratchpad for the macro expander, which is used to store all intermediate data except the details about the runtime. + */ + case class MacroExpanderAttachment(original: Tree, desugared: Tree, role: MacroRole) + + /** Loads underlying MacroExpanderAttachment from a macro expandee or returns a default value for that attachment. + */ + def macroExpanderAttachment(tree: Tree): MacroExpanderAttachment = + tree.attachments.get[MacroExpanderAttachment] getOrElse { + tree match { + case Apply(fn, _) if tree.isInstanceOf[ApplyToImplicitArgs] => macroExpanderAttachment(fn) + case _ => MacroExpanderAttachment(tree, EmptyTree, APPLY_ROLE) + } + } + + /** After macro expansion is completed, links the expandee and the expansion result + * by annotating them both with a `MacroExpansionAttachment`. + */ + def linkExpandeeAndDesugared(expandee: Tree, desugared: Tree, role: MacroRole): Unit = { + val metadata = MacroExpanderAttachment(expandee, desugared, role) + expandee updateAttachment metadata + desugared updateAttachment metadata + } + + /** Is added by the macro engine to originals and results of macro expansions. + * Stores the original expandee as it entered the `macroExpand` function. + */ + case class MacroExpansionAttachment(expandee: Tree, expanded: Any) + + /** Determines whether the target is either an original or a result of a macro expansion. + * The parameter is of type `Any`, because macros can expand both into trees and into annotations. + */ + def hasMacroExpansionAttachment(any: Any): Boolean = any match { + case tree: Tree => tree.attachments.get[MacroExpansionAttachment].isDefined + case _ => false + } + + /** After macro expansion is completed, links the expandee and the expansion result by annotating them both with a `MacroExpansionAttachment`. + * The `expanded` parameter is of type `Any`, because macros can expand both into trees and into annotations. + */ + def linkExpandeeAndExpanded(expandee: Tree, expanded: Any): Unit = { + val metadata = MacroExpansionAttachment(expandee, expanded) + expandee updateAttachment metadata + expanded match { + case expanded: Tree => expanded updateAttachment metadata + case _ => // do nothing + } + } + + /** Checks whether there is any tree resulting from a macro expansion and associated with the current tree. + */ + object ExpandedIntoTree { + def unapply(tree: Tree): Option[Tree] = tree.attachments.get[MacroExpansionAttachment] match { + case Some(MacroExpansionAttachment(_, tree: Tree)) => Some(tree) + case _ => None + } + } + + /** When present, suppresses macro expansion for the host. + * This is occasionally necessary, e.g. to prohibit eta-expansion of macros. + * + * Does not affect expandability of child nodes, there's context.withMacrosDisabled for that + * (but think thrice before using that API - see the discussion at https://github.com/scala/scala/pull/1639). + */ + case object SuppressMacroExpansionAttachment + + /** Suppresses macro expansion of the tree by putting SuppressMacroExpansionAttachment on it. + */ + def suppressMacroExpansion(tree: Tree) = tree.updateAttachment(SuppressMacroExpansionAttachment) + + /** Unsuppresses macro expansion of the tree by removing SuppressMacroExpansionAttachment from it and its children. + */ + def unsuppressMacroExpansion(tree: Tree): Tree = { + tree.removeAttachment[SuppressMacroExpansionAttachment.type] + tree match { + // see the comment to `isMacroExpansionSuppressed` to learn why we need + // a special traversal strategy here + case Apply(fn, _) => unsuppressMacroExpansion(fn) + case TypeApply(fn, _) => unsuppressMacroExpansion(fn) + case _ => // do nothing + } + tree + } + + /** Determines whether a tree should not be expanded, because someone has put SuppressMacroExpansionAttachment on it or one of its children. + */ + def isMacroExpansionSuppressed(tree: Tree): Boolean = + if (tree.attachments.get[SuppressMacroExpansionAttachment.type].isDefined) true + else tree match { + // we have to account for the fact that during typechecking an expandee might become wrapped, + // i.e. surrounded by an inferred implicit argument application or by an inferred type argument application. + // in that case the expandee itself will no longer be suppressed and we need to look at the core + case Apply(fn, _) => isMacroExpansionSuppressed(fn) + case TypeApply(fn, _) => isMacroExpansionSuppressed(fn) + case _ => false + } + /** After being synthesized by the parser, primary constructors aren't fully baked yet. * A call to super in such constructors is just a fill-me-in-later dummy resolved later * by `parentTypes`. This attachment coordinates `parentTypes` and `typedTemplate` and diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 0a653a3e15..a9e2d479db 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1076,11 +1076,8 @@ trait Typers extends Modes with Adaptations with Tags { } if (tree.isType) adaptType() - else if ( - inExprModeButNot(mode, FUNmode) && !tree.isDef && // typechecking application - tree.symbol != null && tree.symbol.isTermMacro && // of a macro - !tree.attachments.get[SuppressMacroExpansionAttachment.type].isDefined) - macroExpand(this, tree, mode, pt) + else if (inExprModeButNot(mode, FUNmode) && treeInfo.isMacroApplication(tree)) + macroExpandApply(this, tree, mode, pt) else if (inAllModes(mode, PATTERNmode | FUNmode)) adaptConstrPattern() else if (shouldInsertApply(tree)) @@ -4954,7 +4951,7 @@ trait Typers extends Modes with Adaptations with Tags { // that typecheck must not trigger macro expansions, so we explicitly prohibit them // however we cannot do `context.withMacrosDisabled` // because `expr` might contain nested macro calls (see SI-6673) - val exprTyped = typed1(expr updateAttachment SuppressMacroExpansionAttachment, mode, pt) + val exprTyped = typed1(suppressMacroExpansion(expr), mode, pt) exprTyped match { case macroDef if treeInfo.isMacroApplication(macroDef) => MacroEtaError(exprTyped) diff --git a/src/reflect/scala/reflect/internal/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala index b782353ed3..6c5bbc9774 100644 --- a/src/reflect/scala/reflect/internal/StdAttachments.scala +++ b/src/reflect/scala/reflect/internal/StdAttachments.scala @@ -29,17 +29,4 @@ trait StdAttachments { * Therefore we need this hack (see `Reshape.toPreTyperTypeTree` for a detailed explanation). */ case class CompoundTypeTreeOriginalAttachment(parents: List[Tree], stats: List[Tree]) - - /** Is added by the macro engine to the results of macro expansions. - * Stores the original expandee as it entered the `macroExpand` function. - */ - case class MacroExpansionAttachment(original: Tree) - - /** When present, suppresses macro expansion for the host. - * This is occasionally necessary, e.g. to prohibit eta-expansion of macros. - * - * Does not affect expandability of child nodes, there's context.withMacrosDisabled for that - * (but think thrice before using that API - see the discussion at https://github.com/scala/scala/pull/1639). - */ - case object SuppressMacroExpansionAttachment } -- cgit v1.2.3 From fe6028476931b031e712c37d3e570125b1d034ae Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Mon, 31 Dec 2012 15:46:47 +0100 Subject: SI-5923 adapt macros when they are deferred Amazingly enough, the fix for the "macro not expanded" problem was super easy. (And I remember spending a day or two trying to find a quick fix somewhen around Scala Days 2012!) The problem was in the implementation of the macro expansion trigger, which was buried in a chain of if-elif-elif. This meant that macro expansion was mutually exclusive with a lot of important adaptations, e.g. with `instantiate`. More precisely, if an expandee contains an undetparam, its expansion should be delayed until all its undetparams are inferred and then retried later. Sometimes such inference can only happen upon a call to instantiate in one of the elif's coming after the macro expansion elif. However this elif would never be called for expandees, because control flow would always enter the macro expansion branch preceding the inference branch. Consequences of this fix are vast. First of all, we can get rid of the "type parameter must be specified" hack. Secondly and most importantly, we can now remove the `materializeImplicit` method from Implicits and rely on implicit macros to materialize tags for us. (This is a tricky change, and I'll do it later after we merge as much of my pending work as possible). Finally, we learn that the current scheme of interaction between macros, type inference and implicits is, in principle, sound! --- src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala | 6 +----- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 6 ++++-- test/files/neg/t5353.check | 2 +- test/files/neg/t5692a.check | 4 ---- test/files/neg/t5692a.flags | 1 - test/files/neg/t5692a/Macros_1.scala | 6 ------ test/files/neg/t5692a/Test_2.scala | 3 --- test/files/neg/t5692b.check | 4 ---- test/files/neg/t5692b.flags | 1 - test/files/neg/t5692b/Macros_1.scala | 6 ------ test/files/neg/t5692b/Test_2.scala | 3 --- test/files/pos/t5692a.check | 4 ++++ test/files/pos/t5692a.flags | 1 + test/files/pos/t5692a/Macros_1.scala | 6 ++++++ test/files/pos/t5692a/Test_2.scala | 3 +++ test/files/pos/t5692b.check | 4 ++++ test/files/pos/t5692b.flags | 1 + test/files/pos/t5692b/Macros_1.scala | 6 ++++++ test/files/pos/t5692b/Test_2.scala | 3 +++ 19 files changed, 34 insertions(+), 36 deletions(-) delete mode 100644 test/files/neg/t5692a.check delete mode 100644 test/files/neg/t5692a.flags delete mode 100644 test/files/neg/t5692a/Macros_1.scala delete mode 100644 test/files/neg/t5692a/Test_2.scala delete mode 100644 test/files/neg/t5692b.check delete mode 100644 test/files/neg/t5692b.flags delete mode 100644 test/files/neg/t5692b/Macros_1.scala delete mode 100644 test/files/neg/t5692b/Test_2.scala create mode 100644 test/files/pos/t5692a.check create mode 100644 test/files/pos/t5692a.flags create mode 100644 test/files/pos/t5692a/Macros_1.scala create mode 100644 test/files/pos/t5692a/Test_2.scala create mode 100644 test/files/pos/t5692b.check create mode 100644 test/files/pos/t5692b.flags create mode 100644 test/files/pos/t5692b/Macros_1.scala create mode 100644 test/files/pos/t5692b/Test_2.scala diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala b/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala index efe7519d5e..c8b7fcee8f 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala @@ -77,11 +77,7 @@ abstract class Pickler extends SubComponent { } if (!t.isDef && t.hasSymbolField && t.symbol.isTermMacro) { - unit.error(t.pos, t.symbol.typeParams.length match { - case 0 => "macro has not been expanded" - case 1 => "this type parameter must be specified" - case _ => "these type parameters must be specified" - }) + unit.error(t.pos, "macro has not been expanded") return } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index a9e2d479db..b0b1341a07 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1061,6 +1061,7 @@ trait Typers extends Modes with Adaptations with Tags { instantiateToMethodType(mt) case _ => + def vanillaAdapt(tree: Tree) = { def shouldInsertApply(tree: Tree) = inAllModes(mode, EXPRmode | FUNmode) && (tree.tpe match { case _: MethodType | _: OverloadedType | _: PolyType => false case _ => applyPossible @@ -1076,8 +1077,6 @@ trait Typers extends Modes with Adaptations with Tags { } if (tree.isType) adaptType() - else if (inExprModeButNot(mode, FUNmode) && treeInfo.isMacroApplication(tree)) - macroExpandApply(this, tree, mode, pt) else if (inAllModes(mode, PATTERNmode | FUNmode)) adaptConstrPattern() else if (shouldInsertApply(tree)) @@ -1206,6 +1205,9 @@ trait Typers extends Modes with Adaptations with Tags { } fallBack } + } + val tree1 = if (inExprModeButNot(mode, FUNmode) && treeInfo.isMacroApplication(tree)) macroExpandApply(this, tree, mode, pt) else tree + if (tree == tree1) vanillaAdapt(tree1) else tree1 } } diff --git a/test/files/neg/t5353.check b/test/files/neg/t5353.check index 75e2435600..bc3f77a4d6 100644 --- a/test/files/neg/t5353.check +++ b/test/files/neg/t5353.check @@ -1,4 +1,4 @@ -t5353.scala:2: error: this type parameter must be specified +t5353.scala:2: error: macro has not been expanded def f(x: Boolean) = if (x) Array("abc") else Array() ^ one error found diff --git a/test/files/neg/t5692a.check b/test/files/neg/t5692a.check deleted file mode 100644 index 7fbfb5dba7..0000000000 --- a/test/files/neg/t5692a.check +++ /dev/null @@ -1,4 +0,0 @@ -Test_2.scala:2: error: this type parameter must be specified - def x = Macros.foo - ^ -one error found diff --git a/test/files/neg/t5692a.flags b/test/files/neg/t5692a.flags deleted file mode 100644 index cd66464f2f..0000000000 --- a/test/files/neg/t5692a.flags +++ /dev/null @@ -1 +0,0 @@ --language:experimental.macros \ No newline at end of file diff --git a/test/files/neg/t5692a/Macros_1.scala b/test/files/neg/t5692a/Macros_1.scala deleted file mode 100644 index 06b5a3de36..0000000000 --- a/test/files/neg/t5692a/Macros_1.scala +++ /dev/null @@ -1,6 +0,0 @@ -import scala.reflect.macros.Context - -object Macros { - def impl[T](c: Context) = c.literalUnit - def foo[T] = macro impl[T] -} \ No newline at end of file diff --git a/test/files/neg/t5692a/Test_2.scala b/test/files/neg/t5692a/Test_2.scala deleted file mode 100644 index 08d510cc6f..0000000000 --- a/test/files/neg/t5692a/Test_2.scala +++ /dev/null @@ -1,3 +0,0 @@ -class Test { - def x = Macros.foo -} \ No newline at end of file diff --git a/test/files/neg/t5692b.check b/test/files/neg/t5692b.check deleted file mode 100644 index 16796826b4..0000000000 --- a/test/files/neg/t5692b.check +++ /dev/null @@ -1,4 +0,0 @@ -Test_2.scala:2: error: these type parameters must be specified - def x = Macros.foo - ^ -one error found diff --git a/test/files/neg/t5692b.flags b/test/files/neg/t5692b.flags deleted file mode 100644 index cd66464f2f..0000000000 --- a/test/files/neg/t5692b.flags +++ /dev/null @@ -1 +0,0 @@ --language:experimental.macros \ No newline at end of file diff --git a/test/files/neg/t5692b/Macros_1.scala b/test/files/neg/t5692b/Macros_1.scala deleted file mode 100644 index b28d19f903..0000000000 --- a/test/files/neg/t5692b/Macros_1.scala +++ /dev/null @@ -1,6 +0,0 @@ -import scala.reflect.macros.Context - -object Macros { - def impl[T, U](c: Context) = c.literalUnit - def foo[T, U] = macro impl[T, U] -} \ No newline at end of file diff --git a/test/files/neg/t5692b/Test_2.scala b/test/files/neg/t5692b/Test_2.scala deleted file mode 100644 index 08d510cc6f..0000000000 --- a/test/files/neg/t5692b/Test_2.scala +++ /dev/null @@ -1,3 +0,0 @@ -class Test { - def x = Macros.foo -} \ No newline at end of file diff --git a/test/files/pos/t5692a.check b/test/files/pos/t5692a.check new file mode 100644 index 0000000000..7fbfb5dba7 --- /dev/null +++ b/test/files/pos/t5692a.check @@ -0,0 +1,4 @@ +Test_2.scala:2: error: this type parameter must be specified + def x = Macros.foo + ^ +one error found diff --git a/test/files/pos/t5692a.flags b/test/files/pos/t5692a.flags new file mode 100644 index 0000000000..cd66464f2f --- /dev/null +++ b/test/files/pos/t5692a.flags @@ -0,0 +1 @@ +-language:experimental.macros \ No newline at end of file diff --git a/test/files/pos/t5692a/Macros_1.scala b/test/files/pos/t5692a/Macros_1.scala new file mode 100644 index 0000000000..06b5a3de36 --- /dev/null +++ b/test/files/pos/t5692a/Macros_1.scala @@ -0,0 +1,6 @@ +import scala.reflect.macros.Context + +object Macros { + def impl[T](c: Context) = c.literalUnit + def foo[T] = macro impl[T] +} \ No newline at end of file diff --git a/test/files/pos/t5692a/Test_2.scala b/test/files/pos/t5692a/Test_2.scala new file mode 100644 index 0000000000..08d510cc6f --- /dev/null +++ b/test/files/pos/t5692a/Test_2.scala @@ -0,0 +1,3 @@ +class Test { + def x = Macros.foo +} \ No newline at end of file diff --git a/test/files/pos/t5692b.check b/test/files/pos/t5692b.check new file mode 100644 index 0000000000..16796826b4 --- /dev/null +++ b/test/files/pos/t5692b.check @@ -0,0 +1,4 @@ +Test_2.scala:2: error: these type parameters must be specified + def x = Macros.foo + ^ +one error found diff --git a/test/files/pos/t5692b.flags b/test/files/pos/t5692b.flags new file mode 100644 index 0000000000..cd66464f2f --- /dev/null +++ b/test/files/pos/t5692b.flags @@ -0,0 +1 @@ +-language:experimental.macros \ No newline at end of file diff --git a/test/files/pos/t5692b/Macros_1.scala b/test/files/pos/t5692b/Macros_1.scala new file mode 100644 index 0000000000..b28d19f903 --- /dev/null +++ b/test/files/pos/t5692b/Macros_1.scala @@ -0,0 +1,6 @@ +import scala.reflect.macros.Context + +object Macros { + def impl[T, U](c: Context) = c.literalUnit + def foo[T, U] = macro impl[T, U] +} \ No newline at end of file diff --git a/test/files/pos/t5692b/Test_2.scala b/test/files/pos/t5692b/Test_2.scala new file mode 100644 index 0000000000..08d510cc6f --- /dev/null +++ b/test/files/pos/t5692b/Test_2.scala @@ -0,0 +1,3 @@ +class Test { + def x = Macros.foo +} \ No newline at end of file -- cgit v1.2.3 From 143cd7a307706a8884c9352e64addf6e9be0a181 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sat, 5 Jan 2013 02:11:47 +0300 Subject: macroExpandAll is now triggered by typed Previously delayed macro expansions (the sole purpose of macroExpandAll) were triggered by `typedArgs`. Probably I wanted to save CPU cycles on not checking whether we have pending macro expansions on every iteration of typecheck. However this optimization is uncalled for, because the check just entails reading a var, therefore benefits of the current approach are negliible, whereas the robustness hit is tangible. After delayed macro expansion mechanism became more robust, it exposed a bug, well-hidden before. If one first delays a macro and then finds out that the expandee is erroneous, subsequent `macroExpandAll` will crash, because it expects a macro runtime attachment to be present. Previously the erroneous code path never got triggered, because the macro expansion never commenced. Luckily the fix was easy. --- src/compiler/scala/tools/nsc/typechecker/Macros.scala | 2 +- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 8 ++++++-- test/files/neg/t5353.check | 4 ---- test/files/neg/t5353.scala | 3 --- test/files/run/t5353.check | 2 ++ test/files/run/t5353.scala | 9 +++++++++ 6 files changed, 18 insertions(+), 10 deletions(-) delete mode 100644 test/files/neg/t5353.check delete mode 100644 test/files/neg/t5353.scala create mode 100644 test/files/run/t5353.check create mode 100644 test/files/run/t5353.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index fa90b4963a..3828dfe94b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -951,7 +951,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { new Transformer { override def transform(tree: Tree) = super.transform(tree match { // todo. expansion should work from the inside out - case tree if (delayed contains tree) && calculateUndetparams(tree).isEmpty => + case tree if (delayed contains tree) && calculateUndetparams(tree).isEmpty && !tree.isErroneous => val context = tree.attachments.get[MacroRuntimeAttachment].get.typerContext delayed -= tree context.implicitsEnabled = typer.context.implicitsEnabled diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index b0b1341a07..73d7f8928a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2892,7 +2892,6 @@ trait Typers extends Modes with Adaptations with Tags { else BYVALmode ) var tree = typedArg(args.head, mode, typedMode, adapted.head) - if (hasPendingMacroExpansions) tree = macroExpandAll(this, tree) // formals may be empty, so don't call tail tree :: loop(args.tail, formals drop 1, adapted.tail) } @@ -5208,7 +5207,12 @@ trait Typers extends Modes with Adaptations with Tags { } tree1 modifyType (addAnnotations(tree1, _)) - val result = if (tree1.isEmpty) tree1 else adapt(tree1, mode, pt, tree) + val result = + if (tree1.isEmpty) tree1 + else { + val result = adapt(tree1, mode, pt, tree) + if (hasPendingMacroExpansions) macroExpandAll(this, result) else result + } if (!alreadyTyped) { printTyping("adapted %s: %s to %s, %s".format( diff --git a/test/files/neg/t5353.check b/test/files/neg/t5353.check deleted file mode 100644 index bc3f77a4d6..0000000000 --- a/test/files/neg/t5353.check +++ /dev/null @@ -1,4 +0,0 @@ -t5353.scala:2: error: macro has not been expanded - def f(x: Boolean) = if (x) Array("abc") else Array() - ^ -one error found diff --git a/test/files/neg/t5353.scala b/test/files/neg/t5353.scala deleted file mode 100644 index 1ee869aac1..0000000000 --- a/test/files/neg/t5353.scala +++ /dev/null @@ -1,3 +0,0 @@ -class A { - def f(x: Boolean) = if (x) Array("abc") else Array() -} diff --git a/test/files/run/t5353.check b/test/files/run/t5353.check new file mode 100644 index 0000000000..a2906793ed --- /dev/null +++ b/test/files/run/t5353.check @@ -0,0 +1,2 @@ +1 +[Ljava.lang.Object; cannot be cast to [Ljava.lang.String; diff --git a/test/files/run/t5353.scala b/test/files/run/t5353.scala new file mode 100644 index 0000000000..5208fe527f --- /dev/null +++ b/test/files/run/t5353.scala @@ -0,0 +1,9 @@ +object Test extends App { + def f(x: Boolean) = if (x) Array("abc") else Array() + try { + println(f(true).length) + println(f(false).length) + } catch { + case ex: Throwable => println(ex.getMessage) + } +} -- cgit v1.2.3