From f8d80ea26a03a39fd1d0160a2fad69752712f574 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 31 May 2013 00:22:50 -0700 Subject: SI-3452 Correct Java generic signatures for mixins, static forwarders [Parts of this patch and some of the commentary are from @paulp] This took me so long to figure out I can't even tell you. Partly because there were two different bugs, one which only arose for trait forwarders and one for mirror class forwarders, and every time I'd make one set of tests work another set would start failing. The runtime failures associated with these bugs were fairly well hidden because you usually have to go through java to encounter them: scala doesn't pay that much attention to generic signatures, so they can be wrong and scala might still generate correct code. But java is not so lucky. Bug #1) During mixin composition, classes which extend traits receive forwarders to the implementations. An attempt was made to give these the correct info (in method "cloneBeforeErasure") but it was prone to giving the wrong answer, because: the key attribute which the forwarder must capture is what the underlying method will erase to *where the implementation is*, not how it appears to the class which contains it. That means the signature of the forwarder must be no more precise than the signature of the inherited implementation unless additional measures will be taken. This subtle difference will put on an unsubtle show for you in test run/t3452.scala. trait C[T] trait Search[M] { def search(input: M): C[Int] = null } object StringSearch extends Search[String] { } StringSearch.search("test"); // java // java.lang.NoSuchMethodError: StringSearch.search(Ljava/lang/String;)LC; The principled thing to do here would be to create a pair of methods in the host class: a mixin forwarder with the erased signature `(String)C[Int]`, and a bridge method with the same erased signature as the trait interface facet. But, this turns out to be pretty hard to retrofit onto the current setup of Mixin and Erasure, mostly due to the fact that mixin happens after erasure which has already taken care of bridging. For a future, release, we should try to move all bridging after mixin, and pursue this approach. But for now, what can we do about `LinkageError`s for Java clients? This commit simply checks if the pre-erasure method signature that we generate for the trait forward erases identically to that of the interface method. If so, we can be precise. If not, we emit the erased signature as the generic signature. Bug #2) The same principle is at work, at a different location. During genjvm, objects without declared companion classes are given static forwarders in the corresponding class, e.g. object Foo { def bar = 5 } which creates these classes (taking minor liberties): class Foo$ { static val MODULE$ = new Foo$ ; def bar = 5 } class Foo { static def bar = Foo$.MODULE$.bar } In generating these, genjvm circumvented the usual process whereby one creates a symbol and gives it an info, preferring to target the bytecode directly. However generic signatures are calculated from symbol info (in this case reusing the info from the module class.) Lacking even the attempt which was being made in mixin to "clone before erasure", we would have runtime failures of this kind: abstract class Foo { type T def f(x: T): List[T] = List() } object Bar extends Foo { type T = String } Bar.f(""); // java // java.lang.NoSuchMethodError: Bar.f(Ljava/lang/String;)Lscala/collection/immutable/List; Before/after this commit: < signature f (Ljava/lang/String;)Lscala/collection/immutable/List; --- > signature f (Ljava/lang/Object;)Lscala/collection/immutable/List; This takes the warning count for compiling collections under `-Ycheck:jvm` from 1521 to 26. --- test/files/neg/t4749.check | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'test/files/neg') diff --git a/test/files/neg/t4749.check b/test/files/neg/t4749.check index 63d5c21532..3539140954 100644 --- a/test/files/neg/t4749.check +++ b/test/files/neg/t4749.check @@ -25,6 +25,10 @@ t4749.scala:26: warning: Fail6 has a main method with parameter type Array[Strin object Fail6 { ^ +t4749.scala:42: warning: Win3 has a main method with parameter type Array[String], but bippy.Win3 will not be a runnable program. + Reason: main method must have exact signature (Array[String])Unit + object Win3 extends WinBippy[Unit] { } + ^ error: No warnings can be incurred under -Xfatal-warnings. -6 warnings found +7 warnings found one error found -- cgit v1.2.3 From a02e053a5dec134f7c7dc53a2c1091039218237d Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Wed, 22 Jan 2014 13:18:35 +0300 Subject: SI-5920 enables default and named args in macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When producing an initial spec for macros two years ago, we sort of glossed over named/default arguments in macro applications, leaving them for future work. Once the aforementioned future has come, I’ve made several attempts at making things operational (e.g. last summer), but it’s always been unclear how to marry the quite complex desugaring that tryNamesDefaults performs with the expectations of macro programmers to see unsugared trees in macro impl parameters. Here’s the list of problems that arise when trying to encode named/default arguments of macro applications: 1) When inside macro impls we don’t really care about synthetic vals that are typically introduced to preserve evaluation order in non-positional method applications. When we inline those synthetics, we lose information about evaluation order, which is something that we wouldn’t like to lose in the general case. 2) More importantly, it’s also not very exciting to see invocations of default getters that stand for unspecified default arguments. Ideally, we would like to provide macro programmers with right-hand sides of those default getters, but that is: a) impossible in the current implementation of default parameters, b) would anyway bring scoping problems that we’re not ready to deal with just yet. Being constantly unhappy with potential solutions to the aforementioned problems, I’ve been unable to nail this down until the last weekend, when I realized that: 1) even though we can’t express potential twists in evaluation order within linearly ordered macro impl params, we can use c.macroApplication to store all the named arguments we want, 2) even though we can’t get exactly what we want for default arguments, we can represent them with EmptyTree’s, which is not ideal, but pretty workable. That’s what has been put into life in this commit. As a pleasant side-effect, now the macro engine doesn’t have to reinvent the wheel wrt reporting errors about insufficient arg or arglist count. Since this logic is intertwined with the tryNamesDefaults desugaring, we previously couldn’t make use of it and had to roll our own logic that checked that the number of arguments and parameters of macro applications correspond to each other. Now it’s all deduplicated and consistent. --- .../tools/nsc/typechecker/ContextErrors.scala | 28 +++---- .../scala/tools/nsc/typechecker/Macros.scala | 93 ++++++++++++++++------ .../tools/nsc/typechecker/StdAttachments.scala | 3 + .../scala/tools/nsc/typechecker/Typers.scala | 19 ++--- src/reflect/scala/reflect/internal/TreeInfo.scala | 11 +-- test/files/neg/macro-argc-mismatch.check | 49 ++++++++++++ test/files/neg/macro-argc-mismatch/Macros_1.scala | 16 ++++ test/files/neg/macro-argc-mismatch/Test_2.scala | 19 +++++ test/files/neg/macro-invalidusage-badargs.check | 5 +- test/files/neg/macro-qmarkqmarkqmark.check | 2 +- test/files/neg/t7157.check | 36 ++++++--- test/files/run/macro-expand-default-named.check | 56 +++++++++++++ .../run/macro-expand-default-named/Impls_1.scala | 37 +++++++++ .../macro-expand-default-named/Macros_Test_2.scala | 71 +++++++++++++++++ test/files/run/macro-expand-ownerchain-a.check | 2 + .../run/macro-expand-ownerchain-a/Macros_1.scala | 11 +++ .../run/macro-expand-ownerchain-a/Test_2.scala | 4 + ...alidusage-partialapplication-with-tparams.check | 2 +- .../macro-invalidusage-partialapplication.check | 2 +- test/files/run/reify-repl-fail-gracefully.check | 2 +- test/pending/run/macro-expand-default.flags | 1 - .../pending/run/macro-expand-default/Impls_1.scala | 10 --- .../run/macro-expand-default/Macros_Test_2.scala | 8 -- test/pending/run/macro-expand-named.flags | 1 - test/pending/run/macro-expand-named/Impls_1.scala | 10 --- .../run/macro-expand-named/Macros_Test_2.scala | 5 -- 26 files changed, 384 insertions(+), 119 deletions(-) create mode 100644 test/files/neg/macro-argc-mismatch.check create mode 100644 test/files/neg/macro-argc-mismatch/Macros_1.scala create mode 100644 test/files/neg/macro-argc-mismatch/Test_2.scala create mode 100644 test/files/run/macro-expand-default-named.check create mode 100644 test/files/run/macro-expand-default-named/Impls_1.scala create mode 100644 test/files/run/macro-expand-default-named/Macros_Test_2.scala create mode 100644 test/files/run/macro-expand-ownerchain-a.check create mode 100644 test/files/run/macro-expand-ownerchain-a/Macros_1.scala create mode 100644 test/files/run/macro-expand-ownerchain-a/Test_2.scala delete mode 100644 test/pending/run/macro-expand-default.flags delete mode 100644 test/pending/run/macro-expand-default/Impls_1.scala delete mode 100644 test/pending/run/macro-expand-default/Macros_Test_2.scala delete mode 100644 test/pending/run/macro-expand-named.flags delete mode 100644 test/pending/run/macro-expand-named/Impls_1.scala delete mode 100644 test/pending/run/macro-expand-named/Macros_Test_2.scala (limited to 'test/files/neg') diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 2043eb5d5d..40b97394f2 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -501,10 +501,6 @@ trait ContextErrors { } // doTypeApply - //tryNamesDefaults - def NamedAndDefaultArgumentsNotSupportedForMacros(tree: Tree, fun: Tree) = - 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)) @@ -603,12 +599,11 @@ trait ContextErrors { //adapt def MissingArgsForMethodTpeError(tree: Tree, meth: Symbol) = { + val errorExplanation = "missing arguments for " + meth.fullLocationString + val suggestPartialApplication = ";\nfollow this method with `_' if you want to treat it as a partially applied function" val message = - 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" - ) + if (meth.isMacro || meth.isConstructor) errorExplanation + else errorExplanation + suggestPartialApplication issueNormalTypeError(tree, message) setError(tree) } @@ -748,15 +743,12 @@ trait ContextErrors { throw MacroExpansionException } - private def MacroTooFewArgumentListsMessage = "too few argument lists for macro invocation" - def MacroTooFewArgumentListsError(expandee: Tree) = macroExpansionError2(expandee, MacroTooFewArgumentListsMessage) - - private def MacroTooManyArgumentListsMessage = "too many argument lists for macro invocation" - def MacroTooManyArgumentListsError(expandee: Tree) = macroExpansionError2(expandee, MacroTooManyArgumentListsMessage) - - def MacroTooFewArgumentsError(expandee: Tree) = macroExpansionError2(expandee, "too few arguments for macro invocation") - - def MacroTooManyArgumentsError(expandee: Tree) = macroExpansionError2(expandee, "too many arguments for macro invocation") + def MacroFastTrackFailed(expandee: Tree) = { + // here we speculate that the reason why FastTrackEntry.validate failed is the lack arguments for a given method + // that's not ideal, but on the other hand this allows us to keep FastTrack simple without hooking errorgen into it + MissingArgsForMethodTpeError(expandee, expandee.symbol) + throw MacroExpansionException + } def MacroGeneratedAbort(expandee: Tree, ex: AbortMacroException) = { // errors have been reported by the macro itself, so we do nothing here diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index cf82d6baac..29b3ec7f3e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -353,7 +353,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { new { val universe: self.global.type = self.global val callsiteTyper: universe.analyzer.Typer = typer.asInstanceOf[global.analyzer.Typer] - val expandee = universe.analyzer.macroExpanderAttachment(expandeeTree).original orElse duplicateAndKeepPositions(expandeeTree) + val expandee = universe.analyzer.macroExpanderAttachment(expandeeTree).desugared orElse duplicateAndKeepPositions(expandeeTree) } with UnaffiliatedMacroContext { val prefix = Expr[Nothing](prefixTree)(TypeTag.Nothing) override def toString = "MacroContext(%s@%s +%d)".format(expandee.symbol.name, expandee.pos, enclosingMacros.length - 1 /* exclude myself */) @@ -371,7 +371,15 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { def standardMacroArgs(typer: Typer, expandee: Tree): MacroArgs = { val macroDef = expandee.symbol val paramss = macroDef.paramss - val treeInfo.Applied(core, targs, argss) = expandee + val treeInfo.Applied(core, targs, maybeNamedArgss) = expandee + val argss = map2(maybeNamedArgss, paramss)((args, params) => { + if (args.exists(_.isInstanceOf[AssignOrNamedArg])) { + val sorted = ListBuffer.fill(params.length)(EmptyTree: Tree) + args foreach { case AssignOrNamedArg(Ident(name), arg) => sorted(params.indexWhere(_.name == name)) = arg } + sorted.toList + } else if (params.length == args.length) args + else args ++ List.fill(params.length - args.length)(EmptyTree) + }) val prefix = core match { case Select(qual, _) => qual; case _ => EmptyTree } val context = expandee.attachments.get[MacroRuntimeAttachment].flatMap(_.macroContext).getOrElse(macroContext(typer, prefix, expandee)) @@ -383,16 +391,11 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { |paramss: $paramss """.trim) - import typer.TyperErrorGen._ - val isNullaryArgsEmptyParams = argss.isEmpty && paramss == ListOfNil - if (paramss.length < argss.length) MacroTooManyArgumentListsError(expandee) - if (paramss.length > argss.length && !isNullaryArgsEmptyParams) MacroTooFewArgumentListsError(expandee) - val macroImplArgs: List[Any] = if (fastTrack contains macroDef) { // Take a dry run of the fast track implementation if (fastTrack(macroDef) validate expandee) argss.flatten - else MacroTooFewArgumentListsError(expandee) + else typer.TyperErrorGen.MacroFastTrackFailed(expandee) } else { def calculateMacroArgs(binding: MacroImplBinding) = { @@ -403,14 +406,6 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { // 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, signature)((args, defParams, implParams) => { - val isVarargs = isVarArgsList(defParams) - if (isVarargs) { - if (defParams.length > args.length + 1) MacroTooFewArgumentsError(expandee) - } else { - if (defParams.length < args.length) MacroTooManyArgumentsError(expandee) - if (defParams.length > args.length) MacroTooFewArgumentsError(expandee) - } - val wrappedArgs = mapWithIndex(args)((arg, j) => { val fingerprint = implParams(min(j, implParams.length - 1)) fingerprint match { @@ -421,7 +416,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { } }) - if (isVarargs) { + if (isVarArgsList(defParams)) { val (normal, varargs) = wrappedArgs splitAt (defParams.length - 1) normal :+ varargs // pack all varargs into a single Seq argument (varargs Scala style) } else wrappedArgs @@ -529,9 +524,11 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { * the expandee with an error marker set if there has been an error */ abstract class MacroExpander(val typer: Typer, val expandee: Tree) { + val symbol = expandee match { case Block(_, expr) => expr.symbol; case tree => tree.symbol } + def onSuccess(expanded: Tree): Tree def onFallback(expanded: Tree): Tree - def onSuppressed(expandee: Tree): Tree = expandee + def onSuppressed(expanded: Tree): Tree = expanded def onDelayed(expanded: Tree): Tree = expanded def onSkipped(expanded: Tree): Tree = expanded def onFailure(expanded: Tree): Tree = { typer.infer.setError(expandee); expandee } @@ -551,15 +548,15 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { if (Statistics.canEnable) Statistics.incCounter(macroExpandCount) try { withInfoLevel(nodePrinters.InfoLevel.Quiet) { // verbose printing might cause recursive macro expansions - if (expandee.symbol.isErroneous || (expandee exists (_.isErroneous))) { - val reason = if (expandee.symbol.isErroneous) "not found or incompatible macro implementation" else "erroneous arguments" + if (symbol.isErroneous || (expandee exists (_.isErroneous)) || (desugared exists (_.isErroneous))) { + val reason = if (symbol.isErroneous) "not found or incompatible macro implementation" else "erroneous arguments" macroLogVerbose(s"cancelled macro expansion because of $reason: $expandee") onFailure(typer.infer.setError(expandee)) } else try { val expanded = { - val runtime = macroRuntime(expandee) - if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime) - else macroExpandWithoutRuntime(typer, expandee) + val runtime = macroRuntime(desugared) + if (runtime != null) macroExpandWithRuntime(typer, desugared, runtime) + else macroExpandWithoutRuntime(typer, desugared) } expanded match { case Success(expanded) => @@ -591,7 +588,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { extends MacroExpander(typer, expandee) { lazy val innerPt = { val tp = if (isNullaryInvocation(expandee)) expandee.tpe.finalResultType else expandee.tpe - if (isBlackbox(expandee)) tp + if (isBlackbox(symbol)) tp else { // approximation is necessary for whitebox macros to guide type inference // read more in the comments for onDelayed below @@ -599,6 +596,50 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { deriveTypeWithWildcards(undetparams)(tp) } } + override protected def expand(desugared: Tree) = { + // SI-5940 in order for a macro expansion that involves named or default arguments + // to see the actual prefix and arguments being passed by the user instead of their desugarings + // we need to inline synthetics in case when `fun` is actually a macro + // underlying macro implementation is going to get explicitly passed arguments in correct order + // and the rest (defaults filled in by the vanilla part of `tryNamesDefaults`) will become empty trees + // in order for the macro to be able to account for evaluation order, the original is provided in `c.macroApplication` + // of course, ideally we would like to provide the impl with right-hand sides of those default arguments + // but currently that is flat out impossible because of the difference in scopes + // anyway this is already an improvement over the former status quo when named/default invocations were outright prohibited + def undoNamesDefaults(tree: Tree): Tree = { + val (qualsym, qual, vdefs0, app @ Applied(_, _, argss)) = tree match { + case Block((qualdef @ ValDef(_, name, _, qual)) +: vdefs, app) if name.startsWith(nme.QUAL_PREFIX) => (qualdef.symbol, qual, vdefs, app) + case Block(vdefs, app) => (NoSymbol, EmptyTree, vdefs, app) + case tree => (NoSymbol, EmptyTree, Nil, tree) + } + val vdefs = vdefs0.map{ case vdef: ValDef => vdef } + def hasNamesDefaults(args: List[Tree]) = { + args.exists(arg => isDefaultGetter(arg) || vdefs.exists(_.symbol == arg.symbol)) + } + def undoNamesDefaults(args: List[Tree], depth: Int) = { + def extractRhs(vdef: ValDef) = vdef.rhs.changeOwner(vdef.symbol -> typer.context.owner) + case class Arg(tree: Tree, ipos: Int, inamed: Int) { val param = app.symbol.paramss(depth)(ipos) } + val indexed = args.map(arg => arg -> vdefs.indexWhere(_.symbol == arg.symbol)).zipWithIndex.flatMap({ + /* default */ case ((arg, _), _) if isDefaultGetter(arg) => None + /* positional */ case ((arg, -1), ipos) => Some(Arg(arg, ipos, -1)) + /* default+named */ case ((_, inamed), _) if isDefaultGetter(extractRhs(vdefs(inamed))) => None + /* named */ case ((arg, inamed), ipos) => Some(Arg(extractRhs(vdefs(inamed)), ipos, inamed)) + }) + if (indexed.forall(_.inamed == -1)) indexed.map(_.tree) + else indexed.sortBy(_.inamed).map(arg => AssignOrNamedArg(Ident(arg.param.name), arg.tree)) + } + def loop(tree: Tree, depth: Int): Tree = tree match { + case Apply(fun, args) if hasNamesDefaults(args) => treeCopy.Apply(tree, loop(fun, depth - 1), undoNamesDefaults(args, depth)) + case Apply(fun, args) => treeCopy.Apply(tree, loop(fun, depth - 1), args) + case TypeApply(core, targs) => treeCopy.TypeApply(tree, core, targs) + case Select(core, name) if qualsym != NoSymbol && core.symbol == qualsym => treeCopy.Select(tree, qual, name) + case core => core + } + if (app.symbol == null || app.symbol == NoSymbol || app.exists(_.isErroneous)) tree + else loop(app, depth = argss.length - 1) + } + super.expand(undoNamesDefaults(desugared)) + } override def onSuccess(expanded0: Tree) = { // prematurely annotate the tree with a macro expansion attachment // so that adapt called indirectly by typer.typed knows that it needs to apply the existential fixup @@ -616,7 +657,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { } } - if (isBlackbox(expandee)) { + if (isBlackbox(symbol)) { val expanded1 = atPos(enclosingMacroPosition.makeTransparent)(Typed(expanded0, TypeTree(innerPt))) typecheck("blackbox typecheck", expanded1, outerPt) } else { @@ -676,7 +717,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { // Thanks to that the materializer can take a look at what's going on and react accordingly. val shouldInstantiate = typer.context.undetparams.nonEmpty && !mode.inPolyMode if (shouldInstantiate) { - if (isBlackbox(expandee)) typer.instantiatePossiblyExpectingUnit(delayed, mode, outerPt) + if (isBlackbox(symbol)) typer.instantiatePossiblyExpectingUnit(delayed, mode, outerPt) else { forced += delayed typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), outerPt, keepNothings = false) diff --git a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala index 57f27a05fd..1a6d2f0011 100644 --- a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala +++ b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala @@ -85,6 +85,7 @@ trait StdAttachments { tree match { // see the comment to `isMacroExpansionSuppressed` to learn why we need // a special traversal strategy here + case Block(_, expr) => unsuppressMacroExpansion(expr) case Apply(fn, _) => unsuppressMacroExpansion(fn) case TypeApply(fn, _) => unsuppressMacroExpansion(fn) case _ => // do nothing @@ -101,6 +102,8 @@ trait StdAttachments { // we have to account for the fact that during typechecking an expandee might become wrapped, // i.e. surrounded by an inferred implicit argument application or by an inferred type argument application. // in that case the expandee itself will no longer be suppressed and we need to look at the core + // upd. we also need to allow for blocks, because that's what names and defaults are often desugared to + case Block(_, expr) => isMacroExpansionSuppressed(expr) case Apply(fn, _) => isMacroExpansionSuppressed(fn) case TypeApply(fn, _) => isMacroExpansionSuppressed(fn) case _ => false diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 10fe530445..020d26c712 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1155,7 +1155,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case mt: MethodType if mode.typingExprNotFunNotLhs && mt.isImplicit => // (4.1) adaptToImplicitMethod(mt) - case mt: MethodType if mode.typingExprNotFunNotLhs && !hasUndetsInMonoMode && !treeInfo.isMacroApplicationOrBlock(tree) => + case mt: MethodType if mode.typingExprNotFunNotLhs && !hasUndetsInMonoMode => instantiateToMethodType(mt) case _ => vanillaAdapt(tree) @@ -3292,12 +3292,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper */ def tryNamesDefaults: Tree = { val lencmp = compareLengths(args, formals) - - def checkNotMacro() = { - if (treeInfo.isMacroApplication(fun)) - tryTupleApply orElse duplErrorTree(NamedAndDefaultArgumentsNotSupportedForMacros(tree, fun)) - } - if (mt.isErroneous) duplErrTree else if (mode.inPatternMode) { // #2064 @@ -3316,18 +3310,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper else if (allArgsArePositional(argPos) && !isNamedApplyBlock(fun)) { // if there's no re-ordering, and fun is not transformed, no need to transform // more than an optimization, e.g. important in "synchronized { x = update-x }" - checkNotMacro() doTypedApply(tree, fun, namelessArgs, mode, pt) } else { - checkNotMacro() - transformNamedApplication(Typer.this, mode, pt)( - treeCopy.Apply(tree, fun, namelessArgs), argPos) + unsuppressMacroExpansion(transformNamedApplication(Typer.this, mode, pt)( + treeCopy.Apply(tree, suppressMacroExpansion(fun), namelessArgs), argPos)) } } else { // defaults are needed. they are added to the argument list in named style as // calls to the default getters. Example: // foo[Int](a)() ==> foo[Int](a)(b = foo$qual.foo$default$2[Int](a)) - checkNotMacro() // SI-8111 transformNamedApplication eagerly shuffles around the application to preserve // evaluation order. During this process, it calls `changeOwner` on symbols that @@ -3350,7 +3341,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper symsOwnedByContextOwner foreach (_.owner = context.owner) } - val fun1 = transformNamedApplication(Typer.this, mode, pt)(fun, x => x) + val fun1 = transformNamedApplication(Typer.this, mode, pt)(suppressMacroExpansion(fun), x => x) if (fun1.isErroneous) duplErrTree else { assert(isNamedApplyBlock(fun1), fun1) @@ -3376,7 +3367,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // useful when a default doesn't match parameter type, e.g. def f[T](x:T="a"); f[Int]() val note = "Error occurred in an application involving default arguments." if (!(context.diagnostic contains note)) context.diagnostic = note :: context.diagnostic - doTypedApply(tree, if (blockIsEmpty) fun else fun1, allArgs, mode, pt) + unsuppressMacroExpansion(doTypedApply(tree, if (blockIsEmpty) fun else fun1, allArgs, mode, pt)) } else { rollbackNamesDefaultsOwnerChanges() tryTupleApply orElse duplErrorTree(NotEnoughArgsError(tree, fun, missing)) diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index 1eb0743bc8..7ff482d021 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -852,13 +852,8 @@ abstract class TreeInfo { case _ => false }) - def isMacroApplication(tree: Tree): Boolean = !tree.isDef && { - val sym = tree.symbol - sym != null && sym.isTermMacro && !sym.isErroneous - } - - def isMacroApplicationOrBlock(tree: Tree): Boolean = tree match { - case Block(_, expr) => isMacroApplicationOrBlock(expr) - case tree => isMacroApplication(tree) + def isMacroApplication(tree: Tree): Boolean = tree match { + case Block(_, expr) => isMacroApplication(expr) + case tree => !tree.isDef && tree.symbol != null && tree.symbol.isTermMacro && !tree.symbol.isErroneous } } diff --git a/test/files/neg/macro-argc-mismatch.check b/test/files/neg/macro-argc-mismatch.check new file mode 100644 index 0000000000..617daa890c --- /dev/null +++ b/test/files/neg/macro-argc-mismatch.check @@ -0,0 +1,49 @@ +Test_2.scala:4: error: missing arguments for macro method one in object Macros + one + ^ +Test_2.scala:5: error: not enough arguments for macro method one: (x: Int)Unit. +Unspecified value parameter x. + one() + ^ +Test_2.scala:6: error: too many arguments for macro method one: (x: Int)Unit + one(2, 3) + ^ +Test_2.scala:7: error: not enough arguments for macro method one: (x: Int)Unit. +Unspecified value parameter x. + one()() + ^ +Test_2.scala:8: error: Unit does not take parameters + one(1)() + ^ +Test_2.scala:10: error: missing arguments for macro method two in object Macros + two + ^ +Test_2.scala:11: error: not enough arguments for macro method two: (x: Int)(y: Int)Unit. +Unspecified value parameter x. + two() + ^ +Test_2.scala:12: error: too many arguments for macro method two: (x: Int)(y: Int)Unit + two(2, 3) + ^ +Test_2.scala:13: error: not enough arguments for macro method two: (x: Int)(y: Int)Unit. +Unspecified value parameter x. + two()() + ^ +Test_2.scala:14: error: missing arguments for macro method two in object Macros + two(1) + ^ +Test_2.scala:15: error: not enough arguments for macro method two: (y: Int)Unit. +Unspecified value parameter y. + two(1)() + ^ +Test_2.scala:16: error: too many arguments for macro method two: (y: Int)Unit + two(1)(2, 3) + ^ +Test_2.scala:17: error: not enough arguments for macro method two: (y: Int)Unit. +Unspecified value parameter y. + two(1)()() + ^ +Test_2.scala:18: error: Unit does not take parameters + two(1)(1)() + ^ +14 errors found diff --git a/test/files/neg/macro-argc-mismatch/Macros_1.scala b/test/files/neg/macro-argc-mismatch/Macros_1.scala new file mode 100644 index 0000000000..4dca644172 --- /dev/null +++ b/test/files/neg/macro-argc-mismatch/Macros_1.scala @@ -0,0 +1,16 @@ +import scala.language.experimental.macros +import scala.reflect.macros.blackbox.Context + +object Macros { + def one(x: Int): Unit = macro oneImpl + def oneImpl(c: Context)(x: c.Tree) = { + import c.universe._ + q"()" + } + + def two(x: Int)(y: Int): Unit = macro twoImpl + def twoImpl(c: Context)(x: c.Tree)(y: c.Tree) = { + import c.universe._ + q"()" + } +} diff --git a/test/files/neg/macro-argc-mismatch/Test_2.scala b/test/files/neg/macro-argc-mismatch/Test_2.scala new file mode 100644 index 0000000000..28f9c35654 --- /dev/null +++ b/test/files/neg/macro-argc-mismatch/Test_2.scala @@ -0,0 +1,19 @@ +import Macros._ + +object Test extends App { + one + one() + one(2, 3) + one()() + one(1)() + + two + two() + two(2, 3) + two()() + two(1) + two(1)() + two(1)(2, 3) + two(1)()() + two(1)(1)() +} \ No newline at end of file diff --git a/test/files/neg/macro-invalidusage-badargs.check b/test/files/neg/macro-invalidusage-badargs.check index 4c1115418b..3fd3c53691 100644 --- a/test/files/neg/macro-invalidusage-badargs.check +++ b/test/files/neg/macro-invalidusage-badargs.check @@ -3,13 +3,14 @@ Macros_Test_2.scala:5: error: type mismatch; required: Int foo("42") ^ -Macros_Test_2.scala:6: error: too few argument lists for macro invocation +Macros_Test_2.scala:6: error: missing arguments for macro method foo in object Macros foo ^ Macros_Test_2.scala:7: error: Int does not take parameters foo(4)(2) ^ -Macros_Test_2.scala:8: error: macro applications do not support named and/or default arguments +Macros_Test_2.scala:8: error: not enough arguments for macro method foo: (x: Int)Int. +Unspecified value parameter x. foo() ^ Macros_Test_2.scala:9: error: too many arguments for macro method foo: (x: Int)Int diff --git a/test/files/neg/macro-qmarkqmarkqmark.check b/test/files/neg/macro-qmarkqmarkqmark.check index bc3e25edaf..b4f8ea905f 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: too few argument lists for macro invocation +macro-qmarkqmarkqmark.scala:8: error: missing arguments for macro method foo2 in object Macros foo2 ^ macro-qmarkqmarkqmark.scala:9: error: macro implementation is missing diff --git a/test/files/neg/t7157.check b/test/files/neg/t7157.check index c6a7af9a23..3988460d4b 100644 --- a/test/files/neg/t7157.check +++ b/test/files/neg/t7157.check @@ -7,7 +7,8 @@ Test_2.scala:6: error: too many arguments for macro method m1_0_0: ()Unit 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 +Test_2.scala:9: error: not enough arguments for macro method m1_1_1: (x: Int)Unit. +Unspecified value parameter x. m1_1_1() ^ Test_2.scala:11: error: too many arguments for macro method m1_1_1: (x: Int)Unit @@ -16,22 +17,27 @@ Test_2.scala:11: error: too many arguments for macro method m1_1_1: (x: Int)Unit 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 +Test_2.scala:14: error: not enough arguments for macro method m1_2_2: (x: Int, y: Int)Unit. +Unspecified value parameters x, y. m1_2_2() ^ -Test_2.scala:15: error: macro applications do not support named and/or default arguments +Test_2.scala:15: error: not enough arguments for macro method m1_2_2: (x: Int, y: Int)Unit. +Unspecified value parameter y. 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 +Test_2.scala:24: error: not enough arguments for macro method m1_1_inf: (x: Int, y: Int*)Unit. +Unspecified value parameters x, y. m1_1_inf() ^ -Test_2.scala:29: error: macro applications do not support named and/or default arguments +Test_2.scala:29: error: not enough arguments for macro method m1_2_inf: (x: Int, y: Int, z: Int*)Unit. +Unspecified value parameters x, y, z. m1_2_inf() ^ -Test_2.scala:30: error: macro applications do not support named and/or default arguments +Test_2.scala:30: error: not enough arguments for macro method m1_2_inf: (x: Int, y: Int, z: Int*)Unit. +Unspecified value parameters y, z. m1_2_inf(1) ^ Test_2.scala:35: error: too many arguments for macro method m2_0_0: ()Unit @@ -43,7 +49,8 @@ Test_2.scala:36: error: too many arguments for macro method m2_0_0: ()Unit 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 +Test_2.scala:39: error: not enough arguments for macro method m2_1_1: (x: Int)Unit. +Unspecified value parameter x. m2_1_1()() ^ Test_2.scala:41: error: too many arguments for macro method m2_1_1: (x: Int)Unit @@ -52,22 +59,27 @@ Test_2.scala:41: error: too many arguments for macro method m2_1_1: (x: Int)Unit 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 +Test_2.scala:44: error: not enough arguments for macro method m2_2_2: (x: Int, y: Int)Unit. +Unspecified value parameters x, y. m2_2_2()() ^ -Test_2.scala:45: error: macro applications do not support named and/or default arguments +Test_2.scala:45: error: not enough arguments for macro method m2_2_2: (x: Int, y: Int)Unit. +Unspecified value parameter y. 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 +Test_2.scala:54: error: not enough arguments for macro method m2_1_inf: (x: Int, y: Int*)Unit. +Unspecified value parameters x, y. m2_1_inf()() ^ -Test_2.scala:59: error: macro applications do not support named and/or default arguments +Test_2.scala:59: error: not enough arguments for macro method m2_2_inf: (x: Int, y: Int, z: Int*)Unit. +Unspecified value parameters x, y, z. m2_2_inf()() ^ -Test_2.scala:60: error: macro applications do not support named and/or default arguments +Test_2.scala:60: error: not enough arguments for macro method m2_2_inf: (x: Int, y: Int, z: Int*)Unit. +Unspecified value parameters y, z. m2_2_inf()(1) ^ 24 errors found diff --git a/test/files/run/macro-expand-default-named.check b/test/files/run/macro-expand-default-named.check new file mode 100644 index 0000000000..2d75772572 --- /dev/null +++ b/test/files/run/macro-expand-default-named.check @@ -0,0 +1,56 @@ +Test.this.one(2, -40) = 42 +Test.this.one(y = -40, x = 2) = 42 +Test.this.one(2, -40) = 42 +Test.this.one(100) = 140 +Test.this.one(y = 100) = -98 +Test.this.one(100) = 140 +Test.this.one() = 42 +Test.this.qualone.one(2, -40) = 42 +Test.this.qualone.one(y = -40, x = 2) = 42 +Test.this.qualone.one(2, -40) = 42 +Test.this.qualone.one(x = 100) = 140 +Test.this.qualone.one(y = 100) = -98 +Test.this.qualone.one(x = 100) = 140 +Test.this.qualone.one() = 42 +Test.this.onezero(2, -40)(1, 2) = 41 +Test.this.onezero(y = -40, x = 2)(z = 3, w = 4) = 41 +Test.this.onezero(2, -40)(5, 6) = 41 +Test.this.onezero(100)(7, 8) = 139 +Test.this.onezero(y = 100)(z = 9, w = 10) = -99 +Test.this.onezero(100)(11, 12) = 139 +Test.this.onezero()(13, 14) = 41 +Test.this.qualonezero.onezero(2, -40)(15, 16) = 41 +Test.this.qualonezero.onezero(y = -40, x = 2)(z = 17, w = 18) = 41 +Test.this.qualonezero.onezero(2, -40)(19, 20) = 41 +Test.this.qualonezero.onezero(x = 100)(z = 21, w = 22) = 139 +Test.this.qualonezero.onezero(y = 100)(z = 23, w = 24) = -99 +Test.this.qualonezero.onezero(x = 100)(z = 25, w = 26) = 139 +Test.this.qualonezero.onezero()(z = 27, w = 28) = 41 +Test.this.zeroone(1, 2)(2, -40) = 41 +Test.this.zeroone(x = 3, y = 4)(w = -40, z = 2) = 41 +Test.this.zeroone(5, 6)(2, -40) = 41 +Test.this.zeroone(x = 7, y = 8)(z = 100) = 139 +Test.this.zeroone(x = 9, y = 10)(w = 100) = -99 +Test.this.zeroone(x = 11, y = 12)(z = 100) = 139 +Test.this.zeroone(x = 13, y = 14)() = 41 +Test.this.qualzeroone.zeroone(15, 16)(2, -40) = 41 +Test.this.qualzeroone.zeroone(x = 17, y = 18)(w = -40, z = 2) = 41 +Test.this.qualzeroone.zeroone(19, 20)(2, -40) = 41 +Test.this.qualzeroone.zeroone(x = 21, y = 22)(z = 100) = 139 +Test.this.qualzeroone.zeroone(x = 23, y = 24)(w = 100) = -99 +Test.this.qualzeroone.zeroone(x = 25, y = 26)(z = 100) = 139 +Test.this.qualzeroone.zeroone(x = 27, y = 28)() = 41 +Test.this.oneone(2, -40)(2, -40) = 84 +Test.this.oneone(y = -40, x = 2)(w = -40, z = 2) = 84 +Test.this.oneone(2, -40)(2, -40) = 84 +Test.this.oneone(x = 100)(z = 100) = 280 +Test.this.oneone(y = 100)(w = 100) = -196 +Test.this.oneone(x = 100)(z = 100) = 280 +Test.this.oneone()() = 84 +Test.this.qualoneone.oneone(2, -40)(2, -40) = 84 +Test.this.qualoneone.oneone(y = -40, x = 2)(w = -40, z = 2) = 84 +Test.this.qualoneone.oneone(2, -40)(2, -40) = 84 +Test.this.qualoneone.oneone(x = 100)(z = 100) = 280 +Test.this.qualoneone.oneone(y = 100)(w = 100) = -196 +Test.this.qualoneone.oneone(x = 100)(z = 100) = 280 +Test.this.qualoneone.oneone()() = 84 diff --git a/test/files/run/macro-expand-default-named/Impls_1.scala b/test/files/run/macro-expand-default-named/Impls_1.scala new file mode 100644 index 0000000000..73774cd56a --- /dev/null +++ b/test/files/run/macro-expand-default-named/Impls_1.scala @@ -0,0 +1,37 @@ +import scala.reflect.macros.blackbox.Context + +object Impls { + def one(c: Context)(x: c.Tree, y: c.Tree) = { + import c.universe._ + val x1 = x orElse q"2" + val y1 = y orElse q"-40" + q"println(${c.macroApplication.toString + " = "} + ($x1 - $y1))" + } + + def onezero(c: Context)(x: c.Tree, y: c.Tree)(z: c.Tree, w: c.Tree) = { + import c.universe._ + val x1 = x orElse q"2" + val y1 = y orElse q"-40" + val z1 = z + val w1 = w + q"println(${c.macroApplication.toString + " = "} + ($x1 - $y1 + $z1 - $w1))" + } + + def zeroone(c: Context)(x: c.Tree, y: c.Tree)(z: c.Tree, w: c.Tree) = { + import c.universe._ + val x1 = x + val y1 = y + val z1 = z orElse q"2" + val w1 = w orElse q"-40" + q"println(${c.macroApplication.toString + " = "} + ($x1 - $y1 + $z1 - $w1))" + } + + def oneone(c: Context)(x: c.Tree, y: c.Tree)(z: c.Tree, w: c.Tree) = { + import c.universe._ + val x1 = x orElse q"2" + val y1 = y orElse q"-40" + val z1 = z orElse q"2" + val w1 = w orElse q"-40" + q"println(${c.macroApplication.toString + " = "} + ($x1 - $y1 + $z1 - $w1))" + } +} \ No newline at end of file diff --git a/test/files/run/macro-expand-default-named/Macros_Test_2.scala b/test/files/run/macro-expand-default-named/Macros_Test_2.scala new file mode 100644 index 0000000000..e58eddd9a3 --- /dev/null +++ b/test/files/run/macro-expand-default-named/Macros_Test_2.scala @@ -0,0 +1,71 @@ +import scala.language.experimental.macros + +object Test extends App { + def one(x: Int = 2, y: Int = -40): Unit = macro Impls.one + one(2, -40) + one(y = -40, x = 2) + one(x = 2, y = -40) + one(x = 100) + one(y = 100) + one(100) + one() + var qualone = this + qualone.one(2, -40) + qualone.one(y = -40, x = 2) + qualone.one(x = 2, y = -40) + qualone.one(x = 100) + qualone.one(y = 100) + qualone.one(100) + qualone.one() + + def onezero(x: Int = 2, y: Int = -40)(z: Int, w: Int): Unit = macro Impls.onezero + onezero(2, -40)(1, 2) + onezero(y = -40, x = 2)(3, 4) + onezero(x = 2, y = -40)(5, 6) + onezero(x = 100)(7, 8) + onezero(y = 100)(9, 10) + onezero(100)(11, 12) + onezero()(13, 14) + var qualonezero = this + qualonezero.onezero(2, -40)(15, 16) + qualonezero.onezero(y = -40, x = 2)(17, 18) + qualonezero.onezero(x = 2, y = -40)(19, 20) + qualonezero.onezero(x = 100)(21, 22) + qualonezero.onezero(y = 100)(23, 24) + qualonezero.onezero(100)(25, 26) + qualonezero.onezero()(27, 28) + + def zeroone(x: Int, y: Int)(z: Int = 2, w: Int = -40): Unit = macro Impls.zeroone + zeroone(1, 2)(2, -40) + zeroone(3, 4)(w = -40, z = 2) + zeroone(5, 6)(z = 2, w = -40) + zeroone(7, 8)(z = 100) + zeroone(9, 10)(w = 100) + zeroone(11, 12)(100) + zeroone(13, 14)() + var qualzeroone = this + qualzeroone.zeroone(15, 16)(2, -40) + qualzeroone.zeroone(17, 18)(w = -40, z = 2) + qualzeroone.zeroone(19, 20)(z = 2, w = -40) + qualzeroone.zeroone(21, 22)(z = 100) + qualzeroone.zeroone(23, 24)(w = 100) + qualzeroone.zeroone(25, 26)(100) + qualzeroone.zeroone(27, 28)() + + def oneone(x: Int = 2, y: Int = -40)(z: Int = 2, w: Int = -40): Unit = macro Impls.oneone + oneone(2, -40)(2, -40) + oneone(y = -40, x = 2)(w = -40, z = 2) + oneone(x = 2, y = -40)(z = 2, w = -40) + oneone(x = 100)(z = 100) + oneone(y = 100)(w = 100) + oneone(100)(100) + oneone()() + var qualoneone = this + qualoneone.oneone(2, -40)(2, -40) + qualoneone.oneone(y = -40, x = 2)(w = -40, z = 2) + qualoneone.oneone(x = 2, y = -40)(z = 2, w = -40) + qualoneone.oneone(x = 100)(z = 100) + qualoneone.oneone(y = 100)(w = 100) + qualoneone.oneone(100)(100) + qualoneone.oneone()() +} \ No newline at end of file diff --git a/test/files/run/macro-expand-ownerchain-a.check b/test/files/run/macro-expand-ownerchain-a.check new file mode 100644 index 0000000000..51993f072d --- /dev/null +++ b/test/files/run/macro-expand-ownerchain-a.check @@ -0,0 +1,2 @@ +2 +2 diff --git a/test/files/run/macro-expand-ownerchain-a/Macros_1.scala b/test/files/run/macro-expand-ownerchain-a/Macros_1.scala new file mode 100644 index 0000000000..0d11c24ad1 --- /dev/null +++ b/test/files/run/macro-expand-ownerchain-a/Macros_1.scala @@ -0,0 +1,11 @@ +import scala.reflect.macros.whitebox._ +import scala.language.experimental.macros + +object Macros { + def impl(c: Context)(x: c.Tree, y: c.Tree) = { + import c.universe._ + q"println($x)" + } + + def foo(x: Int, y: Int): Unit = macro impl +} \ No newline at end of file diff --git a/test/files/run/macro-expand-ownerchain-a/Test_2.scala b/test/files/run/macro-expand-ownerchain-a/Test_2.scala new file mode 100644 index 0000000000..738afc75df --- /dev/null +++ b/test/files/run/macro-expand-ownerchain-a/Test_2.scala @@ -0,0 +1,4 @@ +object Test extends App { + Macros.foo(y = 1, x = ((x: Int) => x)(2)) + Macros.foo(y = 1, x = {val x = 2; x}) +} \ 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 6cbcb9e5af..f1061e00f7 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: -too few argument lists for macro invocation +missing arguments for macro method foo in object Macros diff --git a/test/files/run/macro-invalidusage-partialapplication.check b/test/files/run/macro-invalidusage-partialapplication.check index 6cbcb9e5af..f1061e00f7 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: -too few argument lists for macro invocation +missing arguments for macro method foo in object Macros diff --git a/test/files/run/reify-repl-fail-gracefully.check b/test/files/run/reify-repl-fail-gracefully.check index 29ccee3cc6..c78d95dbed 100644 --- a/test/files/run/reify-repl-fail-gracefully.check +++ b/test/files/run/reify-repl-fail-gracefully.check @@ -10,7 +10,7 @@ import scala.reflect.runtime.universe._ scala> scala> reify -:12: error: too few argument lists for macro invocation +:12: error: missing arguments for macro method reify in class Universe reify ^ diff --git a/test/pending/run/macro-expand-default.flags b/test/pending/run/macro-expand-default.flags deleted file mode 100644 index cd66464f2f..0000000000 --- a/test/pending/run/macro-expand-default.flags +++ /dev/null @@ -1 +0,0 @@ --language:experimental.macros \ No newline at end of file diff --git a/test/pending/run/macro-expand-default/Impls_1.scala b/test/pending/run/macro-expand-default/Impls_1.scala deleted file mode 100644 index fd5d8d7f18..0000000000 --- a/test/pending/run/macro-expand-default/Impls_1.scala +++ /dev/null @@ -1,10 +0,0 @@ -import scala.reflect.macros.blackbox.Context - -object Impls { - def foo(c: Context)(x: c.Expr[Int], y: c.Expr[Int]) = { - import c.universe._ - val sum = Apply(Select(x.tree, TermName("$minus")), List(y.tree)) - val body = Apply(Select(Ident(definitions.PredefModule), TermName("println")), List(sum)) - Expr[Unit](body) - } -} \ No newline at end of file diff --git a/test/pending/run/macro-expand-default/Macros_Test_2.scala b/test/pending/run/macro-expand-default/Macros_Test_2.scala deleted file mode 100644 index 92fe84d04a..0000000000 --- a/test/pending/run/macro-expand-default/Macros_Test_2.scala +++ /dev/null @@ -1,8 +0,0 @@ -object Test extends App { - def foo(x: Int = 2, y: Int = -40) = macro Impls.foo - foo(y = -40, x = 2) - foo(x = 2, y = -40) - foo(x = 100) - foo(y = 100) - foo() -} \ No newline at end of file diff --git a/test/pending/run/macro-expand-named.flags b/test/pending/run/macro-expand-named.flags deleted file mode 100644 index cd66464f2f..0000000000 --- a/test/pending/run/macro-expand-named.flags +++ /dev/null @@ -1 +0,0 @@ --language:experimental.macros \ No newline at end of file diff --git a/test/pending/run/macro-expand-named/Impls_1.scala b/test/pending/run/macro-expand-named/Impls_1.scala deleted file mode 100644 index fd5d8d7f18..0000000000 --- a/test/pending/run/macro-expand-named/Impls_1.scala +++ /dev/null @@ -1,10 +0,0 @@ -import scala.reflect.macros.blackbox.Context - -object Impls { - def foo(c: Context)(x: c.Expr[Int], y: c.Expr[Int]) = { - import c.universe._ - val sum = Apply(Select(x.tree, TermName("$minus")), List(y.tree)) - val body = Apply(Select(Ident(definitions.PredefModule), TermName("println")), List(sum)) - Expr[Unit](body) - } -} \ No newline at end of file diff --git a/test/pending/run/macro-expand-named/Macros_Test_2.scala b/test/pending/run/macro-expand-named/Macros_Test_2.scala deleted file mode 100644 index abebcf8448..0000000000 --- a/test/pending/run/macro-expand-named/Macros_Test_2.scala +++ /dev/null @@ -1,5 +0,0 @@ -object Test extends App { - def foo(x: Int, y: Int) = macro Impls.foo - foo(y = -40, x = 2) - foo(x = 2, y = -40) -} \ No newline at end of file -- cgit v1.2.3 From ff4cfd575a9b731adc60582de447fafb5303eb49 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 11 Feb 2014 13:53:32 -0800 Subject: SI-8266 Deprecate octal escapes in f-interpolator Also turns the f-interpolator into a migration assistant by suggesting alternatives for the standard escapes. --- .../scala/tools/reflect/FormatInterpolator.scala | 51 +++++++++++++++++++++- src/library/scala/StringContext.scala | 11 +++-- test/files/neg/t8266-invalid-interp.check | 10 +++++ test/files/neg/t8266-invalid-interp.scala | 9 ++++ test/files/run/t8266-octal-interp.check | 30 +++++++++++++ test/files/run/t8266-octal-interp.flags | 1 + test/files/run/t8266-octal-interp.scala | 16 +++++++ 7 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 test/files/neg/t8266-invalid-interp.check create mode 100644 test/files/neg/t8266-invalid-interp.scala create mode 100644 test/files/run/t8266-octal-interp.check create mode 100644 test/files/run/t8266-octal-interp.flags create mode 100644 test/files/run/t8266-octal-interp.scala (limited to 'test/files/neg') diff --git a/src/compiler/scala/tools/reflect/FormatInterpolator.scala b/src/compiler/scala/tools/reflect/FormatInterpolator.scala index d5e674ebae..0258002850 100644 --- a/src/compiler/scala/tools/reflect/FormatInterpolator.scala +++ b/src/compiler/scala/tools/reflect/FormatInterpolator.scala @@ -81,7 +81,56 @@ abstract class FormatInterpolator { case Literal(Constant(x: String)) => x case _ => throw new IllegalArgumentException("internal error: argument parts must be a list of string literals") } - val s = StringContext.treatEscapes(s0) + def escapeHatch: PartialFunction[Throwable, String] = { + // trailing backslash, octal escape, or other + case e: StringContext.InvalidEscapeException => + def errPoint = part.pos withPoint (part.pos.point + e.index) + def octalOf(c: Char) = Character.digit(c, 8) + def alt = { + def altOf(i: Int) = i match { + case '\b' => "\\b" + case '\t' => "\\t" + case '\n' => "\\n" + case '\f' => "\\f" + case '\r' => "\\r" + case '\"' => "\\u0022" // $" in future + case '\'' => "'" + case '\\' => """\\""" + case x => "\\u%04x" format x + } + val suggest = { + val r = "([0-7]{1,3}).*".r + (s0 drop e.index + 1) match { + case r(n) => altOf { (0 /: n) { case (a, o) => (8 * a) + (o - '0') } } + case _ => "" + } + } + val txt = + if ("" == suggest) "" + else s", use $suggest instead" + txt + } + def badOctal = { + def msg(what: String) = s"Octal escape literals are $what$alt." + if (settings.future) { + c.error(errPoint, msg("unsupported")) + s0 + } else { + c.enclosingUnit.deprecationWarning(errPoint, msg("deprecated")) + try StringContext.treatEscapes(s0) catch escapeHatch + } + } + if (e.index == s0.length - 1) { + c.error(errPoint, """Trailing '\' escapes nothing.""") + s0 + } else if (octalOf(s0(e.index + 1)) >= 0) { + badOctal + } else { + c.error(errPoint, e.getMessage) + s0 + } + } + val s = try StringContext.processEscapes(s0) catch escapeHatch val ms = fpat findAllMatchIn s def errorLeading(op: Conversion) = op.errorAt(Spec, s"conversions must follow a splice; ${Conversion.literalHelp}") diff --git a/src/library/scala/StringContext.scala b/src/library/scala/StringContext.scala index 2d79452c5d..cd928a2b61 100644 --- a/src/library/scala/StringContext.scala +++ b/src/library/scala/StringContext.scala @@ -172,8 +172,8 @@ object StringContext { * @param str The offending string * @param idx The index of the offending backslash character in `str`. */ - class InvalidEscapeException(str: String, idx: Int) - extends IllegalArgumentException("invalid escape character at index "+idx+" in \""+str+"\"") + class InvalidEscapeException(str: String, @deprecatedName('idx) val index: Int) + extends IllegalArgumentException("invalid escape character at index "+index+" in \""+str+"\"") /** Expands standard Scala escape sequences in a string. * Escape sequences are: @@ -184,7 +184,11 @@ object StringContext { * @param str A string that may contain escape sequences * @return The string with all escape sequences expanded. */ - def treatEscapes(str: String): String = { + def treatEscapes(str: String): String = treatEscapes0(str, strict = false) + + def processEscapes(str: String): String = treatEscapes0(str, strict = true) + + private def treatEscapes0(str: String, strict: Boolean): String = { lazy val bldr = new java.lang.StringBuilder val len = str.length var start = 0 @@ -201,6 +205,7 @@ object StringContext { idx += 1 if (idx >= len) throw new InvalidEscapeException(str, cur) if ('0' <= str(idx) && str(idx) <= '7') { + if (strict) throw new InvalidEscapeException(str, cur) val leadch = str(idx) var oct = leadch - '0' idx += 1 diff --git a/test/files/neg/t8266-invalid-interp.check b/test/files/neg/t8266-invalid-interp.check new file mode 100644 index 0000000000..70dd4081b0 --- /dev/null +++ b/test/files/neg/t8266-invalid-interp.check @@ -0,0 +1,10 @@ +t8266-invalid-interp.scala:4: error: Trailing '\' escapes nothing. + f"a\", + ^ +t8266-invalid-interp.scala:5: error: invalid escape character at index 1 in "a\xc" + f"a\xc", + ^ +t8266-invalid-interp.scala:7: error: invalid escape character at index 1 in "a\vc" + f"a\vc" + ^ +three errors found diff --git a/test/files/neg/t8266-invalid-interp.scala b/test/files/neg/t8266-invalid-interp.scala new file mode 100644 index 0000000000..4b26546880 --- /dev/null +++ b/test/files/neg/t8266-invalid-interp.scala @@ -0,0 +1,9 @@ + +trait X { + def f = Seq( + f"a\", + f"a\xc", + // following could suggest \u000b for vertical tab, similar for \a alert + f"a\vc" + ) +} diff --git a/test/files/run/t8266-octal-interp.check b/test/files/run/t8266-octal-interp.check new file mode 100644 index 0000000000..6e9454119b --- /dev/null +++ b/test/files/run/t8266-octal-interp.check @@ -0,0 +1,30 @@ +t8266-octal-interp.scala:4: warning: Octal escape literals are deprecated, use \b instead. + f"a\10c", + ^ +t8266-octal-interp.scala:5: warning: Octal escape literals are deprecated, use \t instead. + f"a\11c", + ^ +t8266-octal-interp.scala:6: warning: Octal escape literals are deprecated, use \n instead. + f"a\12c", + ^ +t8266-octal-interp.scala:7: warning: Octal escape literals are deprecated, use \r instead. + f"a\15c", + ^ +t8266-octal-interp.scala:8: warning: Octal escape literals are deprecated, use \u0022 instead. + f"a\42c", + ^ +t8266-octal-interp.scala:9: warning: Octal escape literals are deprecated, use \\ instead. + f"a\134c", + ^ +t8266-octal-interp.scala:10: warning: Octal escape literals are deprecated, use \u0069 instead. + f"a\15151515c" + ^ +ac +a c +a +c +a +c +a"c +a\c +ai51515c diff --git a/test/files/run/t8266-octal-interp.flags b/test/files/run/t8266-octal-interp.flags new file mode 100644 index 0000000000..dcc59ebe32 --- /dev/null +++ b/test/files/run/t8266-octal-interp.flags @@ -0,0 +1 @@ +-deprecation diff --git a/test/files/run/t8266-octal-interp.scala b/test/files/run/t8266-octal-interp.scala new file mode 100644 index 0000000000..f85ae0367d --- /dev/null +++ b/test/files/run/t8266-octal-interp.scala @@ -0,0 +1,16 @@ + +trait X { + def f = Seq( + f"a\10c", + f"a\11c", + f"a\12c", + f"a\15c", + f"a\42c", + f"a\134c", + f"a\15151515c" + ) +} + +object Test extends App with X { + f foreach println +} -- cgit v1.2.3 From c85435d91e26b97e22470af74138b4e8d4c3ae41 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Thu, 13 Feb 2014 02:34:14 +0100 Subject: SI-8270 unconfuses bundles and vanilla macros This fixes a mistake in macro impl ref typechecking that used to have an heuristic to figure out whether it looks at a bundle method ref or at a vanilla object method ref. Under some circumstances the heuristic could fail, and then the macro engine would reject perfectly good macro impls. Now every macro impl ref is typechecked twice - once as a bundle method ref and once as a vanilla object method ref. Results are then analyzed, checked against ambiguities (which are now correctly reported instead of incorrectly prioritizing towards bundles) and delivered to the macro engine. The only heuristic left in place is the one that's used to report errors. If both bundle and vanilla typechecks fail, then if a bundle candidate looks sufficiently similar to a bundle, a bundle typecheck error is reported providing some common bundle definition hints. --- .../macros/compiler/DefaultMacroCompiler.scala | 68 +++- .../scala/reflect/macros/compiler/Errors.scala | 173 ++++++----- .../scala/reflect/macros/compiler/Resolvers.scala | 78 ++--- .../scala/reflect/macros/compiler/Validators.scala | 344 +++++++++++---------- .../scala/tools/nsc/typechecker/Typers.scala | 6 +- test/files/neg/macro-bundle-ambiguous.check | 5 + test/files/neg/macro-bundle-ambiguous.scala | 14 + test/files/neg/macro-bundle-priority-bundle.check | 8 + test/files/neg/macro-bundle-priority-bundle.scala | 14 + .../neg/macro-bundle-priority-nonbundle.check | 8 + .../neg/macro-bundle-priority-nonbundle.scala | 14 + test/files/neg/macro-quasiquotes.check | 2 +- .../pos/macro-bundle-disambiguate-bundle.check | 0 .../pos/macro-bundle-disambiguate-bundle.scala | 14 + .../pos/macro-bundle-disambiguate-nonbundle.check | 0 .../pos/macro-bundle-disambiguate-nonbundle.scala | 14 + 16 files changed, 454 insertions(+), 308 deletions(-) create mode 100644 test/files/neg/macro-bundle-ambiguous.check create mode 100644 test/files/neg/macro-bundle-ambiguous.scala create mode 100644 test/files/neg/macro-bundle-priority-bundle.check create mode 100644 test/files/neg/macro-bundle-priority-bundle.scala create mode 100644 test/files/neg/macro-bundle-priority-nonbundle.check create mode 100644 test/files/neg/macro-bundle-priority-nonbundle.scala create mode 100644 test/files/pos/macro-bundle-disambiguate-bundle.check create mode 100644 test/files/pos/macro-bundle-disambiguate-bundle.scala create mode 100644 test/files/pos/macro-bundle-disambiguate-nonbundle.check create mode 100644 test/files/pos/macro-bundle-disambiguate-nonbundle.scala (limited to 'test/files/neg') 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) [].[[]] // vanilla macro impl ref + * 2) [].[[]] // 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 [].[[]] or\n" + "macro [].[[]]") - 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) [].[[]] // vanilla macro def - * 2) [].[[]] // 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 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 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 -- cgit v1.2.3 From 25a445ef875b2e34bbe4e8239a8f7110b123b5f6 Mon Sep 17 00:00:00 2001 From: clhodapp Date: Fri, 14 Feb 2014 15:59:52 -0600 Subject: Add an extremely well-commented test This commit includes a test for some simple existential subtyping checks. It is exceptionally well-commented and may be helpful to someone trying to figure out what the rules are (supposed to be) in the future. --- test/files/neg/literate_existentials.check | 4 + test/files/neg/literate_existentials.scala | 224 +++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+) create mode 100644 test/files/neg/literate_existentials.check create mode 100644 test/files/neg/literate_existentials.scala (limited to 'test/files/neg') diff --git a/test/files/neg/literate_existentials.check b/test/files/neg/literate_existentials.check new file mode 100644 index 0000000000..c98f976f79 --- /dev/null +++ b/test/files/neg/literate_existentials.check @@ -0,0 +1,4 @@ +literate_existentials.scala:189: error: Cannot prove that Int <:< M forSome { type M <: String }. + implicitly[Int <:< (M forSome { type M >: Nothing <: String })] // fails + ^ +one error found diff --git a/test/files/neg/literate_existentials.scala b/test/files/neg/literate_existentials.scala new file mode 100644 index 0000000000..8580347bf9 --- /dev/null +++ b/test/files/neg/literate_existentials.scala @@ -0,0 +1,224 @@ + +object LiterateExistentials { + +// Let's play with Scala's type system a bit. +// +// From adriaanm, we have the following substitution rule, which allows us to +// determine whether a type is a subtype of an existential in Scala: +// +// +// T <: subst(U) for all i: subst(Li) <: Vi /\ Vi <: subst(Hi) +// -------------------------------------------------------------- +// T <: U forSome {type X1 :> L1 <: H1; ...; type Xn :> Ln <: Hn} +// +// where subst(T) = T.subst(Xi, Vi) // Vi fresh type variables +// +// T is a subtype of some existential if all constraints of the existential hold +// after substituting Vi for the existentially quantified type variables Xi, +// and T is a subtype of the underlying type U with the same substitution applied. +// +// +// Since we are not a formal substitution system, we will actually be using +// this rule 'backward' in order to determine whether it allows us to +// truthfully make claims; In each example, we will start with the proposition +// that a type is a subtype of an existential. Then, we will fit the +// proposition into the form on the bottom rule by creating a set of bindings +// which allow one to be transformed into the other. Next, we will express the +// top of the substitution rule in terms of a series of constraints. We will +// simplify those constraints until simple inspection can determine whether +// they are consistent. From this, we can conclude whether the type system / +// environment admit the top of the substitution rule (and thus, the bottom). If +// they do, we can say that the proposition is true. + + +// In each case, we will also probe the compiler to see whether _it_ thinks that +// the proposition holds, using an uncommented implicitly[_ <:< _] line. + + + + +// Proposition: Nothing :< (A forSome { type A >: String <: Any }) +// +// +// Bindings: +// T := Nothing +// U := A +// X1 := A +// L1 := String +// H1 := Any +// +// We need: +// +// Nothing <: V1 // (U, which is "A", which V1 substituted for all instances of A) +// String <: V1 +// V1 <: Any +// +// Which simplify to: +// V1 >: String <: Any +// +// That's not inconsistent, so we can say that: +// T <: U forSome { type X1 >: L1 <: H1 } +// which means (under our mappings): +// Nothing <: A forSome { type A >: String <: Any } + +// Now to ask the compiler: + + implicitly[Nothing <:< (A forSome { type A >: String <: Any })] + + +// Let's try another: +// +// Proposition: Int :< (M forSome { type M >: String <: Any }) +// +// Bindings: +// T := Int +// U := M +// X1 := M +// L1 := String +// H1 := Any +// +// We need: +// +// Int <: V1 +// String <: V1 +// V1 <: Any +// +// Which simplify to: +// +// V1 >: lub(Int, String) <: Any +// +// V1 >: Any <: Any +// +// We have demonstrated consistency! We can say that: +// T :< (U forSome { type U >: L1 <: H1 }) +// Under our bindings, this is: +// Int :< (M forSome { type M >: String <: Any }) + + implicitly[Int <:< (M forSome { type M >: String <: Any })] + + + +// Now, let's do a more complicated one: +// +// Proposition: (Nothing, List[String]) <: ((A, B) forSome { type A >: String <: AnyRef; type B >: Null <: List[A] }) +// +// Bindings: +// T := (Nothing, List[String]) +// U := (A, B) +// X1 := A +// X2 := B +// L1 := String +// H1 := AnyRef +// L2 := Null +// H2 := List[A] +// +// We need: +// +// (Nothing, List[String]) <: (V1, V2) +// String <: V1 +// V1 <: AnyRef +// Null <: V2 +// V2 <: List[V1] +// +// Of course, we can split the first line to make: +// +// Nothing <: V1 +// List[String]) <: V2 +// String <: V1 +// V1 <: AnyRef +// Null <: V2 +// V2 <: List[V1] +// +// Which reorder to: +// +// Nothing <: V1 +// String <: V1 +// V1 <: AnyRef +// List[String]) <: V2 +// Null <: V2 +// V2 <: List[V1] +// +// Which simplify to: +// +// String <: V1 +// V1 <: AnyRef +// List[String]) <: V2 +// V2 <: List[V1] +// +// String <: V1 +// V1 <: AnyRef +// String <: V1 +// +// V1 >: String <: AnyRef +// +// Consistency demonstrated! We can say that: +// T <: U forSome {type X1 :> L1 <: H1; type X2 :> L2 <: H2} +// meaning: +// (Nothing, List[String]) <: ((A, B) forSome { type A >: String <: AnyRef; type B >: Null <: List[A] }) + + implicitly[ + (Nothing, List[String]) <:< ((A, B) forSome { type A >: String <: AnyRef; type B >: Null <: List[A] }) + ] + + + +// Now let's try one that isn't true: +// +// Proposition: Int :< (M forSome { type M >: Nothing <: String }) +// +// Bindings: +// T := Int +// U := M +// X1 := M +// L1 := Nothing +// H1 := String +// +// We need: +// +// Int <: V1 +// Nothing <: V1 +// V1 <: String +// +// V1 >: Int <: String +// +// Alas! These are inconsistent! There is no supertype of Int that is a +// subtype of String! Our substitution rule does not allow us to claim that our +// proposition is true. +// + + implicitly[Int <:< (M forSome { type M >: Nothing <: String })] // fails +// The preceeding line causes the compiler to generate an error message. + + + +// Let's look at one final example, courtesy of paulp. +// Proposition: String :< X forSome { type X >: Nothing <: String } +// +// Bindings: +// T := String +// U := X +// X1 := X +// L1 := Nothing +// H1 := String +// +// We need: +// +// String <: V1 +// Nothing <: V1 +// V1 <: String +// +// Which simplify to: +// +// String <: V1 +// V1 <: String +// +// V1 >: String <: String +// +// So, we can say: +// T <: U forSome { type X1 >: L1 <: H1 } +// which means: +// String :< X forSome { type X >: Nothing <: String } + + implicitly[String <:< (X forSome { type X >: Nothing <: String })] + +} -- cgit v1.2.3