diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Macros.scala | 549 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Namers.scala | 9 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/TreeInfo.scala | 7 | ||||
-rw-r--r-- | test/files/neg/macro-invalidimpl-i.check | 4 | ||||
-rw-r--r-- | test/files/neg/macro-invalidimpl-i.flags | 1 | ||||
-rw-r--r-- | test/files/neg/macro-invalidimpl-i/Impls_1.scala | 7 | ||||
-rw-r--r-- | test/files/neg/macro-invalidimpl-i/Macros_Test_2.scala | 5 | ||||
-rw-r--r-- | test/files/run/t5940.scala | 41 |
8 files changed, 373 insertions, 250 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 7c5d458fee..c8bf70e9e0 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -11,6 +11,7 @@ import reflect.internal.util.Statistics import scala.reflect.macros.util._ import java.lang.{Class => jClass} import java.lang.reflect.{Array => jArray, Method => jMethod} +import scala.reflect.internal.util.Collections._ /** * Code to deal with macros, namely with: @@ -48,6 +49,171 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { val globalMacroCache = collection.mutable.Map[Any, Any]() val perRunMacroCache = perRunCaches.newMap[Symbol, collection.mutable.Map[Any, Any]] + /** `MacroImplBinding` and its companion module are responsible for + * serialization/deserialization of macro def -> impl bindings. + * + * The first officially released version of macros persisted these bindings across compilation runs + * using a neat trick. The right-hand side of a macro definition (which contains a reference to a macro impl) + * was typechecked and then put verbatim into an annotation on the macro definition. + * + * This solution is very simple, but unfortunately it's also lacking. If we use it, then + * signatures of macro defs become transitively dependent on scala-reflect.jar + * (because they refer to macro impls, and macro impls refer to scala.reflect.macros.Context defined in scala-reflect.jar). + * More details can be found in comments to https://issues.scala-lang.org/browse/SI-5940. + * + * Therefore we have to avoid putting macro impls into binding pickles and come up with our own serialization format. + * Situation is further complicated by the fact that it's not enough to just pickle macro impl's class name and method name, + * because macro expansion needs some knowledge about the shape of macro impl's signature (which we can't pickle). + * Hence we precompute necessary stuff (e.g. the layout of type parameters) when compiling macro defs. + */ + + /** Represents all the information that a macro definition needs to know about its implementation. + * Includes a path to load the implementation via Java reflection, + * and various accounting information necessary when composing an argument list for the reflective invocation. + */ + private case class MacroImplBinding( + // Java class name of the class that contains the macro implementation + // is used to load the corresponding object with Java reflection + val className: String, + // method name of the macro implementation + // `className` and `methName` are all we need to reflectively invoke a macro implementation + // because macro implementations cannot be overloaded + val methName: String, + // flattens the macro impl's parameter lists having symbols replaced with metadata + // currently metadata is an index of the type parameter corresponding to that type tag (if applicable) + // f.ex. for: def impl[T: AbsTypeTag, U: AbsTypeTag, V](c: Context)(x: c.Expr[T]): (U, V) = ??? + // `signature` will be equal to List(-1, -1, 0, 1) + val signature: List[Int], + // type arguments part of a macro impl ref (the right-hand side of a macro definition) + // these trees don't refer to a macro impl, so we can pickle them as is + val targs: List[Tree]) + + /** Macro def -> macro impl bindings are serialized into a `macroImpl` annotation + * with synthetic content that carries the payload described in `MacroImplBinding`. + * + * For example, for a pair of macro definition and macro implementation: + * def impl(c: scala.reflect.macros.Context): c.Expr[Unit] = c.literalUnit; + * def foo: Unit = macro impl + * + * We will have the following annotation added on the macro definition `foo`: + * + * @scala.reflect.macros.internal.macroImpl( + * `macro`( + * "signature" = List(-1), + * "methodName" = "impl", + * "versionFormat" = 1, + * "className" = "Macros$")) + */ + private object MacroImplBinding { + val versionFormat = 1 + + def pickleAtom(obj: Any): Tree = + obj match { + case list: List[_] => Apply(Ident(ListModule), list map pickleAtom) + case s: String => Literal(Constant(s)) + case i: Int => Literal(Constant(i)) + } + + def unpickleAtom(tree: Tree): Any = + tree match { + case Apply(list @ Ident(_), args) if list.symbol == ListModule => args map unpickleAtom + case Literal(Constant(s: String)) => s + case Literal(Constant(i: Int)) => i + } + + def pickle(macroImplRef: Tree): Tree = { + val macroImpl = macroImplRef.symbol + val paramss = macroImpl.paramss + + // this logic relies on the assumptions that were valid for the old macro prototype + // namely that macro implementations can only be defined in top-level classes and modules + // with the new prototype that materialized in a SIP, macros need to be statically accessible, which is different + // for example, a macro def could be defined in a trait that is implemented by an object + // there are some more clever cases when seemingly non-static method ends up being statically accessible + // however, the code below doesn't account for these guys, because it'd take a look of time to get it right + // for now I leave it as a todo and move along to more the important stuff + // [Eugene] relies on the fact that macro implementations can only be defined in static classes + // [Martin to Eugene++] There's similar logic buried in Symbol#flatname. Maybe we can refactor? + // [Eugene] we will refactor once I get my hands on https://issues.scala-lang.org/browse/SI-5498 + def className: String = { + def loop(sym: Symbol): String = sym match { + case sym if sym.owner.isPackageClass => + val suffix = if (sym.isModuleClass) "$" else "" + sym.fullName + suffix + case sym => + val separator = if (sym.owner.isModuleClass) "" else "$" + loop(sym.owner) + separator + sym.javaSimpleName.toString + } + + loop(macroImpl.owner.enclClass) + } + + def signature: List[Int] = { + val transformed = transformTypeTagEvidenceParams(paramss, (param, tparam) => tparam) + transformed.flatten map (p => if (p.isTerm) -1 else p.paramPos) + } + + val payload = List[(String, Any)]( + "versionFormat" -> versionFormat, + "className" -> className, + "methodName" -> macroImpl.name.toString, + "signature" -> signature + ) + + // the shape of the nucleus is chosen arbitrarily. it doesn't carry any payload. + // it's only necessary as a stub `fun` for an Apply node that carries metadata in its `args` + // so don't try to find a program element named "macro" that corresponds to the nucleus + // I just named it "macro", because it's macro-related, but I could as well name it "foobar" + val nucleus = Ident(newTermName("macro")) + val wrapped = Apply(nucleus, payload map { case (k, v) => Assign(pickleAtom(k), pickleAtom(v)) }) + val pickle = gen.mkTypeApply(wrapped, treeInfo.typeArguments(macroImplRef.duplicate)) + + // assign NoType to all freshly created AST nodes + // otherwise pickler will choke on tree.tpe being null + // there's another gotcha + // if you don't assign a ConstantType to a constant + // then pickling will crash + new Transformer { + override def transform(tree: Tree) = { + tree match { + case Literal(const @ Constant(x)) if tree.tpe == null => tree setType ConstantType(const) + case _ if tree.tpe == null => tree setType NoType + case _ => ; + } + super.transform(tree) + } + }.transform(pickle) + } + + def unpickle(pickle: Tree): MacroImplBinding = { + val (wrapped, targs) = + pickle match { + case TypeApply(wrapped, targs) => (wrapped, targs) + case wrapped => (wrapped, Nil) + } + val Apply(_, pickledPayload) = wrapped + val payload = pickledPayload.map{ case Assign(k, v) => (unpickleAtom(k), unpickleAtom(v)) }.toMap + + val pickleVersionFormat = payload("versionFormat").asInstanceOf[Int] + if (versionFormat != pickleVersionFormat) throw new Error("macro impl binding format mismatch: expected $versionFormat, actual $pickleVersionFormat") + + val className = payload("className").asInstanceOf[String] + val methodName = payload("methodName").asInstanceOf[String] + val signature = payload("signature").asInstanceOf[List[Int]] + MacroImplBinding(className, methodName, signature, targs) + } + } + + private def bindMacroImpl(macroDef: Symbol, macroImplRef: Tree): Unit = { + val pickle = MacroImplBinding.pickle(macroImplRef) + macroDef withAnnotation AnnotationInfo(MacroImplAnnotation.tpe, List(pickle), Nil) + } + + private def loadMacroImplBinding(macroDef: Symbol): MacroImplBinding = { + val Some(AnnotationInfo(_, List(pickle), _)) = macroDef.getAnnotation(MacroImplAnnotation) + MacroImplBinding.unpickle(pickle) + } + /** A list of compatible macro implementation signatures. * * In the example above: @@ -144,32 +310,22 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { macroTraceVerbose("macroImplSigs are: ")(paramsss, implRetTpe) } - private def transformTypeTagEvidenceParams(paramss: List[List[Symbol]], transform: (Symbol, Symbol) => Option[Symbol]): List[List[Symbol]] = { - if (paramss.length == 0) + private def transformTypeTagEvidenceParams(paramss: List[List[Symbol]], transform: (Symbol, Symbol) => Symbol): List[List[Symbol]] = { + import definitions.{ AbsTypeTagClass, MacroContextClass } + if (paramss.isEmpty || paramss.last.isEmpty) return paramss - val wannabe = if (paramss.head.length == 1) paramss.head.head else NoSymbol - val contextParam = if (wannabe != NoSymbol && wannabe.tpe <:< definitions.MacroContextClass.tpe) wannabe else NoSymbol - - val lastParamList0 = paramss.lastOption getOrElse Nil - val lastParamList = lastParamList0 flatMap (param => param.tpe match { - case TypeRef(SingleType(NoPrefix, contextParam), sym, List(tparam)) => - var wannabe = sym - while (wannabe.isAliasType) wannabe = wannabe.info.typeSymbol - if (wannabe != definitions.AbsTypeTagClass) - List(param) - else - transform(param, tparam.typeSymbol) map (_ :: Nil) getOrElse Nil - case _ => - List(param) - }) - - var result = paramss.dropRight(1) :+ lastParamList - if (lastParamList0.isEmpty ^ lastParamList.isEmpty) { - result = result dropRight 1 + val ContextParam = paramss.head match { + case p :: Nil => p filter (_.tpe <:< definitions.MacroContextClass.tpe) + case _ => NoSymbol } - - result + def isTag(sym: Symbol): Boolean = (sym == AbsTypeTagClass) || (sym.isAliasType && isTag(sym.info.typeSymbol)) + def transformTag(param: Symbol): Symbol = param.tpe match { + case TypeRef(SingleType(NoPrefix, ContextParam), sym, tp :: Nil) if isTag(sym) => transform(param, tp.typeSymbol) + case _ => param + } + val last = paramss.last map transformTag filterNot (_ eq NoSymbol) + if (last.isEmpty) paramss.init else paramss.init :+ last } /** As specified above, body of a macro definition must reference its implementation. @@ -186,6 +342,11 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { import typer.context macroLogVerbose("typechecking macro def %s at %s".format(ddef.symbol, ddef.pos)) + val macroDef = ddef.symbol + val defpos = macroDef.pos + val implpos = ddef.rhs.pos + assert(macroDef.isTermMacro, ddef) + if (fastTrack contains ddef.symbol) { macroLogVerbose("typecheck terminated unexpectedly: macro is hardwired") assert(!ddef.tpt.isEmpty, "hardwired macros must provide result type") @@ -207,17 +368,18 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } } - var hasErrors = false + var _hasError = false + def hasError = _hasError + def setError(): Unit = { + _hasError = true + macroDef setFlag IS_ERROR + } def reportError(pos: Position, msg: String) = { - hasErrors = true + setError() context.error(pos, msg) + macroDef setFlag IS_ERROR } - val macroDef = ddef.symbol - val defpos = macroDef.pos - val implpos = ddef.rhs.pos - assert(macroDef.isTermMacro, ddef) - def invalidBodyError() = reportError(defpos, "macro body has wrong shape:" + @@ -275,7 +437,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { val rhs = ddef.rhs validatePreTyper(rhs) - if (hasErrors) macroTraceVerbose("macro def failed to satisfy trivial preconditions: ")(macroDef) + if (hasError) macroTraceVerbose("macro def failed to satisfy trivial preconditions: ")(macroDef) // we use typed1 instead of typed, because otherwise adapt is going to mess us up // if adapt sees <qualifier>.<method>, it will want to perform eta-expansion and will fail @@ -284,7 +446,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { def typecheckRhs(rhs: Tree): Tree = { try { val prevNumErrors = reporter.ERROR.count // [Eugene] funnily enough, the isErroneous check is not enough - var rhs1 = if (hasErrors) EmptyTree else typer.typed1(rhs, EXPRmode, WildcardType) + var rhs1 = if (hasError) EmptyTree else typer.typed1(rhs, EXPRmode, WildcardType) def typecheckedWithErrors = (rhs1 exists (_.isErroneous)) || reporter.ERROR.count != prevNumErrors def rhsNeedsMacroExpansion = rhs1.symbol != null && rhs1.symbol.isTermMacro && !rhs1.symbol.isErroneous while (!typecheckedWithErrors && rhsNeedsMacroExpansion) { @@ -313,35 +475,41 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { val prevNumErrors = reporter.ERROR.count // funnily enough, the isErroneous check is not enough var rhs1 = typecheckRhs(rhs) - def typecheckedWithErrors = (rhs1 exists (_.isErroneous)) || reporter.ERROR.count != prevNumErrors - hasErrors = hasErrors || typecheckedWithErrors - if (typecheckedWithErrors) macroTraceVerbose("body of a macro def failed to typecheck: ")(ddef) - val macroImpl = rhs1.symbol - macroDef withAnnotation AnnotationInfo(MacroImplAnnotation.tpe, List(rhs1), Nil) - if (!hasErrors) { - if (macroImpl == null) { - invalidBodyError() - } else { - if (!macroImpl.isMethod) + def typecheckedWithErrors = (rhs1 exists (_.isErroneous)) || reporter.ERROR.count != prevNumErrors + if (typecheckedWithErrors) { + setError() + macroTraceVerbose("body of a macro def failed to typecheck: ")(ddef) + } else { + if (!hasError) { + if (macroImpl == null) { invalidBodyError() - if (macroImpl.isOverloaded) - reportError(implpos, "macro implementation cannot be overloaded") - if (!macroImpl.typeParams.isEmpty && (!rhs1.isInstanceOf[TypeApply])) - reportError(implpos, "macro implementation reference needs type arguments") - if (!hasErrors) - validatePostTyper(rhs1) + } else { + if (!macroImpl.isMethod) + invalidBodyError() + if (!macroImpl.isPublic) + reportError(implpos, "macro implementation must be public") + if (macroImpl.isOverloaded) + reportError(implpos, "macro implementation cannot be overloaded") + if (!macroImpl.typeParams.isEmpty && (!rhs1.isInstanceOf[TypeApply])) + reportError(implpos, "macro implementation reference needs type arguments") + if (!hasError) + validatePostTyper(rhs1) + } + if (hasError) + macroTraceVerbose("macro def failed to satisfy trivial preconditions: ")(macroDef) + } + if (!hasError) { + bindMacroImpl(macroDef, rhs1) // we must bind right over here, because return type inference needs this info } - if (hasErrors) - macroTraceVerbose("macro def failed to satisfy trivial preconditions: ")(macroDef) } - if (!hasErrors) { + if (!hasError) { def checkCompatibility(reqparamss: List[List[Symbol]], actparamss: List[List[Symbol]], reqres: Type, actres: Type): List[String] = { - var hasErrors = false + var hasError = false var errors = List[String]() def compatibilityError(msg: String) { - hasErrors = true + hasError = true errors :+= msg } @@ -364,7 +532,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } } - if (!hasErrors) { + if (!hasError) { try { for ((rparams, aparams) <- reqparamss zip actparamss) { if (rparams.length < aparams.length) @@ -374,7 +542,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } // if the implementation signature is already deemed to be incompatible, we bail out // otherwise, high-order type magic employed below might crash in weird ways - if (!hasErrors) { + if (!hasError) { for ((rparams, aparams) <- reqparamss zip actparamss) { for ((rparam, aparam) <- rparams zip aparams) { def isRepeated(param: Symbol) = param.tpe.typeSymbol == RepeatedParamClass @@ -387,7 +555,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { compatibilityError("types incompatible for parameter "+rparam.name+": corresponding is not a vararg parameter") if (!isRepeated(rparam) && isRepeated(aparam)) compatibilityError("types incompatible for parameter "+aparam.name+": corresponding is not a vararg parameter") - if (!hasErrors) { + if (!hasError) { var atpe = aparam.tpe.substSym(flatactparams, flatreqparams).instantiateTypeParams(tparams, tvars) atpe = atpe.dealias // SI-5706 // strip the { type PrefixType = ... } refinement off the Context or otherwise we get compatibility errors @@ -400,11 +568,11 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } } } - if (!hasErrors) { + if (!hasError) { val atpe = actres.substSym(flatactparams, flatreqparams).instantiateTypeParams(tparams, tvars) checkSubType("return type", atpe, reqres) } - if (!hasErrors) { + if (!hasError) { val targs = solvedTypes(tvars, tparams, tparams map varianceInType(actres), false, lubDepth(flatactparams map (_.tpe)) max lubDepth(flatreqparams map (_.tpe))) val boundsOk = typer.silent(_.infer.checkBounds(ddef, NoPrefix, NoSymbol, tparams, targs, "")) @@ -429,7 +597,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } var actparamss = macroImpl.paramss - actparamss = transformTypeTagEvidenceParams(actparamss, (param, tparam) => None) + actparamss = transformTypeTagEvidenceParams(actparamss, (param, tparam) => NoSymbol) val rettpe = if (!ddef.tpt.isEmpty) typer.typedType(ddef.tpt).tpe else computeMacroDefTypeFromMacroImpl(ddef, macroDef, macroImpl) val (reqparamsss0, reqres0) = macroImplSigs(macroDef, ddef.tparams, ddef.vparamss, rettpe) @@ -443,7 +611,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { macroTraceVerbose("macro def failed to satisfy trivial preconditions: ")(macroDef) } - if (!hasErrors) { + if (!hasError) { val reqres = reqres0 val actres = macroImpl.tpe.finalResultType def showMeth(pss: List[List[Symbol]], restpe: Type, abbreviate: Boolean) = { @@ -482,15 +650,6 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } } - // if this macro definition is erroneous, then there's no sense in expanding its usages - // in the previous prototype macro implementations were magically generated from macro definitions - // so macro definitions and its usages couldn't be compiled in the same compilation run - // however, now definitions and implementations are decoupled, so it's everything is possible - // hence, we now use IS_ERROR flag to serve as an indicator that given macro definition is broken - if (hasErrors) { - macroDef setFlag IS_ERROR - } - rhs1 } @@ -526,17 +685,9 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { // sym.paramPos is unreliable (see another case below) val tparams = macroImpl.typeParams map (_.deSkolemize) val paramPos = tparams indexOf sym.deSkolemize - val sym1 = if (paramPos == -1) sym else { - val ann = macroDef.getAnnotation(MacroImplAnnotation) - ann match { - case Some(ann) => - val TypeApply(_, implRefTargs) = ann.args(0) - val implRefTarg = implRefTargs(paramPos).tpe.typeSymbol - implRefTarg - case None => - sym - } - } + val sym1 = + if (paramPos == -1) sym + else loadMacroImplBinding(macroDef).targs(paramPos).tpe.typeSymbol TypeRef(pre, sym1, args) case tpe => tpe @@ -550,7 +701,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { // val defParamss = macroDef.paramss val defParamss = mmap(macroDdef.vparamss)(_.symbol) var implParamss = macroImpl.paramss - implParamss = transformTypeTagEvidenceParams(implParamss, (param, tparam) => None) + implParamss = transformTypeTagEvidenceParams(implParamss, (param, tparam) => NoSymbol) val implCtxParam = if (implParamss.length > 0 && implParamss(0).length > 0) implParamss(0)(0) else null def implParamToDefParam(implParam: Symbol): Symbol = { @@ -619,118 +770,42 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { * 3) Loads the companion of that enclosing class from the macro classloader. * 4) Resolves macro implementation within the loaded companion. * - * @return Some(runtime) if macro implementation can be loaded successfully from either of the mirrors, - * None otherwise. + * @return Requested runtime if macro implementation can be loaded successfully from either of the mirrors, + * null otherwise. */ type MacroRuntime = List[Any] => Any - private val macroRuntimesCache = perRunCaches.newWeakMap[Symbol, Option[MacroRuntime]] - private def macroRuntime(macroDef: Symbol): Option[MacroRuntime] = { + private val macroRuntimesCache = perRunCaches.newWeakMap[Symbol, MacroRuntime] + private def macroRuntime(macroDef: Symbol): MacroRuntime = { macroTraceVerbose("looking for macro implementation: ")(macroDef) if (fastTrack contains macroDef) { macroLogVerbose("macro expansion is serviced by a fast track") - Some(fastTrack(macroDef)) + fastTrack(macroDef) } else { macroRuntimesCache.getOrElseUpdate(macroDef, { - val runtime = { - macroTraceVerbose("macroDef is annotated with: ")(macroDef.annotations) - - val ann = macroDef.getAnnotation(MacroImplAnnotation) - if (ann == None) { macroTraceVerbose("@macroImpl annotation is missing (this means that macro definition failed to typecheck)")(macroDef); return None } - - val macroImpl = ann.get.args(0).symbol - if (macroImpl == NoSymbol) { macroTraceVerbose("@macroImpl annotation is malformed (this means that macro definition failed to typecheck)")(macroDef); return None } - macroLogVerbose("resolved implementation %s at %s".format(macroImpl, macroImpl.pos)) - if (macroImpl.isErroneous) { macroTraceVerbose("macro implementation is erroneous (this means that either macro body or macro implementation signature failed to typecheck)")(macroDef); return None } - - // [Eugene++] I don't use Scala reflection here, because it seems to interfere with JIT magic - // whenever you instantiate a mirror (and not do anything with in, just instantiate), performance drops by 15-20% - // I'm not sure what's the reason - for me it's pure voodoo - def loadMacroImpl(cl: ClassLoader): Option[(Object, jMethod)] = { - try { - // this logic relies on the assumptions that were valid for the old macro prototype - // namely that macro implementations can only be defined in top-level classes and modules - // with the new prototype that materialized in a SIP, macros need to be statically accessible, which is different - // for example, a macro def could be defined in a trait that is implemented by an object - // there are some more clever cases when seemingly non-static method ends up being statically accessible - // however, the code below doesn't account for these guys, because it'd take a look of time to get it right - // for now I leave it as a todo and move along to more the important stuff - - macroTraceVerbose("loading implementation class: ")(macroImpl.owner.fullName) - macroTraceVerbose("classloader is: ")(ReflectionUtils.show(cl)) - - // [Eugene] relies on the fact that macro implementations can only be defined in static classes - // [Martin to Eugene++] There's similar logic buried in Symbol#flatname. Maybe we can refactor? - def classfile(sym: Symbol): String = { - def recur(sym: Symbol): String = sym match { - case sym if sym.owner.isPackageClass => - val suffix = if (sym.isModuleClass) "$" else "" - sym.fullName + suffix - case sym => - val separator = if (sym.owner.isModuleClass) "" else "$" - recur(sym.owner) + separator + sym.javaSimpleName.toString - } - - if (sym.isClass || sym.isModule) recur(sym) - else recur(sym.enclClass) - } - - // [Eugene++] this doesn't work for inner classes - // neither does macroImpl.owner.javaClassName, so I had to roll my own implementation - //val receiverName = macroImpl.owner.fullName - val implClassName = classfile(macroImpl.owner) - val implObj = try { - val implObjClass = jClass.forName(implClassName, true, cl) - implObjClass getField "MODULE$" get null - } catch { - case ex: NoSuchFieldException => macroTraceVerbose("exception when loading implObj: ")(ex); null - case ex: NoClassDefFoundError => macroTraceVerbose("exception when loading implObj: ")(ex); null - case ex: ClassNotFoundException => macroTraceVerbose("exception when loading implObj: ")(ex); null - } - - if (implObj == null) None - else { - // [Eugene++] doh, it seems that I need to copy/paste Scala reflection logic - // see `JavaMirrors.methodToJava` or whatever it's called now - val implMeth = { - def typeToJavaClass(tpe: Type): jClass[_] = tpe match { - case ExistentialType(_, rtpe) => typeToJavaClass(rtpe) - case TypeRef(_, ArrayClass, List(elemtpe)) => jArray.newInstance(typeToJavaClass(elemtpe), 0).getClass - case TypeRef(_, sym: ClassSymbol, _) => jClass.forName(classfile(sym), true, cl) - case _ => throw new NoClassDefFoundError("no Java class corresponding to "+tpe+" found") - } - - val paramClasses = transformedType(macroImpl).paramTypes map typeToJavaClass - try implObj.getClass getDeclaredMethod (macroImpl.name.toString, paramClasses: _*) - catch { - case ex: NoSuchMethodException => - val expandedName = - if (macroImpl.isPrivate) nme.expandedName(macroImpl.name.toTermName, macroImpl.owner).toString - else macroImpl.name.toString - implObj.getClass getDeclaredMethod (expandedName, paramClasses: _*) - } - } - macroLogVerbose("successfully loaded macro impl as (%s, %s)".format(implObj, implMeth)) - Some((implObj, implMeth)) - } - } catch { - case ex: ClassNotFoundException => - macroTraceVerbose("implementation class failed to load: ")(ex.toString) - None - case ex: NoSuchMethodException => - macroTraceVerbose("implementation method failed to load: ")(ex.toString) - None - } - } - - loadMacroImpl(macroClassloader) map { - case (implObj, implMeth) => - def runtime(args: List[Any]) = implMeth.invoke(implObj, (args map (_.asInstanceOf[AnyRef])): _*).asInstanceOf[Any] - runtime _ - } + val binding = loadMacroImplBinding(macroDef) + val className = binding.className + val methName = binding.methName + macroLogVerbose(s"resolved implementation as $className.$methName") + + // [Eugene++] I don't use Scala reflection here, because it seems to interfere with JIT magic + // whenever you instantiate a mirror (and not do anything with in, just instantiate), performance drops by 15-20% + // I'm not sure what's the reason - for me it's pure voodoo + try { + macroTraceVerbose("loading implementation class: ")(className) + macroTraceVerbose("classloader is: ")(ReflectionUtils.show(macroClassloader)) + val implObj = ReflectionUtils.staticSingletonInstance(macroClassloader, className) + // relies on the fact that macro impls cannot be overloaded + // so every methName can resolve to at maximum one method + val implMeths = implObj.getClass.getDeclaredMethods.find(_.getName == methName) + val implMeth = implMeths getOrElse { throw new NoSuchMethodException(s"$className.$methName") } + macroLogVerbose("successfully loaded macro impl as (%s, %s)".format(implObj, implMeth)) + (args: List[Any]) => implMeth.invoke(implObj, (args map (_.asInstanceOf[AnyRef])): _*) + } catch { + case ex: Exception => + macroTraceVerbose(s"macro runtime failed to load: ")(ex.toString) + macroDef setFlag IS_ERROR + null } - - if (runtime == None) macroDef setFlag IS_ERROR - runtime }) } } @@ -755,7 +830,6 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { */ private def macroArgs(typer: Typer, expandee: Tree): Option[List[Any]] = { val macroDef = expandee.symbol - val runtime = macroRuntime(macroDef) orElse { return None } val prefixTree = expandee.collect{ case Select(qual, name) => qual }.headOption.getOrElse(EmptyTree) val context = expandee.attachments.get[MacroRuntimeAttachment].flatMap(_.macroContext).getOrElse(macroContext(typer, prefixTree, expandee)) var typeArgs = List[Tree]() @@ -771,6 +845,11 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { case _ => } collectMacroArgs(expandee) + + val argcDoesntMatch = macroDef.paramss.length != exprArgs.length + val nullaryArgsEmptyParams = exprArgs.isEmpty && macroDef.paramss == List(List()) + if (argcDoesntMatch && !nullaryArgsEmptyParams) { typer.TyperErrorGen.MacroPartialApplicationError(expandee); return None } + var argss: List[List[Any]] = List(context) :: exprArgs.toList macroTraceVerbose("argss: ")(argss) val rawArgss = @@ -787,33 +866,8 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { 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 - } - - // 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 - } + val binding = loadMacroImplBinding(macroDef) + macroTraceVerbose("binding: ")(binding) // if paramss have typetag context bounds, add an arglist to argss if necessary and instantiate the corresponding evidences // consider the following example: @@ -831,43 +885,41 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { // 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) { + val tags = binding.signature filter (_ != -1) map (paramPos => { + val targ = binding.targs(paramPos).tpe.typeSymbol + val tpe = if (targ.isTypeParameterOrSkolem) { + if (targ.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 + // then targ is not a skolem and isn't equal to any of macroDef.typeParams + // val argPos = targ.deSkolemize.paramPos + val argPos = macroDef.typeParams.indexWhere(_.name == targ.name) + typeArgs(argPos).tpe } else - implRefTarg.tpe.asSeenFrom( + targ.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.AbsTypeTagClass => - // do nothing - case _ => - throw new Error("unsupported tpe: " + tpe) - } - Some(tparam) + targ.tpe + if (tpe.isConcrete) context.TypeTag(tpe) else context.AbsTypeTag(tpe) + }) + val hasImplicitParams = macroDef.paramss.flatten.lastOption exists (_.isImplicit) + argss = if (hasImplicitParams) argss.dropRight(1) :+ (tags ++ argss.last) else argss :+ tags + + // transforms argss taking into account varargness of paramss + // not all argument lists in argss map to macroDef.paramss, so we need to apply extra care + // namely: + // 1) the first argument list represents (c: Context) in macroImpl, so it doesn't have correspondence in macroDef + // 2) typetag context bounds are only declared on macroImpls, so this optional arglist also doesn't match macroDef + // nb! varargs can apply to any parameter section, not necessarily to the last one + mapWithIndex(argss)((as, i_argss) => { + val i_paramss = i_argss - 1 + val mapsToParamss = 0 <= i_paramss && i_paramss < macroDef.paramss.length + if (mapsToParamss) { + val ps = macroDef.paramss(i_paramss) + if (isVarArgsList(ps)) as.take(ps.length - 1) :+ as.drop(ps.length - 1) + else as + } else as }) - val tags = paramss.last takeWhile (_.isType) map (resolved(_)) map (tpe => if (tpe.isConcrete) context.TypeTag(tpe) else context.AbsTypeTag(tpe)) - 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 - } - rawArgss } val rawArgs = rawArgss.flatten macroTraceVerbose("rawArgs: ")(rawArgs) @@ -996,12 +1048,9 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { return Cancel(typer.infer.setError(expandee)) } - macroRuntime(expandee.symbol) match { - case Some(runtime) => - macroExpandWithRuntime(typer, expandee, runtime) - case None => - macroExpandWithoutRuntime(typer, expandee) - } + val runtime = macroRuntime(expandee.symbol) + if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime) + else macroExpandWithoutRuntime(typer, expandee) } /** Expands a macro when a runtime (i.e. the macro implementation) can be successfully loaded diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index efb96b173c..92d0d4e228 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -988,6 +988,15 @@ trait Namers extends MethodSynthesis { // (either "macro ???" as they used to or just "???" to maximally simplify their compilation) if (fastTrack contains ddef.symbol) ddef.symbol setFlag MACRO + // macro defs need to be typechecked in advance + // because @macroImpl annotation only gets assigned during typechecking + // otherwise macro defs wouldn't be able to robustly coexist with their clients + // because a client could be typechecked before a macro def that it uses + if (ddef.symbol.isTermMacro) { + val pt = resultPt.substSym(tparamSyms, tparams map (_.symbol)) + typer.computeMacroDefType(ddef, pt) + } + thisMethodType({ val rt = ( if (!tpt.isEmpty) { diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index 92a6156e54..19f264f60e 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -372,6 +372,13 @@ abstract class TreeInfo { case _ => EmptyTree } + /** If this tree represents a type application the type arguments. Otherwise Nil. + */ + def typeArguments(tree: Tree): List[Tree] = tree match { + case TypeApply(_, targs) => targs + case _ => Nil + } + /** If this tree has type parameters, those. Otherwise Nil. */ def typeParameters(tree: Tree): List[TypeDef] = tree match { diff --git a/test/files/neg/macro-invalidimpl-i.check b/test/files/neg/macro-invalidimpl-i.check new file mode 100644 index 0000000000..b6277809a3 --- /dev/null +++ b/test/files/neg/macro-invalidimpl-i.check @@ -0,0 +1,4 @@ +Macros_Test_2.scala:4: error: macro implementation must be public
+ def foo = macro Impls.impl
+ ^
+one error found
diff --git a/test/files/neg/macro-invalidimpl-i.flags b/test/files/neg/macro-invalidimpl-i.flags new file mode 100644 index 0000000000..cd66464f2f --- /dev/null +++ b/test/files/neg/macro-invalidimpl-i.flags @@ -0,0 +1 @@ +-language:experimental.macros
\ No newline at end of file diff --git a/test/files/neg/macro-invalidimpl-i/Impls_1.scala b/test/files/neg/macro-invalidimpl-i/Impls_1.scala new file mode 100644 index 0000000000..c35d8ab3c1 --- /dev/null +++ b/test/files/neg/macro-invalidimpl-i/Impls_1.scala @@ -0,0 +1,7 @@ +package foo + +import scala.reflect.macros.Context + +object Impls { + private[foo] def impl(c: Context) = ??? +}
\ No newline at end of file diff --git a/test/files/neg/macro-invalidimpl-i/Macros_Test_2.scala b/test/files/neg/macro-invalidimpl-i/Macros_Test_2.scala new file mode 100644 index 0000000000..fb129c70be --- /dev/null +++ b/test/files/neg/macro-invalidimpl-i/Macros_Test_2.scala @@ -0,0 +1,5 @@ +package foo + +object Test extends App { + def foo = macro Impls.impl +} diff --git a/test/files/run/t5940.scala b/test/files/run/t5940.scala new file mode 100644 index 0000000000..147ff38256 --- /dev/null +++ b/test/files/run/t5940.scala @@ -0,0 +1,41 @@ +import scala.tools.partest._ + +object Test extends DirectTest { + def code = ??? + + def macros_1 = """ + import scala.reflect.macros.Context + + object Impls { + def impl(c: Context) = c.literalUnit + } + + object Macros { + //import Impls._ + def impl(c: Context) = c.literalUnit + def foo = macro impl + } + """ + def compileMacros() = { + val classpath = List(sys.props("partest.lib"), sys.props("partest.reflect")) mkString sys.props("path.separator") + compileString(newCompiler("-language:experimental.macros", "-cp", classpath, "-d", testOutput.path))(macros_1) + } + + def test_2 = """ + object Test extends App { + println(Macros.foo) + } + """ + def compileTest() = { + val classpath = List(sys.props("partest.lib"), testOutput.path) mkString sys.props("path.separator") + compileString(newCompiler("-cp", classpath, "-d", testOutput.path))(test_2) + } + + def show(): Unit = { + log("Compiling Macros_1...") + if (compileMacros()) { + log("Compiling Test_2...") + if (compileTest()) log("Success!") else log("Failed...") + } + } +}
\ No newline at end of file |