summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugene Burmako <xeno.by@gmail.com>2013-01-05 00:59:13 +0300
committerEugene Burmako <xeno.by@gmail.com>2013-01-09 08:10:48 +0100
commit30e2e3a78f9061ea93352427cb0ca205203041f0 (patch)
tree710fa49e0526a85bcec39a2656db7631a14d631c
parent94de3c87edf420b4d1d8b495fd7976f4b515483d (diff)
downloadscala-30e2e3a78f9061ea93352427cb0ca205203041f0.tar.gz
scala-30e2e3a78f9061ea93352427cb0ca205203041f0.tar.bz2
scala-30e2e3a78f9061ea93352427cb0ca205203041f0.zip
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.
-rw-r--r--src/compiler/scala/reflect/reify/phases/Reshape.scala4
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala13
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Macros.scala217
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala96
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala9
-rw-r--r--src/reflect/scala/reflect/internal/StdAttachments.scala13
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(<term macro>)
+ * Select(<any qualifier>, <term macro>)
+ * TypeApply(<any of the above>, <targs>)
+ * Apply(...Apply(<any of the above>, <args1>)...<argsN>)
+ *
+ * ========= 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
}