From 35e676ded0f9bfd006a5f090841abdea3ff1759c Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Fri, 20 Jan 2012 01:52:40 +0100 Subject: 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. --- .../scala/reflect/internal/Definitions.scala | 1 + src/compiler/scala/reflect/internal/StdNames.scala | 29 ++--- .../scala/reflect/runtime/JavaToScala.scala | 2 +- src/compiler/scala/reflect/runtime/Mirror.scala | 15 ++- src/compiler/scala/tools/nsc/Global.scala | 6 + src/compiler/scala/tools/nsc/MacroContext.scala | 2 +- .../scala/tools/nsc/ast/parser/Parsers.scala | 7 +- .../scala/tools/nsc/settings/ScalaSettings.scala | 1 + .../scala/tools/nsc/typechecker/Macros.scala | 136 +++++++++++++++++++-- .../scala/tools/nsc/typechecker/Namers.scala | 4 +- .../scala/tools/nsc/typechecker/Typers.scala | 20 ++- src/library/scala/reflect/ReflectionUtils.scala | 12 +- src/library/scala/reflect/api/MacroContext.scala | 15 --- src/library/scala/reflect/api/Mirror.scala | 6 +- src/library/scala/reflect/macro/Context.scala | 15 +++ src/library/scala/reflect/package.scala | 2 +- test/files/macros/Printf.scala | 39 ++++++ test/files/macros/Test.scala | 8 ++ test/files/macros/macros_v0001.bat | 40 ++++++ test/files/macros/macros_v0001.sh | 30 +++++ test/files/pos/macros.flags | 2 +- test/files/run/macro-range.check | 9 ++ test/files/run/macro-range.flags | 1 + test/files/run/macro-range/macro_range_1.scala | 94 ++++++++++++++ test/files/run/macro-range/macro_range_2.scala | 94 ++++++++++++++ 25 files changed, 528 insertions(+), 62 deletions(-) delete mode 100644 src/library/scala/reflect/api/MacroContext.scala create mode 100644 src/library/scala/reflect/macro/Context.scala create mode 100644 test/files/macros/Printf.scala create mode 100644 test/files/macros/Test.scala create mode 100644 test/files/macros/macros_v0001.bat create mode 100644 test/files/macros/macros_v0001.sh create mode 100644 test/files/run/macro-range.check create mode 100644 test/files/run/macro-range.flags create mode 100644 test/files/run/macro-range/macro_range_1.scala create mode 100644 test/files/run/macro-range/macro_range_2.scala 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 = "" val CONSTRUCTOR: NameType = "" 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 = "" 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/MacroContext.scala b/src/library/scala/reflect/api/MacroContext.scala deleted file mode 100644 index e23357d26e..0000000000 --- a/src/library/scala/reflect/api/MacroContext.scala +++ /dev/null @@ -1,15 +0,0 @@ -package scala.reflect -package api - -trait MacroContext extends Universe { - - /** Mark a variable as captured; i.e. force boxing in a *Ref type. - */ - def captureVariable(vble: Symbol): Unit - - /** Mark given identifier as a reference to a captured variable itself - * suppressing dereferencing with the `elem` field. - */ - def referenceCapturedVariable(id: Ident): Tree - -} \ No newline at end of file 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/macro/Context.scala b/src/library/scala/reflect/macro/Context.scala new file mode 100644 index 0000000000..d0a2787fdf --- /dev/null +++ b/src/library/scala/reflect/macro/Context.scala @@ -0,0 +1,15 @@ +package scala.reflect +package macro + +trait Context extends api.Universe { + + /** Mark a variable as captured; i.e. force boxing in a *Ref type. + */ + def captureVariable(vble: Symbol): Unit + + /** Mark given identifier as a reference to a captured variable itself + * suppressing dereferencing with the `elem` field. + */ + def referenceCapturedVariable(id: Ident): Tree + +} 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 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 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("") + // 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("") + // 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 + +} -- cgit v1.2.3