diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala | 38 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Macros.scala | 366 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/RefChecks.scala | 2 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Typers.scala | 2 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/Definitions.scala | 10 | ||||
-rw-r--r-- | test/files/neg/macro-qmarkqmarkqmark.check | 2 | ||||
-rw-r--r-- | test/files/neg/t7157.check | 73 | ||||
-rw-r--r-- | test/files/neg/t7157/Impls_Macros_1.scala | 32 | ||||
-rw-r--r-- | test/files/neg/t7157/Test_2.scala | 63 | ||||
-rw-r--r-- | test/files/run/macro-invalidusage-partialapplication-with-tparams.check | 2 | ||||
-rw-r--r-- | test/files/run/macro-invalidusage-partialapplication.check | 2 | ||||
-rw-r--r-- | test/files/run/reify-repl-fail-gracefully.check | 2 | ||||
-rw-r--r-- | test/files/run/t7157.check | 1 | ||||
-rw-r--r-- | test/files/run/t7157/Impls_Macros_1.scala | 15 | ||||
-rw-r--r-- | test/files/run/t7157/Test_2.scala | 5 |
15 files changed, 455 insertions, 160 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 135a79124d..054f96c7b7 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -476,7 +476,7 @@ trait ContextErrors { // doTypeApply //tryNamesDefaults def NamedAndDefaultArgumentsNotSupportedForMacros(tree: Tree, fun: Tree) = - NormalTypeError(tree, "macros application do not support named and/or default arguments") + NormalTypeError(tree, "macro applications do not support named and/or default arguments") def TooManyArgsNamesDefaultsError(tree: Tree, fun: Tree) = NormalTypeError(tree, "too many arguments for "+treeSymTypeMsg(fun)) @@ -571,7 +571,7 @@ trait ContextErrors { //adapt def MissingArgsForMethodTpeError(tree: Tree, meth: Symbol) = { val message = - if (meth.isMacro) MacroPartialApplicationErrorMessage + if (meth.isMacro) MacroTooFewArgumentListsMessage else "missing arguments for " + meth.fullLocationString + ( if (meth.isConstructor) "" else ";\nfollow this method with `_' if you want to treat it as a partially applied function" @@ -703,15 +703,24 @@ trait ContextErrors { throw MacroExpansionException } - def MacroPartialApplicationErrorMessage = "macros cannot be partially applied" - def MacroPartialApplicationError(expandee: Tree) = { + private def macroExpansionError2(expandee: Tree, msg: String) = { // macroExpansionError won't work => swallows positions, hence needed to do issueTypeError // kinda contradictory to the comment in `macroExpansionError`, but this is how it works - issueNormalTypeError(expandee, MacroPartialApplicationErrorMessage) + issueNormalTypeError(expandee, msg) setError(expandee) throw MacroExpansionException } + private def MacroTooFewArgumentListsMessage = "too few argument lists for macro invocation" + def MacroTooFewArgumentLists(expandee: Tree) = macroExpansionError2(expandee, MacroTooFewArgumentListsMessage) + + private def MacroTooManyArgumentListsMessage = "too many argument lists for macro invocation" + def MacroTooManyArgumentLists(expandee: Tree) = macroExpansionError2(expandee, MacroTooManyArgumentListsMessage) + + def MacroTooFewArguments(expandee: Tree) = macroExpansionError2(expandee, "too few arguments for macro invocation") + + def MacroTooManyArguments(expandee: Tree) = macroExpansionError2(expandee, "too many arguments for macro invocation") + def MacroGeneratedAbort(expandee: Tree, ex: AbortMacroException) = { // errors have been reported by the macro itself, so we do nothing here macroLogVerbose("macro expansion has been aborted") @@ -1257,10 +1266,17 @@ trait ContextErrors { // not exactly an error generator, but very related // and I dearly wanted to push it away from Macros.scala - private def checkSubType(slot: String, rtpe: Type, atpe: Type) = { - val ok = if (macroDebugVerbose) { - withTypesExplained(rtpe <:< atpe) - } else rtpe <:< atpe + private def checkConforms(slot: String, rtpe: Type, atpe: Type) = { + val verbose = macroDebugVerbose || settings.explaintypes.value + + def check(rtpe: Type, atpe: Type): Boolean = { + if (rtpe eq atpe) { if (verbose) println(rtpe + " <: " + atpe + "?" + EOL + "true"); true } + else rtpe <:< atpe + } + + val ok = + if (verbose) withTypesExplained(check(rtpe, atpe)) + else check(rtpe, atpe) if (!ok) { if (!macroDebugVerbose) explainTypes(rtpe, atpe) @@ -1341,9 +1357,9 @@ trait ContextErrors { def MacroImplMissingParamsError(aparams: List[Symbol], rparams: List[Symbol]) = compatibilityError(abbreviateCoreAliases(lengthMsg("value", "required", rparams(aparams.length)))) - def checkMacroImplParamTypeMismatch(atpe: Type, rparam: Symbol) = checkSubType("parameter " + rparam.name, rparam.tpe, atpe) + def checkMacroImplParamTypeMismatch(atpe: Type, rparam: Symbol) = checkConforms("parameter " + rparam.name, rparam.tpe, atpe) - def checkMacroImplResultTypeMismatch(atpe: Type, rret: Type) = checkSubType("return type", atpe, rret) + def checkMacroImplResultTypeMismatch(atpe: Type, rret: Type) = checkConforms("return type", atpe, rret) def MacroImplParamNameMismatchError(aparam: Symbol, rparam: Symbol) = compatibilityError("parameter names differ: " + rparam.name + " != " + aparam.name) diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 5ac37251ee..93f73f1bbe 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -1,6 +1,7 @@ package scala.tools.nsc package typechecker +import java.lang.Math.min import symtab.Flags._ import scala.tools.nsc.util._ import scala.reflect.runtime.ReflectionUtils @@ -82,15 +83,22 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { // `className` and `methName` are all we need to reflectively invoke a macro implementation // because macro implementations cannot be overloaded 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: WeakTypeTag, U: WeakTypeTag, V](c: Context)(x: c.Expr[T]): (U, V) = ??? - // `signature` will be equal to List(-1, -1, 0, 1) - signature: List[Int], + // flattens the macro impl's parameter lists having symbols replaced with their fingerprints + // currently fingerprints are calculated solely from types of the symbols: + // * c.Expr[T] => IMPLPARAM_EXPR + // * c.WeakTypeTag[T] => index of the type parameter corresponding to that type tag + // * everything else (e.g. scala.reflect.macros.Context) => IMPLPARAM_OTHER + // f.ex. for: def impl[T: WeakTypeTag, U, V: WeakTypeTag](c: Context)(x: c.Expr[T], y: c.Tree): (U, V) = ??? + // `signature` will be equal to List(List(-1), List(-1, -2), List(0, 2)) + signature: List[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 targs: List[Tree]) + private final val IMPLPARAM_TAG = 0 // actually it's zero and above, this is just a lower bound for >= checks + private final val IMPLPARAM_OTHER = -1 + private final val IMPLPARAM_EXPR = -2 + /** Macro def -> macro impl bindings are serialized into a `macroImpl` annotation * with synthetic content that carries the payload described in `MacroImplBinding`. * @@ -104,11 +112,11 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { * `macro`( * "signature" = List(-1), * "methodName" = "impl", - * "versionFormat" = 1, + * "versionFormat" = <current version format>, * "className" = "Macros$")) */ private object MacroImplBinding { - val versionFormat = 1 + val versionFormat = 2 def pickleAtom(obj: Any): Tree = obj match { @@ -142,9 +150,15 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { loop(owner) } - def signature: List[Int] = { + def signature: List[List[Int]] = { + def fingerprint(tpe: Type): Int = tpe.dealiasWiden match { + case TypeRef(_, RepeatedParamClass, underlying :: Nil) => fingerprint(underlying) + case ExprClassOf(_) => IMPLPARAM_EXPR + case _ => IMPLPARAM_OTHER + } + val transformed = transformTypeTagEvidenceParams(paramss, (param, tparam) => tparam) - transformed.flatten map (p => if (p.isTerm) -1 else p.paramPos) + mmap(transformed)(p => if (p.isTerm) fingerprint(p.info) else p.paramPos) } val payload = List[(String, Any)]( @@ -193,7 +207,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { val className = payload("className").asInstanceOf[String] val methodName = payload("methodName").asInstanceOf[String] - val signature = payload("signature").asInstanceOf[List[Int]] + val signature = payload("signature").asInstanceOf[List[List[Int]]] MacroImplBinding(className, methodName, signature, targs) } } @@ -235,19 +249,40 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { if (transformed.isEmpty) paramss.init else paramss.init :+ transformed } - def computeMacroDefTypeFromMacroImpl(macroDdef: DefDef, macroImpl: Symbol): Type = { - // Step I. Transform c.Expr[T] to T - var runtimeType = macroImpl.tpe.finalResultType.dealias match { - case TypeRef(_, ExprClass, runtimeType :: Nil) => runtimeType - case _ => AnyTpe // so that macro impls with rhs = ??? don't screw up our inference - } + private def dealiasAndRewrap(tp: Type)(fn: Type => Type): Type = { + if (isRepeatedParamType(tp)) scalaRepeatedType(fn(tp.typeArgs.head.dealias)) + else fn(tp.dealias) + } + + /** Increases metalevel of the type, i.e. transforms: + * * T to c.Expr[T] + * + * @see Metalevels.scala for more information and examples about metalevels + */ + private def increaseMetalevel(c: Symbol, tp: Type): Type = dealiasAndRewrap(tp) { + case tp => typeRef(SingleType(NoPrefix, c), MacroContextExprClass, List(tp)) + } + + /** Decreases metalevel of the type, i.e. transforms: + * * c.Expr[T] to T + * + * @see Metalevels.scala for more information and examples about metalevels + */ + private def decreaseMetalevel(tp: Type): Type = dealiasAndRewrap(tp) { + case ExprClassOf(runtimeType) => runtimeType + case _ => AnyClass.tpe // so that macro impls with rhs = ??? don't screw up our inference + } + + def computeMacroDefTypeFromMacroImpl(macroDdef: DefDef, macroImplSig: MacroImplSig): Type = { + // Step I. Transform c.Expr[T] to T and c.Tree to <untyped> + var runtimeType = decreaseMetalevel(macroImplSig.ret) // Step II. Transform type parameters of a macro implementation into type arguments in a macro definition's body - runtimeType = runtimeType.substituteTypes(macroImpl.typeParams, loadMacroImplBinding(macroDdef.symbol).targs.map(_.tpe)) + runtimeType = runtimeType.substituteTypes(macroImplSig.tparams, loadMacroImplBinding(macroDdef.symbol).targs.map(_.tpe)) // Step III. Transform c.prefix.value.XXX to this.XXX and implParam.value.YYY to defParam.YYY def unsigma(tpe: Type): Type = - transformTypeTagEvidenceParams(macroImpl.paramss, (param, tparam) => NoSymbol) match { + transformTypeTagEvidenceParams(macroImplSig.paramss, (param, tparam) => NoSymbol) match { case (implCtxParam :: Nil) :: implParamss => val implToDef = flatMap2(implParamss, macroDdef.vparamss)(map2(_, _)((_, _))).toMap object UnsigmaTypeMap extends TypeMap { @@ -276,39 +311,83 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { unsigma(runtimeType) } - /** A reference macro implementation signature compatible with a given macro definition. + /** Signature of a macro implementation, used to check def <-> impl correspondence. + * + * Technically it can be just an alias to MethodType, but promoting it to a first-class entity + * provides better encapsulation and convenient syntax for pattern matching. + */ + case class MacroImplSig(tparams: List[Symbol], paramss: List[List[Symbol]], ret: Type) + + /** An actual macro implementation signature extracted from a macro implementation method. + * + * In the example above for the following macro impl: + * def fooBar[T: c.WeakTypeTag] + * (c: scala.reflect.macros.Context) + * (xs: c.Expr[List[T]]) + * : c.Expr[T] = ... + * + * This function will return: + * (c: scala.reflect.macros.Context)(xs: c.Expr[List[T]])c.Expr[T] + * + * Note that type tag evidence parameters are not included into the result. + * Type tag context bounds for macro impl tparams are optional. + * Therefore compatibility checks ignore such parameters, and we don't need to bother about them here. + * + * This method cannot be reduced to just macroImpl.info, because macro implementations might + * come in different shapes. If the implementation is an apply method of a Macro-compatible object, + * then it won't have (c: Context) in its parameters, but will rather refer to Macro.c. + * + * @param macroImpl The macro implementation symbol + */ + def macroImplSig(macroImpl: Symbol): MacroImplSig = { + val tparams = macroImpl.typeParams + val paramss = transformTypeTagEvidenceParams(macroImpl.paramss, (param, tparam) => NoSymbol) + val ret = macroImpl.info.finalResultType + MacroImplSig(tparams, paramss, ret) + } + + /** A reference macro implementation signature extracted from a given macro definition. * * In the example above for the following macro def: * def foo[T](xs: List[T]): T = macro fooBar * * This function will return: - * (c: scala.reflect.macros.Context)(xs: c.Expr[List[T]]): c.Expr[T] + * (c: scala.reflect.macros.Context)(xs: c.Expr[List[T]])c.Expr[T] * * Note that type tag evidence parameters are not included into the result. * Type tag context bounds for macro impl tparams are optional. * Therefore compatibility checks ignore such parameters, and we don't need to bother about them here. * - * @param macroDef The macro definition symbol - * @param tparams The type parameters of the macro definition - * @param vparamss The value parameters of the macro definition - * @param retTpe The return type of the macro definition + * Also note that we need a DefDef, not the corresponding MethodSymbol, because that symbol would be of no use for us. + * Macro signatures are verified when typechecking macro defs, which means that at that moment inspecting macroDef.info + * means asking for cyclic reference errors. + * + * We need macro implementation symbol as well, because the return type of the macro definition might be omitted, + * and in that case we'd need to infer it from the return type of the macro implementation. Luckily for us, we can + * use that symbol without a risk of running into cycles. + * + * @param typer Typechecker of `macroDdef` + * @param macroDdef The macro definition tree + * @param macroImpl The macro implementation symbol */ - private def macroImplSig(macroDef: Symbol, tparams: List[TypeDef], vparamss: List[List[ValDef]], retTpe: Type): (List[List[Symbol]], Type) = { + def referenceMacroImplSig(typer: Typer, macroDdef: DefDef, macroImpl: Symbol): MacroImplSig = { // had to move method's body to an object because of the recursive dependencies between sigma and param object SigGenerator { - def WeakTagClass = getMember(MacroContextClass, tpnme.WeakTypeTag) - def ExprClass = getMember(MacroContextClass, tpnme.Expr) - val cache = scala.collection.mutable.Map[Symbol, Symbol]() - val ctxParam = makeParam(nme.macroContext, macroDef.pos, MacroContextClass.tpe, SYNTHETIC) - val paramss = List(ctxParam) :: mmap(vparamss)(param) - val implReturnType = typeRef(singleType(NoPrefix, ctxParam), ExprClass, List(sigma(retTpe))) + val cache = scala.collection.mutable.Map[Symbol, Symbol]() + val macroDef = macroDdef.symbol + val ctxParam = makeParam(nme.macroContext, macroDdef.pos, MacroContextClass.tpe, SYNTHETIC) + val paramss = List(ctxParam) :: mmap(macroDdef.vparamss)(param) + val macroDefRet = + if (!macroDdef.tpt.isEmpty) typer.typedType(macroDdef.tpt).tpe + else computeMacroDefTypeFromMacroImpl(macroDdef, macroImplSig(macroImpl)) + val implReturnType = sigma(increaseMetalevel(ctxParam, macroDefRet)) object SigmaTypeMap extends TypeMap { def mapPrefix(pre: Type) = pre match { case ThisType(sym) if sym == macroDef.owner => singleType(singleType(singleType(NoPrefix, ctxParam), MacroContextPrefix), ExprValue) case SingleType(NoPrefix, sym) => - mfind(vparamss)(_.symbol == sym).fold(pre)(p => singleType(singleType(NoPrefix, param(p)), ExprValue)) + mfind(macroDdef.vparamss)(_.symbol == sym).fold(pre)(p => singleType(singleType(NoPrefix, param(p)), ExprValue)) case _ => mapOver(pre) } @@ -326,32 +405,22 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { def makeParam(name: Name, pos: Position, tpe: Type, flags: Long) = macroDef.newValueParameter(name.toTermName, pos, flags) setInfo tpe - def implType(isType: Boolean, origTpe: Type): Type = { - def tsym = if (isType) WeakTagClass else ExprClass - def targ = origTpe.typeArgs.headOption getOrElse NoType - - if (isRepeatedParamType(origTpe)) - scalaRepeatedType(implType(isType, sigma(targ))) - else - typeRef(singleType(NoPrefix, ctxParam), tsym, List(sigma(origTpe))) - } def param(tree: Tree): Symbol = ( cache.getOrElseUpdate(tree.symbol, { val sym = tree.symbol - makeParam(sym.name, sym.pos, implType(sym.isType, sym.tpe), sym.flags) + assert(sym.isTerm, s"sym = $sym, tree = $tree") + makeParam(sym.name, sym.pos, sigma(increaseMetalevel(ctxParam, sym.tpe)), sym.flags) }) ) } import SigGenerator._ + val result = MacroImplSig(macroDdef.tparams map (_.symbol), paramss, implReturnType) macroLogVerbose(sm""" - |generating macroImplSigs for: $macroDef - |tparams are: $tparams - |vparamss are: $vparamss - |retTpe is: $retTpe - |macroImplSig is: $paramss, $implReturnType + |generating macroImplSig for: $macroDdef + |result is: $result """.trim) - (paramss, implReturnType) + result } /** Verifies that the body of a macro def typechecks to a reference to a static public non-overloaded method, @@ -435,31 +504,26 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { // Phase III: check compatibility between the macro def and its macro impl // this check ignores type tag evidence parameters, because type tag context bounds are optional - // aXXX (e.g. aparamss) => characteristics of the macro impl ("a" stands for "actual") - // rXXX (e.g. rparamss) => characteristics of a reference macro impl signature synthesized from the macro def ("r" stands for "reference") + // aXXX (e.g. aparamss) => characteristics of the actual macro impl signature extracted from the macro impl ("a" stands for "actual") + // rXXX (e.g. rparamss) => characteristics of the reference macro impl signature synthesized from the macro def ("r" stands for "reference") val macroImpl = typed.symbol - val aparamss = transformTypeTagEvidenceParams(macroImpl.paramss, (param, tparam) => NoSymbol) - val aret = macroImpl.tpe.finalResultType - val macroDefRet = - if (!macroDdef.tpt.isEmpty) typer.typedType(macroDdef.tpt).tpe - else computeMacroDefTypeFromMacroImpl(macroDdef, macroImpl) - val (rparamss, rret) = macroImplSig(macroDef, macroDdef.tparams, macroDdef.vparamss, macroDefRet) + val MacroImplSig(atparams, aparamss, aret) = macroImplSig(macroImpl) + val MacroImplSig(_, rparamss, rret) = referenceMacroImplSig(typer, macroDdef, macroImpl) + val atvars = atparams map freshVar + def atpeToRtpe(atpe: Type) = atpe.substSym(aparamss.flatten, rparamss.flatten).instantiateTypeParams(atparams, atvars) + // we only check correspondence between value parameterss + // type parameters of macro defs and macro impls don't have to coincide with each other val implicitParams = aparamss.flatten filter (_.isImplicit) if (implicitParams.nonEmpty) MacroImplNonTagImplicitParameters(implicitParams) if (aparamss.length != rparamss.length) MacroImplParamssMismatchError() - - val atparams = macroImpl.typeParams - val atvars = atparams map freshVar - def atpeToRtpe(atpe: Type) = atpe.substSym(aparamss.flatten, rparamss.flatten).instantiateTypeParams(atparams, atvars) + map2(aparamss, rparamss)((aparams, rparams) => { + if (aparams.length < rparams.length) MacroImplMissingParamsError(aparams, rparams) + if (rparams.length < aparams.length) MacroImplExtraParamsError(aparams, rparams) + }) try { - map2(aparamss, rparamss)((aparams, rparams) => { - if (aparams.length < rparams.length) MacroImplMissingParamsError(aparams, rparams) - if (rparams.length < aparams.length) MacroImplExtraParamsError(aparams, rparams) - }) - - // cannot fuse these loops because if aparamss.flatten != rparamss.flatten + // cannot fuse this map2 and the map2 above because if aparamss.flatten != rparamss.flatten // then `atpeToRtpe` is going to fail with an unsound substitution map2(aparamss.flatten, rparamss.flatten)((aparam, rparam) => { if (aparam.name != rparam.name && !rparam.isSynthetic) MacroImplParamNameMismatchError(aparam, rparam) @@ -560,92 +624,108 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { case class MacroArgs(c: MacroContext, others: List[Any]) private def macroArgs(typer: Typer, expandee: Tree): MacroArgs = { - val macroDef = expandee.symbol - 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]() - val exprArgs = ListBuffer[List[Expr[_]]]() - def collectMacroArgs(tree: Tree): Unit = tree match { - case Apply(fn, args) => - // todo. infer precise typetag for this Expr, namely the declared type of the corresponding macro impl argument - exprArgs.prepend(args map (arg => context.Expr[Nothing](arg)(TypeTag.Nothing))) - collectMacroArgs(fn) - case TypeApply(fn, args) => - typeArgs = args - collectMacroArgs(fn) - case _ => - } - collectMacroArgs(expandee) + val macroDef = expandee.symbol + val paramss = macroDef.paramss + val treeInfo.Applied(core, targs, argss) = expandee + val prefix = core match { case Select(qual, _) => qual; case _ => EmptyTree } + val context = expandee.attachments.get[MacroRuntimeAttachment].flatMap(_.macroContext).getOrElse(macroContext(typer, prefix, expandee)) - val argcDoesntMatch = macroDef.paramss.length != exprArgs.length - val nullaryArgsEmptyParams = exprArgs.isEmpty && macroDef.paramss == ListOfNil - if (argcDoesntMatch && !nullaryArgsEmptyParams) { typer.TyperErrorGen.MacroPartialApplicationError(expandee) } + macroLogVerbose(sm""" + |context: $context + |prefix: $prefix + |targs: $targs + |argss: $argss + |paramss: $paramss + """.trim) - val argss: List[List[Any]] = exprArgs.toList - macroLogVerbose(s"context: $context") - macroLogVerbose(s"argss: $argss") + import typer.TyperErrorGen._ + val isNullaryArgsEmptyParams = argss.isEmpty && paramss == ListOfNil + if (paramss.length < argss.length) MacroTooManyArgumentLists(expandee) + if (paramss.length > argss.length && !isNullaryArgsEmptyParams) MacroTooFewArgumentLists(expandee) - val preparedArgss: List[List[Any]] = + val macroImplArgs: List[Any] = if (fastTrack contains macroDef) { // Take a dry run of the fast track implementation - if (fastTrack(macroDef) validate expandee) argss - else typer.TyperErrorGen.MacroPartialApplicationError(expandee) + if (fastTrack(macroDef) validate expandee) argss.flatten + else typer.TyperErrorGen.MacroTooFewArgumentLists(expandee) } else { - // 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 binding = loadMacroImplBinding(macroDef) - macroLogVerbose(s"binding: $binding") - val tags = binding.signature filter (_ != -1) map (paramPos => { - val targ = binding.targs(paramPos).tpe.typeSymbol - val tpe = if (targ.isTypeParameterOrSkolem) { - if (targ.owner == macroDef) { - // doesn't work when macro def is compiled separately from its usages - // 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 + if (binding.className == Predef_???.owner.fullName.toString && binding.methName == Predef_???.name.encoded) Nil + else { + macroLogVerbose(s"binding: $binding") + + // STEP I: prepare value arguments of the macro expansion + // wrap argss in c.Expr if necessary (i.e. if corresponding macro impl param is of type c.Expr[T]) + // expand varargs (nb! varargs can apply to any parameter section, not necessarily to the last one) + val trees = map3(argss, paramss, binding.signature.tail)((args, defParams, implParams) => { + val isVarargs = isVarArgsList(defParams) + if (isVarargs) { + if (defParams.length > args.length + 1) MacroTooFewArguments(expandee) + } else { + if (defParams.length < args.length) MacroTooManyArguments(expandee) + if (defParams.length > args.length) MacroTooFewArguments(expandee) + } + + val wrappedArgs = mapWithIndex(args)((arg, j) => { + val fingerprint = implParams(min(j, implParams.length - 1)) + fingerprint match { + case IMPLPARAM_EXPR => context.Expr[Nothing](arg)(TypeTag.Nothing) // TODO: SI-5752 + case _ => abort(s"unexpected fingerprint $fingerprint in $binding with paramss being $paramss " + + s"corresponding to arg $arg in $argss") + } + }) + + if (isVarargs) { + val (normal, varargs) = wrappedArgs splitAt (defParams.length - 1) + normal :+ varargs // pack all varargs into a single Seq argument (varargs Scala style) + } else wrappedArgs + }) + macroLogVerbose(s"trees: $trees") + + // STEP II: prepare type arguments of the macro expansion + // 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 tags = binding.signature.flatten filter (_ >= IMPLPARAM_TAG) map (paramPos => { + val targ = binding.targs(paramPos).tpe.typeSymbol + val tpe = if (targ.isTypeParameterOrSkolem) { + if (targ.owner == macroDef) { + // doesn't work when macro def is compiled separately from its usages + // 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) + targs(argPos).tpe + } else + targ.tpe.asSeenFrom( + if (prefix == EmptyTree) macroDef.owner.tpe else prefix.tpe, + macroDef.owner) } else - targ.tpe.asSeenFrom( - if (prefixTree == EmptyTree) macroDef.owner.tpe else prefixTree.tpe, - macroDef.owner) - } else - targ.tpe - context.WeakTypeTag(tpe) - }) - macroLogVerbose(s"tags: $tags") - - // transforms argss taking into account varargness of paramss - // note that typetag context bounds are only declared on macroImpls - // so this optional arglist might not match macroDef's paramlist - // nb! varargs can apply to any parameter section, not necessarily to the last one - mapWithIndex(argss :+ tags)((as, i) => { - val mapsToParamss = macroDef.paramss.indices contains i - if (mapsToParamss) { - val ps = macroDef.paramss(i) - if (isVarArgsList(ps)) { - val (normal, varargs) = as splitAt (ps.length - 1) - normal :+ varargs // pack all varargs into a single List argument - } else as - } else as - }) + targ.tpe + context.WeakTypeTag(tpe) + }) + macroLogVerbose(s"tags: $tags") + + // if present, tags always come in a separate parameter/argument list + // that's because macro impls can't have implicit parameters other than c.WeakTypeTag[T] + (trees :+ tags).flatten + } } - macroLogVerbose(s"preparedArgss: $preparedArgss") - MacroArgs(context, preparedArgss.flatten) + macroLogVerbose(s"macroImplArgs: $macroImplArgs") + MacroArgs(context, macroImplArgs) } /** Keeps track of macros in-flight. @@ -680,7 +760,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { * 1) If necessary desugars the `expandee` to fit into `macroExpand1` * * Then `macroExpand1`: - * 2) Checks whether the expansion needs to be delayed (see `mustDelayMacroExpansion`) + * 2) Checks whether the expansion needs to be delayed * 3) Loads macro implementation using `macroMirror` * 4) Synthesizes invocation arguments for the macro implementation * 5) Checks that the result is a tree or an expr bound to this universe diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 4ccfb31878..c9849eebb5 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -126,7 +126,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans val defaultMethodNames = defaultGetters map (sym => nme.defaultGetterToMethod(sym.name)) defaultMethodNames.toList.distinct foreach { name => - val methods = clazz.info.findMember(name, 0L, METHOD, false).alternatives + val methods = clazz.info.findMember(name, 0L, METHOD, stableOnly = false).alternatives def hasDefaultParam(tpe: Type): Boolean = tpe match { case MethodType(params, restpe) => (params exists (_.hasDefault)) || hasDefaultParam(restpe) case _ => false diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 238727d2e9..5916116ef3 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -5534,7 +5534,7 @@ trait Typers extends Adaptations with Tags { val isMacroBodyOkay = !tree.symbol.isErroneous && !(tree1 exists (_.isErroneous)) && tree1 != EmptyTree val shouldInheritMacroImplReturnType = ddef.tpt.isEmpty - if (isMacroBodyOkay && shouldInheritMacroImplReturnType) computeMacroDefTypeFromMacroImpl(ddef, tree1.symbol) else AnyTpe + if (isMacroBodyOkay && shouldInheritMacroImplReturnType) computeMacroDefTypeFromMacroImpl(ddef, macroImplSig(tree1.symbol)) else AnyTpe } def transformedOr(tree: Tree, op: => Tree): Tree = transformed remove tree match { diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 5cd86b7eed..630572464d 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -505,6 +505,13 @@ trait Definitions extends api.StandardDefinitions { lazy val ExprClass = if (ExprsClass != NoSymbol) getMemberClass(ExprsClass, tpnme.Expr) else NoSymbol def ExprSplice = if (ExprsClass != NoSymbol) getMemberMethod(ExprClass, nme.splice) else NoSymbol def ExprValue = if (ExprsClass != NoSymbol) getMemberMethod(ExprClass, nme.value) else NoSymbol + object ExprClassOf { + def unapply(tpe: Type): Option[Type] = tpe.dealias match { + case ExistentialType(_, underlying) => unapply(underlying) + case TypeRef(_, ExprClass, t :: Nil) => Some(t) + case _ => None + } + } lazy val ClassTagModule = requiredModule[scala.reflect.ClassTag[_]] lazy val ClassTagClass = requiredClass[scala.reflect.ClassTag[_]] @@ -530,6 +537,9 @@ trait Definitions extends api.StandardDefinitions { def MacroContextPrefix = if (MacroContextClass != NoSymbol) getMemberMethod(MacroContextClass, nme.prefix) else NoSymbol def MacroContextPrefixType = if (MacroContextClass != NoSymbol) getTypeMember(MacroContextClass, tpnme.PrefixType) else NoSymbol def MacroContextUniverse = if (MacroContextClass != NoSymbol) getMemberMethod(MacroContextClass, nme.universe) else NoSymbol + def MacroContextExprClass = if (MacroContextClass != NoSymbol) getTypeMember(MacroContextClass, tpnme.Expr) else NoSymbol + def MacroContextWeakTypeTagClass = if (MacroContextClass != NoSymbol) getTypeMember(MacroContextClass, tpnme.WeakTypeTag) else NoSymbol + def MacroContextTreeType = if (MacroContextClass != NoSymbol) getTypeMember(MacroContextClass, tpnme.Tree) else NoSymbol lazy val MacroImplAnnotation = requiredClass[scala.reflect.macros.internal.macroImpl] lazy val StringContextClass = requiredClass[scala.StringContext] diff --git a/test/files/neg/macro-qmarkqmarkqmark.check b/test/files/neg/macro-qmarkqmarkqmark.check index afd49e7d90..bc3e25edaf 100644 --- a/test/files/neg/macro-qmarkqmarkqmark.check +++ b/test/files/neg/macro-qmarkqmarkqmark.check @@ -1,7 +1,7 @@ macro-qmarkqmarkqmark.scala:5: error: macro implementation is missing foo1 ^ -macro-qmarkqmarkqmark.scala:8: error: macros cannot be partially applied +macro-qmarkqmarkqmark.scala:8: error: too few argument lists for macro invocation foo2 ^ macro-qmarkqmarkqmark.scala:9: error: macro implementation is missing diff --git a/test/files/neg/t7157.check b/test/files/neg/t7157.check new file mode 100644 index 0000000000..c6a7af9a23 --- /dev/null +++ b/test/files/neg/t7157.check @@ -0,0 +1,73 @@ +Test_2.scala:5: error: too many arguments for macro method m1_0_0: ()Unit + m1_0_0(1) + ^ +Test_2.scala:6: error: too many arguments for macro method m1_0_0: ()Unit + m1_0_0(1, 2) + ^ +Test_2.scala:7: error: too many arguments for macro method m1_0_0: ()Unit + m1_0_0(1, 2, 3) + ^ +Test_2.scala:9: error: macro applications do not support named and/or default arguments + m1_1_1() + ^ +Test_2.scala:11: error: too many arguments for macro method m1_1_1: (x: Int)Unit + m1_1_1(1, 2) + ^ +Test_2.scala:12: error: too many arguments for macro method m1_1_1: (x: Int)Unit + m1_1_1(1, 2, 3) + ^ +Test_2.scala:14: error: macro applications do not support named and/or default arguments + m1_2_2() + ^ +Test_2.scala:15: error: macro applications do not support named and/or default arguments + m1_2_2(1) + ^ +Test_2.scala:17: error: too many arguments for macro method m1_2_2: (x: Int, y: Int)Unit + m1_2_2(1, 2, 3) + ^ +Test_2.scala:24: error: macro applications do not support named and/or default arguments + m1_1_inf() + ^ +Test_2.scala:29: error: macro applications do not support named and/or default arguments + m1_2_inf() + ^ +Test_2.scala:30: error: macro applications do not support named and/or default arguments + m1_2_inf(1) + ^ +Test_2.scala:35: error: too many arguments for macro method m2_0_0: ()Unit + m2_0_0()(1) + ^ +Test_2.scala:36: error: too many arguments for macro method m2_0_0: ()Unit + m2_0_0()(1, 2) + ^ +Test_2.scala:37: error: too many arguments for macro method m2_0_0: ()Unit + m2_0_0()(1, 2, 3) + ^ +Test_2.scala:39: error: macro applications do not support named and/or default arguments + m2_1_1()() + ^ +Test_2.scala:41: error: too many arguments for macro method m2_1_1: (x: Int)Unit + m2_1_1()(1, 2) + ^ +Test_2.scala:42: error: too many arguments for macro method m2_1_1: (x: Int)Unit + m2_1_1()(1, 2, 3) + ^ +Test_2.scala:44: error: macro applications do not support named and/or default arguments + m2_2_2()() + ^ +Test_2.scala:45: error: macro applications do not support named and/or default arguments + m2_2_2()(1) + ^ +Test_2.scala:47: error: too many arguments for macro method m2_2_2: (x: Int, y: Int)Unit + m2_2_2()(1, 2, 3) + ^ +Test_2.scala:54: error: macro applications do not support named and/or default arguments + m2_1_inf()() + ^ +Test_2.scala:59: error: macro applications do not support named and/or default arguments + m2_2_inf()() + ^ +Test_2.scala:60: error: macro applications do not support named and/or default arguments + m2_2_inf()(1) + ^ +24 errors found diff --git a/test/files/neg/t7157/Impls_Macros_1.scala b/test/files/neg/t7157/Impls_Macros_1.scala new file mode 100644 index 0000000000..09f423fbab --- /dev/null +++ b/test/files/neg/t7157/Impls_Macros_1.scala @@ -0,0 +1,32 @@ +import scala.reflect.macros.Context +import language.experimental.macros + +object Macros { + def impl1_0_0(c: Context)() = c.literalUnit + def impl1_1_1(c: Context)(x: c.Expr[Int]) = c.literalUnit + def impl1_2_2(c: Context)(x: c.Expr[Int], y: c.Expr[Int]) = c.literalUnit + def m1_0_0() = macro impl1_0_0 + def m1_1_1(x: Int) = macro impl1_1_1 + def m1_2_2(x: Int, y: Int) = macro impl1_2_2 + + def impl1_0_inf(c: Context)(x: c.Expr[Int]*) = c.literalUnit + def impl1_1_inf(c: Context)(x: c.Expr[Int], y: c.Expr[Int]*) = c.literalUnit + def impl1_2_inf(c: Context)(x: c.Expr[Int], y: c.Expr[Int], z: c.Expr[Int]*) = c.literalUnit + def m1_0_inf(x: Int*) = macro impl1_0_inf + def m1_1_inf(x: Int, y: Int*) = macro impl1_1_inf + def m1_2_inf(x: Int, y: Int, z: Int*) = macro impl1_2_inf + + def impl2_0_0(c: Context)()() = c.literalUnit + def impl2_1_1(c: Context)()(x: c.Expr[Int]) = c.literalUnit + def impl2_2_2(c: Context)()(x: c.Expr[Int], y: c.Expr[Int]) = c.literalUnit + def m2_0_0()() = macro impl2_0_0 + def m2_1_1()(x: Int) = macro impl2_1_1 + def m2_2_2()(x: Int, y: Int) = macro impl2_2_2 + + def impl2_0_inf(c: Context)()(x: c.Expr[Int]*) = c.literalUnit + def impl2_1_inf(c: Context)()(x: c.Expr[Int], y: c.Expr[Int]*) = c.literalUnit + def impl2_2_inf(c: Context)()(x: c.Expr[Int], y: c.Expr[Int], z: c.Expr[Int]*) = c.literalUnit + def m2_0_inf()(x: Int*) = macro impl2_0_inf + def m2_1_inf()(x: Int, y: Int*) = macro impl2_1_inf + def m2_2_inf()(x: Int, y: Int, z: Int*) = macro impl2_2_inf +}
\ No newline at end of file diff --git a/test/files/neg/t7157/Test_2.scala b/test/files/neg/t7157/Test_2.scala new file mode 100644 index 0000000000..45a6026399 --- /dev/null +++ b/test/files/neg/t7157/Test_2.scala @@ -0,0 +1,63 @@ +import Macros._ + +object Test extends App { + m1_0_0() + m1_0_0(1) + m1_0_0(1, 2) + m1_0_0(1, 2, 3) + + m1_1_1() + m1_1_1(1) + m1_1_1(1, 2) + m1_1_1(1, 2, 3) + + m1_2_2() + m1_2_2(1) + m1_2_2(1, 2) + m1_2_2(1, 2, 3) + + m1_0_inf() + m1_0_inf(1) + m1_0_inf(1, 2) + m1_0_inf(1, 2, 3) + + m1_1_inf() + m1_1_inf(1) + m1_1_inf(1, 2) + m1_1_inf(1, 2, 3) + + m1_2_inf() + m1_2_inf(1) + m1_2_inf(1, 2) + m1_2_inf(1, 2, 3) + + m2_0_0()() + m2_0_0()(1) + m2_0_0()(1, 2) + m2_0_0()(1, 2, 3) + + m2_1_1()() + m2_1_1()(1) + m2_1_1()(1, 2) + m2_1_1()(1, 2, 3) + + m2_2_2()() + m2_2_2()(1) + m2_2_2()(1, 2) + m2_2_2()(1, 2, 3) + + m2_0_inf()() + m2_0_inf()(1) + m2_0_inf()(1, 2) + m2_0_inf()(1, 2, 3) + + m2_1_inf()() + m2_1_inf()(1) + m2_1_inf()(1, 2) + m2_1_inf()(1, 2, 3) + + m2_2_inf()() + m2_2_inf()(1) + m2_2_inf()(1, 2) + m2_2_inf()(1, 2, 3) +}
\ No newline at end of file diff --git a/test/files/run/macro-invalidusage-partialapplication-with-tparams.check b/test/files/run/macro-invalidusage-partialapplication-with-tparams.check index f1d5e925fa..326f3e08ca 100644 --- a/test/files/run/macro-invalidusage-partialapplication-with-tparams.check +++ b/test/files/run/macro-invalidusage-partialapplication-with-tparams.check @@ -1,3 +1,3 @@ reflective compilation has failed: -macros cannot be partially applied +too few argument lists for macro invocation diff --git a/test/files/run/macro-invalidusage-partialapplication.check b/test/files/run/macro-invalidusage-partialapplication.check index f1d5e925fa..326f3e08ca 100644 --- a/test/files/run/macro-invalidusage-partialapplication.check +++ b/test/files/run/macro-invalidusage-partialapplication.check @@ -1,3 +1,3 @@ reflective compilation has failed: -macros cannot be partially applied +too few argument lists for macro invocation diff --git a/test/files/run/reify-repl-fail-gracefully.check b/test/files/run/reify-repl-fail-gracefully.check index 1b0f3f2162..18cfd5a7ef 100644 --- a/test/files/run/reify-repl-fail-gracefully.check +++ b/test/files/run/reify-repl-fail-gracefully.check @@ -12,7 +12,7 @@ import scala.reflect.runtime.universe._ scala> scala> reify -<console>:12: error: macros cannot be partially applied +<console>:12: error: too few argument lists for macro invocation reify ^ diff --git a/test/files/run/t7157.check b/test/files/run/t7157.check new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/test/files/run/t7157.check @@ -0,0 +1 @@ +1 diff --git a/test/files/run/t7157/Impls_Macros_1.scala b/test/files/run/t7157/Impls_Macros_1.scala new file mode 100644 index 0000000000..ad3d96eb85 --- /dev/null +++ b/test/files/run/t7157/Impls_Macros_1.scala @@ -0,0 +1,15 @@ +import scala.reflect.macros.Context +import language.experimental.macros + +object Macros { + object AImpl { + def a(ctx: Context)(args: ctx.Expr[Any]*): ctx.Expr[Unit] = { + import ctx.universe._ + ctx.Expr[Unit](Apply(Ident(TermName("println")), List(Literal(Constant(1))))) + } + } + + implicit class A(context: StringContext) { + def a(args: Any*): Unit = macro AImpl.a + } +}
\ No newline at end of file diff --git a/test/files/run/t7157/Test_2.scala b/test/files/run/t7157/Test_2.scala new file mode 100644 index 0000000000..cceb5ca177 --- /dev/null +++ b/test/files/run/t7157/Test_2.scala @@ -0,0 +1,5 @@ +import Macros._ + +object Test extends App { + a"" +}
\ No newline at end of file |