summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2011-11-28 11:01:12 +0000
committerMartin Odersky <odersky@gmail.com>2011-11-28 11:01:12 +0000
commit311d813910a2ec590b11b84c28fac2ae6e086270 (patch)
tree4a35454e77a8d09d822a7b7c3126b10c7c3861bb /src/compiler/scala/tools/nsc
parent0bea2ab5f6b211a83bbf14ea46fe57b8163c6334 (diff)
downloadscala-311d813910a2ec590b11b84c28fac2ae6e086270.tar.gz
scala-311d813910a2ec590b11b84c28fac2ae6e086270.tar.bz2
scala-311d813910a2ec590b11b84c28fac2ae6e086270.zip
Experimental version of macro definitions.
Diffstat (limited to 'src/compiler/scala/tools/nsc')
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Parsers.scala89
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Analyzer.scala1
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Macros.scala68
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Namers.scala56
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala13
5 files changed, 172 insertions, 55 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
index cfe8716d9f..e27d5cacda 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
@@ -2408,6 +2408,7 @@ self =>
* FunDef ::= FunSig `:' Type `=' Expr
* | FunSig [nl] `{' Block `}'
* | this ParamClause ParamClauses (`=' ConstrExpr | [nl] ConstrBlock)
+ * | `macro' FunSig [`:' Type] `=' Expr
* FunDcl ::= FunSig [`:' Type]
* FunSig ::= id [FunTypeParamClause] ParamClauses
* }}}
@@ -2426,36 +2427,47 @@ self =>
}
}
else {
- var newmods = mods
val nameOffset = in.offset
val name = ident()
- val result = atPos(start, if (name == nme.ERROR) start else nameOffset) {
- // contextBoundBuf is for context bounded type parameters of the form
- // [T : B] or [T : => B]; it contains the equivalent implicit parameter type,
- // i.e. (B[T] or T => B)
- val contextBoundBuf = new ListBuffer[Tree]
- val tparams = typeParamClauseOpt(name, contextBoundBuf)
- val vparamss = paramClauses(name, contextBoundBuf.toList, false)
- newLineOptWhenFollowedBy(LBRACE)
- var restype = fromWithinReturnType(typedOpt())
- val rhs =
- if (isStatSep || in.token == RBRACE) {
- if (restype.isEmpty) restype = scalaUnitConstr
- newmods |= Flags.DEFERRED
- EmptyTree
- } else if (restype.isEmpty && in.token == LBRACE) {
- restype = scalaUnitConstr
- blockExpr()
- } else {
- equalsExpr()
- }
- DefDef(newmods, name, tparams, vparamss, restype, rhs)
- }
- signalParseProgress(result.pos)
- result
+ if (name == nme.macro_ && isIdent && settings.Xexperimental.value)
+ funDefRest(start, in.offset, mods | Flags.MACRO, ident())
+ else
+ funDefRest(start, nameOffset, mods, name)
}
}
+ def funDefRest(start: Int, nameOffset: Int, mods: Modifiers, name: Name): Tree = {
+ val result = atPos(start, if (name.toTermName == nme.ERROR) start else nameOffset) {
+ val isMacro = mods hasFlag Flags.MACRO
+ val isTypeMacro = isMacro && name.isTypeName
+ var newmods = mods
+ // contextBoundBuf is for context bounded type parameters of the form
+ // [T : B] or [T : => B]; it contains the equivalent implicit parameter type,
+ // i.e. (B[T] or T => B)
+ val contextBoundBuf = new ListBuffer[Tree]
+ val tparams = typeParamClauseOpt(name, contextBoundBuf)
+ val vparamss = paramClauses(name, contextBoundBuf.toList, false)
+ if (!isMacro) newLineOptWhenFollowedBy(LBRACE)
+ var restype = if (isTypeMacro) TypeTree() else fromWithinReturnType(typedOpt())
+ val rhs =
+ if (isMacro)
+ equalsExpr()
+ else if (isStatSep || in.token == RBRACE) {
+ if (restype.isEmpty) restype = scalaUnitConstr
+ newmods |= Flags.DEFERRED
+ EmptyTree
+ } else if (restype.isEmpty && in.token == LBRACE) {
+ restype = scalaUnitConstr
+ blockExpr()
+ } else {
+ equalsExpr()
+ }
+ DefDef(newmods, name, tparams, vparamss, restype, rhs)
+ }
+ signalParseProgress(result.pos)
+ result
+ }
+
/** {{{
* ConstrExpr ::= SelfInvocation
* | ConstrBlock
@@ -2498,6 +2510,7 @@ self =>
/** {{{
* TypeDef ::= type Id [TypeParamClause] `=' Type
+ * | `macro' FunSig `=' Expr
* TypeDcl ::= type Id [TypeParamClause] TypeBounds
* }}}
*/
@@ -2506,17 +2519,21 @@ self =>
newLinesOpt()
atPos(start, in.offset) {
val name = identForType()
- // @M! a type alias as well as an abstract type may declare type parameters
- val tparams = typeParamClauseOpt(name, null)
- in.token match {
- case EQUALS =>
- in.nextToken()
- TypeDef(mods, name, tparams, typ())
- case SUPERTYPE | SUBTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE =>
- TypeDef(mods | Flags.DEFERRED, name, tparams, typeBounds())
- case _ =>
- syntaxErrorOrIncomplete("`=', `>:', or `<:' expected", true)
- EmptyTree
+ if (name == nme.macro_.toTypeName && isIdent && settings.Xexperimental.value) {
+ funDefRest(start, in.offset, mods | Flags.MACRO, identForType())
+ } else {
+ // @M! a type alias as well as an abstract type may declare type parameters
+ val tparams = typeParamClauseOpt(name, null)
+ in.token match {
+ case EQUALS =>
+ in.nextToken()
+ TypeDef(mods, name, tparams, typ())
+ case SUPERTYPE | SUBTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE =>
+ TypeDef(mods | Flags.DEFERRED, name, tparams, typeBounds())
+ case _ =>
+ syntaxErrorOrIncomplete("`=', `>:', or `<:' expected", true)
+ EmptyTree
+ }
}
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala
index e3786c154f..16d55c26ca 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala
@@ -20,6 +20,7 @@ trait Analyzer extends AnyRef
with EtaExpansion
with SyntheticMethods
with Unapplies
+ with Macros
with NamesDefaults
with TypeDiagnostics
{
diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala
new file mode 100644
index 0000000000..05eadb03ae
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala
@@ -0,0 +1,68 @@
+package scala.tools.nsc
+package typechecker
+
+import symtab.Flags._
+
+trait Macros { self: Analyzer =>
+ import global._
+ import definitions._
+
+ def macroMethName(name: Name) =
+ newTermName((if (name.isTypeName) "type" else "def") + "macro$" + name)
+
+ def macroMeth(mac: Symbol): Symbol = {
+ var owner = mac.owner
+ if (!owner.isModuleClass) owner = owner.companionModule.moduleClass
+ owner.info.decl(macroMethName(mac.name))
+ }
+
+ /**
+ * The definition of the method implementing a macro. Example:
+ * Say we have
+ *
+ * def macro foo[T](xs: List[T]): T = expr
+ *
+ * Then the following macro method is generated for `foo`:
+ *
+ * def foo(glob: scala.reflect.api.Universe)
+ * (_this: glob.Tree)
+ * (T: glob.Type)
+ * (xs: glob.Tree): glob.Tree = {
+ * implicit val $glob = glob
+ * expr
+ * }
+ */
+ def macroMethDef(mdef: DefDef): Tree = {
+ def paramDef(name: Name, tpt: Tree) = ValDef(Modifiers(PARAM), name, tpt, EmptyTree)
+ val universeType = TypeTree(ReflectApiUniverse.tpe)
+ val globParam = paramDef("glob", universeType)
+ def globSelect(name: Name) = Select(Ident("glob"), name)
+ def globTree = globSelect(newTypeName("Tree"))
+ def globType = globSelect(newTypeName("Type"))
+ val thisParam = paramDef("_this", globTree)
+ def tparamInMacro(tdef: TypeDef) = paramDef(tdef.name.toTermName, globType)
+ def vparamInMacro(vdef: ValDef): ValDef = paramDef(vdef.name, globTree)
+ def wrapImplicit(tree: Tree) = atPos(tree.pos) {
+ Block(List(ValDef(Modifiers(IMPLICIT), "$glob", universeType, Ident("glob"))), tree)
+ }
+
+ treeCopy.DefDef(
+ mdef,
+ mods = mdef.mods &~ MACRO,
+ name = mdef.name.toTermName,
+ tparams = List(),
+ vparamss = List(globParam) :: List(thisParam) :: (mdef.tparams map tparamInMacro) ::
+ (mdef.vparamss map (_ map vparamInMacro)),
+ tpt = globTree,
+ wrapImplicit(mdef.rhs))
+ }
+
+ def addMacroMethods(templ: Template, namer: Namer): Unit = {
+ for (ddef @ DefDef(mods, _, _, _, _, _) <- templ.body if mods hasFlag MACRO) {
+ namer.enterSyntheticSym(macroMethDef(ddef))
+ }
+ }
+
+ def macroExpand(tree: Tree): Tree = ???
+
+} \ No newline at end of file
diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala
index d503371f5d..4c85830311 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala
@@ -59,7 +59,7 @@ trait Namers extends MethodSynthesis {
// is stored in this map. The map is cleared lazily, i.e. when the new symbol
// is created with the same name, the old one (if present) is wiped out, or the
// entry is deleted when it is used and no longer needed.
- private val caseClassOfModuleClass = perRunCaches.newWeakMap[Symbol, WeakReference[ClassDef]]()
+ private val classOfModuleClass = perRunCaches.newWeakMap[Symbol, WeakReference[ClassDef]]()
// Default getters of constructors are added to the companion object in the
// typeCompleter of the constructor (methodSig). To compute the signature,
@@ -421,8 +421,8 @@ trait Namers extends MethodSynthesis {
* class definition tree.
* @return the companion object symbol.
*/
- def ensureCompanionObject(tree: ClassDef, creator: => Tree): Symbol = {
- val m = companionModuleOf(tree.symbol, context)
+ def ensureCompanionObject(cdef: ClassDef, creator: ClassDef => Tree = companionModuleDef(_)): Symbol = {
+ val m = companionModuleOf(cdef.symbol, context)
// @luc: not sure why "currentRun.compiles(m)" is needed, things breaks
// otherwise. documentation welcome.
//
@@ -435,8 +435,16 @@ trait Namers extends MethodSynthesis {
// Map(class Foo -> Nil)
// What exactly this implies and whether this is a sensible way to
// enforce it, I don't know.
+ //
+ // @martin: currentRun.compiles is needed because we might have a stale
+ // companion object from another run in scope. In that case we should still
+ // overwrite the object. I.e.
+ // Compile run #1: object Foo { ... }
+ // Compile run #2: case class Foo ...
+ // The object Foo is still in scope, but because it is not compiled in current run
+ // it should be ditched and a new one created.
if (m != NoSymbol && currentRun.compiles(m)) m
- else enterSyntheticSym(creator)
+ else enterSyntheticSym(creator(cdef))
}
private def checkSelectors(tree: Import): Unit = {
@@ -599,6 +607,11 @@ trait Namers extends MethodSynthesis {
enterCopyMethodOrGetter(tree, tparams)
else
sym setInfo completerOf(tree, tparams)
+
+ if (mods hasFlag MACRO) {
+ if (!(sym.owner.isClass && sym.owner.isStatic))
+ context.error(tree.pos, "macro definition must appear in globally accessible class")
+ }
}
def enterClassDef(tree: ClassDef) {
@@ -610,17 +623,25 @@ trait Namers extends MethodSynthesis {
if (treeInfo.firstConstructorArgs(impl.body).size > MaxFunctionArity)
context.error(tree.pos, "Implementation restriction: case classes cannot have more than " + MaxFunctionArity + " parameters.")
- val m = ensureCompanionObject(tree, caseModuleDef(tree))
- caseClassOfModuleClass(m.moduleClass) = new WeakReference(tree)
+ val m = ensureCompanionObject(tree, caseModuleDef)
+ classOfModuleClass(m.moduleClass) = new WeakReference(tree)
}
val hasDefault = impl.body exists {
case DefDef(_, nme.CONSTRUCTOR, _, vparamss, _, _) => listutil.mexists(vparamss)(_.mods.hasDefault)
case _ => false
}
if (hasDefault) {
- val m = ensureCompanionObject(tree, companionModuleDef(tree))
+ val m = ensureCompanionObject(tree)
classAndNamerOfModule(m) = (tree, null)
}
+ val hasMacro = impl.body exists {
+ case DefDef(mods, _, _, _, _, _) => mods hasFlag MACRO
+ case _ => false
+ }
+ if (hasMacro) {
+ val m = ensureCompanionObject(tree)
+ classOfModuleClass(m.moduleClass) = new WeakReference(tree)
+ }
val owner = tree.symbol.owner
if (owner.isPackageObjectClass) {
context.unit.warning(tree.pos,
@@ -816,25 +837,27 @@ trait Namers extends MethodSynthesis {
// add apply and unapply methods to companion objects of case classes,
// unless they exist already; here, "clazz" is the module class
if (clazz.isModuleClass) {
- Namers.this.caseClassOfModuleClass get clazz foreach { cdefRef =>
+ Namers.this.classOfModuleClass get clazz foreach { cdefRef =>
val cdef = cdefRef()
- addApplyUnapply(cdef, templateNamer)
- caseClassOfModuleClass -= clazz
+ if (cdef.mods.isCase) addApplyUnapply(cdef, templateNamer)
+ addMacroMethods(cdef.impl, templateNamer)
+ classOfModuleClass -= clazz
}
+ addMacroMethods(templ, templateNamer)
}
// add the copy method to case classes; this needs to be done here, not in SyntheticMethods, because
// the namer phase must traverse this copy method to create default getters for its parameters.
// here, clazz is the ClassSymbol of the case class (not the module).
// @check: this seems to work only if the type completer of the class runs before the one of the
- // module class: the one from the module class removes the entry from caseClassOfModuleClass (see above).
+ // module class: the one from the module class removes the entry from classOfModuleClass (see above).
if (clazz.isClass && !clazz.hasModuleFlag) {
val modClass = companionModuleOf(clazz, context).moduleClass
- Namers.this.caseClassOfModuleClass get modClass map { cdefRef =>
+ Namers.this.classOfModuleClass get modClass map { cdefRef =>
val cdef = cdefRef()
def hasCopy(decls: Scope) = (decls lookup nme.copy) != NoSymbol
- if (!hasCopy(decls) &&
+ if (cdef.mods.isCase && !hasCopy(decls) &&
!parents.exists(p => hasCopy(p.typeSymbol.info.decls)) &&
!parents.flatMap(_.baseClasses).distinct.exists(bc => hasCopy(bc.info.decls)))
addCopyMethod(cdef, templateNamer)
@@ -977,13 +1000,16 @@ trait Namers extends MethodSynthesis {
thisMethodType({
val rt = (
- if (tpt.isEmpty) {
+ if (!tpt.isEmpty) {
+ typer.typedType(tpt).tpe
+ } else if (meth.isMacro) {
+ AnyClass.tpe
+ } else {
// replace deSkolemized symbols with skolemized ones
// (for resultPt computed by looking at overridden symbol, right?)
val pt = resultPt.substSym(tparamSyms, tparams map (_.symbol))
assignTypeToTree(ddef, typer, pt)
}
- else typer.typedType(tpt).tpe
)
// #2382: return type of default getters are always @uncheckedVariance
if (meth.hasDefaultFlag)
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index 62ad78c64d..f9c056e16d 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -755,7 +755,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser {
case Block(_, tree1) => tree1.symbol
case _ => tree.symbol
}
- if (!meth.isConstructor && isFunctionType(pt)) { // (4.2)
+ if (!meth.isConstructor && !meth.isMacro && isFunctionType(pt)) { // (4.2)
debuglog("eta-expanding " + tree + ":" + tree.tpe + " to " + pt)
checkParamsConvertible(tree.pos, tree.tpe)
val tree0 = etaExpand(context.unit, tree)
@@ -1676,6 +1676,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser {
*/
def typedDefDef(ddef: DefDef): DefDef = {
val meth = ddef.symbol.initialize
+ if (meth.isMacro) return ddef
reenterTypeParams(ddef.tparams)
reenterValueParams(ddef.vparamss)
@@ -2100,13 +2101,14 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser {
var e1 = scope.lookupNextEntry(e)
while ((e1 ne null) && e1.owner == scope) {
if (!accesses(e.sym, e1.sym) && !accesses(e1.sym, e.sym) &&
- (e.sym.isType || inBlock || (e.sym.tpe matches e1.sym.tpe)))
+ (e.sym.isType || inBlock || (e.sym.tpe matches e1.sym.tpe) || e.sym.isMacro && e1.sym.isMacro))
// default getters are defined twice when multiple overloads have defaults. an
// error for this is issued in RefChecks.checkDefaultsInOverloaded
if (!e.sym.isErroneous && !e1.sym.isErroneous && !e.sym.hasDefaultFlag &&
!e.sym.hasAnnotation(BridgeClass) && !e1.sym.hasAnnotation(BridgeClass)) {
error(e.sym.pos, e1.sym+" is defined twice"+
- {if(!settings.debug.value) "" else " in "+unit.toString})
+ {if(!settings.debug.value) "" else " in "+unit.toString}+
+ {if (e.sym.isMacro && e1.sym.isMacro) " \n(note that macros cannot be overloaded)" else ""})
scope.unlink(e1) // need to unlink to avoid later problems with lub; see #2779
}
e1 = scope.lookupNextEntry(e1)
@@ -3442,7 +3444,10 @@ 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 res
+ } else if (fun2.hasSymbol && fun2.symbol.isMacro)
+ typed1(macroExpand(res), mode, pt)
+ else
+ res
case ex: TypeError =>
fun match {
case Select(qual, name)