summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugene Burmako <xeno.by@gmail.com>2012-01-20 01:52:40 +0100
committerEugene Burmako <xeno.by@gmail.com>2012-01-20 06:54:20 +0100
commit35e676ded0f9bfd006a5f090841abdea3ff1759c (patch)
treeade8247e0ebc66964189875194f78b85c6596d27
parent58cb15c40dc431e45eaa0a5278874d9996e42104 (diff)
downloadscala-35e676ded0f9bfd006a5f090841abdea3ff1759c.tar.gz
scala-35e676ded0f9bfd006a5f090841abdea3ff1759c.tar.bz2
scala-35e676ded0f9bfd006a5f090841abdea3ff1759c.zip
Progress with macros
A short recap: * Macro expansion now works finely for instance macro invocations * Macros are now hidden behind -Xmacros * Bodies of macros now have "import _context._" in their preamble * Macros are now loaded from classpath, much like regular libraries * Macros can now override methods (in that case macro expansion does not crash if macro is not found, it just falls back to super) Review by @odersky.
-rw-r--r--src/compiler/scala/reflect/internal/Definitions.scala1
-rw-r--r--src/compiler/scala/reflect/internal/StdNames.scala29
-rw-r--r--src/compiler/scala/reflect/runtime/JavaToScala.scala2
-rw-r--r--src/compiler/scala/reflect/runtime/Mirror.scala15
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala6
-rw-r--r--src/compiler/scala/tools/nsc/MacroContext.scala2
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Parsers.scala7
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaSettings.scala1
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Macros.scala136
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Namers.scala4
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala20
-rw-r--r--src/library/scala/reflect/ReflectionUtils.scala12
-rw-r--r--src/library/scala/reflect/api/Mirror.scala6
-rw-r--r--src/library/scala/reflect/macro/Context.scala (renamed from src/library/scala/reflect/api/MacroContext.scala)6
-rw-r--r--src/library/scala/reflect/package.scala2
-rw-r--r--test/files/macros/Printf.scala39
-rw-r--r--test/files/macros/Test.scala8
-rw-r--r--test/files/macros/macros_v0001.bat40
-rw-r--r--test/files/macros/macros_v0001.sh30
-rw-r--r--test/files/pos/macros.flags2
-rw-r--r--test/files/run/macro-range.check9
-rw-r--r--test/files/run/macro-range.flags1
-rw-r--r--test/files/run/macro-range/macro_range_1.scala94
-rw-r--r--test/files/run/macro-range/macro_range_2.scala94
24 files changed, 516 insertions, 50 deletions
diff --git a/src/compiler/scala/reflect/internal/Definitions.scala b/src/compiler/scala/reflect/internal/Definitions.scala
index d38b62cbb4..f7e276fdab 100644
--- a/src/compiler/scala/reflect/internal/Definitions.scala
+++ b/src/compiler/scala/reflect/internal/Definitions.scala
@@ -393,6 +393,7 @@ trait Definitions extends reflect.api.StandardDefinitions {
// scala.reflect
lazy val ReflectApiUniverse = getRequiredClass("scala.reflect.api.Universe")
+ lazy val ReflectMacroContext = getRequiredClass("scala.reflect.macro.Context")
lazy val ReflectRuntimeMirror = getRequiredModule("scala.reflect.runtime.Mirror")
def freeValueMethod = getMember(ReflectRuntimeMirror, nme.freeValue)
lazy val ReflectPackage = getPackageObject("scala.reflect")
diff --git a/src/compiler/scala/reflect/internal/StdNames.scala b/src/compiler/scala/reflect/internal/StdNames.scala
index 507621ea42..aba00088f9 100644
--- a/src/compiler/scala/reflect/internal/StdNames.scala
+++ b/src/compiler/scala/reflect/internal/StdNames.scala
@@ -13,7 +13,7 @@ import annotation.switch
trait StdNames extends NameManglers { self: SymbolTable =>
def encode(str: String): TermName = newTermNameCached(NameTransformer.encode(str))
-
+
implicit def lowerTermNames(n: TermName): String = "" + n
// implicit def stringToTermName(s: String): TermName = newTermName(s)
@@ -182,7 +182,7 @@ trait StdNames extends NameManglers { self: SymbolTable =>
trait TermNames extends Keywords with CommonNames {
// Compiler internal names
val EXPAND_SEPARATOR_STRING = "$$"
-
+
val ANYNAME: NameType = "<anyname>"
val CONSTRUCTOR: NameType = "<init>"
val FAKE_LOCAL_THIS: NameType = "this$"
@@ -207,7 +207,7 @@ trait StdNames extends NameManglers { self: SymbolTable =>
final val Predef: NameType = "Predef"
final val ScalaRunTime: NameType = "ScalaRunTime"
final val Some: NameType = "Some"
-
+
val _1 : NameType = "_1"
val _2 : NameType = "_2"
val _3 : NameType = "_3"
@@ -301,6 +301,8 @@ trait StdNames extends NameManglers { self: SymbolTable =>
val classOf: NameType = "classOf"
val clone_ : NameType = if (forMSIL) "MemberwiseClone" else "clone" // sn.OClone causes checkinit failure
val conforms: NameType = "conforms"
+ val context : NameType = "_context"
+ val contextImplicit : NameType = "$context"
val copy: NameType = "copy"
val delayedInit: NameType = "delayedInit"
val delayedInitArg: NameType = "delayedInit$body"
@@ -324,7 +326,6 @@ trait StdNames extends NameManglers { self: SymbolTable =>
val freeValue : NameType = "freeValue"
val genericArrayOps: NameType = "genericArrayOps"
val get: NameType = "get"
- val glob : NameType = "glob"
val hasNext: NameType = "hasNext"
val hashCode_ : NameType = if (forMSIL) "GetHashCode" else "hashCode"
val hash_ : NameType = "hash"
@@ -430,7 +431,7 @@ trait StdNames extends NameManglers { self: SymbolTable =>
val REFINE_CLASS_NAME: NameType = "<refinement>"
val ANON_CLASS_NAME: NameType = "$anon"
}
-
+
/** For fully qualified type names.
*/
object fulltpnme extends TypeNames {
@@ -450,11 +451,11 @@ trait StdNames extends NameManglers { self: SymbolTable =>
val RuntimeNothing = toBinary(fulltpnme.RuntimeNothing).toTypeName
val RuntimeNull = toBinary(fulltpnme.RuntimeNull).toTypeName
}
-
+
object fullnme extends TermNames {
type NameType = TermName
protected implicit def createNameType(name: String): TermName = newTermNameCached(name)
-
+
val MirrorPackage: NameType = "scala.reflect.mirror"
}
@@ -516,7 +517,7 @@ trait StdNames extends NameManglers { self: SymbolTable =>
def moduleVarName(name: TermName): TermName =
newTermNameCached("" + name + MODULE_VAR_SUFFIX)
-
+
val ROOTPKG: TermName = "_root_"
/** Base strings from which synthetic names are derived. */
@@ -531,7 +532,7 @@ trait StdNames extends NameManglers { self: SymbolTable =>
val INTERPRETER_VAR_PREFIX = "res"
val INTERPRETER_WRAPPER_SUFFIX = "$object"
val WHILE_PREFIX = "while$"
-
+
val EQEQ_LOCAL_VAR: TermName = newTermName(EQEQ_LOCAL_VAR_STRING)
def getCause = sn.GetCause
@@ -568,18 +569,18 @@ trait StdNames extends NameManglers { self: SymbolTable =>
val UNARY_+ = encode("unary_+")
val UNARY_- = encode("unary_-")
val UNARY_! = encode("unary_!")
-
+
// Grouped here so Cleanup knows what tests to perform.
val CommonOpNames = Set[Name](OR, XOR, AND, EQ, NE)
val ConversionNames = Set[Name](toByte, toChar, toDouble, toFloat, toInt, toLong, toShort)
val BooleanOpNames = Set[Name](ZOR, ZAND, UNARY_!) ++ CommonOpNames
val NumberOpNames = (
- Set[Name](ADD, SUB, MUL, DIV, MOD, LSL, LSR, ASR, LT, LE, GE, GT)
- ++ Set(UNARY_+, UNARY_-, UNARY_!)
+ Set[Name](ADD, SUB, MUL, DIV, MOD, LSL, LSR, ASR, LT, LE, GE, GT)
+ ++ Set(UNARY_+, UNARY_-, UNARY_!)
++ ConversionNames
++ CommonOpNames
)
-
+
val add: NameType = "add"
val complement: NameType = "complement"
val divide: NameType = "divide"
@@ -670,7 +671,7 @@ trait StdNames extends NameManglers { self: SymbolTable =>
reflMethodName
)
def isReflectionCacheName(name: Name) = reflectionCacheNames exists (name startsWith _)
-
+
@switch def productAccessorName(j: Int): TermName = j match {
case 1 => nme._1
case 2 => nme._2
diff --git a/src/compiler/scala/reflect/runtime/JavaToScala.scala b/src/compiler/scala/reflect/runtime/JavaToScala.scala
index 61b03a9a29..bc5d616ae3 100644
--- a/src/compiler/scala/reflect/runtime/JavaToScala.scala
+++ b/src/compiler/scala/reflect/runtime/JavaToScala.scala
@@ -45,7 +45,7 @@ trait JavaToScala extends ConversionUtil { self: SymbolTable =>
def javaClass(path: String): jClass[_] =
javaClass(path, defaultReflectiveClassLoader())
def javaClass(path: String, classLoader: JClassLoader): jClass[_] =
- classLoader.loadClass(path)
+ Class.forName(path, true, classLoader)
/** Does `path` correspond to a Java class with that fully qualified name? */
def isJavaClass(path: String): Boolean =
diff --git a/src/compiler/scala/reflect/runtime/Mirror.scala b/src/compiler/scala/reflect/runtime/Mirror.scala
index 9490dc4ad7..09a4bbe198 100644
--- a/src/compiler/scala/reflect/runtime/Mirror.scala
+++ b/src/compiler/scala/reflect/runtime/Mirror.scala
@@ -12,7 +12,16 @@ class Mirror extends Universe with RuntimeTypes with TreeBuildUtil with ToolBoxe
import definitions._
- def classWithName(name: String): Symbol = classToScala(javaClass(name))
+ def classWithName(name: String): Symbol = {
+ val clazz = javaClass(name, defaultReflectiveClassLoader())
+ classToScala(clazz)
+ }
+
+ def getCompanionObject(clazz: Symbol): AnyRef = {
+ val singleton = ReflectionUtils.singletonInstance(clazz.fullName, defaultReflectiveClassLoader())
+ singleton
+ }
+
def getClass(obj: AnyRef): Symbol = classToScala(obj.getClass)
def getType(obj: AnyRef): Type = typeToScala(obj.getClass)
// to do add getClass/getType for instances of primitive types, probably like this:
@@ -32,7 +41,9 @@ class Mirror extends Universe with RuntimeTypes with TreeBuildUtil with ToolBoxe
case nme.update => return Array.set(receiver, args(0).asInstanceOf[Int], args(1))
}
}
- methodToJava(meth).invoke(receiver, args.asInstanceOf[Seq[AnyRef]]: _*)
+
+ val jmeth = methodToJava(meth)
+ jmeth.invoke(receiver, args.asInstanceOf[Seq[AnyRef]]: _*)
}
override def classToType(jclazz: java.lang.Class[_]): Type = typeToScala(jclazz)
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala
index c8db996de2..e805b4e75e 100644
--- a/src/compiler/scala/tools/nsc/Global.scala
+++ b/src/compiler/scala/tools/nsc/Global.scala
@@ -901,6 +901,9 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb
/** Counts for certain classes of warnings during this run. */
var deprecationWarnings: List[(Position, String)] = Nil
var uncheckedWarnings: List[(Position, String)] = Nil
+
+ /** A flag whether macro expansions failed */
+ var macroExpansionFailed = false
/** Progress tracking. Measured in "progress units" which are 1 per
* compilation unit per phase completed.
@@ -1083,6 +1086,9 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb
)
warn(deprecationWarnings.size, "deprecation", settings.deprecation)
warn(uncheckedWarnings.size, "unchecked", settings.unchecked)
+ if (macroExpansionFailed)
+ 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/MacroContext.scala b/src/compiler/scala/tools/nsc/MacroContext.scala
index e739eade3a..72662291f8 100644
--- a/src/compiler/scala/tools/nsc/MacroContext.scala
+++ b/src/compiler/scala/tools/nsc/MacroContext.scala
@@ -2,7 +2,7 @@ package scala.tools.nsc
import symtab.Flags._
-trait MacroContext extends reflect.api.MacroContext { self: Global =>
+trait MacroContext extends reflect.macro.Context { self: Global =>
def captureVariable(vble: Symbol): Unit = vble setFlag CAPTURED
diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
index ce41bc456e..fe6dcc9138 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
@@ -2449,7 +2449,7 @@ self =>
else {
val nameOffset = in.offset
val name = ident()
- if (name == nme.macro_ && isIdent && settings.Xexperimental.value)
+ if (name == nme.macro_ && isIdent && settings.Xmacros.value)
funDefRest(start, in.offset, mods | Flags.MACRO, ident())
else
funDefRest(start, nameOffset, mods, name)
@@ -2480,6 +2480,9 @@ self =>
restype = scalaUnitConstr
blockExpr()
} else {
+ if (name == nme.macro_ && isIdent && in.token != EQUALS) {
+ warning("this syntactically invalid code resembles a macro definition. have you forgotten to enable -Xmacros?")
+ }
equalsExpr()
}
DefDef(newmods, name, tparams, vparamss, restype, rhs)
@@ -2539,7 +2542,7 @@ self =>
newLinesOpt()
atPos(start, in.offset) {
val name = identForType()
- if (name == nme.macro_.toTypeName && isIdent && settings.Xexperimental.value) {
+ if (name == nme.macro_.toTypeName && isIdent && settings.Xmacros.value) {
funDefRest(start, in.offset, mods | Flags.MACRO, identForType())
} else {
// @M! a type alias as well as an abstract type may declare type parameters
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
index efd5323ce2..6806ca03ba 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -96,6 +96,7 @@ trait ScalaSettings extends AbsScalaSettings
val Xexperimental = BooleanSetting ("-Xexperimental", "Enable experimental extensions.") .
withPostSetHook(set => List(YmethodInfer, overrideObjects) foreach (_.value = set.value))
// YdepMethTpes, YvirtClasses,
+ val Xmacros = BooleanSetting ("-Xmacros", "Enable macros.")
/** Compatibility stubs for options whose value name did
* not previously match the option name.
diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala
index b9264aae55..7f9e56a926 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala
@@ -2,6 +2,8 @@ package scala.tools.nsc
package typechecker
import symtab.Flags._
+import scala.tools.nsc.util._
+import scala.reflect.ReflectionUtils
trait Macros { self: Analyzer =>
import global._
@@ -13,6 +15,20 @@ trait Macros { self: Analyzer =>
owner.info.decl(nme.macroMethodName(mac.name))
}
+ def macroArgs(tree: Tree): (List[List[Tree]]) = tree match {
+ case Apply(fn, args) =>
+ macroArgs(fn) :+ args
+ case TypeApply(fn, args) =>
+ macroArgs(fn) :+ args
+ case Select(qual, name) if !isStaticMacro(tree.symbol) =>
+ List(List(qual))
+ case _ =>
+ List(List())
+ }
+
+ private def isStaticMacro(mac: Symbol): Boolean =
+ mac.owner.isModuleClass
+
/**
* The definition of the method implementing a macro. Example:
* Say we have in a class C
@@ -33,25 +49,32 @@ trait Macros { self: Analyzer =>
*/
def macroMethDef(mdef: DefDef): Tree = {
def paramDef(name: Name, tpt: Tree) = ValDef(Modifiers(PARAM), name, tpt, EmptyTree)
- val universeType = TypeTree(ReflectApiUniverse.tpe)
- val globParamSec = List(paramDef(nme.glob, universeType))
- def globSelect(name: Name) = Select(Ident(nme.glob), name)
+ val contextType = TypeTree(ReflectMacroContext.tpe)
+ val globParamSec = List(paramDef(nme.context, contextType))
+ def globSelect(name: Name) = Select(Ident(nme.context), name)
def globTree = globSelect(newTypeName("Tree"))
def globType = globSelect(newTypeName("Type"))
- val thisParamSec = if (mdef.symbol.owner.isModuleClass) List() else List(paramDef(newTermName("_this"), globTree))
+ val thisParamSec = if (isStaticMacro(mdef.symbol)) List() else List(paramDef(newTermName("_this"), globTree))
def tparamInMacro(tdef: TypeDef) = paramDef(tdef.name.toTermName, globType)
- def vparamInMacro(vdef: ValDef): ValDef = paramDef(vdef.name, globTree)
+ def vparamInMacro(vdef: ValDef): ValDef = paramDef(vdef.name, vdef.tpt match {
+ case tpt @ AppliedTypeTree(hk, _) if treeInfo.isRepeatedParamType(tpt) => AppliedTypeTree(hk, List(globTree))
+ case _ => globTree
+ })
def wrapImplicit(tree: Tree) = atPos(tree.pos) {
- Block(List(ValDef(Modifiers(IMPLICIT), newTermName("$" + nme.glob), universeType, Ident(nme.glob))), tree)
+ // implicit hasn't proven useful so far, so I'm disabling it
+ //val implicitDecl = ValDef(Modifiers(IMPLICIT), nme.contextImplicit, SingletonTypeTree(Ident(nme.context)), Ident(nme.context))
+ val importGlob = Import(Ident(nme.context), List(ImportSelector(nme.WILDCARD, -1, null, -1)))
+ Block(List(importGlob), tree)
}
+ var formals = (mdef.vparamss map (_ map vparamInMacro))
+ if (mdef.tparams.nonEmpty) formals = (mdef.tparams map tparamInMacro) :: formals
atPos(mdef.pos) {
new DefDef( // can't call DefDef here; need to find out why
- mods = mdef.mods &~ MACRO,
+ mods = mdef.mods &~ MACRO &~ OVERRIDE,
name = nme.macroMethodName(mdef.name),
tparams = List(),
- vparamss = globParamSec :: thisParamSec :: (mdef.tparams map tparamInMacro) ::
- (mdef.vparamss map (_ map vparamInMacro)),
+ vparamss = globParamSec :: thisParamSec :: formals,
tpt = globTree,
wrapImplicit(mdef.rhs))
}
@@ -59,11 +82,98 @@ trait Macros { self: Analyzer =>
def addMacroMethods(templ: Template, namer: Namer): Unit = {
for (ddef @ DefDef(mods, _, _, _, _, _) <- templ.body if mods hasFlag MACRO) {
- val sym = namer.enterSyntheticSym(util.trace("macro def: ")(macroMethDef(ddef)))
- println("added to "+namer.context.owner.enclClass+": "+sym)
+ val trace = scala.tools.nsc.util.trace when settings.debug.value
+ val sym = namer.enterSyntheticSym(trace("macro def: ")(macroMethDef(ddef)))
+ trace("added to "+namer.context.owner.enclClass+": ")(sym)
}
}
- def macroExpand(tree: Tree): Tree = ???
+ lazy val mirror = new scala.reflect.runtime.Mirror {
+ lazy val libraryClassLoader = {
+ val classpath = global.classPath.asURLs
+ ScalaClassLoader.fromURLs(classpath, self.getClass.getClassLoader)
+ }
+
+ override def defaultReflectiveClassLoader() = libraryClassLoader
+ }
+
+ class MacroExpandError(val msg: String) extends Exception(msg)
-} \ No newline at end of file
+ /** Return optionally address of companion object and implementation method symbol
+ * of given macro; or None if implementation classfile cannot be loaded or does
+ * not contain the macro implementation.
+ */
+ def macroImpl(mac: Symbol): Option[(AnyRef, mirror.Symbol)] = {
+ try {
+ val mmeth = macroMeth(mac)
+ if (mmeth == NoSymbol) None
+ else {
+ val receiverClass: mirror.Symbol = mirror.classWithName(mmeth.owner.fullName)
+ val receiverObj = receiverClass.companionModule
+ if (receiverObj == NoSymbol) None
+ else {
+ val receiver = mirror.getCompanionObject(receiverClass)
+ val rmeth = receiverObj.info.member(mirror.newTermName(mmeth.name.toString))
+ Some((receiver, rmeth))
+ }
+ }
+ } catch {
+ case ex: ClassNotFoundException =>
+ None
+ }
+ }
+
+ /** Return result of macro expansion.
+ * Or, if that fails, and the macro overrides a method return
+ * tree that calls this method instead of the macro.
+ */
+ def macroExpand(tree: Tree): Any = {
+ val macroDef = tree.symbol
+ macroImpl(macroDef) match {
+ case Some((receiver, rmeth)) =>
+ val argss = List(global) :: macroArgs(tree)
+ val paramss = macroMeth(macroDef).paramss
+ val rawArgss = for ((as, ps) <- argss zip paramss) yield {
+ if (isVarArgsList(ps)) as.take(ps.length - 1) :+ as.drop(ps.length - 1)
+ else as
+ }
+ val rawArgs: Seq[Any] = rawArgss.flatten
+ try {
+ mirror.invoke(receiver, rmeth, rawArgs: _*)
+ } catch {
+ case ex =>
+ val realex = ReflectionUtils.unwrapThrowable(ex)
+ val stacktrace = new java.io.StringWriter()
+ realex.printStackTrace(new java.io.PrintWriter(stacktrace))
+ val msg = System.getProperty("line.separator") + stacktrace
+ throw new MacroExpandError("exception during macro expansion: " + msg)
+ }
+ case None =>
+ val trace = scala.tools.nsc.util.trace when settings.debug.value
+ def notFound() = throw new MacroExpandError("macro implementation not found: " + macroDef.name)
+ def fallBackToOverridden(tree: Tree): Tree = {
+ tree match {
+ case Select(qual, name) if (macroDef.isMacro) =>
+ macroDef.allOverriddenSymbols match {
+ case first :: others =>
+ return Select(qual, name) setPos tree.pos setSymbol first
+ case _ =>
+ trace("macro is not overridden: ")(tree)
+ notFound()
+ }
+ case Apply(fn, args) =>
+ Apply(fallBackToOverridden(fn), args) setPos tree.pos
+ case TypeApply(fn, args) =>
+ TypeApply(fallBackToOverridden(fn), args) setPos tree.pos
+ case _ =>
+ trace("unexpected tree in fallback: ")(tree)
+ notFound()
+ }
+ }
+ val tree1 = fallBackToOverridden(tree)
+ trace("falling back to ")(tree1)
+ currentRun.macroExpansionFailed = true
+ tree1
+ }
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala
index 354b8caaa3..e04d89047b 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala
@@ -842,10 +842,10 @@ trait Namers extends MethodSynthesis {
Namers.this.classOfModuleClass get clazz foreach { cdefRef =>
val cdef = cdefRef()
if (cdef.mods.isCase) addApplyUnapply(cdef, templateNamer)
- addMacroMethods(cdef.impl, templateNamer)
+ if (settings.Xmacros.value) addMacroMethods(cdef.impl, templateNamer)
classOfModuleClass -= clazz
}
- addMacroMethods(templ, templateNamer)
+ if (settings.Xmacros.value) addMacroMethods(templ, templateNamer)
}
// add the copy method to case classes; this needs to be done here, not in SyntheticMethods, because
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index 884ad7af3d..5aaad9da2f 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -911,7 +911,10 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser {
}
if (tree.isType)
adaptType()
- else if ((mode & (PATTERNmode | FUNmode)) == (PATTERNmode | FUNmode))
+ else if (inExprModeButNot(mode, FUNmode) && tree.symbol != null && tree.symbol.isMacro && !tree.isDef) {
+ val tree1 = expandMacro(tree)
+ if (tree1.isErroneous) tree1 else typed(tree1, mode, pt)
+ } else if ((mode & (PATTERNmode | FUNmode)) == (PATTERNmode | FUNmode))
adaptConstrPattern()
else if (inAllModes(mode, EXPRmode | FUNmode) &&
!tree.tpe.isInstanceOf[MethodType] &&
@@ -3471,9 +3474,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser {
// (calling typed1 more than once for the same tree)
if (checked ne res) typed { atPos(tree.pos)(checked) }
else res
- } else if ((mode & FUNmode) == 0 && fun2.hasSymbol && fun2.symbol.isMacro)
- typed1(macroExpand(res), mode, pt)
- else
+ } else
res
case ex: TypeError =>
fun match {
@@ -3483,7 +3484,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser {
if (treeInfo.isVariableOrGetter(qual1)) {
stopTimer(failedOpEqNanos, opeqStart)
convertToAssignment(fun, qual1, name, args, ex)
- }
+ }
else {
stopTimer(failedApplyNanos, appStart)
reportTypeError(fun.pos, ex)
@@ -4430,6 +4431,15 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser {
}
}
+ def expandMacro(tree: Tree): Tree = try {
+ macroExpand(tree) match {
+ case t: Tree => t
+ case t => errorTree(tree, "macros must return a compiler-specific tree; returned class is: " + t.getClass)
+ }
+ } catch {
+ case ex: MacroExpandError => errorTree(tree, ex.msg)
+ }
+
def atOwner(owner: Symbol): Typer =
newTyper(context.make(context.tree, owner))
diff --git a/src/library/scala/reflect/ReflectionUtils.scala b/src/library/scala/reflect/ReflectionUtils.scala
index b63a8645de..dfadfb4976 100644
--- a/src/library/scala/reflect/ReflectionUtils.scala
+++ b/src/library/scala/reflect/ReflectionUtils.scala
@@ -27,11 +27,15 @@ object ReflectionUtils {
case ex if pf isDefinedAt unwrapThrowable(ex) => pf(unwrapThrowable(ex))
}
- // Retrieves the MODULE$ field for the given class name.
- def singletonInstance(className: String, cl: ClassLoader = getClass.getClassLoader): Option[AnyRef] = {
+ def singletonInstance(className: String, cl: ClassLoader = getClass.getClassLoader): AnyRef = {
val name = if (className endsWith "$") className else className + "$"
+ val clazz = java.lang.Class.forName(name, true, cl)
+ val singleton = clazz getField "MODULE$" get null
+ singleton
+ }
- try Some(java.lang.Class.forName(name, true, cl) getField "MODULE$" get null)
+ // Retrieves the MODULE$ field for the given class name.
+ def singletonInstanceOpt(className: String, cl: ClassLoader = getClass.getClassLoader): Option[AnyRef] =
+ try Some(singletonInstance(className, cl))
catch { case _: ClassNotFoundException => None }
- }
}
diff --git a/src/library/scala/reflect/api/Mirror.scala b/src/library/scala/reflect/api/Mirror.scala
index 53ac84f8cb..136f52b05f 100644
--- a/src/library/scala/reflect/api/Mirror.scala
+++ b/src/library/scala/reflect/api/Mirror.scala
@@ -13,7 +13,11 @@ trait Mirror extends Universe with RuntimeTypes with TreeBuildUtil {
* to do: throws anything else?
*/
def classWithName(name: String): Symbol
-
+
+ /** Return a reference to the companion object of this class symbol
+ */
+ def getCompanionObject(clazz: Symbol): AnyRef
+
/** The Scala class symbol corresponding to the runtime class of given object
* @param The object from which the class is returned
* @throws ?
diff --git a/src/library/scala/reflect/api/MacroContext.scala b/src/library/scala/reflect/macro/Context.scala
index e23357d26e..d0a2787fdf 100644
--- a/src/library/scala/reflect/api/MacroContext.scala
+++ b/src/library/scala/reflect/macro/Context.scala
@@ -1,7 +1,7 @@
package scala.reflect
-package api
+package macro
-trait MacroContext extends Universe {
+trait Context extends api.Universe {
/** Mark a variable as captured; i.e. force boxing in a *Ref type.
*/
@@ -12,4 +12,4 @@ trait MacroContext extends Universe {
*/
def referenceCapturedVariable(id: Ident): Tree
-} \ No newline at end of file
+}
diff --git a/src/library/scala/reflect/package.scala b/src/library/scala/reflect/package.scala
index 62592baa27..1c3e618520 100644
--- a/src/library/scala/reflect/package.scala
+++ b/src/library/scala/reflect/package.scala
@@ -8,7 +8,7 @@ package object reflect {
// initialization, but in response to a doomed attempt to utilize it.
lazy val mirror: api.Mirror = {
// we use (Java) reflection here so that we can keep reflect.runtime and reflect.internals in a seperate jar
- ReflectionUtils.singletonInstance("scala.reflect.runtime.Mirror") collect { case x: api.Mirror => x } getOrElse {
+ ReflectionUtils.singletonInstanceOpt("scala.reflect.runtime.Mirror") collect { case x: api.Mirror => x } getOrElse {
throw new UnsupportedOperationException("Scala reflection not available on this platform")
}
}
diff --git a/test/files/macros/Printf.scala b/test/files/macros/Printf.scala
new file mode 100644
index 0000000000..4a88e5b069
--- /dev/null
+++ b/test/files/macros/Printf.scala
@@ -0,0 +1,39 @@
+// macros should be built separately from their clients, so simple "scalac Printf.scala Test.scala" won't work
+// 1) first build this file with "scalac -Xmacros Printf.scala"
+// 2) the build the test with "scalac -cp <output directory of compiling Printf.scala> Test.scala"
+
+object Printf extends App {
+ def macro printf(format: String, params: Any*) : String = {
+ var i = 0
+ def gensym(name: String) = { i += 1; newTermName(name + i) }
+
+ def createTempValDef(value: Tree, clazz: Class[_]): (Option[Tree], Tree) = {
+ val local = gensym("temp")
+ val tpe = if (clazz == classOf[Int]) Ident(newTypeName("Int"))
+ else if (clazz == classOf[String]) Select(Select(Ident(newTermName("java")), newTermName("lang")), newTypeName("String"))
+ else throw new Exception("unknown class " + clazz.toString)
+ (Some(ValDef(Modifiers(), local, tpe, value)), Ident(local))
+ }
+
+ def tree_printf(format: Tree, params: Tree*) = {
+ val Literal(Constant(s_format: String)) = format
+ val paramsStack = scala.collection.mutable.Stack(params: _*)
+ val parsed = s_format.split("(?<=%[\\w%])|(?=%[\\w%])") map {
+ case "%d" => createTempValDef(paramsStack.pop, classOf[Int])
+ case "%s" => createTempValDef(paramsStack.pop, classOf[String])
+ case "%%" => (None, Literal(Constant("%")))
+ case part => (None, Literal(Constant(part)))
+ }
+
+ val evals = for ((Some(eval), _) <- parsed if eval != None) yield eval
+ val prints = for ((_, ref) <- parsed) yield {
+ val print = Select(Select(Ident(newTermName("scala")), newTermName("Predef")), newTermName("print"))
+ Apply(print, List(ref))
+ }
+
+ Block((evals ++ prints).toList, Literal(Constant(())))
+ }
+
+ tree_printf(format, params: _*)
+ }
+}
diff --git a/test/files/macros/Test.scala b/test/files/macros/Test.scala
new file mode 100644
index 0000000000..d8cdcf6756
--- /dev/null
+++ b/test/files/macros/Test.scala
@@ -0,0 +1,8 @@
+// macros should be built separately from their clients, so simple "scalac Printf.scala Test.scala" won't work
+// 1) first build the printf macro with "scalac -Xmacros Printf.scala"
+// 2) the build this file with "scalac -cp <output directory of compiling Printf.scala> Test.scala"
+
+object Test extends App {
+ import Printf._
+ printf("hello %s", "world")
+} \ No newline at end of file
diff --git a/test/files/macros/macros_v0001.bat b/test/files/macros/macros_v0001.bat
new file mode 100644
index 0000000000..3395d2e3c1
--- /dev/null
+++ b/test/files/macros/macros_v0001.bat
@@ -0,0 +1,40 @@
+@echo off
+
+set scalahome=%~dp0\..\..\..
+set scaladeps=%scalahome%\lib\jline.jar;%scalahome%\lib\fjbg.jar
+set scalalib=%scalahome%\build\pack\lib\scala-library.jar
+if not exist "%scalalib%" set scalalib=%scalahome%\build\locker\classes\library
+set scalacomp="%scalahome%\build\pack\lib\scala-compiler.jar"
+if not exist "%scalacomp%" set scalacomp=%scalahome%\build\locker\classes\compiler
+set stdcp=%scaladeps%;%scalalib%;%scalacomp%
+
+echo Compiling macros...
+set cp=%stdcp%
+call :scalac -Xmacros "%~dp0\Printf.scala"
+
+echo Compiling the program...
+set cp=%stdcp%;%~dp0.
+call :scalac "%~dp0\Test.scala"
+
+echo.
+echo NOW LOOK!!!
+echo ===============================================
+set cp=%stdcp%;%~dp0.
+call :scala Test
+echo.
+echo ===============================================
+goto :eof
+
+:scalac
+setlocal
+call set args=%*
+rem echo java -cp "%cp%" -Dscala.usejavacp=true scala.tools.nsc.Main %args%
+java -cp "%cp%" -Dscala.usejavacp=true scala.tools.nsc.Main %args%
+endlocal&goto :eof
+
+:scala
+setlocal
+call set args=%*
+rem echo java -cp "%cp%" -Dscala.usejavacp=true scala.tools.nsc.MainGenericRunner %args%
+java -cp "%cp%" -Dscala.usejavacp=true scala.tools.nsc.MainGenericRunner %args%
+endlocal&goto :eof
diff --git a/test/files/macros/macros_v0001.sh b/test/files/macros/macros_v0001.sh
new file mode 100644
index 0000000000..abe09836bb
--- /dev/null
+++ b/test/files/macros/macros_v0001.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+set -o errexit
+
+if [[ $(uname -s) == CYGWIN* ]]; then cpsep=";"; else cpsep=":"; fi
+scripthome="$(dirname "$0")"
+scalahome="$scripthome/../../.."
+scaladeps="$scalahome/lib/jline.jar;$scalahome/lib/fjbg.jar"
+scalalib="$scalahome/build/pack/lib/scala-library.jar"
+if [ ! -f "$scalalib" ]; then scalalib="$scalahome/build/locker/classes/library"; fi
+scalacomp="$scalahome/build/pack/lib/scala-compiler.jar"
+if [ ! -f "$scalacomp" ]; then scalacomp="$scalahome/build/locker/classes/compiler"; fi
+stdcp="$scaladeps$cpsep$scalalib$cpsep$scalacomp"
+function scalac { java -cp "$cp" -Dscala.usejavacp=true scala.tools.nsc.Main $*; }
+function scala { java -cp "$cp" -Dscala.usejavacp=true scala.tools.nsc.MainGenericRunner $*; }
+
+echo "Compiling macros..."
+cp="$stdcp"
+scalac -Xmacros "$scripthome/Printf.scala"
+
+echo "Compiling the program..."
+cp="$stdcp$cpsep$scripthome"
+scalac "$scripthome/Test.scala"
+
+echo ""
+echo "NOW LOOK"
+echo "==============================================="
+cp="$stdcp$cpsep$scripthome"
+scala Test
+echo ""
+echo "==============================================="
diff --git a/test/files/pos/macros.flags b/test/files/pos/macros.flags
index e1b37447c9..7fea2ff901 100644
--- a/test/files/pos/macros.flags
+++ b/test/files/pos/macros.flags
@@ -1 +1 @@
--Xexperimental \ No newline at end of file
+-Xmacros \ No newline at end of file
diff --git a/test/files/run/macro-range.check b/test/files/run/macro-range.check
new file mode 100644
index 0000000000..0719398930
--- /dev/null
+++ b/test/files/run/macro-range.check
@@ -0,0 +1,9 @@
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/test/files/run/macro-range.flags b/test/files/run/macro-range.flags
new file mode 100644
index 0000000000..06a7b31f11
--- /dev/null
+++ b/test/files/run/macro-range.flags
@@ -0,0 +1 @@
+-Xmacros
diff --git a/test/files/run/macro-range/macro_range_1.scala b/test/files/run/macro-range/macro_range_1.scala
new file mode 100644
index 0000000000..15a018fcff
--- /dev/null
+++ b/test/files/run/macro-range/macro_range_1.scala
@@ -0,0 +1,94 @@
+import reflect.api.Modifier
+import reflect.macro.Context
+
+abstract class RangeDefault {
+ val from, to: Int
+ def foreach(f: Int => Unit) = {
+ var i = from
+ while (i < to) { f(i); i += 1 }
+ }
+}
+
+/** This class should go into reflect.macro once it is a bit more stable. */
+abstract class Utils {
+ val context: Context
+ import context._
+
+ class TreeSubstituter(from: List[Symbol], to: List[Tree]) extends Transformer {
+ override def transform(tree: Tree): Tree = tree match {
+ case Ident(_) =>
+ def subst(from: List[Symbol], to: List[Tree]): Tree =
+ if (from.isEmpty) tree
+ else if (tree.symbol == from.head) to.head.duplicate // TODO: does it ever make sense *not* to perform a shallowDuplicate on `to.head`?
+ else subst(from.tail, to.tail);
+ subst(from, to)
+ case _ =>
+ val tree1 = super.transform(tree)
+ if (tree1 ne tree) tree1.tpe = null
+ tree1
+ }
+ }
+ def makeApply(fn: Tree, args: List[Tree]): Tree = fn match {
+ case Function(vparams, body) =>
+ new TreeSubstituter(vparams map (_.symbol), args) transform body
+ case Block(stats, expr) =>
+ Block(stats, makeApply(expr, args))
+ case _ =>
+ println("no beta on "+fn+" "+fn.getClass)
+ Apply(fn, args)
+ }
+ def makeWhile(lname: TermName, cond: Tree, body: Tree): Tree = {
+ val continu = Apply(Ident(lname), Nil)
+ val rhs = If(cond, Block(List(body), continu), Literal(Constant()))
+ LabelDef(lname, Nil, rhs)
+ }
+ def makeBinop(left: Tree, op: String, right: Tree): Tree =
+ Apply(Select(left, newTermName(op)), List(right))
+}
+
+class Range(val from: Int, val to: Int) extends RangeDefault {
+ override def macro foreach(f: Int => Unit): Unit = {
+ println("macro-expand, _this = "+ _this)
+ import _context._
+ object utils extends Utils {
+ val context: _context.type = _context
+ }
+ import utils._
+
+ val initName = newTermName("<init>")
+ // Either:
+ // scala"{ var i = $low; val h = $hi; while (i < h) { $f(i); i = i + 1 } }
+ // or:
+ // scala"($_this: RangeDefault).foreach($f)"
+ _this match {
+ case Apply(Select(New(tpt), initName), List(lo, hi)) if tpt.symbol.fullName == "Range" =>
+ val iname = newTermName("$i")
+ val hname = newTermName("$h")
+ def iref = Ident(iname)
+ def href = Ident(hname)
+ val labelname = newTermName("$while")
+ val cond = makeBinop(iref, "$less", href)
+ val body = Block(
+ List(makeApply(f, List(iref))),
+ Assign(iref, makeBinop(iref, "$plus", Literal(Constant(1)))))
+ tools.nsc.util.trace("generated: ")(
+ Block(
+ List(
+ ValDef(Modifiers(Set(Modifier.mutable)), iname, TypeTree(), lo),
+ ValDef(Modifiers(), hname, TypeTree(), hi)),
+ makeWhile(labelname, cond, body)))
+ case _ =>
+ Apply(
+ Select(
+ Typed(_this, Ident(newTypeName("RangeDefault"))),
+ newTermName("foreach")),
+ List(f))
+ }
+ }
+}
+
+object Test extends App {
+
+ new Range(1, 10) foreach println
+
+}
diff --git a/test/files/run/macro-range/macro_range_2.scala b/test/files/run/macro-range/macro_range_2.scala
new file mode 100644
index 0000000000..15a018fcff
--- /dev/null
+++ b/test/files/run/macro-range/macro_range_2.scala
@@ -0,0 +1,94 @@
+import reflect.api.Modifier
+import reflect.macro.Context
+
+abstract class RangeDefault {
+ val from, to: Int
+ def foreach(f: Int => Unit) = {
+ var i = from
+ while (i < to) { f(i); i += 1 }
+ }
+}
+
+/** This class should go into reflect.macro once it is a bit more stable. */
+abstract class Utils {
+ val context: Context
+ import context._
+
+ class TreeSubstituter(from: List[Symbol], to: List[Tree]) extends Transformer {
+ override def transform(tree: Tree): Tree = tree match {
+ case Ident(_) =>
+ def subst(from: List[Symbol], to: List[Tree]): Tree =
+ if (from.isEmpty) tree
+ else if (tree.symbol == from.head) to.head.duplicate // TODO: does it ever make sense *not* to perform a shallowDuplicate on `to.head`?
+ else subst(from.tail, to.tail);
+ subst(from, to)
+ case _ =>
+ val tree1 = super.transform(tree)
+ if (tree1 ne tree) tree1.tpe = null
+ tree1
+ }
+ }
+ def makeApply(fn: Tree, args: List[Tree]): Tree = fn match {
+ case Function(vparams, body) =>
+ new TreeSubstituter(vparams map (_.symbol), args) transform body
+ case Block(stats, expr) =>
+ Block(stats, makeApply(expr, args))
+ case _ =>
+ println("no beta on "+fn+" "+fn.getClass)
+ Apply(fn, args)
+ }
+ def makeWhile(lname: TermName, cond: Tree, body: Tree): Tree = {
+ val continu = Apply(Ident(lname), Nil)
+ val rhs = If(cond, Block(List(body), continu), Literal(Constant()))
+ LabelDef(lname, Nil, rhs)
+ }
+ def makeBinop(left: Tree, op: String, right: Tree): Tree =
+ Apply(Select(left, newTermName(op)), List(right))
+}
+
+class Range(val from: Int, val to: Int) extends RangeDefault {
+ override def macro foreach(f: Int => Unit): Unit = {
+ println("macro-expand, _this = "+ _this)
+ import _context._
+ object utils extends Utils {
+ val context: _context.type = _context
+ }
+ import utils._
+
+ val initName = newTermName("<init>")
+ // Either:
+ // scala"{ var i = $low; val h = $hi; while (i < h) { $f(i); i = i + 1 } }
+ // or:
+ // scala"($_this: RangeDefault).foreach($f)"
+ _this match {
+ case Apply(Select(New(tpt), initName), List(lo, hi)) if tpt.symbol.fullName == "Range" =>
+ val iname = newTermName("$i")
+ val hname = newTermName("$h")
+ def iref = Ident(iname)
+ def href = Ident(hname)
+ val labelname = newTermName("$while")
+ val cond = makeBinop(iref, "$less", href)
+ val body = Block(
+ List(makeApply(f, List(iref))),
+ Assign(iref, makeBinop(iref, "$plus", Literal(Constant(1)))))
+ tools.nsc.util.trace("generated: ")(
+ Block(
+ List(
+ ValDef(Modifiers(Set(Modifier.mutable)), iname, TypeTree(), lo),
+ ValDef(Modifiers(), hname, TypeTree(), hi)),
+ makeWhile(labelname, cond, body)))
+ case _ =>
+ Apply(
+ Select(
+ Typed(_this, Ident(newTypeName("RangeDefault"))),
+ newTermName("foreach")),
+ List(f))
+ }
+ }
+}
+
+object Test extends App {
+
+ new Range(1, 10) foreach println
+
+}