summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugene Burmako <xeno.by@gmail.com>2012-08-17 15:49:38 +0200
committerEugene Burmako <xeno.by@gmail.com>2012-08-18 09:00:55 +0200
commitac430ac8ba554d1b976d44598400d95ce5cf3816 (patch)
tree92f709ec56cca5895cd960e82a1ebd10a34ad98a
parent7fc860963a4f76cb18e44c20f2bdfb49641033f3 (diff)
downloadscala-ac430ac8ba554d1b976d44598400d95ce5cf3816.tar.gz
scala-ac430ac8ba554d1b976d44598400d95ce5cf3816.tar.bz2
scala-ac430ac8ba554d1b976d44598400d95ce5cf3816.zip
cleanup for macroExpand
Error reporting is moved to ContextErrors to disentangle stuff in Macros.scala. With logics and error reporting intertwined it was an awful mess. Exceptions are used for the same reason. Instead of threading failures through the code polluting it with options/ifs, I outline the success path. It worked much better for typedMacroBody, but I'm also happy with the resulting code of macroExpand. To me a major factor towards applicability of exceptions was that they are short-lived and that there might be max one error per domain, after which we unconditionally bail.
-rw-r--r--src/compiler/scala/reflect/macros/runtime/AbortMacroException.scala3
-rw-r--r--src/compiler/scala/reflect/macros/runtime/FrontEnds.scala5
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala6
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala111
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Macros.scala334
-rw-r--r--src/reflect/scala/reflect/internal/TreeInfo.scala11
-rw-r--r--src/reflect/scala/reflect/internal/Types.scala6
-rw-r--r--test/files/neg/macro-invalidusage-badbounds-a.check (renamed from test/files/neg/macro-invalidusage-badbounds.check)0
-rw-r--r--test/files/neg/macro-invalidusage-badbounds-a.flags (renamed from test/files/neg/macro-invalidusage-badbounds.flags)0
-rw-r--r--test/files/neg/macro-invalidusage-badbounds-a/Impls_1.scala5
-rw-r--r--test/files/neg/macro-invalidusage-badbounds-a/Macros_Test_2.scala (renamed from test/files/neg/macro-invalidusage-badbounds/Macros_Test_2.scala)0
-rw-r--r--test/pending/neg/macro-invalidusage-badbounds-b.check4
-rw-r--r--test/pending/neg/macro-invalidusage-badbounds-b.flags1
-rw-r--r--test/pending/neg/macro-invalidusage-badbounds-b/Impls_1.scala (renamed from test/files/neg/macro-invalidusage-badbounds/Impls_1.scala)0
-rw-r--r--test/pending/neg/macro-invalidusage-badbounds-b/Macros_Test_2.scala8
15 files changed, 229 insertions, 265 deletions
diff --git a/src/compiler/scala/reflect/macros/runtime/AbortMacroException.scala b/src/compiler/scala/reflect/macros/runtime/AbortMacroException.scala
index f45dde8a85..4e4d88c0be 100644
--- a/src/compiler/scala/reflect/macros/runtime/AbortMacroException.scala
+++ b/src/compiler/scala/reflect/macros/runtime/AbortMacroException.scala
@@ -2,5 +2,6 @@ package scala.reflect.macros
package runtime
import scala.reflect.internal.util.Position
+import scala.util.control.ControlThrowable
-class AbortMacroException(val pos: Position, val msg: String) extends Throwable(msg)
+class AbortMacroException(val pos: Position, val msg: String) extends Throwable(msg) with ControlThrowable \ No newline at end of file
diff --git a/src/compiler/scala/reflect/macros/runtime/FrontEnds.scala b/src/compiler/scala/reflect/macros/runtime/FrontEnds.scala
index c5c1c84cde..9f328eb82b 100644
--- a/src/compiler/scala/reflect/macros/runtime/FrontEnds.scala
+++ b/src/compiler/scala/reflect/macros/runtime/FrontEnds.scala
@@ -35,10 +35,7 @@ trait FrontEnds extends scala.tools.reflect.FrontEnds {
def error(pos: Position, msg: String): Unit = callsiteTyper.context.error(pos, msg)
- def abort(pos: Position, msg: String): Nothing = {
- callsiteTyper.context.error(pos, msg)
- throw new AbortMacroException(pos, msg)
- }
+ def abort(pos: Position, msg: String): Nothing = throw new AbortMacroException(pos, msg)
def interactive(): Unit = universe.reporter match {
case reporter: scala.tools.nsc.reporters.AbstractReporter => reporter.displayPrompt()
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala
index 574129a2f1..80e9ede271 100644
--- a/src/compiler/scala/tools/nsc/Global.scala
+++ b/src/compiler/scala/tools/nsc/Global.scala
@@ -1220,8 +1220,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
var reportedFeature = Set[Symbol]()
- /** A flag whether macro expansions failed */
- var macroExpansionFailed = false
+ /** Has any macro expansion used a fallback during this run? */
+ var seenMacroExpansionsFallingBack = false
/** To be initialized from firstPhase. */
private var terminalPhase: Phase = NoPhase
@@ -1511,7 +1511,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
else {
allConditionalWarnings foreach (_.summarize)
- if (macroExpansionFailed)
+ if (seenMacroExpansionsFallingBack)
warning("some macros could not be expanded and code fell back to overridden methods;"+
"\nrecompiling with generated classfiles on the classpath might help.")
// todo: migrationWarnings
diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
index 5a110739cd..99ce22ddb9 100644
--- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
@@ -10,6 +10,8 @@ import scala.collection.{ mutable, immutable }
import scala.reflect.internal.util.StringOps.{ countElementsAsString, countAsString }
import symtab.Flags.{ PRIVATE, PROTECTED, IS_ERROR }
import scala.compat.Platform.EOL
+import scala.reflect.runtime.ReflectionUtils
+import scala.reflect.macros.runtime.AbortMacroException
trait ContextErrors {
self: Analyzer =>
@@ -323,16 +325,6 @@ trait ContextErrors {
setError(tree)
}
- def MacroEtaError(tree: Tree) = {
- issueNormalTypeError(tree, "macros cannot be eta-expanded")
- setError(tree)
- }
-
- def MacroPartialApplicationError(tree: Tree) = {
- issueNormalTypeError(tree, "macros cannot be partially applied")
- setError(tree)
- }
-
//typedReturn
def ReturnOutsideOfDefError(tree: Tree) = {
issueNormalTypeError(tree, "return outside method definition")
@@ -636,6 +628,105 @@ trait ContextErrors {
def CyclicReferenceError(errPos: Position, lockedSym: Symbol) =
issueTypeError(PosAndMsgTypeError(errPos, "illegal cyclic reference involving " + lockedSym))
+
+ // macro-related errors (also see MacroErrors below)
+
+ def MacroEtaError(tree: Tree) = {
+ issueNormalTypeError(tree, "macros cannot be eta-expanded")
+ setError(tree)
+ }
+
+ // same reason as for MacroBodyTypecheckException
+ case object MacroExpansionException extends Exception with scala.util.control.ControlThrowable
+
+ private def macroExpansionError(expandee: Tree, msg: String = null, pos: Position = NoPosition) = {
+ def msgForLog = if (msg != null && (msg contains "exception during macro expansion")) msg.split(EOL).drop(1).headOption.getOrElse("?") else msg
+ macroLogLite("macro expansion has failed: %s".format(msgForLog))
+ val errorPos = if (pos != NoPosition) pos else (if (expandee.pos != NoPosition) expandee.pos else enclosingMacroPosition)
+ if (msg != null) context.error(pos, msg) // issueTypeError(PosAndMsgTypeError(..)) won't work => swallows positions
+ setError(expandee)
+ throw MacroExpansionException
+ }
+
+ 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")
+ setError(expandee)
+ throw MacroExpansionException
+ }
+
+ def MacroGeneratedAbort(expandee: Tree, ex: AbortMacroException) = {
+ // errors have been reported by the macro itself, so we do nothing here
+ macroLogVerbose("macro expansion has been aborted")
+ macroExpansionError(expandee, ex.msg, ex.pos)
+ }
+
+ def MacroGeneratedTypeError(expandee: Tree, err: TypeError = null) =
+ if (err == null) {
+ // errors have been reported by the macro itself, so we do nothing here
+ macroExpansionError(expandee, null)
+ } else {
+ macroLogLite("macro expansion has failed: %s at %s".format(err.msg, err.pos))
+ throw err // this error must be propagated, don't report
+ }
+
+ def MacroGeneratedException(expandee: Tree, ex: Throwable) = {
+ val realex = ReflectionUtils.unwrapThrowable(ex)
+ val message = {
+ try {
+ // [Eugene] is there a better way?
+ // [Paul] See Exceptional.scala and Origins.scala.
+ val relevancyThreshold = realex.getStackTrace().indexWhere(_.getMethodName endsWith "macroExpand1")
+ if (relevancyThreshold == -1) None
+ else {
+ var relevantElements = realex.getStackTrace().take(relevancyThreshold + 1)
+ def isMacroInvoker(este: StackTraceElement) = este.isNativeMethod || (este.getClassName != null && (este.getClassName contains "fastTrack"))
+ var threshold = relevantElements.reverse.indexWhere(isMacroInvoker) + 1
+ while (threshold != relevantElements.length && isMacroInvoker(relevantElements(relevantElements.length - threshold - 1))) threshold += 1
+ relevantElements = relevantElements dropRight threshold
+
+ realex.setStackTrace(relevantElements)
+ val message = new java.io.StringWriter()
+ realex.printStackTrace(new java.io.PrintWriter(message))
+ Some(EOL + message)
+ }
+ } catch {
+ // if the magic above goes boom, just fall back to uninformative, but better than nothing, getMessage
+ case ex: Throwable =>
+ None
+ }
+ } getOrElse {
+ val msg = realex.getMessage
+ if (msg != null) msg else realex.getClass.getName
+ }
+ macroExpansionError(expandee, "exception during macro expansion: " + message)
+ }
+
+ def MacroFreeSymbolError(expandee: Tree, sym: FreeSymbol) = {
+ def template(kind: String) = (
+ s"Macro expansion contains free $kind variable %s. Have you forgotten to use %s? "
+ + s"If you have troubles tracking free $kind variables, consider using -Xlog-free-${kind}s"
+ )
+ val forgotten = (
+ if (sym.isTerm) "splice when splicing this variable into a reifee"
+ else "c.AbsTypeTag annotation for this type parameter"
+ )
+ macroExpansionError(expandee, template(sym.name.nameKind).format(sym.name + " " + sym.origin, forgotten))
+ }
+
+ def MacroExpansionIsNotExprError(expandee: Tree, expanded: Any) =
+ macroExpansionError(expandee,
+ "macro must return a compiler-specific expr; returned value is " + (
+ if (expanded == null) "null"
+ else if (expanded.isInstanceOf[Expr[_]]) " Expr, but it doesn't belong to this compiler's universe"
+ else " of " + expanded.getClass
+ ))
+
+ def MacroImplementationNotFoundError(expandee: Tree) =
+ macroExpansionError(expandee,
+ "macro implementation not found: " + expandee.symbol.name + " " +
+ "(the most common reason for that is that you cannot use macro implementations in the same compilation run that defines them)")
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala
index 4601f63809..ed38c1750b 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala
@@ -12,6 +12,8 @@ import scala.reflect.macros.util._
import java.lang.{Class => jClass}
import java.lang.reflect.{Array => jArray, Method => jMethod}
import scala.reflect.internal.util.Collections._
+import scala.util.control.ControlThrowable
+import scala.reflect.macros.runtime.AbortMacroException
/**
* Code to deal with macros, namely with:
@@ -553,19 +555,13 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces {
val callsiteTyper: universe.analyzer.Typer = typer.asInstanceOf[global.analyzer.Typer]
val expandee = expandeeTree
} with UnaffiliatedMacroContext {
- // todo. infer precise typetag for this Expr, namely the PrefixType member of the Context refinement
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.
- *
- * This includes inferring the exact type and instance of the macro context to pass, and also
- * allowing for missing parameter sections in macro implementation (see `macroImplParamsss` for more info).
- *
- * @return list of runtime objects to pass to the implementation obtained by `macroRuntime`
*/
- private def macroArgs(typer: Typer, expandee: Tree): Option[List[Any]] = {
+ private def macroArgs(typer: Typer, expandee: Tree): List[Any] = {
val macroDef = expandee.symbol
val prefixTree = expandee.collect{ case Select(qual, name) => qual }.headOption.getOrElse(EmptyTree)
val context = expandee.attachments.get[MacroRuntimeAttachment].flatMap(_.macroContext).getOrElse(macroContext(typer, prefixTree, expandee))
@@ -585,23 +581,15 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces {
val argcDoesntMatch = macroDef.paramss.length != exprArgs.length
val nullaryArgsEmptyParams = exprArgs.isEmpty && macroDef.paramss == ListOfNil
- if (argcDoesntMatch && !nullaryArgsEmptyParams) { typer.TyperErrorGen.MacroPartialApplicationError(expandee); return None }
+ if (argcDoesntMatch && !nullaryArgsEmptyParams) { typer.TyperErrorGen.MacroPartialApplicationError(expandee) }
var argss: List[List[Any]] = List(context) :: exprArgs.toList
macroTraceVerbose("argss: ")(argss)
+
val rawArgss =
if (fastTrack contains macroDef) {
if (fastTrack(macroDef) validate argss) argss
- else {
- // if you're getting here, it's not necessarily partial application that is at fault
- // for example, if a signature of a hardwired macro has been changed without updated FastTrack
- // then the corresponding partial function in FastTrack will refuse to process the expandee
- // validation will return false, and control flow will end up here
- // however, for simplicity sake, I didn't introduce the notion of error handling to FastTrack
- // so all kinds of validation errors produce `MacroPartialApplicationError`
- typer.TyperErrorGen.MacroPartialApplicationError(expandee)
- return None
- }
+ else typer.TyperErrorGen.MacroPartialApplicationError(expandee)
} else {
val binding = loadMacroImplBinding(macroDef)
macroTraceVerbose("binding: ")(binding)
@@ -658,9 +646,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces {
} else as
})
}
- val rawArgs = rawArgss.flatten
- macroTraceVerbose("rawArgs: ")(rawArgs)
- Some(rawArgs)
+ macroTraceVerbose("rawArgs: ")(rawArgss.flatten)
}
/** Keeps track of macros in-flight.
@@ -669,6 +655,15 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces {
var openMacros = List[MacroContext]()
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)
+
/** Performs macro expansion:
* 1) Checks whether the expansion needs to be delayed (see `mustDelayMacroExpansion`)
* 2) Loads macro implementation using `macroMirror`
@@ -692,48 +687,25 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces {
* 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 = {
- def fail(what: String, tree: Tree): Tree = {
- val err = typer.context.errBuffer.head
- this.fail(typer, tree, err.errPos, "failed to %s: %s".format(what, err.errMsg))
- return expandee
- }
val start = Statistics.startTimer(macroExpandNanos)
Statistics.incCounter(macroExpandCount)
try {
macroExpand1(typer, expandee) match {
- case Success(expanded0) =>
+ case Success(expanded) =>
try {
- val expanded = expanded0 // virtpatmat swallows the local for expandee from the match
- // so I added this dummy local for the ease of debugging
- var expectedTpe = expandee.tpe
-
- val isNullaryInvocation = expandee match {
- case TypeApply(Select(_, _), _) => true
- case TypeApply(Ident(_), _) => true
- case Select(_, _) => true
- case Ident(_) => true
- case _ => false
+ 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)
}
- if (isNullaryInvocation) expectedTpe match {
- case NullaryMethodType(restpe) =>
- macroTraceVerbose("nullary invocation of a nullary method. unwrapping expectedTpe from " + expectedTpe + " to: ")(restpe)
- expectedTpe = restpe
- case MethodType(Nil, restpe) =>
- macroTraceVerbose("nullary invocation of a method with an empty parameter list. unwrapping expectedTpe from " + expectedTpe + " to: ")(restpe)
- expectedTpe = restpe
- case _ => ;
- }
-
- macroLogVerbose("typechecking1 against %s: %s".format(expectedTpe, expanded))
- var typechecked = typer.context.withImplicitsEnabled(typer.typed(expanded, EXPRmode, expectedTpe))
- if (typer.context.hasErrors) fail("typecheck against macro def return type", expanded)
- macroLogVerbose("typechecked1:%n%s%n%s".format(typechecked, showRaw(typechecked)))
-
- macroLogVerbose("typechecking2 against %s: %s".format(pt, expanded))
- typechecked = typer.context.withImplicitsEnabled(typer.typed(typechecked, EXPRmode, pt))
- if (typer.context.hasErrors) fail("typecheck against expected type", expanded)
- macroLogVerbose("typechecked2:%n%s%n%s".format(typechecked, showRaw(typechecked)))
+ var expectedTpe = expandee.tpe
+ if (isNullaryInvocation(expandee)) expectedTpe = expectedTpe.finalResultType
+ var typechecked = typecheck("macro def return type", expanded, expectedTpe)
+ typechecked = typecheck("expected type", typechecked, pt)
typechecked addAttachment MacroExpansionAttachment(expandee)
} finally {
openMacros = openMacros.tail
@@ -748,230 +720,108 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces {
}
}
- private sealed abstract class MacroExpansionResult extends Product with Serializable
- private case class Success(expanded: Tree) extends MacroExpansionResult
- private case class Fallback(fallback: Tree) extends MacroExpansionResult
- 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)
- private def fail(typer: Typer, expandee: Tree, pos: Position = NoPosition, msg: String = null) = {
- def msgForLog = if (msg != null && (msg contains "exception during macro expansion")) msg.split(EOL).drop(1).headOption.getOrElse("?") else msg
- macroLogLite("macro expansion has failed: %s".format(msgForLog))
- val errorPos = if (pos != NoPosition) pos else (if (expandee.pos != NoPosition) expandee.pos else enclosingMacroPosition)
- if (msg != null) typer.context.error(errorPos, msg)
- typer.infer.setError(expandee)
- 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 =
- // InfoLevel.Verbose examines and prints out infos of symbols
- // by the means of this'es these symbols can climb up the lexical scope
- // when these symbols will be examined by a node printer
- // they will enumerate and analyze their children (ask for infos and tpes)
- // if one of those children involves macro expansion, things might get nasty
- // that's why I'm temporarily turning this behavior off
+ // verbose printing might cause recursive macro expansions, so I'm shutting it down here
withInfoLevel(nodePrinters.InfoLevel.Quiet) {
- // if a macro implementation is incompatible or any of the arguments are erroneous
- // there is no sense to expand the macro itself => it will only make matters worse
if (expandee.symbol.isErroneous || (expandee exists (_.isErroneous))) {
val reason = if (expandee.symbol.isErroneous) "not found or incompatible macro implementation" else "erroneous arguments"
macroTraceVerbose("cancelled macro expansion because of %s: ".format(reason))(expandee)
return Cancel(typer.infer.setError(expandee))
}
- val runtime = macroRuntime(expandee.symbol)
- if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime)
- else macroExpandWithoutRuntime(typer, expandee)
+ try {
+ val runtime = macroRuntime(expandee.symbol)
+ if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime)
+ else macroExpandWithoutRuntime(typer, expandee)
+ } catch {
+ case typer.TyperErrorGen.MacroExpansionException => Failure(expandee)
+ }
}
/** 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 = {
- def issueFreeError(sym: FreeSymbol) = {
- val template = (
- "Macro expansion contains free @kind@ variable %s. Have you forgotten to use %s? "
- + "If you have troubles tracking free @kind@ variables, consider using -Xlog-free-@kind@s"
- )
- val forgotten = (
- if (sym.isTerm) "splice when splicing this variable into a reifee"
- else "c.AbsTypeTag annotation for this type parameter"
- )
- typer.context.error(expandee.pos,
- template.replaceAllLiterally("@kind@", sym.name.nameKind).format(
- sym.name + " " + sym.origin, forgotten)
- )
- }
- def macroExpandInternal = {
- val wasDelayed = isDelayed(expandee)
- val undetparams = calculateUndetparams(expandee)
- val nowDelayed = !typer.context.macrosEnabled || undetparams.nonEmpty
-
- def failExpansion(msg: String = null) = fail(typer, expandee, msg = msg)
+ import typer.TyperErrorGen._
+ try {
def performExpansion(args: List[Any]): MacroExpansionResult = {
val numErrors = reporter.ERROR.count
def hasNewErrors = reporter.ERROR.count > numErrors
-
val expanded = runtime(args)
+ if (hasNewErrors) MacroGeneratedTypeError(expandee)
- if (hasNewErrors)
- failExpansion() // errors have been reported by the macro itself
- else expanded match {
+ expanded match {
case expanded: Expr[_] =>
macroLogVerbose("original:")
macroLogLite("" + expanded.tree + "\n" + showRaw(expanded.tree))
-
- expanded.tree.freeTerms foreach issueFreeError
- expanded.tree.freeTypes foreach issueFreeError
- if (hasNewErrors) failExpansion()
-
- // inherit the position from the first position-ful expandee in macro callstack
- // this is essential for sane error messages
- // now macro expansion gets typechecked against the macro definition return type
- // however, this happens in macroExpand, not here in macroExpand1
- else Success(atPos(enclosingMacroPosition.focus)(expanded.tree))
+ val freeSyms = expanded.tree.freeTerms ++ expanded.tree.freeTypes
+ freeSyms foreach (sym => MacroFreeSymbolError(expandee, sym))
+ Success(atPos(enclosingMacroPosition.focus)(expanded.tree))
case _ =>
- failExpansion(
- "macro must return a compiler-specific expr; returned value is " + (
- if (expanded.isInstanceOf[Expr[_]]) " Expr, but it doesn't belong to this compiler's universe"
- else " of " + expanded.getClass
- )
- )
+ MacroExpansionIsNotExprError(expandee, expanded)
}
}
- if (wasDelayed) {
- if (nowDelayed) Delay(expandee)
- else Skip(macroExpandAll(typer, expandee))
- }
- else {
- macroLogLite("typechecking macro expansion %s at %s".format(expandee, expandee.pos))
- macroArgs(typer, expandee).fold(failExpansion(): MacroExpansionResult) {
- args => (args: @unchecked) match {
- // crashes virtpatmat:
- // case args @ ((context: MacroContext) :: _) =>
- // todo. extract a minimized test case
- case args @ (context0 :: _) =>
- val context = context0.asInstanceOf[MacroContext]
- if (nowDelayed) {
- macroLogLite("macro expansion is delayed: %s".format(expandee))
- delayed += expandee -> undetparams
- // need to save typer context for `macroExpandAll`
- // need to save macro context to preserve enclosures
- expandee addAttachment MacroRuntimeAttachment(delayed = true, typerContext = typer.context, macroContext = Some(context.asInstanceOf[MacroContext]))
- Delay(expandee)
- }
- else {
- // adding stuff to openMacros is easy, but removing it is a nightmare
- // it needs to be sprinkled over several different code locations
- // why? https://github.com/scala/scala/commit/bd3eacbae21f39b1ac7fe8ade4ed71fa98e1a28d#L2R1137
- // todo. will be improved
- openMacros ::= context
- var isSuccess = false
- try performExpansion(args) match {
- case x: Success => isSuccess = true ; x
- case x => x
- }
- finally {
- expandee.removeAttachment[MacroRuntimeAttachment]
- if (!isSuccess) openMacros = openMacros.tail
- }
- }
- }
+ val wasDelayed = isDelayed(expandee)
+ val undetparams = calculateUndetparams(expandee)
+ val nowDelayed = !typer.context.macrosEnabled || undetparams.nonEmpty
+
+ if (wasDelayed || nowDelayed) {
+ if (!wasDelayed && nowDelayed) {
+ macroLogLite("macro expansion is delayed: %s".format(expandee))
+ delayed += expandee -> undetparams
+ val macroContext = macroArgs(typer, expandee).head.asInstanceOf[MacroContext]
+ expandee addAttachment MacroRuntimeAttachment(delayed = true, typerContext = typer.context, macroContext = Some(macroContext))
+ Delay(expandee)
+ } else if (wasDelayed && nowDelayed) {
+ Delay(expandee)
+ } else /* if (wasDelayed && !nowDelayed) */ {
+ Skip(macroExpandAll(typer, expandee))
+ }
+ } else {
+ macroLogLite("performing macro expansion %s at %s".format(expandee, expandee.pos))
+ val args = macroArgs(typer, expandee)
+ openMacros ::= args.head.asInstanceOf[MacroContext]
+ var isSuccess = false
+ try performExpansion(args) match {
+ case x: Success => isSuccess = true ; x
+ case x => x
+ } finally {
+ expandee.removeAttachment[MacroRuntimeAttachment]
+ if (!isSuccess) openMacros = openMacros.tail
}
}
+ } catch {
+ case ex: Throwable =>
+ val realex = ReflectionUtils.unwrapThrowable(ex)
+ realex match {
+ case ex: AbortMacroException => MacroGeneratedAbort(expandee, ex)
+ case ex: ControlThrowable => throw ex
+ case ex: TypeError => MacroGeneratedTypeError(expandee, ex)
+ case _ => MacroGeneratedException(expandee, realex)
+ }
}
-
- try macroExpandInternal
- catch { case ex: Throwable => handleMacroExpansionException(typer, expandee, ex) }
}
+ /** 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 = {
- val macroDef = expandee.symbol
- def notFound() = {
- typer.context.error(expandee.pos, "macro implementation not found: " + macroDef.name + " " +
- "(the most common reason for that is that you cannot use macro implementations in the same compilation run that defines them)")
- None
- }
- def fallBackToOverridden(tree: Tree): Option[Tree] = {
+ val fallbackSym = expandee.symbol.allOverriddenSymbols.headOption.getOrElse(NoSymbol)
+ if (fallbackSym == NoSymbol) typer.TyperErrorGen.MacroImplementationNotFoundError(expandee)
+ macroTraceLite("falling back to: ")(fallbackSym)
+
+ def mkFallbackTree(tree: Tree): Tree = {
tree match {
- case Select(qual, name) if (macroDef.isTermMacro) =>
- macroDef.allOverriddenSymbols match {
- case first :: _ =>
- Some(Select(qual, name) setPos tree.pos setSymbol first)
- case _ =>
- macroTraceVerbose("macro is not overridden: ")(tree)
- notFound()
- }
- case Apply(fn, args) =>
- fallBackToOverridden(fn) match {
- case Some(fn1) => Some(Apply(fn1, args) setPos tree.pos)
- case _ => None
- }
- case TypeApply(fn, args) =>
- fallBackToOverridden(fn) match {
- case Some(fn1) => Some(TypeApply(fn1, args) setPos tree.pos)
- case _ => None
- }
- case _ =>
- macroTraceVerbose("unexpected tree in fallback: ")(tree)
- notFound()
+ case Select(qual, name) => Select(qual, name) setPos tree.pos setSymbol fallbackSym
+ case Apply(fn, args) => Apply(mkFallbackTree(fn), args) setPos tree.pos
+ case TypeApply(fn, args) => TypeApply(mkFallbackTree(fn), args) setPos tree.pos
}
}
- fallBackToOverridden(expandee) match {
- case Some(tree1) =>
- macroTraceLite("falling back to: ")(tree1)
- currentRun.macroExpansionFailed = true
- Fallback(tree1)
- case None =>
- fail(typer, expandee)
- }
- }
-
- private def handleMacroExpansionException(typer: Typer, expandee: Tree, ex: Throwable): MacroExpansionResult = {
- val realex = ReflectionUtils.unwrapThrowable(ex)
- realex match {
- case realex: reflect.macros.runtime.AbortMacroException =>
- macroLogVerbose("macro expansion has failed: %s".format(realex.msg))
- fail(typer, expandee) // error has been reported by abort
- case err: TypeError =>
- macroLogLite("macro expansion has failed: %s at %s".format(err.msg, err.pos))
- throw err // error should be propagated, don't report
- case _ =>
- val message = {
- try {
- // [Eugene] is there a better way?
- // [Paul] See Exceptional.scala and Origins.scala.
- val relevancyThreshold = realex.getStackTrace().indexWhere(_.getMethodName endsWith "macroExpand1")
- if (relevancyThreshold == -1) None
- else {
- var relevantElements = realex.getStackTrace().take(relevancyThreshold + 1)
- def isMacroInvoker(este: StackTraceElement) = este.isNativeMethod || (este.getClassName != null && (este.getClassName contains "fastTrack"))
- var threshold = relevantElements.reverse.indexWhere(isMacroInvoker) + 1
- while (threshold != relevantElements.length && isMacroInvoker(relevantElements(relevantElements.length - threshold - 1))) threshold += 1
- relevantElements = relevantElements dropRight threshold
-
- realex.setStackTrace(relevantElements)
- val message = new java.io.StringWriter()
- realex.printStackTrace(new java.io.PrintWriter(message))
- Some(EOL + message)
- }
- } catch {
- // if the magic above goes boom, just fall back to uninformative, but better than nothing, getMessage
- case ex: Throwable =>
- None
- }
- } getOrElse {
- val msg = realex.getMessage
- if (msg != null) msg else realex.getClass.getName
- }
- fail(typer, expandee, msg = "exception during macro expansion: " + message)
- }
+ Fallback(mkFallbackTree(expandee))
}
/** Without any restrictions on macro expansion, macro applications will expand at will,
diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala
index 3a6acdcc2b..3a930a195b 100644
--- a/src/reflect/scala/reflect/internal/TreeInfo.scala
+++ b/src/reflect/scala/reflect/internal/TreeInfo.scala
@@ -603,9 +603,10 @@ abstract class TreeInfo {
}
}
- def stripOffPrefixTypeRefinement(tpe: Type): Type =
- tpe.dealias match {
- case RefinedType(List(tpe), Scope(sym)) if tpe == MacroContextClass.tpe && sym.allOverriddenSymbols.contains(MacroContextPrefixType) => tpe
- case _ => tpe
- }
+ def isNullaryInvocation(tree: Tree): Boolean =
+ tree.symbol != null && tree.symbol.isMethod && (tree match {
+ case TypeApply(fun, _) => isNullaryInvocation(fun)
+ case tree: RefTree => true
+ case _ => false
+ })
}
diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala
index 9514898ce5..52dd4a55cf 100644
--- a/src/reflect/scala/reflect/internal/Types.scala
+++ b/src/reflect/scala/reflect/internal/Types.scala
@@ -6939,6 +6939,12 @@ trait Types extends api.Types { self: SymbolTable =>
else (ps :+ SerializableClass.tpe).toList
)
+ def stripOffPrefixTypeRefinement(tpe: Type): Type =
+ tpe.dealias match {
+ case RefinedType(List(tpe), Scope(sym)) if tpe == MacroContextClass.tpe && sym.allOverriddenSymbols.contains(MacroContextPrefixType) => tpe
+ case _ => tpe
+ }
+
def objToAny(tp: Type): Type =
if (!phase.erasedTypes && tp.typeSymbol == ObjectClass) AnyClass.tpe
else tp
diff --git a/test/files/neg/macro-invalidusage-badbounds.check b/test/files/neg/macro-invalidusage-badbounds-a.check
index fd0b64533e..fd0b64533e 100644
--- a/test/files/neg/macro-invalidusage-badbounds.check
+++ b/test/files/neg/macro-invalidusage-badbounds-a.check
diff --git a/test/files/neg/macro-invalidusage-badbounds.flags b/test/files/neg/macro-invalidusage-badbounds-a.flags
index cd66464f2f..cd66464f2f 100644
--- a/test/files/neg/macro-invalidusage-badbounds.flags
+++ b/test/files/neg/macro-invalidusage-badbounds-a.flags
diff --git a/test/files/neg/macro-invalidusage-badbounds-a/Impls_1.scala b/test/files/neg/macro-invalidusage-badbounds-a/Impls_1.scala
new file mode 100644
index 0000000000..6ee71a3628
--- /dev/null
+++ b/test/files/neg/macro-invalidusage-badbounds-a/Impls_1.scala
@@ -0,0 +1,5 @@
+import scala.reflect.macros.{Context => Ctx}
+
+object Impls {
+ def foo[U <: String](c: Ctx) = c.literalUnit
+}
diff --git a/test/files/neg/macro-invalidusage-badbounds/Macros_Test_2.scala b/test/files/neg/macro-invalidusage-badbounds-a/Macros_Test_2.scala
index 3139599108..3139599108 100644
--- a/test/files/neg/macro-invalidusage-badbounds/Macros_Test_2.scala
+++ b/test/files/neg/macro-invalidusage-badbounds-a/Macros_Test_2.scala
diff --git a/test/pending/neg/macro-invalidusage-badbounds-b.check b/test/pending/neg/macro-invalidusage-badbounds-b.check
new file mode 100644
index 0000000000..fd0b64533e
--- /dev/null
+++ b/test/pending/neg/macro-invalidusage-badbounds-b.check
@@ -0,0 +1,4 @@
+Macros_Test_2.scala:7: error: type arguments [Int] do not conform to macro method foo's type parameter bounds [U <: String]
+ foo[Int]
+ ^
+one error found
diff --git a/test/pending/neg/macro-invalidusage-badbounds-b.flags b/test/pending/neg/macro-invalidusage-badbounds-b.flags
new file mode 100644
index 0000000000..cd66464f2f
--- /dev/null
+++ b/test/pending/neg/macro-invalidusage-badbounds-b.flags
@@ -0,0 +1 @@
+-language:experimental.macros \ No newline at end of file
diff --git a/test/files/neg/macro-invalidusage-badbounds/Impls_1.scala b/test/pending/neg/macro-invalidusage-badbounds-b/Impls_1.scala
index 89020de7dd..89020de7dd 100644
--- a/test/files/neg/macro-invalidusage-badbounds/Impls_1.scala
+++ b/test/pending/neg/macro-invalidusage-badbounds-b/Impls_1.scala
diff --git a/test/pending/neg/macro-invalidusage-badbounds-b/Macros_Test_2.scala b/test/pending/neg/macro-invalidusage-badbounds-b/Macros_Test_2.scala
new file mode 100644
index 0000000000..3139599108
--- /dev/null
+++ b/test/pending/neg/macro-invalidusage-badbounds-b/Macros_Test_2.scala
@@ -0,0 +1,8 @@
+object Macros {
+ def foo[U <: String] = macro Impls.foo[U]
+}
+
+object Test extends App {
+ import Macros._
+ foo[Int]
+} \ No newline at end of file