diff options
16 files changed, 454 insertions, 308 deletions
diff --git a/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala b/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala index 2e82e34bd9..1413065a27 100644 --- a/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala +++ b/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala @@ -8,6 +8,11 @@ abstract class DefaultMacroCompiler extends Resolvers with Errors { val global: Global import global._ + import analyzer._ + import treeInfo._ + import definitions._ + val runDefinitions = currentRun.runDefinitions + import runDefinitions.{Predef_???, _} val typer: global.analyzer.Typer val context = typer.context @@ -15,13 +20,72 @@ abstract class DefaultMacroCompiler extends Resolvers val macroDdef: DefDef lazy val macroDef = macroDdef.symbol + case class MacroImplRefCompiler(untypedMacroImplRef: Tree, isImplBundle: Boolean) extends Resolver with Validator with Error private case class MacroImplResolutionException(pos: Position, msg: String) extends Exception def abort(pos: Position, msg: String) = throw MacroImplResolutionException(pos, msg) + /** Resolves a macro impl reference provided in the right-hand side of the given macro definition. + * + * Acceptable shapes of the right-hand side: + * 1) [<static object>].<method name>[[<type args>]] // vanilla macro impl ref + * 2) [<macro bundle>].<method name>[[<type args>]] // shiny new macro bundle impl ref + * + * Produces a tree, which represents a reference to a macro implementation if everything goes well, + * otherwise reports found errors and returns EmptyTree. The resulting tree should have the following format: + * + * qualifier.method[targs] + * + * Qualifier here might be omitted (local macro defs), be a static object (vanilla macro defs) + * or be a dummy instance of a macro bundle (e.g. new MyMacro(???).expand). + */ def resolveMacroImpl: Tree = { + def tryCompile(compiler: MacroImplRefCompiler): scala.util.Try[Tree] = { + try { compiler.validateMacroImplRef(); scala.util.Success(compiler.macroImplRef) } + catch { case ex: MacroImplResolutionException => scala.util.Failure(ex) } + } + val vanillaImplRef = MacroImplRefCompiler(macroDdef.rhs.duplicate, isImplBundle = false) + val (maybeBundleRef, methName, targs) = macroDdef.rhs.duplicate match { + case Applied(Select(Applied(RefTree(qual, bundleName), _, Nil), methName), targs, Nil) => + (RefTree(qual, bundleName.toTypeName), methName, targs) + case Applied(Ident(methName), targs, Nil) => + (Ident(context.owner.enclClass), methName, targs) + case _ => + (EmptyTree, TermName(""), Nil) + } + val bundleImplRef = MacroImplRefCompiler( + atPos(macroDdef.rhs.pos)(gen.mkTypeApply(Select(New(maybeBundleRef, List(List(Ident(Predef_???)))), methName), targs)), + isImplBundle = true + ) + val vanillaResult = tryCompile(vanillaImplRef) + val bundleResult = tryCompile(bundleImplRef) + + def ensureUnambiguousSuccess() = { + // we now face a hard choice of whether to report ambiguity: + // 1) when there are eponymous methods in both bundle and object + // 2) when both references to eponymous methods are resolved successfully + // doing #1 would cause less confusion in the long run, but it would also cause more frequent source incompatibilities + // e.g. it would fail to compile https://github.com/ReifyIt/basis + // therefore here we go for #2 + // if (vanillaImplRef.looksCredible && bundleImplRef.looksCredible) MacroImplAmbiguousError() + if (vanillaResult.isSuccess && bundleResult.isSuccess) MacroImplAmbiguousError() + } + + def reportMostAppropriateFailure() = { + typer.silent(_.typedTypeConstructor(maybeBundleRef)) match { + case SilentResultValue(result) if looksLikeMacroBundleType(result.tpe) => + val bundle = result.tpe.typeSymbol + if (!isMacroBundleType(bundle.tpe)) MacroBundleWrongShapeError() + if (!bundle.owner.isStaticOwner) MacroBundleNonStaticError() + bundleResult.get + case _ => + vanillaResult.get + } + } + try { - validateMacroImplRef() - macroImplRef + if (vanillaResult.isSuccess || bundleResult.isSuccess) ensureUnambiguousSuccess() + if (vanillaResult.isFailure && bundleResult.isFailure) reportMostAppropriateFailure() + vanillaResult.orElse(bundleResult).get } catch { case MacroImplResolutionException(pos, msg) => context.error(pos, msg) diff --git a/src/compiler/scala/reflect/macros/compiler/Errors.scala b/src/compiler/scala/reflect/macros/compiler/Errors.scala index 280baa2a42..490ab3657a 100644 --- a/src/compiler/scala/reflect/macros/compiler/Errors.scala +++ b/src/compiler/scala/reflect/macros/compiler/Errors.scala @@ -13,12 +13,9 @@ trait Errors extends Traces { import treeInfo._ import typer.TyperErrorGen._ import typer.infer.InferErrorGen._ - private val runDefinitions = currentRun.runDefinitions import runDefinitions._ def globalSettings = global.settings - // sanity check errors - private def implRefError(message: String) = { val Applied(culprit, _, _) = macroDdef.rhs abort(culprit.pos, message) @@ -33,111 +30,125 @@ trait Errors extends Traces { abort(culprit.pos, message) } - def MacroImplReferenceWrongShapeError() = implRefError( + def MacroImplAmbiguousError() = implRefError( + "macro implementation reference is ambiguous: makes sense both as\n"+ + "a macro bundle method reference and a vanilla object method reference") + + def MacroBundleNonStaticError() = bundleRefError("macro bundles must be static") + + def MacroBundleWrongShapeError() = bundleRefError("macro bundles must be concrete classes having a single constructor with a `val c: Context` parameter") + + trait Error { + self: MacroImplRefCompiler => + + // sanity check errors + + def MacroImplReferenceWrongShapeError() = implRefError( "macro implementation reference has wrong shape. required:\n"+ "macro [<static object>].<method name>[[<type args>]] or\n" + "macro [<macro bundle>].<method name>[[<type args>]]") - def MacroImplWrongNumberOfTypeArgumentsError() = { - val diagnostic = if (macroImpl.typeParams.length > targs.length) "has too few type arguments" else "has too many arguments" - implRefError(s"macro implementation reference $diagnostic for " + treeSymTypeMsg(macroImplRef)) - } - - def MacroImplNotPublicError() = implRefError("macro implementation must be public") + def MacroImplWrongNumberOfTypeArgumentsError() = { + val diagnostic = if (macroImpl.typeParams.length > targs.length) "has too few type arguments" else "has too many arguments" + implRefError(s"macro implementation reference $diagnostic for " + treeSymTypeMsg(macroImplRef)) + } - def MacroImplOverloadedError() = implRefError("macro implementation cannot be overloaded") + private def macroImplementationWording = + if (isImplBundle) "bundle implementation" + else "macro implementation" - def MacroImplNonTagImplicitParameters(params: List[Symbol]) = implRefError("macro implementations cannot have implicit parameters other than WeakTypeTag evidences") + def MacroImplNotPublicError() = implRefError(s"${macroImplementationWording} must be public") - def MacroBundleNonStaticError() = bundleRefError("macro bundles must be static") + def MacroImplOverloadedError() = implRefError(s"${macroImplementationWording} cannot be overloaded") - def MacroBundleWrongShapeError() = bundleRefError("macro bundles must be concrete classes having a single constructor with a `val c: Context` parameter") + def MacroImplNonTagImplicitParameters(params: List[Symbol]) = implRefError(s"${macroImplementationWording}s cannot have implicit parameters other than WeakTypeTag evidences") - // compatibility errors + // compatibility errors - // helpers + // helpers - private def lengthMsg(flavor: String, violation: String, extra: Symbol) = { - val noun = if (flavor == "value") "parameter" else "type parameter" - val message = noun + " lists have different length, " + violation + " extra " + noun - val suffix = if (extra ne NoSymbol) " " + extra.defString else "" - message + suffix - } + private def lengthMsg(flavor: String, violation: String, extra: Symbol) = { + val noun = if (flavor == "value") "parameter" else "type parameter" + val message = noun + " lists have different length, " + violation + " extra " + noun + val suffix = if (extra ne NoSymbol) " " + extra.defString else "" + message + suffix + } - private def abbreviateCoreAliases(s: String): String = { - val coreAliases = List("WeakTypeTag", "Expr", "Tree") - coreAliases.foldLeft(s)((res, x) => res.replace("c.universe." + x, "c." + x)) - } + private def abbreviateCoreAliases(s: String): String = { + val coreAliases = List("WeakTypeTag", "Expr", "Tree") + coreAliases.foldLeft(s)((res, x) => res.replace("c.universe." + x, "c." + x)) + } - private def showMeth(pss: List[List[Symbol]], restpe: Type, abbreviate: Boolean, untype: Boolean) = { - def preprocess(tpe: Type) = if (untype) untypeMetalevel(tpe) else tpe - var pssPart = (pss map (ps => ps map (p => p.defStringSeenAs(preprocess(p.info))) mkString ("(", ", ", ")"))).mkString - if (abbreviate) pssPart = abbreviateCoreAliases(pssPart) - var retPart = preprocess(restpe).toString - if (abbreviate || macroDdef.tpt.tpe == null) retPart = abbreviateCoreAliases(retPart) - pssPart + ": " + retPart - } + private def showMeth(pss: List[List[Symbol]], restpe: Type, abbreviate: Boolean, untype: Boolean) = { + def preprocess(tpe: Type) = if (untype) untypeMetalevel(tpe) else tpe + var pssPart = (pss map (ps => ps map (p => p.defStringSeenAs(preprocess(p.info))) mkString ("(", ", ", ")"))).mkString + if (abbreviate) pssPart = abbreviateCoreAliases(pssPart) + var retPart = preprocess(restpe).toString + if (abbreviate || macroDdef.tpt.tpe == null) retPart = abbreviateCoreAliases(retPart) + pssPart + ": " + retPart + } - // not exactly an error generator, but very related - // and I dearly wanted to push it away from Macros.scala - private def checkConforms(slot: String, rtpe: Type, atpe: Type) = { - val verbose = macroDebugVerbose - - def check(rtpe: Type, atpe: Type): Boolean = { - def success() = { if (verbose) println(rtpe + " <: " + atpe + "?" + EOL + "true"); true } - (rtpe, atpe) match { - case _ if rtpe eq atpe => success() - case (TypeRef(_, RepeatedParamClass, rtpe :: Nil), TypeRef(_, RepeatedParamClass, atpe :: Nil)) => check(rtpe, atpe) - case (ExprClassOf(_), TreeType()) if rtpe.prefix =:= atpe.prefix => success() - case (SubtreeType(), ExprClassOf(_)) if rtpe.prefix =:= atpe.prefix => success() - case _ => rtpe <:< atpe + // not exactly an error generator, but very related + // and I dearly wanted to push it away from Macros.scala + private def checkConforms(slot: String, rtpe: Type, atpe: Type) = { + val verbose = macroDebugVerbose + + def check(rtpe: Type, atpe: Type): Boolean = { + def success() = { if (verbose) println(rtpe + " <: " + atpe + "?" + EOL + "true"); true } + (rtpe, atpe) match { + case _ if rtpe eq atpe => success() + case (TypeRef(_, RepeatedParamClass, rtpe :: Nil), TypeRef(_, RepeatedParamClass, atpe :: Nil)) => check(rtpe, atpe) + case (ExprClassOf(_), TreeType()) if rtpe.prefix =:= atpe.prefix => success() + case (SubtreeType(), ExprClassOf(_)) if rtpe.prefix =:= atpe.prefix => success() + case _ => rtpe <:< atpe + } } - } - val ok = - if (verbose) withTypesExplained(check(rtpe, atpe)) - else check(rtpe, atpe) - if (!ok) { - if (!verbose) explainTypes(rtpe, atpe) - val msg = { - val ss = Seq(rtpe, atpe) map (this abbreviateCoreAliases _.toString) - s"type mismatch for $slot: ${ss(0)} does not conform to ${ss(1)}" + val ok = + if (verbose) withTypesExplained(check(rtpe, atpe)) + else check(rtpe, atpe) + if (!ok) { + if (!verbose) explainTypes(rtpe, atpe) + val msg = { + val ss = Seq(rtpe, atpe) map (this abbreviateCoreAliases _.toString) + s"type mismatch for $slot: ${ss(0)} does not conform to ${ss(1)}" + } + compatibilityError(msg) } - compatibilityError(msg) } - } - private def compatibilityError(message: String) = - implRefError( - "macro implementation has incompatible shape:"+ - "\n required: " + showMeth(rparamss, rret, abbreviate = true, untype = false) + - "\n or : " + showMeth(rparamss, rret, abbreviate = true, untype = true) + - "\n found : " + showMeth(aparamss, aret, abbreviate = false, untype = false) + - "\n" + message) + private def compatibilityError(message: String) = + implRefError( + s"${macroImplementationWording} has incompatible shape:"+ + "\n required: " + showMeth(rparamss, rret, abbreviate = true, untype = false) + + "\n or : " + showMeth(rparamss, rret, abbreviate = true, untype = true) + + "\n found : " + showMeth(aparamss, aret, abbreviate = false, untype = false) + + "\n" + message) - def MacroImplParamssMismatchError() = compatibilityError("number of parameter sections differ") + def MacroImplParamssMismatchError() = compatibilityError("number of parameter sections differ") - def MacroImplExtraParamsError(aparams: List[Symbol], rparams: List[Symbol]) = compatibilityError(lengthMsg("value", "found", aparams(rparams.length))) + def MacroImplExtraParamsError(aparams: List[Symbol], rparams: List[Symbol]) = compatibilityError(lengthMsg("value", "found", aparams(rparams.length))) - def MacroImplMissingParamsError(aparams: List[Symbol], rparams: List[Symbol]) = compatibilityError(abbreviateCoreAliases(lengthMsg("value", "required", rparams(aparams.length)))) + def MacroImplMissingParamsError(aparams: List[Symbol], rparams: List[Symbol]) = compatibilityError(abbreviateCoreAliases(lengthMsg("value", "required", rparams(aparams.length)))) - def checkMacroImplParamTypeMismatch(atpe: Type, rparam: Symbol) = checkConforms("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) = checkConforms("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) + def MacroImplParamNameMismatchError(aparam: Symbol, rparam: Symbol) = compatibilityError("parameter names differ: " + rparam.name + " != " + aparam.name) - def MacroImplVarargMismatchError(aparam: Symbol, rparam: Symbol) = { - def fail(paramName: Name) = compatibilityError("types incompatible for parameter " + paramName + ": corresponding is not a vararg parameter") - if (isRepeated(rparam) && !isRepeated(aparam)) fail(rparam.name) - if (!isRepeated(rparam) && isRepeated(aparam)) fail(aparam.name) - } + def MacroImplVarargMismatchError(aparam: Symbol, rparam: Symbol) = { + def fail(paramName: Name) = compatibilityError("types incompatible for parameter " + paramName + ": corresponding is not a vararg parameter") + if (isRepeated(rparam) && !isRepeated(aparam)) fail(rparam.name) + if (!isRepeated(rparam) && isRepeated(aparam)) fail(aparam.name) + } - def MacroImplTargMismatchError(atargs: List[Type], atparams: List[Symbol]) = - compatibilityError(NotWithinBoundsErrorMessage("", atargs, atparams, macroDebugVerbose || settings.explaintypes.value)) + def MacroImplTargMismatchError(atargs: List[Type], atparams: List[Symbol]) = + compatibilityError(NotWithinBoundsErrorMessage("", atargs, atparams, macroDebugVerbose || settings.explaintypes.value)) - def MacroImplTparamInstantiationError(atparams: List[Symbol], e: NoInstance) = { - val badps = atparams map (_.defString) mkString ", " - compatibilityError(f"type parameters $badps cannot be instantiated%n${e.getMessage}") + def MacroImplTparamInstantiationError(atparams: List[Symbol], e: NoInstance) = { + val badps = atparams map (_.defString) mkString ", " + compatibilityError(f"type parameters $badps cannot be instantiated%n${e.getMessage}") + } } } diff --git a/src/compiler/scala/reflect/macros/compiler/Resolvers.scala b/src/compiler/scala/reflect/macros/compiler/Resolvers.scala index d35f1c32a9..807fb688a0 100644 --- a/src/compiler/scala/reflect/macros/compiler/Resolvers.scala +++ b/src/compiler/scala/reflect/macros/compiler/Resolvers.scala @@ -12,61 +12,35 @@ trait Resolvers { import definitions._ import treeInfo._ import gen._ - private val runDefinitions = currentRun.runDefinitions - import runDefinitions.{Predef_???, _} + import runDefinitions._ - /** Resolves a macro impl reference provided in the right-hand side of the given macro definition. - * - * Acceptable shapes of the right-hand side: - * 1) [<static object>].<method name>[[<type args>]] // vanilla macro def - * 2) [<macro bundle>].<method name>[[<type args>]] // shiny new macro bundle - * - * Produces a tree, which represents a reference to a macro implementation if everything goes well, - * otherwise reports found errors and returns EmptyTree. The resulting tree should have the following format: - * - * qualifier.method[targs] - * - * Qualifier here might be omitted (local macro defs), be a static object (vanilla macro defs) - * or be a dummy instance of a macro bundle (e.g. new MyMacro(???).expand). - */ - lazy val macroImplRef: Tree = { - val (maybeBundleRef, methName, targs) = macroDdef.rhs match { - case Applied(Select(Applied(RefTree(qual, bundleName), _, Nil), methName), targs, Nil) => - (RefTree(qual, bundleName.toTypeName), methName, targs) - case Applied(Ident(methName), targs, Nil) => - (Ident(context.owner.enclClass), methName, targs) - case _ => - (EmptyTree, TermName(""), Nil) - } + trait Resolver { + self: MacroImplRefCompiler => - val untypedImplRef = typer.silent(_.typedTypeConstructor(maybeBundleRef)) match { - case SilentResultValue(result) if looksLikeMacroBundleType(result.tpe) => - val bundle = result.tpe.typeSymbol - if (!isMacroBundleType(bundle.tpe)) MacroBundleWrongShapeError() - if (!bundle.owner.isStaticOwner) MacroBundleNonStaticError() - atPos(macroDdef.rhs.pos)(gen.mkTypeApply(Select(New(bundle, Ident(Predef_???)), methName), targs)) - case _ => - macroDdef.rhs - } + val isImplBundle: Boolean + val isImplMethod = !isImplBundle - val typedImplRef = typer.silent(_.typed(markMacroImplRef(untypedImplRef)), reportAmbiguousErrors = false) - typedImplRef match { - case SilentResultValue(success) => success - case SilentTypeError(err) => abort(err.errPos, err.errMsg) + lazy val looksCredible: Boolean = { + val Applied(core, _, _) = untypedMacroImplRef + typer.silent(_.typed(markMacroImplRef(core)), reportAmbiguousErrors = false).nonEmpty } - } - // FIXME: cannot write this concisely because of SI-7507 - // lazy val (isImplBundle, macroImplOwner, macroImpl, macroImplTargs) = - private lazy val dissectedMacroImplRef = - macroImplRef match { - case MacroImplReference(isBundle, isBlackbox, owner, meth, targs) => (isBundle, isBlackbox, owner, meth, targs) - case _ => MacroImplReferenceWrongShapeError() - } - lazy val isImplBundle = dissectedMacroImplRef._1 - lazy val isImplMethod = !isImplBundle - lazy val isImplBlackbox = dissectedMacroImplRef._2 - lazy val macroImplOwner = dissectedMacroImplRef._3 - lazy val macroImpl = dissectedMacroImplRef._4 - lazy val targs = dissectedMacroImplRef._5 + lazy val macroImplRef: Tree = + typer.silent(_.typed(markMacroImplRef(untypedMacroImplRef)), reportAmbiguousErrors = false) match { + case SilentResultValue(success) => success + case SilentTypeError(err) => abort(err.errPos, err.errMsg) + } + + // FIXME: cannot write this concisely because of SI-7507 + // lazy val (_, macroImplOwner, macroImpl, macroImplTargs) = + private lazy val dissectedMacroImplRef = + macroImplRef match { + case MacroImplReference(isBundle, isBlackbox, owner, meth, targs) => (isBlackbox, owner, meth, targs) + case _ => MacroImplReferenceWrongShapeError() + } + lazy val isImplBlackbox = dissectedMacroImplRef._1 + lazy val macroImplOwner = dissectedMacroImplRef._2 + lazy val macroImpl = dissectedMacroImplRef._3 + lazy val targs = dissectedMacroImplRef._4 + } } diff --git a/src/compiler/scala/reflect/macros/compiler/Validators.scala b/src/compiler/scala/reflect/macros/compiler/Validators.scala index 02c1f7c431..fc118028dd 100644 --- a/src/compiler/scala/reflect/macros/compiler/Validators.scala +++ b/src/compiler/scala/reflect/macros/compiler/Validators.scala @@ -1,9 +1,7 @@ package scala.reflect.macros package compiler -import java.util.UUID.randomUUID import scala.reflect.internal.Flags._ -import scala.reflect.macros.TypecheckException trait Validators { self: DefaultMacroCompiler => @@ -11,189 +9,193 @@ trait Validators { import global._ import analyzer._ import definitions._ - private val runDefinitions = currentRun.runDefinitions import runDefinitions.{Predef_???, _} - def validateMacroImplRef() = { - sanityCheck() - if (macroImpl != Predef_???) checkMacroDefMacroImplCorrespondence() - } + trait Validator { + self: MacroImplRefCompiler => - private def sanityCheck() = { - if (!macroImpl.isMethod) MacroImplReferenceWrongShapeError() - if (macroImpl.typeParams.length != targs.length) MacroImplWrongNumberOfTypeArgumentsError() - if (!macroImpl.isPublic) MacroImplNotPublicError() - if (macroImpl.isOverloaded) MacroImplOverloadedError() - val implicitParams = aparamss.flatten filter (_.isImplicit) - if (implicitParams.nonEmpty) MacroImplNonTagImplicitParameters(implicitParams) - val effectiveOwner = if (isImplMethod) macroImplOwner else macroImplOwner.owner - val declaredInStaticObject = effectiveOwner.isStaticOwner || effectiveOwner.moduleClass.isStaticOwner - if (!declaredInStaticObject) MacroImplReferenceWrongShapeError() - } + def validateMacroImplRef() = { + sanityCheck() + if (macroImpl != Predef_???) checkMacroDefMacroImplCorrespondence() + } - private def checkMacroDefMacroImplCorrespondence() = { - val atvars = atparams map freshVar - def atpeToRtpe(atpe: Type) = atpe.substSym(aparamss.flatten, rparamss.flatten).instantiateTypeParams(atparams, atvars) - - // we only check strict correspondence between value parameterss - // type parameters of macro defs and macro impls don't have to coincide with each other - if (aparamss.length != rparamss.length) MacroImplParamssMismatchError() - map2(aparamss, rparamss)((aparams, rparams) => { - if (aparams.length < rparams.length) MacroImplMissingParamsError(aparams, rparams) - if (rparams.length < aparams.length) MacroImplExtraParamsError(aparams, rparams) - }) - - try { - // 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) - if (isRepeated(aparam) ^ isRepeated(rparam)) MacroImplVarargMismatchError(aparam, rparam) - val aparamtpe = aparam.tpe match { - case MacroContextType(tpe) => tpe - case tpe => tpe - } - checkMacroImplParamTypeMismatch(atpeToRtpe(aparamtpe), rparam) + private def sanityCheck() = { + if (!macroImpl.isMethod) MacroImplReferenceWrongShapeError() + if (macroImpl.typeParams.length != targs.length) MacroImplWrongNumberOfTypeArgumentsError() + if (!macroImpl.isPublic) MacroImplNotPublicError() + if (macroImpl.isOverloaded) MacroImplOverloadedError() + val implicitParams = aparamss.flatten filter (_.isImplicit) + if (implicitParams.nonEmpty) MacroImplNonTagImplicitParameters(implicitParams) + val effectiveOwner = if (isImplMethod) macroImplOwner else macroImplOwner.owner + val effectivelyStatic = effectiveOwner.isStaticOwner || effectiveOwner.moduleClass.isStaticOwner + val correctBundleness = if (isImplMethod) macroImplOwner.isModuleClass else macroImplOwner.isClass && !macroImplOwner.isModuleClass + if (!effectivelyStatic || !correctBundleness) MacroImplReferenceWrongShapeError() + } + + private def checkMacroDefMacroImplCorrespondence() = { + val atvars = atparams map freshVar + def atpeToRtpe(atpe: Type) = atpe.substSym(aparamss.flatten, rparamss.flatten).instantiateTypeParams(atparams, atvars) + + // we only check strict correspondence between value parameterss + // type parameters of macro defs and macro impls don't have to coincide with each other + if (aparamss.length != rparamss.length) MacroImplParamssMismatchError() + map2(aparamss, rparamss)((aparams, rparams) => { + if (aparams.length < rparams.length) MacroImplMissingParamsError(aparams, rparams) + if (rparams.length < aparams.length) MacroImplExtraParamsError(aparams, rparams) }) - checkMacroImplResultTypeMismatch(atpeToRtpe(aret), rret) + try { + // 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) + if (isRepeated(aparam) ^ isRepeated(rparam)) MacroImplVarargMismatchError(aparam, rparam) + val aparamtpe = aparam.tpe match { + case MacroContextType(tpe) => tpe + case tpe => tpe + } + checkMacroImplParamTypeMismatch(atpeToRtpe(aparamtpe), rparam) + }) - val maxLubDepth = lubDepth(aparamss.flatten map (_.tpe)) max lubDepth(rparamss.flatten map (_.tpe)) - val atargs = solvedTypes(atvars, atparams, atparams map varianceInType(aret), upper = false, maxLubDepth) - val boundsOk = typer.silent(_.infer.checkBounds(macroDdef, NoPrefix, NoSymbol, atparams, atargs, "")) - boundsOk match { - case SilentResultValue(true) => // do nothing, success - case SilentResultValue(false) | SilentTypeError(_) => MacroImplTargMismatchError(atargs, atparams) + checkMacroImplResultTypeMismatch(atpeToRtpe(aret), rret) + + val maxLubDepth = lubDepth(aparamss.flatten map (_.tpe)) max lubDepth(rparamss.flatten map (_.tpe)) + val atargs = solvedTypes(atvars, atparams, atparams map varianceInType(aret), upper = false, maxLubDepth) + val boundsOk = typer.silent(_.infer.checkBounds(macroDdef, NoPrefix, NoSymbol, atparams, atargs, "")) + boundsOk match { + case SilentResultValue(true) => // do nothing, success + case SilentResultValue(false) | SilentTypeError(_) => MacroImplTargMismatchError(atargs, atparams) + } + } catch { + case ex: NoInstance => MacroImplTparamInstantiationError(atparams, ex) } - } catch { - case ex: NoInstance => MacroImplTparamInstantiationError(atparams, ex) } - } - // 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") - // FIXME: cannot write this concisely because of SI-7507 - //lazy val MacroImplSig(atparams, aparamss, aret) = macroImplSig - //lazy val MacroImplSig(_, rparamss, rret) = referenceMacroImplSig - lazy val atparams = macroImplSig.tparams - lazy val aparamss = macroImplSig.paramss - lazy val aret = macroImplSig.ret - lazy val rparamss = referenceMacroImplSig.paramss - lazy val rret = referenceMacroImplSig.ret - - // Technically this can be just an alias to MethodType, but promoting it to a first-class entity - // provides better encapsulation and convenient syntax for pattern matching. - private case class MacroImplSig(tparams: List[Symbol], paramss: List[List[Symbol]], ret: Type) { - private def tparams_s = if (tparams.isEmpty) "" else tparams.map(_.defString).mkString("[", ", ", "]") - private def paramss_s = paramss map (ps => ps.map(s => s"${s.name}: ${s.tpe_*}").mkString("(", ", ", ")")) mkString "" - override def toString = "MacroImplSig(" + tparams_s + paramss_s + ret + ")" - } + // 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") + // FIXME: cannot write this concisely because of SI-7507 + //lazy val MacroImplSig(atparams, aparamss, aret) = macroImplSig + //lazy val MacroImplSig(_, rparamss, rret) = referenceMacroImplSig + lazy val atparams = macroImplSig.tparams + lazy val aparamss = macroImplSig.paramss + lazy val aret = macroImplSig.ret + lazy val rparamss = referenceMacroImplSig.paramss + lazy val rret = referenceMacroImplSig.ret + + // Technically this can be just an alias to MethodType, but promoting it to a first-class entity + // provides better encapsulation and convenient syntax for pattern matching. + private case class MacroImplSig(tparams: List[Symbol], paramss: List[List[Symbol]], ret: Type) { + private def tparams_s = if (tparams.isEmpty) "" else tparams.map(_.defString).mkString("[", ", ", "]") + private def paramss_s = paramss map (ps => ps.map(s => s"${s.name}: ${s.tpe_*}").mkString("(", ", ", ")")) mkString "" + override def toString = "MacroImplSig(" + tparams_s + paramss_s + ret + ")" + } - /** An actual macro implementation signature extracted from a macro implementation method. - * - * For the following macro impl: - * def fooBar[T: c.WeakTypeTag] - * (c: scala.reflect.macros.blackbox.Context) - * (xs: c.Expr[List[T]]) - * : c.Expr[T] = ... - * - * This function will return: - * (c: scala.reflect.macros.blackbox.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 *box.Macro-compatible object, - * then it won't have (c: *box.Context) in its parameters, but will rather refer to *boxMacro.c. - * - * @param macroImpl The macro implementation symbol - */ - private lazy val macroImplSig: MacroImplSig = { - val tparams = macroImpl.typeParams - val paramss = transformTypeTagEvidenceParams(macroImplRef, (param, tparam) => NoSymbol) - val ret = macroImpl.info.finalResultType - MacroImplSig(tparams, paramss, ret) - } + /** An actual macro implementation signature extracted from a macro implementation method. + * + * For the following macro impl: + * def fooBar[T: c.WeakTypeTag] + * (c: scala.reflect.macros.blackbox.Context) + * (xs: c.Expr[List[T]]) + * : c.Expr[T] = ... + * + * This function will return: + * (c: scala.reflect.macros.blackbox.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 *box.Macro-compatible object, + * then it won't have (c: *box.Context) in its parameters, but will rather refer to *boxMacro.c. + * + * @param macroImpl The macro implementation symbol + */ + private lazy val macroImplSig: MacroImplSig = { + val tparams = macroImpl.typeParams + val paramss = transformTypeTagEvidenceParams(macroImplRef, (param, tparam) => NoSymbol) + val ret = macroImpl.info.finalResultType + MacroImplSig(tparams, paramss, ret) + } - /** A reference macro implementation signature extracted from a given macro definition. - * - * For the following macro def: - * def foo[T](xs: List[T]): T = macro fooBar - * - * This function will return: - * (c: scala.reflect.macros.blackbox.Context)(xs: c.Expr[List[T]])c.Expr[T] or - * (c: scala.reflect.macros.whitebox.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. - * - * 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 lazy val referenceMacroImplSig: MacroImplSig = { - // had to move method's body to an object because of the recursive dependencies between sigma and param - object SigGenerator { - val cache = scala.collection.mutable.Map[Symbol, Symbol]() - val ctxTpe = if (isImplBlackbox) BlackboxContextClass.tpe else WhiteboxContextClass.tpe - val ctxPrefix = - if (isImplMethod) singleType(NoPrefix, makeParam(nme.macroContext, macroDdef.pos, ctxTpe, SYNTHETIC)) - else singleType(ThisType(macroImpl.owner), macroImpl.owner.tpe.member(nme.c)) - val paramss = - if (isImplMethod) List(ctxPrefix.termSymbol) :: mmap(macroDdef.vparamss)(param) - else mmap(macroDdef.vparamss)(param) - val macroDefRet = - if (!macroDdef.tpt.isEmpty) typer.typedType(macroDdef.tpt).tpe - else computeMacroDefTypeFromMacroImplRef(macroDdef, macroImplRef) orElse AnyTpe - val implReturnType = sigma(increaseMetalevel(ctxPrefix, macroDefRet)) - - object SigmaTypeMap extends TypeMap { - def mapPrefix(pre: Type) = pre match { - case ThisType(sym) if sym == macroDef.owner => - singleType(singleType(ctxPrefix, MacroContextPrefix), ExprValue) - case SingleType(NoPrefix, sym) => - mfind(macroDdef.vparamss)(_.symbol == sym).fold(pre)(p => singleType(singleType(NoPrefix, param(p)), ExprValue)) - case _ => - mapOver(pre) - } - def apply(tp: Type): Type = tp match { - case TypeRef(pre, sym, args) => - val pre1 = mapPrefix(pre) - val args1 = mapOverArgs(args, sym.typeParams) - if ((pre eq pre1) && (args eq args1)) tp - else typeRef(pre1, sym, args1) - case _ => - mapOver(tp) + /** A reference macro implementation signature extracted from a given macro definition. + * + * For the following macro def: + * def foo[T](xs: List[T]): T = macro fooBar + * + * This function will return: + * (c: scala.reflect.macros.blackbox.Context)(xs: c.Expr[List[T]])c.Expr[T] or + * (c: scala.reflect.macros.whitebox.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. + * + * 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 lazy val referenceMacroImplSig: MacroImplSig = { + // had to move method's body to an object because of the recursive dependencies between sigma and param + object SigGenerator { + val cache = scala.collection.mutable.Map[Symbol, Symbol]() + val ctxTpe = if (isImplBlackbox) BlackboxContextClass.tpe else WhiteboxContextClass.tpe + val ctxPrefix = + if (isImplMethod) singleType(NoPrefix, makeParam(nme.macroContext, macroDdef.pos, ctxTpe, SYNTHETIC)) + else singleType(ThisType(macroImpl.owner), macroImpl.owner.tpe.member(nme.c)) + val paramss = + if (isImplMethod) List(ctxPrefix.termSymbol) :: mmap(macroDdef.vparamss)(param) + else mmap(macroDdef.vparamss)(param) + val macroDefRet = + if (!macroDdef.tpt.isEmpty) typer.typedType(macroDdef.tpt).tpe + else computeMacroDefTypeFromMacroImplRef(macroDdef, macroImplRef) orElse AnyTpe + val implReturnType = sigma(increaseMetalevel(ctxPrefix, macroDefRet)) + + object SigmaTypeMap extends TypeMap { + def mapPrefix(pre: Type) = pre match { + case ThisType(sym) if sym == macroDef.owner => + singleType(singleType(ctxPrefix, MacroContextPrefix), ExprValue) + case SingleType(NoPrefix, sym) => + mfind(macroDdef.vparamss)(_.symbol == sym).fold(pre)(p => singleType(singleType(NoPrefix, param(p)), ExprValue)) + case _ => + mapOver(pre) + } + def apply(tp: Type): Type = tp match { + case TypeRef(pre, sym, args) => + val pre1 = mapPrefix(pre) + val args1 = mapOverArgs(args, sym.typeParams) + if ((pre eq pre1) && (args eq args1)) tp + else typeRef(pre1, sym, args1) + case _ => + mapOver(tp) + } } + def sigma(tpe: Type): Type = SigmaTypeMap(tpe) + + def makeParam(name: Name, pos: Position, tpe: Type, flags: Long) = + macroDef.newValueParameter(name.toTermName, pos, flags) setInfo tpe + def param(tree: Tree): Symbol = ( + cache.getOrElseUpdate(tree.symbol, { + val sym = tree.symbol + assert(sym.isTerm, s"sym = $sym, tree = $tree") + makeParam(sym.name, sym.pos, sigma(increaseMetalevel(ctxPrefix, sym.tpe)), sym.flags) + }) + ) } - def sigma(tpe: Type): Type = SigmaTypeMap(tpe) - - def makeParam(name: Name, pos: Position, tpe: Type, flags: Long) = - macroDef.newValueParameter(name.toTermName, pos, flags) setInfo tpe - def param(tree: Tree): Symbol = ( - cache.getOrElseUpdate(tree.symbol, { - val sym = tree.symbol - assert(sym.isTerm, s"sym = $sym, tree = $tree") - makeParam(sym.name, sym.pos, sigma(increaseMetalevel(ctxPrefix, sym.tpe)), sym.flags) - }) - ) - } - import SigGenerator._ - macroLogVerbose(s"generating macroImplSigs for: $macroDdef") - val result = MacroImplSig(macroDdef.tparams map (_.symbol), paramss, implReturnType) - macroLogVerbose(s"result is: $result") - result + import SigGenerator._ + macroLogVerbose(s"generating macroImplSigs for: $macroDdef") + val result = MacroImplSig(macroDdef.tparams map (_.symbol), paramss, implReturnType) + macroLogVerbose(s"result is: $result") + result + } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 1a53fef4aa..093bb46284 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -56,6 +56,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } sealed abstract class SilentResult[+T] { + def isEmpty: Boolean + def nonEmpty = !isEmpty + @inline final def fold[U](none: => U)(f: T => U): U = this match { case SilentResultValue(value) => f(value) case _ => none @@ -74,6 +77,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } class SilentTypeError private(val errors: List[AbsTypeError]) extends SilentResult[Nothing] { + override def isEmpty = true def err: AbsTypeError = errors.head def reportableErrors = errors match { case (e1: AmbiguousImplicitTypeError) +: _ => @@ -87,7 +91,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def unapply(error: SilentTypeError): Option[AbsTypeError] = error.errors.headOption } - case class SilentResultValue[+T](value: T) extends SilentResult[T] { } + case class SilentResultValue[+T](value: T) extends SilentResult[T] { override def isEmpty = false } def newTyper(context: Context): Typer = new NormalTyper(context) diff --git a/test/files/neg/macro-bundle-ambiguous.check b/test/files/neg/macro-bundle-ambiguous.check new file mode 100644 index 0000000000..8430496455 --- /dev/null +++ b/test/files/neg/macro-bundle-ambiguous.check @@ -0,0 +1,5 @@ +macro-bundle-ambiguous.scala:13: error: macro implementation reference is ambiguous: makes sense both as +a macro bundle method reference and a vanilla object method reference + def foo: Unit = macro Macros.impl + ^ +one error found diff --git a/test/files/neg/macro-bundle-ambiguous.scala b/test/files/neg/macro-bundle-ambiguous.scala new file mode 100644 index 0000000000..92c359d9a9 --- /dev/null +++ b/test/files/neg/macro-bundle-ambiguous.scala @@ -0,0 +1,14 @@ +import scala.reflect.macros.whitebox._ +import scala.language.experimental.macros + +class Macros(val c: Context) { + def impl = ??? +} + +object Macros { + def impl(c: Context) = ??? +} + +object Test extends App { + def foo: Unit = macro Macros.impl +}
\ No newline at end of file diff --git a/test/files/neg/macro-bundle-priority-bundle.check b/test/files/neg/macro-bundle-priority-bundle.check new file mode 100644 index 0000000000..c6cea72ba6 --- /dev/null +++ b/test/files/neg/macro-bundle-priority-bundle.check @@ -0,0 +1,8 @@ +macro-bundle-priority-bundle.scala:13: error: bundle implementation has incompatible shape: + required: : Macros.this.c.Expr[Unit] + or : : Macros.this.c.Tree + found : (x: Macros.this.c.Tree): Nothing +number of parameter sections differ + def foo: Unit = macro Macros.impl + ^ +one error found diff --git a/test/files/neg/macro-bundle-priority-bundle.scala b/test/files/neg/macro-bundle-priority-bundle.scala new file mode 100644 index 0000000000..ce831a7121 --- /dev/null +++ b/test/files/neg/macro-bundle-priority-bundle.scala @@ -0,0 +1,14 @@ +import scala.reflect.macros.whitebox._ +import scala.language.experimental.macros + +class Macros(val c: Context) { + def impl(x: c.Tree) = ??? +} + +object Macros { + def impl(c: Context)(x: c.Tree) = ??? +} + +object Test extends App { + def foo: Unit = macro Macros.impl +}
\ No newline at end of file diff --git a/test/files/neg/macro-bundle-priority-nonbundle.check b/test/files/neg/macro-bundle-priority-nonbundle.check new file mode 100644 index 0000000000..0d03b5074b --- /dev/null +++ b/test/files/neg/macro-bundle-priority-nonbundle.check @@ -0,0 +1,8 @@ +macro-bundle-priority-nonbundle.scala:13: error: macro implementation has incompatible shape: + required: (c: scala.reflect.macros.whitebox.Context): c.Expr[Unit] + or : (c: scala.reflect.macros.whitebox.Context): c.Tree + found : (c: scala.reflect.macros.whitebox.Context)(x: c.Tree): Nothing +number of parameter sections differ + def foo: Unit = macro Macros.impl + ^ +one error found diff --git a/test/files/neg/macro-bundle-priority-nonbundle.scala b/test/files/neg/macro-bundle-priority-nonbundle.scala new file mode 100644 index 0000000000..8dc00f6dd3 --- /dev/null +++ b/test/files/neg/macro-bundle-priority-nonbundle.scala @@ -0,0 +1,14 @@ +import scala.reflect.macros.whitebox._ +import scala.language.experimental.macros + +class Macros(val c: scala.reflect.api.Universe) { + def impl(x: c.Tree) = ??? +} + +object Macros { + def impl(c: Context)(x: c.Tree) = ??? +} + +object Test extends App { + def foo: Unit = macro Macros.impl +}
\ No newline at end of file diff --git a/test/files/neg/macro-quasiquotes.check b/test/files/neg/macro-quasiquotes.check index c690b61fe1..a985aee156 100644 --- a/test/files/neg/macro-quasiquotes.check +++ b/test/files/neg/macro-quasiquotes.check @@ -1,4 +1,4 @@ -Macros_1.scala:14: error: macro implementation has incompatible shape: +Macros_1.scala:14: error: bundle implementation has incompatible shape: required: (x: Impls.this.c.Expr[Int]): Impls.this.c.Expr[Unit] or : (x: Impls.this.c.Tree): Impls.this.c.Tree found : (x: Impls.this.c.universe.Block): Impls.this.c.Tree diff --git a/test/files/pos/macro-bundle-disambiguate-bundle.check b/test/files/pos/macro-bundle-disambiguate-bundle.check new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/files/pos/macro-bundle-disambiguate-bundle.check diff --git a/test/files/pos/macro-bundle-disambiguate-bundle.scala b/test/files/pos/macro-bundle-disambiguate-bundle.scala new file mode 100644 index 0000000000..04809317e1 --- /dev/null +++ b/test/files/pos/macro-bundle-disambiguate-bundle.scala @@ -0,0 +1,14 @@ +import scala.reflect.macros.whitebox._ +import scala.language.experimental.macros + +class Macros(val c: Context) { + def impl = ??? +} + +object Macros { + def impl(c: Context)(x: c.Tree) = ??? +} + +object Test extends App { + def foo: Unit = macro Macros.impl +}
\ No newline at end of file diff --git a/test/files/pos/macro-bundle-disambiguate-nonbundle.check b/test/files/pos/macro-bundle-disambiguate-nonbundle.check new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/files/pos/macro-bundle-disambiguate-nonbundle.check diff --git a/test/files/pos/macro-bundle-disambiguate-nonbundle.scala b/test/files/pos/macro-bundle-disambiguate-nonbundle.scala new file mode 100644 index 0000000000..cb66f28a0b --- /dev/null +++ b/test/files/pos/macro-bundle-disambiguate-nonbundle.scala @@ -0,0 +1,14 @@ +import scala.reflect.macros.whitebox._ +import scala.language.experimental.macros + +class Macros(val c: Context) { + def impl(x: c.Tree) = ??? +} + +object Macros { + def impl(c: Context) = ??? +} + +object Test extends App { + def foo: Unit = macro Macros.impl +}
\ No newline at end of file |