diff options
44 files changed, 484 insertions, 165 deletions
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/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/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)) 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/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/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/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/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index ea03aca8c4..2d4054e93b 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,14 @@ trait ContextErrors { setError(tree) } + def MacroTooManyArgumentListsError(expandee: Tree, fun: Symbol) = { + 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 @@ -681,10 +691,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 } @@ -750,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 64d76d6a83..5b27fd9352 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 @@ -357,7 +358,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 +373,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) => @@ -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,15 +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 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. */ @@ -645,21 +652,37 @@ 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 - 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) + /** 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") /** 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 + * + * ========= 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. @@ -670,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: Mode = 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: Mode, 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: Mode, 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))) { @@ -736,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 @@ -758,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 => @@ -787,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) @@ -857,13 +951,13 @@ 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 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/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index b5160c0519..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) @@ -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/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 8a34d58e6e..e88447c46d 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/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 ff6752703f..96a93e2a20 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -830,7 +830,7 @@ trait Typers extends 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) @@ -1059,10 +1059,11 @@ trait Typers extends Adaptations with Tags { adaptToImplicitMethod(mt) case mt: MethodType if (((mode & (EXPRmode | FUNmode | LHSmode)) == EXPRmode) && - (context.undetparams.isEmpty || mode.inPolyMode)) && !(tree.symbol != null && tree.symbol.isTermMacro) => + (context.undetparams.isEmpty || mode.inPolyMode)) && !treeInfo.isMacroApplicationOrBlock(tree) => instantiateToMethodType(mt) case _ => + def vanillaAdapt(tree: Tree) = { def shouldInsertApply(tree: Tree) = mode.inAll(EXPRmode | FUNmode) && (tree.tpe match { case _: MethodType | _: OverloadedType | _: PolyType => false case _ => applyPossible @@ -1078,11 +1079,6 @@ trait Typers extends Adaptations with Tags { } if (tree.isType) adaptType() - else if ( - mode.inExprModeButNot(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 (mode.inAll(PATTERNmode | FUNmode)) adaptConstrPattern() else if (shouldInsertApply(tree)) @@ -1211,6 +1207,9 @@ trait Typers extends Adaptations with Tags { } fallBack } + } + val tree1 = if (mode.inExprModeButNot(FUNmode) && treeInfo.isMacroApplication(tree)) macroExpandApply(this, tree, mode, pt) else tree + if (tree == tree1) vanillaAdapt(tree1) else tree1 } } @@ -1589,7 +1588,11 @@ trait Typers extends 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 } @@ -1632,7 +1635,7 @@ trait Typers extends 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 { @@ -1654,7 +1657,7 @@ trait Typers extends 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) @@ -1765,7 +1768,7 @@ trait Typers extends 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) @@ -1805,7 +1808,7 @@ trait Typers extends 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() @@ -2205,7 +2208,7 @@ trait Typers extends 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)) @@ -2912,7 +2915,6 @@ trait Typers extends 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) } @@ -3078,7 +3080,7 @@ trait Typers extends 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)) } @@ -3249,7 +3251,8 @@ trait Typers extends 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)) } } @@ -4972,9 +4975,9 @@ trait Typers extends 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 macroDef.symbol != null && macroDef.symbol.isTermMacro && !macroDef.symbol.isErroneous => + case macroDef if treeInfo.isMacroApplication(macroDef) => MacroEtaError(exprTyped) case _ => typedEta(checkDead(exprTyped)) @@ -5227,7 +5230,12 @@ trait Typers extends 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( @@ -5360,7 +5368,7 @@ trait Typers extends 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) @@ -5369,8 +5377,8 @@ trait Typers extends 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/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 } diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 0127bd988b..2d42d2df58 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -654,7 +654,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 @@ -2241,7 +2241,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") 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/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 ", ") 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. * 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") } } 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 diff --git a/test/files/neg/t5353.check b/test/files/neg/t5353.check deleted file mode 100644 index 75e2435600..0000000000 --- a/test/files/neg/t5353.check +++ /dev/null @@ -1,4 +0,0 @@ -t5353.scala:2: error: this type parameter must be specified - 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/neg/t5692a.check b/test/files/pos/t5692a.check index 7fbfb5dba7..7fbfb5dba7 100644 --- a/test/files/neg/t5692a.check +++ b/test/files/pos/t5692a.check diff --git a/test/files/neg/t5692a.flags b/test/files/pos/t5692a.flags index cd66464f2f..cd66464f2f 100644 --- a/test/files/neg/t5692a.flags +++ b/test/files/pos/t5692a.flags diff --git a/test/files/neg/t5692a/Macros_1.scala b/test/files/pos/t5692a/Macros_1.scala index 06b5a3de36..06b5a3de36 100644 --- a/test/files/neg/t5692a/Macros_1.scala +++ b/test/files/pos/t5692a/Macros_1.scala diff --git a/test/files/neg/t5692a/Test_2.scala b/test/files/pos/t5692a/Test_2.scala index 08d510cc6f..08d510cc6f 100644 --- a/test/files/neg/t5692a/Test_2.scala +++ b/test/files/pos/t5692a/Test_2.scala diff --git a/test/files/neg/t5692b.check b/test/files/pos/t5692b.check index 16796826b4..16796826b4 100644 --- a/test/files/neg/t5692b.check +++ b/test/files/pos/t5692b.check diff --git a/test/files/neg/t5692b.flags b/test/files/pos/t5692b.flags index cd66464f2f..cd66464f2f 100644 --- a/test/files/neg/t5692b.flags +++ b/test/files/pos/t5692b.flags diff --git a/test/files/neg/t5692b/Macros_1.scala b/test/files/pos/t5692b/Macros_1.scala index b28d19f903..b28d19f903 100644 --- a/test/files/neg/t5692b/Macros_1.scala +++ b/test/files/pos/t5692b/Macros_1.scala diff --git a/test/files/neg/t5692b/Test_2.scala b/test/files/pos/t5692b/Test_2.scala index 08d510cc6f..08d510cc6f 100644 --- a/test/files/neg/t5692b/Test_2.scala +++ b/test/files/pos/t5692b/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) +} 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/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) + } +} 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 |