From 1708a7fffdb653a638927c2b4ff30a7a0be0f3fe Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Wed, 6 Jun 2012 02:05:10 +0200 Subject: macros: refactoring of fast track infrastructure As a result, hardwired macros don't need implementation stubs. This is very important, because in a few commits scala.reflect.makro.Context will move out from scala-library.jar. Also adding fast track entries doesn't require jumping through hoops with PDTs. It's as simple as defining PartialFunction[Tree, Any]. --- lib/scala-compiler.jar.desired.sha1 | 2 +- lib/scala-library.jar.desired.sha1 | 2 +- .../scala/reflect/internal/Definitions.scala | 3 + src/compiler/scala/reflect/internal/StdNames.scala | 2 +- src/compiler/scala/reflect/reify/Taggers.scala | 104 +++++++++ .../scala/tools/nsc/typechecker/Macros.scala | 245 ++++++++++----------- .../scala/tools/nsc/typechecker/RefChecks.scala | 10 +- .../tools/nsc/typechecker/StdAttachments.scala | 4 +- .../scala/tools/nsc/typechecker/Typers.scala | 3 +- src/compiler/scala/tools/reflect/FastTrack.scala | 46 ++++ src/library/scala/reflect/api/Universe.scala | 14 +- src/library/scala/reflect/makro/Context.scala | 15 +- .../scala/reflect/makro/internal/Utils.scala | 148 ------------- .../scala/reflect/makro/internal/macroImpl.scala | 17 +- .../scala/reflect/makro/internal/package.scala | 17 ++ ...alidusage-partialapplication-with-tparams.check | 3 + ...alidusage-partialapplication-with-tparams.flags | 1 + .../Impls_Macros_1.scala | 13 ++ .../Test_2.scala | 8 + 19 files changed, 346 insertions(+), 311 deletions(-) create mode 100644 src/compiler/scala/reflect/reify/Taggers.scala create mode 100644 src/compiler/scala/tools/reflect/FastTrack.scala delete mode 100644 src/library/scala/reflect/makro/internal/Utils.scala create mode 100644 src/library/scala/reflect/makro/internal/package.scala create mode 100644 test/files/run/macro-invalidusage-partialapplication-with-tparams.check create mode 100644 test/files/run/macro-invalidusage-partialapplication-with-tparams.flags create mode 100644 test/files/run/macro-invalidusage-partialapplication-with-tparams/Impls_Macros_1.scala create mode 100644 test/files/run/macro-invalidusage-partialapplication-with-tparams/Test_2.scala diff --git a/lib/scala-compiler.jar.desired.sha1 b/lib/scala-compiler.jar.desired.sha1 index b2d1ae35eb..624a57ada7 100644 --- a/lib/scala-compiler.jar.desired.sha1 +++ b/lib/scala-compiler.jar.desired.sha1 @@ -1 +1 @@ -0939b5f357e4b0a6ecb3b6218a1a53841aae258d ?scala-compiler.jar +fbdefcf1a7a2bcd71373f08acd13e84d869ceeaf ?scala-compiler.jar diff --git a/lib/scala-library.jar.desired.sha1 b/lib/scala-library.jar.desired.sha1 index ebc617f19a..21e2ed8665 100644 --- a/lib/scala-library.jar.desired.sha1 +++ b/lib/scala-library.jar.desired.sha1 @@ -1 +1 @@ -2abaea76bd10ccb7afe29604b1156fb7f8c6c36e ?scala-library.jar +e19956225d174f0f3bc0a1d014f208b68d399d66 ?scala-library.jar diff --git a/src/compiler/scala/reflect/internal/Definitions.scala b/src/compiler/scala/reflect/internal/Definitions.scala index 96819eab91..6141cc184d 100644 --- a/src/compiler/scala/reflect/internal/Definitions.scala +++ b/src/compiler/scala/reflect/internal/Definitions.scala @@ -486,6 +486,9 @@ trait Definitions extends reflect.api.StandardDefinitions { // [Eugene] is this a good place for ReflectMirrorPrefix? def ReflectMirrorPrefix = gen.mkAttributedRef(ReflectMirror) setType singleType(ReflectMirror.owner.thisPrefix, ReflectMirror) + lazy val ApiUniverseClass = requiredClass[scala.reflect.api.Universe] + def ApiUniverseReify = getMemberMethod(ApiUniverseClass, nme.reify) + lazy val PartialManifestClass = requiredClass[scala.reflect.ClassManifest[_]] lazy val PartialManifestModule = requiredModule[scala.reflect.ClassManifest.type] lazy val FullManifestClass = requiredClass[scala.reflect.Manifest[_]] diff --git a/src/compiler/scala/reflect/internal/StdNames.scala b/src/compiler/scala/reflect/internal/StdNames.scala index bd4d9a9f34..14d42387f7 100644 --- a/src/compiler/scala/reflect/internal/StdNames.scala +++ b/src/compiler/scala/reflect/internal/StdNames.scala @@ -130,6 +130,7 @@ trait StdNames { final val ClassTag: NameType = "ClassTag" final val TypeTag : NameType = "TypeTag" final val ConcreteTypeTag: NameType = "ConcreteTypeTag" + final val String: NameType = "String" // fictions we use as both types and terms final val ERROR: NameType = "" @@ -223,7 +224,6 @@ trait StdNames { final val Product: NameType = "Product" final val Serializable: NameType = "Serializable" final val Singleton: NameType = "Singleton" - final val String: NameType = "String" final val Throwable: NameType = "Throwable" final val Annotation: NameType = "Annotation" diff --git a/src/compiler/scala/reflect/reify/Taggers.scala b/src/compiler/scala/reflect/reify/Taggers.scala new file mode 100644 index 0000000000..5665c0948e --- /dev/null +++ b/src/compiler/scala/reflect/reify/Taggers.scala @@ -0,0 +1,104 @@ +package scala.reflect.reify + +import scala.reflect.makro.{ReificationError, UnexpectedReificationError} +import scala.reflect.makro.runtime.Context + +abstract class Taggers { + val c: Context + + import c.mirror._ + import definitions._ + + val coreTags = Map( + ByteClass.asType -> newTermName("Byte"), + ShortClass.asType -> newTermName("Short"), + CharClass.asType -> newTermName("Char"), + IntClass.asType -> newTermName("Int"), + LongClass.asType -> newTermName("Long"), + FloatClass.asType -> newTermName("Float"), + DoubleClass.asType -> newTermName("Double"), + BooleanClass.asType -> newTermName("Boolean"), + UnitClass.asType -> newTermName("Unit"), + AnyClass.asType -> newTermName("Any"), + ObjectClass.asType -> newTermName("Object"), + AnyValClass.asType -> newTermName("AnyVal"), + AnyRefClass.asType -> newTermName("AnyRef"), + NothingClass.asType -> newTermName("Nothing"), + NullClass.asType -> newTermName("Null"), + StringClass.asType -> newTermName("String")) + + // todo. the following two methods won't be necessary once we implement implicit macro generators for tags + + def materializeArrayTag(prefix: Tree, tpe: Type): Tree = + materializeClassTag(prefix, tpe) + + def materializeErasureTag(prefix: Tree, tpe: Type, concrete: Boolean): Tree = + if (concrete) materializeClassTag(prefix, tpe) else materializeTypeTag(prefix, tpe, concrete = false) + + def materializeClassTag(prefix: Tree, tpe: Type): Tree = + materializeTag(prefix, tpe, ClassTagModule, { + val erasure = c.reifyErasure(tpe, concrete = true) + val factory = TypeApply(Select(Ident(ClassTagModule), "apply"), List(TypeTree(tpe))) + Apply(factory, List(erasure)) + }) + + def materializeTypeTag(prefix: Tree, tpe: Type, concrete: Boolean): Tree = { + val tagModule = if (concrete) ConcreteTypeTagModule else TypeTagModule + materializeTag(prefix, tpe, tagModule, c.reifyType(prefix, tpe, dontSpliceAtTopLevel = true, concrete = concrete)) + } + + private def materializeTag(prefix: Tree, tpe: Type, tagModule: Symbol, materializer: => Tree): Tree = { + val result = + tpe match { + case coreTpe if coreTags contains coreTpe => + val ref = if (tagModule.owner.isPackageClass) Ident(tagModule) else Select(prefix, tagModule.name) + Select(ref, coreTags(coreTpe)) + case _ => + val manifestInScope = nonSyntheticManifestInScope(tpe) + if (manifestInScope.isEmpty) translatingReificationErrors(materializer) + else gen.mkMethodCall(staticModule("scala.reflect.package"), newTermName("manifestToConcreteTypeTag"), List(tpe), List(manifestInScope)) + } + try c.typeCheck(result) + catch { case terr @ c.TypeError(pos, msg) => failTag(result, terr) } + } + + private def nonSyntheticManifestInScope(tpe: Type) = { + val ManifestClass = staticClass("scala.reflect.Manifest") + val ManifestModule = staticModule("scala.reflect.Manifest") + val manifest = c.inferImplicitValue(appliedType(ManifestClass.asTypeConstructor, List(tpe))) + val notOk = manifest.isEmpty || (manifest exists (sub => sub.symbol != null && (sub.symbol == ManifestModule || sub.symbol.owner == ManifestModule))) + if (notOk) EmptyTree else manifest + } + + def materializeExpr(prefix: Tree, expr: Tree): Tree = { + val result = translatingReificationErrors(c.reifyTree(prefix, expr)) + try c.typeCheck(result) + catch { case terr @ c.TypeError(pos, msg) => failExpr(result, terr) } + } + + private def translatingReificationErrors(materializer: => Tree): Tree = { + try materializer + catch { + case ReificationError(pos, msg) => + c.error(pos.asInstanceOf[c.Position], msg) // this cast is a very small price for the sanity of exception handling + EmptyTree + case UnexpectedReificationError(pos, err, cause) if cause != null => + throw cause + } + } + + private def failTag(result: Tree, reason: Any): Nothing = { + val Apply(TypeApply(fun, List(tpeTree)), _) = c.macroApplication + val tpe = tpeTree.tpe + val PolyType(_, MethodType(_, tagTpe)) = fun.tpe + val tagModule = tagTpe.typeSymbol.companionSymbol + if (c.compilerSettings.contains("-Xlog-implicits")) + c.echo(c.enclosingPosition, s"cannot materialize ${tagModule.name}[$tpe] as $result because:\n$reason") + c.abort(c.enclosingPosition, "No %s available for %s".format(tagModule.name, tpe)) + } + + private def failExpr(result: Tree, reason: Any): Nothing = { + val Apply(_, expr :: Nil) = c.macroApplication + c.abort(c.enclosingPosition, s"Cannot materialize $expr as $result because:\n$reason") + } +} diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index d43dceca58..8895642893 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -7,7 +7,6 @@ import scala.tools.nsc.util.ClassPath._ import scala.reflect.ReflectionUtils import scala.collection.mutable.ListBuffer import scala.compat.Platform.EOL -import scala.reflect.makro.runtime.{Context => MacroContext} import util.Statistics._ import scala.reflect.makro.util._ import java.lang.{Class => jClass} @@ -38,7 +37,7 @@ import java.lang.reflect.{Array => jArray, Method => jMethod} * (Expr(elems)) * (TypeTag(Int)) */ -trait Macros extends Traces { +trait Macros extends scala.tools.reflect.FastTrack with Traces { self: Analyzer => import global._ @@ -186,6 +185,12 @@ trait Macros extends Traces { import typer.context macroLogVerbose("typechecking macro def %s at %s".format(ddef.symbol, ddef.pos)) + if (fastTrack contains ddef.symbol) { + macroLogVerbose("typecheck terminated unexpectedly: macro is hardwired") + assert(!ddef.tpt.isEmpty, "hardwired macros must provide result type") + return EmptyTree + } + if (!typer.checkFeature(ddef.pos, MacrosFeature, immediate = true)) { macroLogVerbose("typecheck terminated unexpectedly: language.experimental.macros feature is not enabled") ddef.symbol setFlag IS_ERROR @@ -635,39 +640,8 @@ trait Macros extends Traces { * @return Some(runtime) if macro implementation can be loaded successfully from either of the mirrors, * None otherwise. */ - private type MacroRuntime = List[Any] => Any + type MacroRuntime = List[Any] => Any private val macroRuntimesCache = perRunCaches.newWeakMap[Symbol, Option[MacroRuntime]] - private lazy val fastTrack: Map[Symbol, MacroRuntime] = { - import scala.reflect.api.Universe - import scala.reflect.makro.internal._ - Map( // challenge: how can we factor out the common code? Does not seem to be easy. - MacroInternal_materializeArrayTag -> (args => { - assert(args.length == 3, args) - val c = args(0).asInstanceOf[MacroContext] - materializeArrayTag_impl(c)(args(1).asInstanceOf[c.Expr[Universe]])(args(2).asInstanceOf[c.TypeTag[_]]) - }), - MacroInternal_materializeErasureTag -> (args => { - assert(args.length == 3, args) - val c = args(0).asInstanceOf[MacroContext] - materializeErasureTag_impl(c)(args(1).asInstanceOf[c.Expr[Universe]])(args(2).asInstanceOf[c.TypeTag[_]]) - }), - MacroInternal_materializeClassTag -> (args => { - assert(args.length == 3, args) - val c = args(0).asInstanceOf[MacroContext] - materializeClassTag_impl(c)(args(1).asInstanceOf[c.Expr[Universe]])(args(2).asInstanceOf[c.TypeTag[_]]) - }), - MacroInternal_materializeTypeTag -> (args => { - assert(args.length == 3, args) - val c = args(0).asInstanceOf[MacroContext] - materializeTypeTag_impl(c)(args(1).asInstanceOf[c.Expr[Universe]])(args(2).asInstanceOf[c.TypeTag[_]]) - }), - MacroInternal_materializeConcreteTypeTag -> (args => { - assert(args.length == 3, args) - val c = args(0).asInstanceOf[MacroContext] - materializeConcreteTypeTag_impl(c)(args(1).asInstanceOf[c.Expr[Universe]])(args(2).asInstanceOf[c.TypeTag[_]]) - }) - ) - } private def macroRuntime(macroDef: Symbol): Option[MacroRuntime] = { macroTraceVerbose("looking for macro implementation: ")(macroDef) if (fastTrack contains macroDef) { @@ -794,15 +768,14 @@ trait Macros extends Traces { } } - /** Should become private again once we're done with migrating typetag generation from implicits */ - def macroContext(typer: Typer, prefixTree: Tree, expandeeTree: Tree): MacroContext { val mirror: global.type } = + private def macroContext(typer: Typer, prefixTree: Tree, expandeeTree: Tree): MacroContext = new { - val mirror: global.type = global + val mirror: self.global.type = self.global val callsiteTyper: mirror.analyzer.Typer = typer.asInstanceOf[global.analyzer.Typer] - // todo. infer precise typetag for this Expr, namely the PrefixType member of the Context refinement - val prefix = Expr(prefixTree)(TypeTag.Nothing) val expandee = expandeeTree - } with MacroContext { + } with UnaffiliatedMacroContext { + // todo. infer precise typetag for this Expr, namely the PrefixType member of the Context refinement + val prefix = Expr[Nothing](prefixTree)(TypeTag.Nothing) override def toString = "MacroContext(%s@%s +%d)".format(expandee.symbol.name, expandee.pos, enclosingMacros.length - 1 /* exclude myself */) } @@ -836,96 +809,111 @@ trait Macros extends Traces { val context = expandee.attachmentOpt[MacroAttachment].flatMap(_.macroContext).getOrElse(macroContext(typer, prefixTree, expandee)) var argss: List[List[Any]] = List(context) :: exprArgs.toList macroTraceVerbose("argss: ")(argss) + val rawArgss = + if (fastTrack contains macroDef) { + if (fastTrack(macroDef) validate argss) argss + else { + // if you're getting here, it's not necessarily partial application that is at fault + // for example, if a signature of a hardwired macro has been changed without updated FastTrack + // then the corresponding partial function in FastTrack will refuse to process the expandee + // validation will return false, and control flow will end up here + // however, for simplicity sake, I didn't introduce the notion of error handling to FastTrack + // so all kinds of validation errors produce `MacroPartialApplicationError` + typer.TyperErrorGen.MacroPartialApplicationError(expandee) + return None + } + } else { + val ann = macroDef.getAnnotation(MacroImplAnnotation).getOrElse(throw new Error("assertion failed. %s: %s".format(macroDef, macroDef.annotations))) + val macroImpl = ann.args(0).symbol + var paramss = macroImpl.paramss + val tparams = macroImpl.typeParams + macroTraceVerbose("paramss: ")(paramss) + + // we need to take care of all possible combos of nullary/empty-paramlist macro defs vs nullary/empty-arglist invocations + // nullary def + nullary invocation => paramss and argss match, everything is okay + // nullary def + empty-arglist invocation => illegal Scala code, impossible, everything is okay + // empty-paramlist def + nullary invocation => uh-oh, we need to append a List() to argss + // empty-paramlist def + empty-arglist invocation => paramss and argss match, everything is okay + // that's almost it, but we need to account for the fact that paramss might have context bounds that mask the empty last paramlist + val paramss_without_evidences = transformTypeTagEvidenceParams(paramss, (param, tparam) => None) + val isEmptyParamlistDef = paramss_without_evidences.nonEmpty && paramss_without_evidences.last.isEmpty + val isEmptyArglistInvocation = argss.nonEmpty && argss.last.isEmpty + if (isEmptyParamlistDef && !isEmptyArglistInvocation) { + macroLogVerbose("isEmptyParamlistDef && !isEmptyArglistInvocation: appending a List() to argss") + argss = argss :+ Nil + } - val ann = macroDef.getAnnotation(MacroImplAnnotation).getOrElse(throw new Error("assertion failed. %s: %s".format(macroDef, macroDef.annotations))) - val macroImpl = ann.args(0).symbol - var paramss = macroImpl.paramss - val tparams = macroImpl.typeParams - macroTraceVerbose("paramss: ")(paramss) - - // we need to take care of all possible combos of nullary/empty-paramlist macro defs vs nullary/empty-arglist invocations - // nullary def + nullary invocation => paramss and argss match, everything is okay - // nullary def + empty-arglist invocation => illegal Scala code, impossible, everything is okay - // empty-paramlist def + nullary invocation => uh-oh, we need to append a List() to argss - // empty-paramlist def + empty-arglist invocation => paramss and argss match, everything is okay - // that's almost it, but we need to account for the fact that paramss might have context bounds that mask the empty last paramlist - val paramss_without_evidences = transformTypeTagEvidenceParams(paramss, (param, tparam) => None) - val isEmptyParamlistDef = paramss_without_evidences.nonEmpty && paramss_without_evidences.last.isEmpty - val isEmptyArglistInvocation = argss.nonEmpty && argss.last.isEmpty - if (isEmptyParamlistDef && !isEmptyArglistInvocation) { - macroLogVerbose("isEmptyParamlistDef && !isEmptyArglistInvocation: appending a List() to argss") - argss = argss :+ Nil - } + // nb! check partial application against paramss without evidences + val numParamLists = paramss_without_evidences.length + val numArgLists = argss.length + if (numParamLists != numArgLists) { + typer.TyperErrorGen.MacroPartialApplicationError(expandee) + return None + } - // nb! check partial application against paramss without evidences - val numParamLists = paramss_without_evidences.length - val numArgLists = argss.length - if (numParamLists != numArgLists) { - typer.TyperErrorGen.MacroPartialApplicationError(expandee) - return None - } + // if paramss have typetag context bounds, add an arglist to argss if necessary and instantiate the corresponding evidences + // consider the following example: + // + // class D[T] { + // class C[U] { + // def foo[V] = macro Impls.foo[T, U, V] + // } + // } + // + // val outer1 = new D[Int] + // val outer2 = new outer1.C[String] + // outer2.foo[Boolean] + // + // then T and U need to be inferred from the lexical scope of the call using ``asSeenFrom'' + // whereas V won't be resolved by asSeenFrom and need to be loaded directly from ``expandee'' which needs to contain a TypeApply node + // also, macro implementation reference may contain a regular type as a type argument, then we pass it verbatim + val resolved = collection.mutable.Map[Symbol, Type]() + paramss = transformTypeTagEvidenceParams(paramss, (param, tparam) => { + val TypeApply(_, implRefTargs) = ann.args(0) + var implRefTarg = implRefTargs(tparam.paramPos).tpe.typeSymbol + val tpe = if (implRefTarg.isTypeParameterOrSkolem) { + if (implRefTarg.owner == macroDef) { + // [Eugene] doesn't work when macro def is compiled separately from its usages + // then implRefTarg is not a skolem and isn't equal to any of macroDef.typeParams + // val paramPos = implRefTarg.deSkolemize.paramPos + val paramPos = macroDef.typeParams.indexWhere(_.name == implRefTarg.name) + typeArgs(paramPos).tpe + } else + implRefTarg.tpe.asSeenFrom( + if (prefixTree == EmptyTree) macroDef.owner.tpe else prefixTree.tpe, + macroDef.owner) + } else + implRefTarg.tpe + macroLogVerbose("resolved tparam %s as %s".format(tparam, tpe)) + resolved(tparam) = tpe + param.tpe.typeSymbol match { + case definitions.TypeTagClass => + // do nothing + case definitions.ConcreteTypeTagClass => + if (!tpe.isConcrete) context.abort(context.enclosingPosition, "cannot create ConcreteTypeTag from a type %s having unresolved type parameters".format(tpe)) + // otherwise do nothing + case _ => + throw new Error("unsupported tpe: " + tpe) + } + Some(tparam) + }) + val tags = paramss.last takeWhile (_.isType) map (resolved(_)) map (tpe => { + // generally speaking, it's impossible to calculate erasure from a tpe here + // the tpe might be compiled by this run, so its jClass might not exist yet + // hence I just pass `null` instead and leave this puzzle to macro programmers + val ttag = TypeTag(tpe, null) + if (ttag.isConcrete) ttag.toConcrete else ttag + }) + if (paramss.lastOption map (params => !params.isEmpty && params.forall(_.isType)) getOrElse false) argss = argss :+ Nil + argss = argss.dropRight(1) :+ (tags ++ argss.last) // todo. add support for context bounds in argss - // if paramss have typetag context bounds, add an arglist to argss if necessary and instantiate the corresponding evidences - // consider the following example: - // - // class D[T] { - // class C[U] { - // def foo[V] = macro Impls.foo[T, U, V] - // } - // } - // - // val outer1 = new D[Int] - // val outer2 = new outer1.C[String] - // outer2.foo[Boolean] - // - // then T and U need to be inferred from the lexical scope of the call using ``asSeenFrom'' - // whereas V won't be resolved by asSeenFrom and need to be loaded directly from ``expandee'' which needs to contain a TypeApply node - // also, macro implementation reference may contain a regular type as a type argument, then we pass it verbatim - val resolved = collection.mutable.Map[Symbol, Type]() - paramss = transformTypeTagEvidenceParams(paramss, (param, tparam) => { - val TypeApply(_, implRefTargs) = ann.args(0) - var implRefTarg = implRefTargs(tparam.paramPos).tpe.typeSymbol - val tpe = if (implRefTarg.isTypeParameterOrSkolem) { - if (implRefTarg.owner == macroDef) { - // [Eugene] doesn't work when macro def is compiled separately from its usages - // then implRefTarg is not a skolem and isn't equal to any of macroDef.typeParams -// val paramPos = implRefTarg.deSkolemize.paramPos - val paramPos = macroDef.typeParams.indexWhere(_.name == implRefTarg.name) - typeArgs(paramPos).tpe - } else - implRefTarg.tpe.asSeenFrom( - if (prefixTree == EmptyTree) macroDef.owner.tpe else prefixTree.tpe, - macroDef.owner) - } else - implRefTarg.tpe - macroLogVerbose("resolved tparam %s as %s".format(tparam, tpe)) - resolved(tparam) = tpe - param.tpe.typeSymbol match { - case definitions.TypeTagClass => - // do nothing - case definitions.ConcreteTypeTagClass => - if (!tpe.isConcrete) context.abort(context.enclosingPosition, "cannot create ConcreteTypeTag from a type %s having unresolved type parameters".format(tpe)) - // otherwise do nothing - case _ => - throw new Error("unsupported tpe: " + tpe) + assert(argss.length == paramss.length, "argss: %s, paramss: %s".format(argss, 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 + } + rawArgss } - Some(tparam) - }) - val tags = paramss.last takeWhile (_.isType) map (resolved(_)) map (tpe => { - // generally speaking, it's impossible to calculate erasure from a tpe here - // the tpe might be compiled by this run, so its jClass might not exist yet - // hence I just pass `null` instead and leave this puzzle to macro programmers - val ttag = TypeTag(tpe, null) - if (ttag.isConcrete) ttag.toConcrete else ttag - }) - if (paramss.lastOption map (params => !params.isEmpty && params.forall(_.isType)) getOrElse false) argss = argss :+ Nil - argss = argss.dropRight(1) :+ (tags ++ argss.last) // todo. add support for context bounds in argss - - assert(argss.length == paramss.length, "argss: %s, paramss: %s".format(argss, 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 = rawArgss.flatten macroTraceVerbose("rawArgs: ")(rawArgs) Some(rawArgs) @@ -1124,7 +1112,10 @@ trait Macros extends Traces { else { macroLogLite("typechecking macro expansion %s at %s".format(expandee, expandee.pos)) macroArgs(typer, expandee).fold(failExpansion(): MacroExpansionResult) { - case args @ ((context: MacroContext) :: _) => + // [Eugene++] crashes virtpatmat: + // case args @ ((context: MacroContext) :: _) => + case args @ (context0 :: _) => + val context = context0.asInstanceOf[MacroContext] if (nowDelayed) { macroLogLite("macro expansion is delayed: %s".format(expandee)) delayed += expandee -> undetparams diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 6d9c9c4ce8..553294d0fe 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -1309,9 +1309,9 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R } List(tree1) } - case Import(_, _) => Nil - case DefDef(mods, _, _, _, _, _) if (mods hasFlag MACRO) => Nil - case _ => List(transform(tree)) + case Import(_, _) => Nil + case DefDef(mods, _, _, _, _, _) if (mods hasFlag MACRO) || (tree.symbol hasFlag MACRO) => Nil + case _ => List(transform(tree)) } /* Check whether argument types conform to bounds of type parameters */ @@ -1496,7 +1496,7 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R private def transformCaseApply(tree: Tree, ifNot: => Unit) = { val sym = tree.symbol - + def isClassTypeAccessible(tree: Tree): Boolean = tree match { case TypeApply(fun, targs) => isClassTypeAccessible(fun) @@ -1505,7 +1505,7 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R // the companion class is actually not a ClassSymbol, but a reference to an abstract type. module.symbol.companionClass.isClass } - + val doTransform = sym.isSourceMethod && sym.isCase && diff --git a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala index 329a247106..3033230603 100644 --- a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala +++ b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala @@ -1,10 +1,10 @@ package scala.tools.nsc package typechecker -import scala.reflect.makro.runtime.{Context => MacroContext} - trait StdAttachments { self: Analyzer => + type UnaffiliatedMacroContext = scala.reflect.makro.runtime.Context + type MacroContext = UnaffiliatedMacroContext { val mirror: self.global.type } case class MacroAttachment(delayed: Boolean, typerContext: Context, macroContext: Option[MacroContext]) } \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 88118adbe5..a2ef06fe38 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -5213,7 +5213,8 @@ trait Typers extends Modes with Adaptations with Taggings { } val isMacroBodyOkay = !tree.symbol.isErroneous && !(tree1 exists (_.isErroneous)) - if (isMacroBodyOkay) computeMacroDefTypeFromMacroImpl(ddef, tree.symbol, tree1.symbol) else AnyClass.tpe + val shouldInheritMacroImplReturnType = ddef.tpt.isEmpty + if (isMacroBodyOkay && shouldInheritMacroImplReturnType) computeMacroDefTypeFromMacroImpl(ddef, tree.symbol, tree1.symbol) else AnyClass.tpe } def transformedOr(tree: Tree, op: => Tree): Tree = transformed.get(tree) match { diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala new file mode 100644 index 0000000000..738bcd01a7 --- /dev/null +++ b/src/compiler/scala/tools/reflect/FastTrack.scala @@ -0,0 +1,46 @@ +package scala.tools +package reflect + +import scala.reflect.reify.Taggers +import scala.tools.nsc.typechecker.{Analyzer, Macros} + +/** Optimizes system macro expansions by hardwiring them directly to their implementations + * bypassing standard reflective load and invoke to avoid the overhead of Java/Scala reflection. + */ +trait FastTrack { + self: Macros with Analyzer => + + import global._ + import definitions._ + + import language.implicitConversions + private implicit def context2taggers(c0: MacroContext) : Taggers { val c: c0.type } = new { val c: c0.type = c0 } with Taggers + + implicit def fastTrackEntry2MacroRuntime(entry: FastTrackEntry): MacroRuntime = args => entry.run(args) + type FastTrackExpander = PartialFunction[(MacroContext, Tree), Tree] + case class FastTrackEntry(sym: Symbol, expander: FastTrackExpander) { + def validate(argss: List[List[Any]]): Boolean = { + val c = argss.flatten.apply(0).asInstanceOf[MacroContext] + val isValid = expander isDefinedAt (c, c.expandee) + isValid + } + def run(args: List[Any]): Any = { + val c = args(0).asInstanceOf[MacroContext] + val result = expander((c, c.expandee)) + c.Expr[Nothing](result)(c.TypeTag.Nothing) + } + } + + lazy val fastTrack: Map[Symbol, FastTrackEntry] = { + var registry = Map[Symbol, FastTrackEntry]() + implicit class BindTo(sym: Symbol) { def bindTo(expander: FastTrackExpander): Unit = if (sym != NoSymbol) registry += sym -> FastTrackEntry(sym, expander) } + MacroInternal_materializeArrayTag bindTo { case (c, Apply(TypeApply(_, List(tt)), List(u))) => c.materializeArrayTag(u, tt.tpe) } + MacroInternal_materializeErasureTag bindTo { case (c, Apply(TypeApply(_, List(tt)), List(u))) => c.materializeErasureTag(u, tt.tpe, concrete = false) } + MacroInternal_materializeClassTag bindTo { case (c, Apply(TypeApply(_, List(tt)), List(u))) => c.materializeClassTag(u, tt.tpe) } + MacroInternal_materializeTypeTag bindTo { case (c, Apply(TypeApply(_, List(tt)), List(u))) => c.materializeTypeTag(u, tt.tpe, concrete = false) } + MacroInternal_materializeConcreteTypeTag bindTo { case (c, Apply(TypeApply(_, List(tt)), List(u))) => c.materializeTypeTag(u, tt.tpe, concrete = true) } + ApiUniverseReify bindTo { case (c, Apply(TypeApply(_, List(tt)), List(expr))) => c.materializeExpr(c.prefix.tree, expr) } + MacroContextReify bindTo { case (c, Apply(TypeApply(_, List(tt)), List(expr))) => c.materializeExpr(c.prefix.tree, expr) } + registry + } +} \ No newline at end of file diff --git a/src/library/scala/reflect/api/Universe.scala b/src/library/scala/reflect/api/Universe.scala index 05b5963c73..ce1b806812 100755 --- a/src/library/scala/reflect/api/Universe.scala +++ b/src/library/scala/reflect/api/Universe.scala @@ -33,7 +33,7 @@ abstract class Universe extends Symbols * }}} * * The reifier transforms it to the following expression: - * + * * {{{ * <[ * val $mr: scala.reflect.api.Universe = @@ -66,12 +66,6 @@ abstract class Universe extends Symbols * * Since reified trees can be compiled outside of the scope they've been created in, * special measures are taken to ensure that all members accessed in the reifee remain visible */ - def reify[T](expr: T): Expr[T] = macro Universe.reify[T] -} - -object Universe { - def reify[T](cc: scala.reflect.makro.Context{ type PrefixType = Universe })(expr: cc.Expr[T]): cc.Expr[cc.prefix.value.Expr[T]] = { - import scala.reflect.makro.internal._ - cc.Expr(cc.materializeExpr(cc.prefix.tree, expr.tree)) - } -} + // implementation is magically hardwired to `scala.reflect.reify.Taggers` + def reify[T](expr: T): Expr[T] = macro ??? +} \ No newline at end of file diff --git a/src/library/scala/reflect/makro/Context.scala b/src/library/scala/reflect/makro/Context.scala index b8fb0dcce5..b0e15fd572 100644 --- a/src/library/scala/reflect/makro/Context.scala +++ b/src/library/scala/reflect/makro/Context.scala @@ -28,17 +28,6 @@ trait Context extends Aliases val prefix: Expr[PrefixType] /** Alias to the underlying mirror's reify */ - def reify[T](expr: T): Expr[T] = macro Context.reify[T] -} - -object Context { - def reify[T](cc: Context{ type PrefixType = Context })(expr: cc.Expr[T]): cc.Expr[cc.prefix.value.Expr[T]] = { - import cc.mirror._ - import scala.reflect.makro.internal._ - // [Eugene] how do I typecheck this without undergoing this tiresome (and, in general, incorrect) procedure? - val prefix: Tree = Select(cc.prefix.tree, newTermName("mirror")) - val prefixTpe = cc.typeCheck(TypeApply(Select(prefix, newTermName("asInstanceOf")), List(SingletonTypeTree(prefix)))).tpe - prefix setType prefixTpe - cc.Expr(cc.materializeExpr(prefix, expr.tree)) - } + // implementation is magically hardwired to `scala.reflect.reify.Taggers` + def reify[T](expr: T): Expr[T] = macro ??? } diff --git a/src/library/scala/reflect/makro/internal/Utils.scala b/src/library/scala/reflect/makro/internal/Utils.scala deleted file mode 100644 index 3032d8e05d..0000000000 --- a/src/library/scala/reflect/makro/internal/Utils.scala +++ /dev/null @@ -1,148 +0,0 @@ -package scala.reflect.makro - -import scala.reflect.api.Universe -import language.implicitConversions -import language.experimental.macros - -/** This package is required by the compiler and should not be used in client code. */ -package object internal { - /** This method is required by the compiler and should not be used in client code. */ - def materializeArrayTag[T](u: Universe): ArrayTag[T] = macro materializeArrayTag_impl[T] - - /** This method is required by the compiler and should not be used in client code. */ - def materializeArrayTag_impl[T: c.TypeTag](c: Context)(u: c.Expr[Universe]): c.Expr[ArrayTag[T]] = - c.Expr[Nothing](c.materializeArrayTag(u.tree, implicitly[c.TypeTag[T]].tpe))(c.TypeTag.Nothing) - - /** This method is required by the compiler and should not be used in client code. */ - def materializeErasureTag[T](u: Universe): ErasureTag[T] = macro materializeErasureTag_impl[T] - - /** This method is required by the compiler and should not be used in client code. */ - def materializeErasureTag_impl[T: c.TypeTag](c: Context)(u: c.Expr[Universe]): c.Expr[ErasureTag[T]] = - c.Expr[Nothing](c.materializeErasureTag(u.tree, implicitly[c.TypeTag[T]].tpe, concrete = false))(c.TypeTag.Nothing) - - /** This method is required by the compiler and should not be used in client code. */ - def materializeClassTag[T](u: Universe): ClassTag[T] = macro materializeClassTag_impl[T] - - /** This method is required by the compiler and should not be used in client code. */ - def materializeClassTag_impl[T: c.TypeTag](c: Context)(u: c.Expr[Universe]): c.Expr[ClassTag[T]] = - c.Expr[Nothing](c.materializeClassTag(u.tree, implicitly[c.TypeTag[T]].tpe))(c.TypeTag.Nothing) - - /** This method is required by the compiler and should not be used in client code. */ - def materializeTypeTag[T](u: Universe): u.TypeTag[T] = macro materializeTypeTag_impl[T] - - /** This method is required by the compiler and should not be used in client code. */ - def materializeTypeTag_impl[T: c.TypeTag](c: Context)(u: c.Expr[Universe]): c.Expr[u.value.TypeTag[T]] = - c.Expr[Nothing](c.materializeTypeTag(u.tree, implicitly[c.TypeTag[T]].tpe, concrete = false))(c.TypeTag.Nothing) - - /** This method is required by the compiler and should not be used in client code. */ - def materializeConcreteTypeTag[T](u: Universe): u.ConcreteTypeTag[T] = macro materializeConcreteTypeTag_impl[T] - - /** This method is required by the compiler and should not be used in client code. */ - def materializeConcreteTypeTag_impl[T: c.TypeTag](c: Context)(u: c.Expr[Universe]): c.Expr[u.value.ConcreteTypeTag[T]] = - c.Expr[Nothing](c.materializeTypeTag(u.tree, implicitly[c.TypeTag[T]].tpe, concrete = true))(c.TypeTag.Nothing) - - /** This method is required by the compiler and should not be used in client code. */ - private[scala] implicit def context2utils(c0: Context) : Utils { val c: c0.type } = new { val c: c0.type = c0 } with Utils -} - -package internal { - private[scala] abstract class Utils { - val c: Context - - import c.mirror._ - import definitions._ - - val coreTags = Map( - ByteClass.asType -> newTermName("Byte"), - ShortClass.asType -> newTermName("Short"), - CharClass.asType -> newTermName("Char"), - IntClass.asType -> newTermName("Int"), - LongClass.asType -> newTermName("Long"), - FloatClass.asType -> newTermName("Float"), - DoubleClass.asType -> newTermName("Double"), - BooleanClass.asType -> newTermName("Boolean"), - UnitClass.asType -> newTermName("Unit"), - AnyClass.asType -> newTermName("Any"), - ObjectClass.asType -> newTermName("Object"), - AnyValClass.asType -> newTermName("AnyVal"), - AnyRefClass.asType -> newTermName("AnyRef"), - NothingClass.asType -> newTermName("Nothing"), - NullClass.asType -> newTermName("Null"), - StringClass.asType -> newTermName("String")) - - // todo. the following two methods won't be necessary once we implement implicit macro generators for tags - - def materializeArrayTag(prefix: Tree, tpe: Type): Tree = - materializeClassTag(prefix, tpe) - - def materializeErasureTag(prefix: Tree, tpe: Type, concrete: Boolean): Tree = - if (concrete) materializeClassTag(prefix, tpe) else materializeTypeTag(prefix, tpe, concrete = false) - - def materializeClassTag(prefix: Tree, tpe: Type): Tree = - materializeTag(prefix, tpe, ClassTagModule, { - val erasure = c.reifyErasure(tpe, concrete = true) - val factory = TypeApply(Select(Ident(ClassTagModule), "apply"), List(TypeTree(tpe))) - Apply(factory, List(erasure)) - }) - - def materializeTypeTag(prefix: Tree, tpe: Type, concrete: Boolean): Tree = { - val tagModule = if (concrete) ConcreteTypeTagModule else TypeTagModule - materializeTag(prefix, tpe, tagModule, c.reifyType(prefix, tpe, dontSpliceAtTopLevel = true, concrete = concrete)) - } - - private def materializeTag(prefix: Tree, tpe: Type, tagModule: Symbol, materializer: => Tree): Tree = { - val result = - tpe match { - case coreTpe if coreTags contains coreTpe => - val ref = if (tagModule.owner.isPackageClass) Ident(tagModule) else Select(prefix, tagModule.name) - Select(ref, coreTags(coreTpe)) - case _ => - val manifestInScope = nonSyntheticManifestInScope(tpe) - if (manifestInScope.isEmpty) translatingReificationErrors(materializer) - else gen.mkMethodCall(staticModule("scala.reflect.package"), newTermName("manifestToConcreteTypeTag"), List(tpe), List(manifestInScope)) - } - try c.typeCheck(result) - catch { case terr @ c.TypeError(pos, msg) => failTag(result, terr) } - } - - private def nonSyntheticManifestInScope(tpe: Type) = { - val ManifestClass = staticClass("scala.reflect.Manifest") - val ManifestModule = staticModule("scala.reflect.Manifest") - val manifest = c.inferImplicitValue(appliedType(ManifestClass.asTypeConstructor, List(tpe))) - val notOk = manifest.isEmpty || (manifest exists (sub => sub.symbol != null && (sub.symbol == ManifestModule || sub.symbol.owner == ManifestModule))) - if (notOk) EmptyTree else manifest - } - - def materializeExpr(prefix: Tree, expr: Tree): Tree = { - val result = translatingReificationErrors(c.reifyTree(prefix, expr)) - try c.typeCheck(result) - catch { case terr @ c.TypeError(pos, msg) => failExpr(result, terr) } - } - - private def translatingReificationErrors(materializer: => Tree): Tree = { - try materializer - catch { - case ReificationError(pos, msg) => - c.error(pos.asInstanceOf[c.Position], msg) // this cast is a very small price for the sanity of exception handling - EmptyTree - case UnexpectedReificationError(pos, err, cause) if cause != null => - throw cause - } - } - - private def failTag(result: Tree, reason: Any): Nothing = { - val Apply(TypeApply(fun, List(tpeTree)), _) = c.macroApplication - val tpe = tpeTree.tpe - val PolyType(_, MethodType(_, tagTpe)) = fun.tpe - val tagModule = tagTpe.typeSymbol.companionSymbol - if (c.compilerSettings.contains("-Xlog-implicits")) - c.echo(c.enclosingPosition, s"cannot materialize ${tagModule.name}[$tpe] as $result because:\n$reason") - c.abort(c.enclosingPosition, "No %s available for %s".format(tagModule.name, tpe)) - } - - private def failExpr(result: Tree, reason: Any): Nothing = { - val Apply(_, expr :: Nil) = c.macroApplication - c.abort(c.enclosingPosition, s"Cannot materialize $expr as $result because:\n$reason") - } - } -} diff --git a/src/library/scala/reflect/makro/internal/macroImpl.scala b/src/library/scala/reflect/makro/internal/macroImpl.scala index 9cf4d23072..0dfa8d1654 100644 --- a/src/library/scala/reflect/makro/internal/macroImpl.scala +++ b/src/library/scala/reflect/makro/internal/macroImpl.scala @@ -1,5 +1,18 @@ package scala.reflect.makro package internal -/** This type is required by the compiler and should not be used in client code. */ -class macroImpl(val referenceToMacroImpl: Any) extends annotation.StaticAnnotation +/** Links macro definitions with their implementation. + * This is necessary to preserve macro def -> macro impl links between compilation runs. + * + * More precisely, after typechecking right-hand side of a macro def + * `typedMacroBody` slaps `macroImpl` annotation onto the macro def + * with the result of typechecking as a sole parameter. + * + * As an unfortunate consequence, this annotation must be defined in scala-library.jar, + * because anyone (even those programmers who compile their programs with only scala-library on classpath) + * must be able to define macros. + * + * To lessen the weirdness we define this annotation as `private[scala]`. + * It will not prevent pickling, but it will prevent application developers (and scaladocs) from seeing the annotation. + */ +private[scala] class macroImpl(val referenceToMacroImpl: Any) extends annotation.StaticAnnotation diff --git a/src/library/scala/reflect/makro/internal/package.scala b/src/library/scala/reflect/makro/internal/package.scala new file mode 100644 index 0000000000..4c4acec096 --- /dev/null +++ b/src/library/scala/reflect/makro/internal/package.scala @@ -0,0 +1,17 @@ +package scala.reflect.makro + +import language.experimental.macros +import scala.reflect.api.{Universe => ApiUniverse} + +// anchors for materialization macros emitted during tag materialization in Implicits.scala +// implementation is magically hardwired into `scala.reflect.reify.Taggers` +// +// todo. once we have implicit macros for tag generation, we can remove these anchors +// [Eugene++] how do I hide this from scaladoc? +package object internal { + private[scala] def materializeArrayTag[T](u: ApiUniverse): ArrayTag[T] = macro ??? + private[scala] def materializeErasureTag[T](u: ApiUniverse): ErasureTag[T] = macro ??? + private[scala] def materializeClassTag[T](u: ApiUniverse): ClassTag[T] = macro ??? + private[scala] def materializeTypeTag[T](u: ApiUniverse): u.TypeTag[T] = macro ??? + private[scala] def materializeConcreteTypeTag[T](u: ApiUniverse): u.ConcreteTypeTag[T] = macro ??? +} diff --git a/test/files/run/macro-invalidusage-partialapplication-with-tparams.check b/test/files/run/macro-invalidusage-partialapplication-with-tparams.check new file mode 100644 index 0000000000..73f57b0b81 --- /dev/null +++ b/test/files/run/macro-invalidusage-partialapplication-with-tparams.check @@ -0,0 +1,3 @@ +reflective compilation has failed: + +macros cannot be partially applied diff --git a/test/files/run/macro-invalidusage-partialapplication-with-tparams.flags b/test/files/run/macro-invalidusage-partialapplication-with-tparams.flags new file mode 100644 index 0000000000..cd66464f2f --- /dev/null +++ b/test/files/run/macro-invalidusage-partialapplication-with-tparams.flags @@ -0,0 +1 @@ +-language:experimental.macros \ No newline at end of file diff --git a/test/files/run/macro-invalidusage-partialapplication-with-tparams/Impls_Macros_1.scala b/test/files/run/macro-invalidusage-partialapplication-with-tparams/Impls_Macros_1.scala new file mode 100644 index 0000000000..67a19a6d62 --- /dev/null +++ b/test/files/run/macro-invalidusage-partialapplication-with-tparams/Impls_Macros_1.scala @@ -0,0 +1,13 @@ +import scala.reflect.makro.{Context => Ctx} + +object Impls { + def foo[T: c.TypeTag](c: Ctx)(x: c.Expr[T]) = { + import c.universe._ + val body = Apply(Select(Ident(definitions.PredefModule), newTermName("println")), List(Literal(Constant(x.tree.toString)))) + c.Expr[Unit](body) + } +} + +object Macros { + def foo[T](x: T) = macro Impls.foo[T] +} \ No newline at end of file diff --git a/test/files/run/macro-invalidusage-partialapplication-with-tparams/Test_2.scala b/test/files/run/macro-invalidusage-partialapplication-with-tparams/Test_2.scala new file mode 100644 index 0000000000..f51cc7e699 --- /dev/null +++ b/test/files/run/macro-invalidusage-partialapplication-with-tparams/Test_2.scala @@ -0,0 +1,8 @@ +object Test extends App { + import scala.reflect.runtime.universe._ + import scala.reflect.runtime.{currentMirror => cm} + import scala.tools.reflect.ToolBox + val tree = Select(Ident("Macros"), newTermName("foo")) + try cm.mkToolBox().runExpr(tree) + catch { case ex: Throwable => println(ex.getMessage) } +} -- cgit v1.2.3