diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/typechecker')
10 files changed, 1473 insertions, 417 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index ff0bdf7580..b400743469 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -277,11 +277,6 @@ trait ContextErrors { setError(tree) } - def MultiDimensionalArrayError(tree: Tree) = { - issueNormalTypeError(tree, "cannot create a generic multi-dimensional array of more than "+ definitions.MaxArrayDims+" dimensions") - setError(tree) - } - //typedSuper def MixinMissingParentClassNameError(tree: Tree, mix: Name, clazz: Symbol) = issueNormalTypeError(tree, mix+" does not name a parent class of "+clazz) @@ -344,6 +339,11 @@ trait ContextErrors { setError(tree) } + def MacroEtaError(tree: Tree) = { + issueNormalTypeError(tree, "macros cannot be eta-expanded") + setError(tree) + } + //typedReturn def ReturnOutsideOfDefError(tree: Tree) = { issueNormalTypeError(tree, "return outside method definition") @@ -453,6 +453,9 @@ trait ContextErrors { // doTypeApply //tryNamesDefaults + def NamedAndDefaultArgumentsNotSupportedForMacros(tree: Tree, fun: Tree) = + NormalTypeError(tree, "macros application do not support named and/or default arguments") + def WrongNumberOfArgsError(tree: Tree, fun: Tree) = NormalTypeError(tree, "wrong number of arguments for "+ treeSymTypeMsg(fun)) @@ -581,9 +584,9 @@ trait ContextErrors { def AbstractExistentiallyOverParamerizedTpeError(tree: Tree, tp: Type) = issueNormalTypeError(tree, "can't existentially abstract over parameterized type " + tp) - //manifestTreee - def MissingManifestError(tree: Tree, full: Boolean, tp: Type) = { - issueNormalTypeError(tree, "cannot find "+(if (full) "" else "class ")+"manifest for element type "+tp) + // classTagTree + def MissingClassTagError(tree: Tree, tp: Type) = { + issueNormalTypeError(tree, "cannot find class tag for element type "+tp) setError(tree) } @@ -622,7 +625,6 @@ trait ContextErrors { def DefDefinedTwiceError(sym0: Symbol, sym1: Symbol) = { val isBug = sym0.isAbstractType && sym1.isAbstractType && (sym0.name startsWith "_$") issueSymbolTypeError(sym0, sym1+" is defined twice in " + context0.unit - + ( if (sym0.isMacro && sym1.isMacro) "\n(note that macros cannot be overloaded)" else "" ) + ( if (isBug) "\n(this error is likely due to a bug in the scala compiler involving wildcards in package objects)" else "" ) ) } @@ -848,6 +850,19 @@ trait ContextErrors { def TypeSigError(tree: Tree, ex: TypeError) = { ex match { + case CyclicReference(_, _) if tree.symbol.isTermMacro => + // say, we have a macro def `foo` and its macro impl `impl` + // if impl: 1) omits return type, 2) has anything implicit in its body, 3) sees foo + // + // then implicit search will trigger an error + // (note that this is not a compilation error, it's an artifact of implicit search algorithm) + // normally, such "errors" are discarded by `isCyclicOrErroneous` in Implicits.scala + // but in our case this won't work, because isCyclicOrErroneous catches CyclicReference exceptions + // while our error will manifest itself as a "recursive method needs a return type" + // + // hence we (together with reportTypeError in TypeDiagnostics) make sure that this CyclicReference + // evades all the handlers on its way and successfully reaches `isCyclicOrErroneous` in Implicits + throw ex case CyclicReference(sym, info: TypeCompleter) => issueNormalTypeError(tree, typer.cyclicReferenceMessage(sym, info.tree) getOrElse ex.getMessage()) case _ => diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 9b1f395ad0..fe1c90fe67 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -105,8 +105,8 @@ trait Contexts { self: Analyzer => // not inherited to child contexts var depth: Int = 0 var imports: List[ImportInfo] = List() // currently visible imports - var openImplicits: List[(Type,Symbol)] = List() // types for which implicit arguments - // are currently searched + var openImplicits: List[(Type,Tree)] = List() // types for which implicit arguments + // are currently searched // for a named application block (Tree) the corresponding NamedApplyInfo var namedApplyBlockInfo: Option[(Tree, NamedApplyInfo)] = None var prefix: Type = NoPrefix @@ -119,6 +119,7 @@ trait Contexts { self: Analyzer => var diagnostic: List[String] = Nil // these messages are printed when issuing an error var implicitsEnabled = false + var macrosEnabled = true var checking = false var retyping = false @@ -181,6 +182,13 @@ trait Contexts { self: Analyzer => def logError(err: AbsTypeError) = buffer += err + def withImplicitsEnabled[T](op: => T): T = { + val saved = implicitsEnabled + implicitsEnabled = true + try op + finally implicitsEnabled = saved + } + def withImplicitsDisabled[T](op: => T): T = { val saved = implicitsEnabled implicitsEnabled = false @@ -188,6 +196,20 @@ trait Contexts { self: Analyzer => finally implicitsEnabled = saved } + def withMacrosEnabled[T](op: => T): T = { + val saved = macrosEnabled + macrosEnabled = true + try op + finally macrosEnabled = saved + } + + def withMacrosDisabled[T](op: => T): T = { + val saved = macrosEnabled + macrosEnabled = false + try op + finally macrosEnabled = saved + } + def make(unit: CompilationUnit, tree: Tree, owner: Symbol, scope: Scope, imports: List[ImportInfo]): Context = { val c = new Context @@ -223,6 +245,7 @@ trait Contexts { self: Analyzer => c.diagnostic = this.diagnostic c.typingIndentLevel = typingIndentLevel c.implicitsEnabled = this.implicitsEnabled + c.macrosEnabled = this.macrosEnabled c.checking = this.checking c.retyping = this.retyping c.openImplicits = this.openImplicits @@ -237,6 +260,7 @@ trait Contexts { self: Analyzer => val c = make(unit, EmptyTree, owner, scope, imports) c.setReportErrors() c.implicitsEnabled = true + c.macrosEnabled = true c } @@ -312,6 +336,7 @@ trait Contexts { self: Analyzer => def issue(err: AbsTypeError) { debugwarn("issue error: " + err.errMsg) + if (settings.Yissuedebug.value) (new Exception).printStackTrace() if (reportErrors) unitError(err.errPos, addDiagString(err.errMsg)) else if (bufferErrors) { buffer += err } else throw new TypeError(err.errPos, err.errMsg) @@ -319,6 +344,7 @@ trait Contexts { self: Analyzer => def issueAmbiguousError(pre: Type, sym1: Symbol, sym2: Symbol, err: AbsTypeError) { debugwarn("issue ambiguous error: " + err.errMsg) + if (settings.Yissuedebug.value) (new Exception).printStackTrace() if (ambiguousErrors) { if (!pre.isErroneous && !sym1.isErroneous && !sym2.isErroneous) unitError(err.errPos, err.errMsg) @@ -328,6 +354,7 @@ trait Contexts { self: Analyzer => def issueAmbiguousError(err: AbsTypeError) { debugwarn("issue ambiguous error: " + err.errMsg) + if (settings.Yissuedebug.value) (new Exception).printStackTrace() if (ambiguousErrors) unitError(err.errPos, addDiagString(err.errMsg)) else if (bufferErrors) { buffer += err } diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 75440a1136..8aa257983a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -32,7 +32,10 @@ trait Implicits { import global.typer.{ printTyping, deindentTyping, indentTyping, printInference } def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context): SearchResult = - inferImplicit(tree, pt, reportAmbiguous, isView, context, true) + inferImplicit(tree, pt, reportAmbiguous, isView, context, true, NoPosition) + + def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean): SearchResult = + inferImplicit(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent, NoPosition) /** Search for an implicit value. See the comment on `result` at the end of class `ImplicitSearch` * for more info how the search is conducted. @@ -48,9 +51,12 @@ trait Implicits { * @param saveAmbiguousDivergent False if any divergent/ambiguous errors should be ignored after * implicits search, * true if they should be reported (used in further typechecking). + * @param pos Position that is should be used for tracing and error reporting + * (useful when we infer synthetic stuff and pass EmptyTree in the `tree` argument) + * If it's set NoPosition, then position-based services will use `tree.pos` * @return A search result */ - def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean): SearchResult = { + def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean, pos: Position): SearchResult = { printInference("[infer %s] %s with pt=%s in %s".format( if (isView) "view" else "implicit", tree, pt, context.owner.enclClass) @@ -71,9 +77,11 @@ trait Implicits { if (printInfers && !tree.isEmpty && !context.undetparams.isEmpty) printTyping("typing implicit: %s %s".format(tree, context.undetparamsString)) val implicitSearchContext = context.makeImplicit(reportAmbiguous) - val result = new ImplicitSearch(tree, pt, isView, implicitSearchContext).bestImplicit - if (saveAmbiguousDivergent && implicitSearchContext.hasErrors) + val result = new ImplicitSearch(tree, pt, isView, implicitSearchContext, pos).bestImplicit + if (saveAmbiguousDivergent && implicitSearchContext.hasErrors) { context.updateBuffer(implicitSearchContext.errBuffer.filter(err => err.kind == ErrorKinds.Ambiguous || err.kind == ErrorKinds.Divergent)) + debugwarn("update buffer: " + implicitSearchContext.errBuffer) + } printInference("[infer implicit] inferred " + result) context.undetparams = context.undetparams filterNot result.subst.from.contains @@ -100,8 +108,6 @@ trait Implicits { improvesCache.clear() } - private val ManifestSymbols = Set(PartialManifestClass, FullManifestClass, OptManifestClass) - /* Map a polytype to one in which all type parameters and argument-dependent types are replaced by wildcards. * Consider `implicit def b(implicit x: A): x.T = error("")`. We need to approximate DebruijnIndex types * when checking whether `b` is a valid implicit, as we haven't even searched a value for the implicit arg `x`, @@ -251,8 +257,11 @@ trait Implicits { * @param pt The original expected type of the implicit. * @param isView We are looking for a view * @param context0 The context used for the implicit search + * @param pos0 Position that is preferable for use in tracing and error reporting + * (useful when we infer synthetic stuff and pass EmptyTree in the `tree` argument) + * If it's set to NoPosition, then position-based services will use `tree.pos` */ - class ImplicitSearch(tree: Tree, pt: Type, isView: Boolean, context0: Context) + class ImplicitSearch(tree: Tree, pt: Type, isView: Boolean, context0: Context, pos0: Position = NoPosition) extends Typer(context0) with ImplicitsContextErrors { printTyping( ptBlock("new ImplicitSearch", @@ -264,6 +273,13 @@ trait Implicits { ) ) // assert(tree.isEmpty || tree.pos.isDefined, tree) + def pos = if (pos0 != NoPosition) pos0 else tree.pos + + def failure(what: Any, reason: String, pos: Position = this.pos): SearchResult = { + if (settings.XlogImplicits.value) + reporter.echo(pos, what+" is not a valid implicit value for "+pt+" because:\n"+reason) + SearchFailure + } import infer._ /** Is implicit info `info1` better than implicit info `info2`? @@ -351,13 +367,13 @@ trait Implicits { * @pre `info.tpe` does not contain an error */ private def typedImplicit(info: ImplicitInfo, ptChecked: Boolean): SearchResult = { - (context.openImplicits find { case (tp, sym) => sym == tree.symbol && dominates(pt, tp)}) match { + (context.openImplicits find { case (tp, tree1) => tree1.symbol == tree.symbol && dominates(pt, tp)}) match { case Some(pending) => // println("Pending implicit "+pending+" dominates "+pt+"/"+undetParams) //@MDEBUG throw DivergentImplicit case None => try { - context.openImplicits = (pt, tree.symbol) :: context.openImplicits + context.openImplicits = (pt, tree) :: context.openImplicits // println(" "*context.openImplicits.length+"typed implicit "+info+" for "+pt) //@MDEBUG typedImplicit0(info, ptChecked) } catch { @@ -515,7 +531,7 @@ trait Implicits { private def typedImplicit1(info: ImplicitInfo): SearchResult = { incCounter(matchingImplicits) - val itree = atPos(tree.pos.focus) { + val itree = atPos(pos.focus) { if (info.pre == NoPrefix) Ident(info.name) else Select(gen.mkAttributedQualifier(info.pre), info.name) } @@ -523,11 +539,7 @@ trait Implicits { typeDebug.ptTree(itree), wildPt, info.name, info.tpe) ) - def fail(reason: String): SearchResult = { - if (settings.XlogImplicits.value) - inform(itree+" is not a valid implicit value for "+pt+" because:\n"+reason) - SearchFailure - } + def fail(reason: String): SearchResult = failure(itree, reason) try { val itree1 = if (isView) { @@ -707,6 +719,7 @@ trait Implicits { info.isCyclicOrErroneous || isView && isPredefMemberNamed(info.sym, nme.conforms) || isShadowed(info.name) + || (!context.macrosEnabled && info.sym.isTermMacro) ) /** True if a given ImplicitInfo (already known isValid) is eligible. @@ -825,7 +838,7 @@ trait Implicits { throw DivergentImplicit if (invalidImplicits.nonEmpty) - setAddendum(tree.pos, () => + setAddendum(pos, () => "\n Note: implicit "+invalidImplicits.head+" is not applicable here"+ " because it comes after the application point and it lacks an explicit result type") } @@ -1085,111 +1098,58 @@ trait Implicits { implicitInfoss1 } - /** Creates a tree that calls the relevant factory method in object - * reflect.Manifest for type 'tp'. An EmptyTree is returned if - * no manifest is found. todo: make this instantiate take type params as well? - */ - private def manifestOfType(tp: Type, full: Boolean): SearchResult = { - - /** Creates a tree that calls the factory method called constructor in object reflect.Manifest */ - def manifestFactoryCall(constructor: String, tparg: Type, args: Tree*): Tree = - if (args contains EmptyTree) EmptyTree - else typedPos(tree.pos.focus) { - val mani = gen.mkManifestFactoryCall(full, constructor, tparg, args.toList) - if (settings.debug.value) println("generated manifest: "+mani) // DEBUG - mani - } + // these should be lazy, otherwise we wouldn't be able to compile scala-library with starr + private val TagSymbols = Set(ClassTagClass, TypeTagClass, GroundTypeTagClass) + private val TagMaterializers = Map( + ClassTagClass -> MacroInternal_materializeClassTag, + TypeTagClass -> MacroInternal_materializeTypeTag, + GroundTypeTagClass -> MacroInternal_materializeGroundTypeTag + ) - /** Creates a tree representing one of the singleton manifests.*/ - def findSingletonManifest(name: String) = typedPos(tree.pos.focus) { - Select(gen.mkAttributedRef(FullManifestModule), name) - } + def tagOfType(pre: Type, tp: Type, tagClass: Symbol): SearchResult = { + def success(arg: Tree) = + try { + val tree1 = typed(atPos(pos.focus)(arg)) + def isErroneous = tree exists (_.isErroneous) + if (context.hasErrors) failure(tp, "failed to typecheck the materialized typetag: %n%s".format(context.errBuffer.head.errMsg), context.errBuffer.head.errPos) + else new SearchResult(tree1, EmptyTreeTypeSubstituter) + } catch { + case ex: TypeError => + failure(arg, "failed to typecheck the materialized typetag: %n%s".format(ex.msg), ex.pos) + } - /** Re-wraps a type in a manifest before calling inferImplicit on the result */ - def findManifest(tp: Type, manifestClass: Symbol = if (full) FullManifestClass else PartialManifestClass) = - inferImplicit(tree, appliedType(manifestClass, tp), true, false, context).tree - - def findSubManifest(tp: Type) = findManifest(tp, if (full) FullManifestClass else OptManifestClass) - def mot(tp0: Type, from: List[Symbol], to: List[Type]): SearchResult = { - implicit def wrapResult(tree: Tree): SearchResult = - if (tree == EmptyTree) SearchFailure else new SearchResult(tree, if (from.isEmpty) EmptyTreeTypeSubstituter else new TreeTypeSubstituter(from, to)) - - val tp1 = tp0.normalize - tp1 match { - case ThisType(_) | SingleType(_, _) => - // can't generate a reference to a value that's abstracted over by an existential - if (containsExistential(tp1)) EmptyTree - else manifestFactoryCall("singleType", tp, gen.mkAttributedQualifier(tp1)) - case ConstantType(value) => - manifestOfType(tp1.deconst, full) - case TypeRef(pre, sym, args) => - if (isPrimitiveValueClass(sym) || isPhantomClass(sym)) { - findSingletonManifest(sym.name.toString) - } else if (sym == ObjectClass || sym == AnyRefClass) { - findSingletonManifest("Object") - } else if (sym == RepeatedParamClass || sym == ByNameParamClass) { - EmptyTree - } else if (sym == ArrayClass && args.length == 1) { - manifestFactoryCall("arrayType", args.head, findManifest(args.head)) - } else if (sym.isClass) { - val classarg0 = gen.mkClassOf(tp1) - val classarg = tp match { - case _: ExistentialType => gen.mkCast(classarg0, ClassType(tp)) - case _ => classarg0 - } - val suffix = classarg :: (args map findSubManifest) - manifestFactoryCall( - "classType", tp, - (if ((pre eq NoPrefix) || pre.typeSymbol.isStaticOwner) suffix - else findSubManifest(pre) :: suffix): _*) - } else if (sym.isExistentiallyBound && full) { - manifestFactoryCall("wildcardType", tp, - findManifest(tp.bounds.lo), findManifest(tp.bounds.hi)) - } - // looking for a manifest of a type parameter that hasn't been inferred by now, - // can't do much, but let's not fail - else if (undetParams contains sym) { - // #3859: need to include the mapping from sym -> NothingClass.tpe in the SearchResult - mot(NothingClass.tpe, sym :: from, NothingClass.tpe :: to) - } else { - // a manifest should have been found by normal searchImplicit - EmptyTree - } - case RefinedType(parents, decls) => // !!! not yet: if !full || decls.isEmpty => - // refinement is not generated yet - if (hasLength(parents, 1)) findManifest(parents.head) - else if (full) manifestFactoryCall("intersectionType", tp, parents map findSubManifest: _*) - else mot(erasure.intersectionDominator(parents), from, to) - case ExistentialType(tparams, result) => - mot(tp1.skolemizeExistential, from, to) - case _ => - EmptyTree -/* !!! the following is almost right, but we have to splice nested manifest - * !!! types into this type. This requires a substantial extension of - * !!! reifiers. - val reifier = new Reifier() - val rtree = reifier.reifyTopLevel(tp1) - manifestFactoryCall("apply", tp, rtree) -*/ - } + val prefix = (tagClass, pre) match { + // ClassTags only exist for scala.reflect.mirror, so their materializer doesn't care about prefixes + case (ClassTagClass, _) => + gen.mkAttributedRef(Reflect_mirror) setType singleType(Reflect_mirror.owner.thisPrefix, Reflect_mirror) + // [Eugene to Martin] this is the crux of the interaction between implicits and reifiers + // here we need to turn a (supposedly path-dependent) type into a tree that will be used as a prefix + // I'm not sure if I've done this right - please, review + case (_, SingleType(prePre, preSym)) => + gen.mkAttributedRef(prePre, preSym) setType pre + // necessary only to compile typetags used inside the Universe cake + case (_, ThisType(thisSym)) => + gen.mkAttributedThis(thisSym) + case _ => + // if ``pre'' is not a PDT, e.g. if someone wrote + // implicitly[scala.reflect.makro.Context#TypeTag[Int]] + // then we need to fail, because we don't know the prefix to use during type reification + return failure(tp, "tag error: unsupported prefix type %s (%s)".format(pre, pre.kind)) } - mot(tp, Nil, Nil) + // todo. migrate hardcoded materialization in Implicits to corresponding implicit macros + var materializer = atPos(pos.focus)(Apply(TypeApply(Ident(TagMaterializers(tagClass)), List(TypeTree(tp))), List(prefix))) + if (settings.XlogImplicits.value) println("materializing requested %s.%s[%s] using %s".format(pre, tagClass.name, tp, materializer)) + success(materializer) } - def wrapResult(tree: Tree): SearchResult = - if (tree == EmptyTree) SearchFailure else new SearchResult(tree, EmptyTreeTypeSubstituter) - /** The manifest corresponding to type `pt`, provided `pt` is an instance of Manifest. */ - private def implicitManifestOrOfExpectedType(pt: Type): SearchResult = pt.dealias match { - case TypeRef(_, sym, args) if ManifestSymbols(sym) => - manifestOfType(args.head, sym == FullManifestClass) match { - case SearchFailure if sym == OptManifestClass => wrapResult(gen.mkAttributedRef(NoManifest)) - case result => result - } + private def implicitTagOrOfExpectedType(pt: Type): SearchResult = pt.dealias match { + case TypeRef(pre, sym, args) if TagSymbols(sym) => + tagOfType(pre, args.head, sym) case tp@TypeRef(_, sym, _) if sym.isAbstractType => - implicitManifestOrOfExpectedType(tp.bounds.lo) // #3977: use tp (==pt.dealias), not pt (if pt is a type alias, pt.bounds.lo == pt) + implicitTagOrOfExpectedType(tp.bounds.lo) // #3977: use tp (==pt.dealias), not pt (if pt is a type alias, pt.bounds.lo == pt) case _ => searchImplicit(implicitsOfExpectedType, false) // shouldn't we pass `pt` to `implicitsOfExpectedType`, or is the recursive case @@ -1199,7 +1159,9 @@ trait Implicits { /** The result of the implicit search: * First search implicits visible in current context. * If that fails, search implicits in expected type `pt`. - * If that fails, and `pt` is an instance of Manifest, try to construct a manifest. + * // [Eugene] the following two lines should be deleted after we migrate delegate manifest materialization to implicit macros + * If that fails, and `pt` is an instance of a ClassTag, try to construct a class tag. + * If that fails, and `pt` is an instance of a TypeTag, try to construct a type tag. * If all fails return SearchFailure */ def bestImplicit: SearchResult = { @@ -1219,7 +1181,7 @@ trait Implicits { val failstart = startTimer(oftypeFailNanos) val succstart = startTimer(oftypeSucceedNanos) - result = implicitManifestOrOfExpectedType(pt) + result = implicitTagOrOfExpectedType(pt) if (result == SearchFailure) { context.updateBuffer(previousErrs) diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index ebf8e3fc9a..98b8d7673e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -67,7 +67,7 @@ trait Infer { */ def freshVar(tparam: Symbol): TypeVar = TypeVar(tparam) - private class NoInstance(msg: String) extends Throwable(msg) with ControlThrowable { } + class NoInstance(msg: String) extends Throwable(msg) with ControlThrowable { } private class DeferredNoInstance(getmsg: () => String) extends NoInstance("") { override def getMessage(): String = getmsg() } @@ -267,6 +267,16 @@ trait Infer { setError(tree) } else { + if (context.owner.isTermMacro && (sym1 hasFlag LOCKED)) { + // we must not let CyclicReference to be thrown from sym1.info + // because that would mark sym1 erroneous, which it is not + // but if it's a true CyclicReference then macro def will report it + // see comments to TypeSigError for an explanation of this special case + // [Eugene] is there a better way? + val dummy = new TypeCompleter { val tree = EmptyTree; override def complete(sym: Symbol) {} } + throw CyclicReference(sym1, dummy) + } + if (sym1.isTerm) sym1.cookJavaRawInfo() // xform java rawtypes into existentials @@ -310,6 +320,8 @@ trait Infer { /** Like weakly compatible but don't apply any implicit conversions yet. * Used when comparing the result type of a method with its prototype. + * [Martin] I think Infer is also created by Erasure, with the default + * implementation of isCoercible */ def isConservativelyCompatible(tp: Type, pt: Type): Boolean = context.withImplicitsDisabled(isWeaklyCompatible(tp, pt)) @@ -426,6 +438,9 @@ trait Infer { tvars map (tvar => WildcardType) } + /** [Martin] Can someone comment this please? I have no idea what it's for + * and the code is not exactly readable. + */ object AdjustedTypeArgs { val Result = collection.mutable.LinkedHashMap type Result = collection.mutable.LinkedHashMap[Symbol, Option[Type]] @@ -992,6 +1007,7 @@ trait Infer { PolymorphicExpressionInstantiationError(tree, undetparams, pt) } else { new TreeTypeSubstituter(undetparams, targs).traverse(tree) + notifyUndetparamsInferred(undetparams, targs) } } @@ -1028,6 +1044,7 @@ trait Infer { if (checkBounds(fn, NoPrefix, NoSymbol, undetparams, allargs, "inferred ")) { val treeSubst = new TreeTypeSubstituter(okparams, okargs) treeSubst traverseTrees fn :: args + notifyUndetparamsInferred(okparams, okargs) leftUndet match { case Nil => Nil @@ -1116,6 +1133,7 @@ trait Infer { (inferFor(pt) orElse inferForApproxPt) map { targs => new TreeTypeSubstituter(undetparams, targs).traverse(tree) + notifyUndetparamsInferred(undetparams, targs) } getOrElse { debugwarn("failed inferConstructorInstance for "+ tree +" : "+ tree.tpe +" under "+ undetparams +" pt = "+ pt +(if(isFullyDefined(pt)) " (fully defined)" else " (not fully defined)")) // if (settings.explaintypes.value) explainTypes(resTp.instantiateTypeParams(undetparams, tvars), pt) @@ -1568,9 +1586,9 @@ trait Infer { else infer } - /** Assign <code>tree</code> the type of unique polymorphic alternative + /** Assign <code>tree</code> the type of all polymorphic alternatives * with <code>nparams</code> as the number of type parameters, if it exists. - * If several or none such polymorphic alternatives exist, error. + * If no such polymorphic alternative exist, error. * * @param tree ... * @param nparams ... diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index e43b1fab0b..3b270a92ad 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -3,135 +3,682 @@ package typechecker import symtab.Flags._ import scala.tools.nsc.util._ +import scala.tools.nsc.util.ClassPath._ import scala.reflect.ReflectionUtils +import scala.collection.mutable.ListBuffer +import scala.compat.Platform.EOL +import scala.reflect.makro.runtime.{Context => MacroContext} +import scala.reflect.runtime.Mirror +/** + * Code to deal with macros, namely with: + * * Compilation of macro definitions + * * Expansion of macro applications + * + * Say we have in a class C: + * + * def foo[T](xs: List[T]): T = macro fooBar + * + * Then fooBar needs to point to a static method of the following form: + * + * def fooBar[T: c.TypeTag] + * (c: scala.reflect.makro.Context) + * (xs: c.Expr[List[T]]) + * : c.mirror.Tree = { + * ... + * } + * + * Then, if foo is called in qual.foo[Int](elems), where qual: D, + * the macro application is expanded to a reflective invocation of fooBar with parameters + * + * (simpleMacroContext{ type PrefixType = D; val prefix = qual }) + * (Expr(elems)) + * (TypeTag(Int)) + */ trait Macros { self: Analyzer => import global._ import definitions._ - def macroMeth(mac: Symbol): Symbol = { - var owner = mac.owner - if (!owner.isModuleClass) owner = owner.companionModule.moduleClass - owner.info.decl(nme.macroMethodName(mac.name)) - } + val macroDebug = settings.Ymacrodebug.value + val macroCopypaste = settings.Ymacrocopypaste.value + val macroTrace = scala.tools.nsc.util.trace when macroDebug - def macroArgs(tree: Tree): (List[List[Tree]]) = tree match { - case Apply(fn, args) => - macroArgs(fn) :+ args - case TypeApply(fn, args) => - macroArgs(fn) :+ args - case Select(qual, name) => - List(List(qual)) - case _ => - List(List()) - } + val globalMacroCache = collection.mutable.Map[Any, Any]() + val perRunMacroCache = perRunCaches.newMap[Symbol, collection.mutable.Map[Any, Any]] - /** - * The definition of the method implementing a macro. Example: - * Say we have in a class C + /** A list of compatible macro implementation signatures. * - * def macro foo[T](xs: List[T]): T = expr + * In the example above: + * (c: scala.reflect.makro.Context)(xs: c.Expr[List[T]]): c.Expr[T] * - * Then the following macro method is generated for `foo`: - * - * def defmacro$foo - * (_context: scala.reflect.macro.Context) - * (_this: _context.Tree) - * (T: _context.TypeTree) - * (xs: _context.Tree): _context.Tree = { - * import _context._ // this means that all methods of Context can be used unqualified in macro's body - * expr - * } + * @param macroDef The macro definition symbol + * @param tparams The type parameters of the macro definition + * @param vparamss The value parameters of the macro definition + * @param retTpe The return type of the macro definition + */ + private def macroImplSigs(macroDef: Symbol, tparams: List[TypeDef], vparamss: List[List[ValDef]], retTpe: Type): (List[List[List[Symbol]]], Type) = { + // had to move method's body to an object because of the recursive dependencies between sigma and param + object SigGenerator { + val hasThis = macroDef.owner.isClass + val ownerTpe = macroDef.owner match { + case owner if owner.isModuleClass => new UniqueThisType(macroDef.owner) + case owner if owner.isClass => macroDef.owner.tpe + case _ => NoType + } + val hasTparams = !tparams.isEmpty + + def sigma(tpe: Type): Type = { + class SigmaTypeMap extends TypeMap { + def apply(tp: Type): Type = tp match { + case TypeRef(pre, sym, args) => + val pre1 = pre match { + case ThisType(sym) if sym == macroDef.owner => + SingleType(SingleType(SingleType(NoPrefix, paramsCtx(0)), MacroContextPrefix), ExprValue) + case SingleType(NoPrefix, sym) => + vparamss.flatten.find(_.symbol == sym) match { + case Some(macroDefParam) => + SingleType(SingleType(NoPrefix, param(macroDefParam)), ExprValue) + case _ => + pre + } + case _ => + pre + } + val args1 = args map mapOver + TypeRef(pre1, sym, args1) + case _ => + mapOver(tp) + } + } + + new SigmaTypeMap() apply tpe + } + + def makeParam(name: Name, pos: Position, tpe: Type, flags: Long = 0L) = + macroDef.newValueParameter(name, pos, flags) setInfo tpe + val ctxParam = makeParam(nme.macroContext, macroDef.pos, MacroContextClass.tpe, SYNTHETIC) + def implType(isType: Boolean, origTpe: Type): Type = + if (isRepeatedParamType(origTpe)) + appliedType( + RepeatedParamClass.typeConstructor, + List(implType(isType, sigma(origTpe.typeArgs.head)))) + else { + val tsym = getMember(MacroContextClass, if (isType) tpnme.TypeTag else tpnme.Expr) + typeRef(singleType(NoPrefix, ctxParam), tsym, List(sigma(origTpe))) + } + val paramCache = collection.mutable.Map[Symbol, Symbol]() + def param(tree: Tree): Symbol = + paramCache.getOrElseUpdate(tree.symbol, { + // [Eugene] deskolemization became necessary once I implemented inference of macro def return type + // please, verify this solution, but for now I'll leave it here - cargo cult for the win + val sym = tree.symbol.deSkolemize + val sigParam = makeParam(sym.name, sym.pos, implType(sym.isType, sym.tpe)) + if (sym.isSynthetic) sigParam.flags |= SYNTHETIC + sigParam + }) + + val paramsCtx = List(ctxParam) + val paramsThis = List(makeParam(nme.macroThis, macroDef.pos, implType(false, ownerTpe), SYNTHETIC)) + val paramsTparams = tparams map param + val paramssParams = vparamss map (_ map param) + + var paramsss = List[List[List[Symbol]]]() + // tparams are no longer part of a signature, they get into macro implementations via context bounds +// if (hasTparams && hasThis) paramsss :+= paramsCtx :: paramsThis :: paramsTparams :: paramssParams +// if (hasTparams) paramsss :+= paramsCtx :: paramsTparams :: paramssParams + // _this params are no longer part of a signature, its gets into macro implementations via Context.prefix +// if (hasThis) paramsss :+= paramsCtx :: paramsThis :: paramssParams + paramsss :+= paramsCtx :: paramssParams + + val tsym = getMember(MacroContextClass, tpnme.Expr) + val implRetTpe = typeRef(singleType(NoPrefix, ctxParam), tsym, List(sigma(retTpe))) + } + + import SigGenerator._ + macroTrace("generating macroImplSigs for: ")(macroDef) + macroTrace("tparams are: ")(tparams) + macroTrace("vparamss are: ")(vparamss) + macroTrace("retTpe is: ")(retTpe) + macroTrace("macroImplSigs are: ")(paramsss, implRetTpe) + } + + private def transformTypeTagEvidenceParams(paramss: List[List[Symbol]], transform: (Symbol, Symbol) => Option[Symbol]): List[List[Symbol]] = { + if (paramss.length == 0) + return paramss + + val wannabe = if (paramss.head.length == 1) paramss.head.head else NoSymbol + val contextParam = if (wannabe != NoSymbol && wannabe.tpe <:< definitions.MacroContextClass.tpe) wannabe else NoSymbol + + val lastParamList0 = paramss.lastOption getOrElse Nil + val lastParamList = lastParamList0 flatMap (param => param.tpe match { + case TypeRef(SingleType(NoPrefix, contextParam), sym, List(tparam)) => + var wannabe = sym + while (wannabe.isAliasType) wannabe = wannabe.info.typeSymbol + if (wannabe != definitions.TypeTagClass) + List(param) + else + transform(param, tparam.typeSymbol) map (_ :: Nil) getOrElse Nil + case _ => + List(param) + }) + + var result = paramss.dropRight(1) :+ lastParamList + if (lastParamList0.isEmpty ^ lastParamList.isEmpty) { + result = result dropRight 1 + } + + result + } + + /** As specified above, body of a macro definition must reference its implementation. + * This function verifies that the body indeed refers to a method, and that + * the referenced macro implementation is compatible with the given macro definition. * - * If macro has no type arguments, the third parameter list is omitted (it's not empty, but omitted altogether). + * This means that macro implementation (fooBar in example above) must: + * 1) Refer to a statically accessible, non-overloaded method. + * 2) Have the right parameter lists as outlined in the SIP / in the doc comment of this class. * - * To find out the desugared representation of your particular macro, compile it with -Ymacro-debug. + * @return typechecked rhs of the given macro definition */ - def macroMethDef(mdef: DefDef): Tree = { - def paramDef(name: Name, tpt: Tree) = ValDef(Modifiers(PARAM), name, tpt, EmptyTree) - val contextType = TypeTree(ReflectMacroContext.tpe) - val globParamSec = List(paramDef(nme.macroContext, contextType)) - def globSelect(name: Name) = Select(Ident(nme.macroContext), name) - def globTree = globSelect(tpnme.Tree) - def globTypeTree = globSelect(tpnme.TypeTree) - val thisParamSec = List(paramDef(newTermName(nme.macroThis), globTree)) - def tparamInMacro(tdef: TypeDef) = paramDef(tdef.name.toTermName, globTypeTree) - def vparamInMacro(vdef: ValDef): ValDef = paramDef(vdef.name, vdef.tpt match { - case tpt @ AppliedTypeTree(hk, _) if treeInfo.isRepeatedParamType(tpt) => AppliedTypeTree(hk, List(globTree)) - case _ => globTree - }) - def wrapImplicit(tree: Tree) = atPos(tree.pos) { - // implicit hasn't proven useful so far, so I'm disabling it - //val implicitDecl = ValDef(Modifiers(IMPLICIT), nme.macroContextImplicit, SingletonTypeTree(Ident(nme.macroContext)), Ident(nme.macroContext)) - val importGlob = Import(Ident(nme.macroContext), List(ImportSelector(nme.WILDCARD, -1, null, -1))) - Block(List(importGlob), tree) + def typedMacroBody(typer: Typer, ddef: DefDef): Tree = { + import typer.context + if (macroDebug) println("typechecking macro def %s at %s".format(ddef.symbol, ddef.pos)) + + implicit def augmentString(s: String) = new AugmentedString(s) + class AugmentedString(s: String) { + def abbreviateCoreAliases: String = { // hack! + var result = s + result = result.replace("c.mirror.TypeTag", "c.TypeTag") + result = result.replace("c.mirror.Expr", "c.Expr") + result + } } - var formals = (mdef.vparamss map (_ map vparamInMacro)) - if (mdef.tparams.nonEmpty) formals = (mdef.tparams map tparamInMacro) :: formals - - atPos(mdef.pos) { - new DefDef( // can't call DefDef here; need to find out why - mods = mdef.mods &~ MACRO &~ OVERRIDE, - name = nme.macroMethodName(mdef.name), - tparams = List(), - vparamss = globParamSec :: thisParamSec :: formals, - tpt = globTree, - wrapImplicit(mdef.rhs)) + + var hasErrors = false + def reportError(pos: Position, msg: String) = { + hasErrors = true + context.error(pos, msg) + } + + val macroDef = ddef.symbol + val defpos = macroDef.pos + val implpos = ddef.rhs.pos + assert(macroDef.isTermMacro, ddef) + + def invalidBodyError() = + reportError(defpos, + "macro body has wrong shape:" + + "\n required: macro <reference to implementation object>.<implementation method name>" + + "\n or : macro <implementation method name>") + def validatePreTyper(rhs: Tree): Unit = rhs match { + // we do allow macro invocations inside macro bodies + // personally I don't mind if pre-typer tree is a macro invocation + // that later resolves to a valid reference to a macro implementation + // however, I don't think that invalidBodyError() should hint at that + // let this be an Easter Egg :) + case Apply(_, _) => ; + case TypeApply(_, _) => ; + case Super(_, _) => ; + case This(_) => ; + case Ident(_) => ; + case Select(_, _) => ; + case _ => invalidBodyError() } + def validatePostTyper(rhs1: Tree): Unit = { + def loop(tree: Tree): Unit = { + def errorNotStatic() = + reportError(implpos, "macro implementation must be in statically accessible object") + + def ensureRoot(sym: Symbol) = + if (!sym.isModule && !sym.isModuleClass) errorNotStatic() + + def ensureModule(sym: Symbol) = + if (!sym.isModule) errorNotStatic() + + tree match { + case TypeApply(fun, _) => + loop(fun) + case Super(qual, _) => + ensureRoot(macroDef.owner) + loop(qual) + case This(_) => + ensureRoot(tree.symbol) + case Select(qual, name) if name.isTypeName => + loop(qual) + case Select(qual, name) if name.isTermName => + if (tree.symbol != rhs1.symbol) ensureModule(tree.symbol) + loop(qual) + case Ident(name) if name.isTypeName => + ; + case Ident(name) if name.isTermName => + if (tree.symbol != rhs1.symbol) ensureModule(tree.symbol) + case _ => + invalidBodyError() + } + } + + loop(rhs1) + } + + val rhs = ddef.rhs + validatePreTyper(rhs) + if (hasErrors) macroTrace("macro def failed to satisfy trivial preconditions: ")(macroDef) + + // we use typed1 instead of typed, because otherwise adapt is going to mess us up + // if adapt sees <qualifier>.<method>, it will want to perform eta-expansion and will fail + // unfortunately, this means that we have to manually trigger macro expansion + // because it's adapt which is responsible for automatic expansion during typechecking + def typecheckRhs(rhs: Tree): Tree = { + try { + val prevNumErrors = reporter.ERROR.count // [Eugene] funnily enough, the isErroneous check is not enough + var rhs1 = if (hasErrors) EmptyTree else typer.typed1(rhs, EXPRmode, WildcardType) + def typecheckedWithErrors = (rhs1 exists (_.isErroneous)) || reporter.ERROR.count != prevNumErrors + def rhsNeedsMacroExpansion = rhs1.symbol != null && rhs1.symbol.isTermMacro && !rhs1.symbol.isErroneous + while (!typecheckedWithErrors && rhsNeedsMacroExpansion) { + rhs1 = macroExpand1(typer, rhs1) match { + case Success(expanded) => + try { + val typechecked = typer.typed1(expanded, EXPRmode, WildcardType) + if (macroDebug) { + println("typechecked1:") + println(typechecked) + println(showRaw(typechecked)) + } + + typechecked + } finally { + openMacros = openMacros.tail + } + case Fallback(fallback) => + typer.typed1(fallback, EXPRmode, WildcardType) + case Other(result) => + result + } + } + rhs1 + } catch { + case ex: TypeError => + typer.reportTypeError(context, rhs.pos, ex) + typer.infer.setError(rhs) + } + } + + val prevNumErrors = reporter.ERROR.count // funnily enough, the isErroneous check is not enough + var rhs1 = typecheckRhs(rhs) + def typecheckedWithErrors = (rhs1 exists (_.isErroneous)) || reporter.ERROR.count != prevNumErrors + hasErrors = hasErrors || typecheckedWithErrors + if (typecheckedWithErrors) macroTrace("body of a macro def failed to typecheck: ")(ddef) + + val macroImpl = rhs1.symbol + macroDef withAnnotation AnnotationInfo(MacroImplAnnotation.tpe, List(rhs1), Nil) + if (!hasErrors) { + if (macroImpl == null) { + invalidBodyError() + } else { + if (!macroImpl.isMethod) + invalidBodyError() + if (macroImpl.isOverloaded) + reportError(implpos, "macro implementation cannot be overloaded") + if (!macroImpl.typeParams.isEmpty && (!rhs1.isInstanceOf[TypeApply])) + reportError(implpos, "macro implementation reference needs type arguments") + if (!hasErrors) + validatePostTyper(rhs1) + } + if (hasErrors) + macroTrace("macro def failed to satisfy trivial preconditions: ")(macroDef) + } + + if (!hasErrors) { + def checkCompatibility(reqparamss: List[List[Symbol]], actparamss: List[List[Symbol]], reqres: Type, actres: Type): List[String] = { + var hasErrors = false + var errors = List[String]() + def compatibilityError(msg: String) { + hasErrors = true + errors :+= msg + } + + val flatreqparams = reqparamss.flatten + val flatactparams = actparamss.flatten + val tparams = macroImpl.typeParams + val tvars = tparams map freshVar + def lengthMsg(which: String, extra: Symbol) = + "parameter lists have different length, "+which+" extra parameter "+extra.defString + if (actparamss.length != reqparamss.length) + compatibilityError("number of parameter sections differ") + + if (!hasErrors) { + try { + for ((rparams, aparams) <- reqparamss zip actparamss) { + if (rparams.length < aparams.length) + compatibilityError(lengthMsg("found", aparams(rparams.length))) + if (aparams.length < rparams.length) + compatibilityError(lengthMsg("required", rparams(aparams.length)).abbreviateCoreAliases) + } + // if the implementation signature is already deemed to be incompatible, we bail out + // otherwise, high-order type magic employed below might crash in weird ways + if (!hasErrors) { + for ((rparams, aparams) <- reqparamss zip actparamss) { + for ((rparam, aparam) <- rparams zip aparams) { + def isRepeated(param: Symbol) = param.tpe.typeSymbol == RepeatedParamClass + if (rparam.name != aparam.name && !rparam.isSynthetic) { + val rparam1 = rparam + val aparam1 = aparam + compatibilityError("parameter names differ: "+rparam.name+" != "+aparam.name) + } + if (isRepeated(rparam) && !isRepeated(aparam)) + compatibilityError("types incompatible for parameter "+rparam.name+": corresponding is not a vararg parameter") + if (!isRepeated(rparam) && isRepeated(aparam)) + compatibilityError("types incompatible for parameter "+aparam.name+": corresponding is not a vararg parameter") + if (!hasErrors) { + var atpe = aparam.tpe.substSym(flatactparams, flatreqparams).instantiateTypeParams(tparams, tvars) + + // strip the { type PrefixType = ... } refinement off the Context or otherwise we get compatibility errors + atpe = atpe match { + case RefinedType(List(tpe), Scope(sym)) if tpe == MacroContextClass.tpe && sym.allOverriddenSymbols.contains(MacroContextPrefixType) => tpe + case _ => atpe + } + + val ok = if (macroDebug) withTypesExplained(rparam.tpe <:< atpe) else rparam.tpe <:< atpe + if (!ok) { + compatibilityError("type mismatch for parameter "+rparam.name+": "+rparam.tpe.toString.abbreviateCoreAliases+" does not conform to "+atpe) + } + } + } + } + } + if (!hasErrors) { + val atpe = actres.substSym(flatactparams, flatreqparams).instantiateTypeParams(tparams, tvars) + val ok = if (macroDebug) withTypesExplained(atpe <:< reqres) else atpe <:< reqres + if (!ok) { + compatibilityError("type mismatch for return type : "+reqres.toString.abbreviateCoreAliases+" does not conform to "+(if (ddef.tpt.tpe != null) atpe.toString else atpe.toString.abbreviateCoreAliases)) + } + } + if (!hasErrors) { + val targs = solvedTypes(tvars, tparams, tparams map varianceInType(actres), false, + lubDepth(flatactparams map (_.tpe)) max lubDepth(flatreqparams map (_.tpe))) + val boundsOk = typer.silent(_.infer.checkBounds(ddef, NoPrefix, NoSymbol, tparams, targs, "")) + boundsOk match { + case SilentResultValue(true) => ; + case SilentResultValue(false) | SilentTypeError(_) => + val bounds = tparams map (tp => tp.info.instantiateTypeParams(tparams, targs).bounds) + compatibilityError("type arguments " + targs.mkString("[", ",", "]") + + " do not conform to " + tparams.head.owner + "'s type parameter bounds " + + (tparams map (_.defString)).mkString("[", ",", "]")) + } + } + } catch { + case ex: NoInstance => + compatibilityError( + "type parameters "+(tparams map (_.defString) mkString ", ")+" cannot be instantiated\n"+ + ex.getMessage) + } + } + + errors.toList + } + + var actparamss = macroImpl.paramss + actparamss = transformTypeTagEvidenceParams(actparamss, (param, tparam) => None) + + val rettpe = if (ddef.tpt.tpe != null) ddef.tpt.tpe else computeMacroDefTypeFromMacroImpl(ddef, macroDef, macroImpl) + val (reqparamsss0, reqres0) = macroImplSigs(macroDef, ddef.tparams, ddef.vparamss, rettpe) + var reqparamsss = reqparamsss0 + + // prohibit implicit params on macro implementations + // we don't have to do this, but it appears to be more clear than allowing them + val implicitParams = actparamss.flatten filter (_.isImplicit) + if (implicitParams.length > 0) { + reportError(implicitParams.head.pos, "macro implementations cannot have implicit parameters other than TypeTag evidences") + macroTrace("macro def failed to satisfy trivial preconditions: ")(macroDef) + } + + if (!hasErrors) { + val reqres = reqres0 + val actres = macroImpl.tpe.finalResultType + def showMeth(pss: List[List[Symbol]], restpe: Type, abbreviate: Boolean) = { + var argsPart = (pss map (ps => ps map (_.defString) mkString ("(", ", ", ")"))).mkString + if (abbreviate) argsPart = argsPart.abbreviateCoreAliases + var retPart = restpe.toString + if (abbreviate || ddef.tpt.tpe == null) retPart = retPart.abbreviateCoreAliases + argsPart + ": " + retPart + } + def compatibilityError(addendum: String) = + reportError(implpos, + "macro implementation has wrong shape:"+ + "\n required: "+showMeth(reqparamsss.head, reqres, true) + + (reqparamsss.tail map (paramss => "\n or : "+showMeth(paramss, reqres, true)) mkString "")+ + "\n found : "+showMeth(actparamss, actres, false)+ + "\n"+addendum) + + macroTrace("considering " + reqparamsss.length + " possibilities of compatible macro impl signatures for macro def: ")(ddef.name) + val results = reqparamsss map (checkCompatibility(_, actparamss, reqres, actres)) + if (macroDebug) (reqparamsss zip results) foreach { case (reqparamss, result) => + println("%s %s".format(if (result.isEmpty) "[ OK ]" else "[FAILED]", reqparamss)) + result foreach (errorMsg => println(" " + errorMsg)) + } + + if (results forall (!_.isEmpty)) { + var index = reqparamsss indexWhere (_.length == actparamss.length) + if (index == -1) index = 0 + val mostRelevantMessage = results(index).head + compatibilityError(mostRelevantMessage) + } else { + assert((results filter (_.isEmpty)).length == 1, results) + if (macroDebug) (reqparamsss zip results) filter (_._2.isEmpty) foreach { case (reqparamss, result) => + println("typechecked macro impl as: " + reqparamss) + } + } + } + } + + // if this macro definition is erroneous, then there's no sense in expanding its usages + // in the previous prototype macro implementations were magically generated from macro definitions + // so macro definitions and its usages couldn't be compiled in the same compilation run + // however, now definitions and implementations are decoupled, so it's everything is possible + // hence, we now use IS_ERROR flag to serve as an indicator that given macro definition is broken + if (hasErrors) { + macroDef setFlag IS_ERROR + } + + rhs1 } - def addMacroMethods(templ: Template, namer: Namer): Unit = { - for (ddef @ DefDef(mods, _, _, _, _, _) <- templ.body if mods hasFlag MACRO) { - val trace = scala.tools.nsc.util.trace when settings.Ymacrodebug.value - val sym = namer.enterSyntheticSym(trace("macro def: ")(macroMethDef(ddef))) - trace("added to "+namer.context.owner.enclClass+": ")(sym) + def computeMacroDefTypeFromMacroImpl(macroDdef: DefDef, macroDef: Symbol, macroImpl: Symbol): Type = { + // get return type from method type + def unwrapRet(tpe: Type): Type = { + def loop(tpe: Type) = tpe match { + case NullaryMethodType(ret) => ret + case mtpe @ MethodType(_, ret) => unwrapRet(ret) + case _ => tpe + } + + tpe match { + case PolyType(_, tpe) => loop(tpe) + case _ => loop(tpe) + } + } + var metaType = unwrapRet(macroImpl.tpe) + + // downgrade from metalevel-0 to metalevel-1 + def inferRuntimeType(metaType: Type): Type = metaType match { + case TypeRef(pre, sym, args) if sym.name == tpnme.Expr && args.length == 1 => + args.head + case _ => + AnyClass.tpe + } + var runtimeType = inferRuntimeType(metaType) + + // transform type parameters of a macro implementation into type parameters of a macro definition + runtimeType = runtimeType map { + case TypeRef(pre, sym, args) => + // [Eugene] not sure which of these deSkolemizes are necessary + // sym.paramPos is unreliable (see another case below) + val tparams = macroImpl.typeParams map (_.deSkolemize) + val paramPos = tparams indexOf sym.deSkolemize + val sym1 = if (paramPos == -1) sym else { + val ann = macroDef.getAnnotation(MacroImplAnnotation) + ann match { + case Some(ann) => + val TypeApply(_, implRefTargs) = ann.args(0) + val implRefTarg = implRefTargs(paramPos).tpe.typeSymbol + implRefTarg + case None => + sym + } + } + TypeRef(pre, sym1, args) + case tpe => + tpe + } + + // as stated in the spec, before being matched to macroimpl, type and value parameters of macrodef + // undergo a special transformation, sigma, that adapts them to the different metalevel macroimpl lives in + // as a result, we need to reverse this transformation when inferring macrodef ret from macroimpl ret + def unsigma(tpe: Type): Type = { + // unfortunately, we cannot dereference ``paramss'', because we're in the middle of inferring a type for ``macroDef'' +// val defParamss = macroDef.paramss + val defParamss = macroDdef.vparamss map (_ map (_.symbol)) + var implParamss = macroImpl.paramss + implParamss = transformTypeTagEvidenceParams(implParamss, (param, tparam) => None) + + val implCtxParam = if (implParamss.length > 0 && implParamss(0).length > 0) implParamss(0)(0) else null + def implParamToDefParam(implParam: Symbol): Symbol = { + val indices = (implParamss drop 1 zipWithIndex) map { case (implParams, index) => (index, implParams indexOf implParam) } filter (_._2 != -1) headOption; + val defParam = indices flatMap { + case (plistIndex, pIndex) => + if (defParamss.length <= plistIndex) None + else if (defParamss(plistIndex).length <= pIndex) None + else Some(defParamss(plistIndex)(pIndex)) + } + defParam orNull + } + + class UnsigmaTypeMap extends TypeMap { + def apply(tp: Type): Type = tp match { + case TypeRef(pre, sym, args) => + val pre1 = pre match { + case SingleType(SingleType(SingleType(NoPrefix, param), prefix), value) if param == implCtxParam && prefix == MacroContextPrefix && value == ExprValue => + ThisType(macroDef.owner) + case SingleType(SingleType(NoPrefix, param), value) if implParamToDefParam(param) != null && value == ExprValue => + val macroDefParam = implParamToDefParam(param) + SingleType(NoPrefix, macroDefParam) + case _ => + pre + } + val args1 = args map mapOver + TypeRef(pre1, sym, args1) + case _ => + mapOver(tp) + } + } + + new UnsigmaTypeMap() apply tpe } + runtimeType = unsigma(runtimeType) + + runtimeType } - lazy val mirror = new scala.reflect.runtime.Mirror { - lazy val libraryClassLoader = { - // todo. this is more or less okay, but not completely correct - // see https://issues.scala-lang.org/browse/SI-5433 for more info - val classpath = global.classPath.asURLs - var loader: ClassLoader = ScalaClassLoader.fromURLs(classpath, self.getClass.getClassLoader) - - // an heuristic to detect REPL - if (global.settings.exposeEmptyPackage.value) { - import scala.tools.nsc.interpreter._ - val virtualDirectory = global.settings.outputDirs.getSingleOutput.get - loader = new AbstractFileClassLoader(virtualDirectory, loader) {} + /** Primary mirror that is used to resolve and run macro implementations. + * Loads classes from -Xmacro-primary-classpath, or from -cp if the option is not specified. + */ + private lazy val primaryMirror: Mirror = { + if (global.forMSIL) + throw new UnsupportedOperationException("Scala reflection not available on this platform") + + val libraryClassLoader = { + if (settings.XmacroPrimaryClasspath.value != "") { + if (macroDebug) println("primary macro mirror: initializing from -Xmacro-primary-classpath: %s".format(settings.XmacroPrimaryClasspath.value)) + val classpath = toURLs(settings.XmacroFallbackClasspath.value) + ScalaClassLoader.fromURLs(classpath, self.getClass.getClassLoader) + } else { + if (macroDebug) println("primary macro mirror: initializing from -cp: %s".format(global.classPath.asURLs)) + val classpath = global.classPath.asURLs + var loader: ClassLoader = ScalaClassLoader.fromURLs(classpath, self.getClass.getClassLoader) + + // [Eugene] a heuristic to detect REPL + if (global.settings.exposeEmptyPackage.value) { + import scala.tools.nsc.interpreter._ + val virtualDirectory = global.settings.outputDirs.getSingleOutput.get + loader = new AbstractFileClassLoader(virtualDirectory, loader) {} + } + + loader } + } + + new Mirror(libraryClassLoader) { override def toString = "<primary macro mirror>" } + } - loader + /** Fallback mirror that is used to resolve and run macro implementations. + * Loads classes from -Xmacro-fallback-classpath aka "macro fallback classpath". + */ + private lazy val fallbackMirror: Mirror = { + if (global.forMSIL) + throw new UnsupportedOperationException("Scala reflection not available on this platform") + + val fallbackClassLoader = { + if (macroDebug) println("fallback macro mirror: initializing from -Xmacro-fallback-classpath: %s".format(settings.XmacroFallbackClasspath.value)) + val classpath = toURLs(settings.XmacroFallbackClasspath.value) + ScalaClassLoader.fromURLs(classpath, self.getClass.getClassLoader) } - override def defaultReflectiveClassLoader() = libraryClassLoader + new Mirror(fallbackClassLoader) { override def toString = "<fallback macro mirror>" } } - /** Return optionally address of companion object and implementation method symbol - * of given macro; or None if implementation classfile cannot be loaded or does - * not contain the macro implementation. + /** Produces a function that can be used to invoke macro implementation for a given macro definition: + * 1) Looks up macro implementation symbol in this universe. + * 2) Loads its enclosing class from the primary mirror. + * 3) Loads the companion of that enclosing class from the primary mirror. + * 4) Resolves macro implementation within the loaded companion. + * 5) If 2-4 fails, repeats them for the fallback mirror. + * + * @return Some(runtime) if macro implementation can be loaded successfully from either of the mirrors, + * None otherwise. */ - def macroImpl(mac: Symbol): Option[(AnyRef, mirror.Symbol)] = { - val debug = settings.Ymacrodebug.value - val trace = scala.tools.nsc.util.trace when debug - trace("looking for macro implementation: ")(mac.fullNameString) - - try { - val mmeth = macroMeth(mac) - trace("found implementation at: ")(mmeth.fullNameString) - - if (mmeth == NoSymbol) None - else { - trace("loading implementation class: ")(mmeth.owner.fullName) - trace("classloader is: ")("%s of type %s".format(mirror.libraryClassLoader, mirror.libraryClassLoader.getClass)) + private def macroRuntime(macroDef: Symbol): Option[List[Any] => Any] = { + macroTrace("looking for macro implementation: ")(macroDef) + macroTrace("macroDef is annotated with: ")(macroDef.annotations) + + val ann = macroDef.getAnnotation(MacroImplAnnotation) + if (ann == None) { + macroTrace("@macroImpl annotation is missing (this means that macro definition failed to typecheck)")(macroDef) + return None + } + + val macroImpl = ann.get.args(0).symbol + if (macroImpl == NoSymbol) { + macroTrace("@macroImpl annotation is malformed (this means that macro definition failed to typecheck)")(macroDef) + return None + } + + if (macroDebug) println("resolved implementation %s at %s".format(macroImpl, macroImpl.pos)) + if (macroImpl.isErroneous) { + macroTrace("macro implementation is erroneous (this means that either macro body or macro implementation signature failed to typecheck)")(macroDef) + return None + } + + def loadMacroImpl(macroMirror: Mirror): Option[(Object, macroMirror.Symbol)] = { + try { + // this logic relies on the assumptions that were valid for the old macro prototype + // namely that macro implementations can only be defined in top-level classes and modules + // with the new prototype that materialized in a SIP, macros need to be statically accessible, which is different + // for example, a macro def could be defined in a trait that is implemented by an object + // there are some more clever cases when seemingly non-static method ends up being statically accessible + // however, the code below doesn't account for these guys, because it'd take a look of time to get it right + // for now I leave it as a todo and move along to more the important stuff + + macroTrace("loading implementation class from %s: ".format(macroMirror))(macroImpl.owner.fullName) + macroTrace("classloader is: ")("%s of type %s".format(macroMirror.classLoader, if (macroMirror.classLoader != null) macroMirror.classLoader.getClass.toString else "primordial classloader")) def inferClasspath(cl: ClassLoader) = cl match { case cl: java.net.URLClassLoader => "[" + (cl.getURLs mkString ",") + "]" + case null => "[" + scala.tools.util.PathResolver.Environment.javaBootClassPath + "]" case _ => "<unknown>" } - trace("classpath is: ")(inferClasspath(mirror.libraryClassLoader)) + macroTrace("classpath is: ")(inferClasspath(macroMirror.classLoader)) - // @xeno.by: relies on the fact that macros can only be defined in static classes + // [Eugene] relies on the fact that macro implementations can only be defined in static classes + // [Martin to Eugene] There's similar logic buried in Symbol#flatname. Maybe we can refactor? def classfile(sym: Symbol): String = { def recur(sym: Symbol): String = sym match { case sym if sym.owner.isPackageClass => @@ -146,145 +693,535 @@ trait Macros { self: Analyzer => else recur(sym.enclClass) } - // @xeno.by: this doesn't work for inner classes - // neither does mmeth.owner.javaClassName, so I had to roll my own implementation - //val receiverName = mmeth.owner.fullName - val receiverName = classfile(mmeth.owner) - val receiverClass: mirror.Symbol = mirror.symbolForName(receiverName) + // [Eugene] this doesn't work for inner classes + // neither does macroImpl.owner.javaClassName, so I had to roll my own implementation + //val receiverName = macroImpl.owner.fullName + val implClassName = classfile(macroImpl.owner) + val implClassSymbol: macroMirror.Symbol = macroMirror.symbolForName(implClassName) - if (debug) { - println("receiverClass is: " + receiverClass.fullNameString) + if (macroDebug) { + println("implClassSymbol is: " + implClassSymbol.fullNameString) - val jreceiverClass = mirror.classToJava(receiverClass) - val jreceiverSource = jreceiverClass.getProtectionDomain.getCodeSource - println("jreceiverClass is %s from %s".format(jreceiverClass, jreceiverSource)) - println("jreceiverClassLoader is %s with classpath %s".format(jreceiverClass.getClassLoader, inferClasspath(jreceiverClass.getClassLoader))) + if (implClassSymbol != macroMirror.NoSymbol) { + val implClass = macroMirror.classToJava(implClassSymbol) + val implSource = implClass.getProtectionDomain.getCodeSource + println("implClass is %s from %s".format(implClass, implSource)) + println("implClassLoader is %s with classpath %s".format(implClass.getClassLoader, inferClasspath(implClass.getClassLoader))) + } } - val receiverObj = receiverClass.companionModule - trace("receiverObj is: ")(receiverObj.fullNameString) + val implObjSymbol = implClassSymbol.companionModule + macroTrace("implObjSymbol is: ")(implObjSymbol.fullNameString) - if (receiverObj == mirror.NoSymbol) None + if (implObjSymbol == macroMirror.NoSymbol) None else { - // @xeno.by: yet another reflection method that doesn't work for inner classes - //val receiver = mirror.companionInstance(receiverClass) - val clazz = java.lang.Class.forName(receiverName, true, mirror.libraryClassLoader) - val receiver = clazz getField "MODULE$" get null - - val rmeth = receiverObj.info.member(mirror.newTermName(mmeth.name.toString)) - if (debug) { - println("rmeth is: " + rmeth.fullNameString) - println("jrmeth is: " + mirror.methodToJava(rmeth)) + // yet another reflection method that doesn't work for inner classes + //val receiver = macroMirror.companionInstance(receiverClass) + val implObj = try { + val implObjClass = java.lang.Class.forName(implClassName, true, macroMirror.classLoader) + implObjClass getField "MODULE$" get null + } catch { + case ex: NoSuchFieldException => macroTrace("exception when loading implObj: ")(ex); null + case ex: NoClassDefFoundError => macroTrace("exception when loading implObj: ")(ex); null + case ex: ClassNotFoundException => macroTrace("exception when loading implObj: ")(ex); null } - if (rmeth == mirror.NoSymbol) None + if (implObj == null) None else { - Some((receiver, rmeth)) + val implMethSymbol = implObjSymbol.info.member(macroMirror.newTermName(macroImpl.name.toString)) + if (macroDebug) { + println("implMethSymbol is: " + implMethSymbol.fullNameString) + println("jimplMethSymbol is: " + macroMirror.methodToJava(implMethSymbol)) + } + + if (implMethSymbol == macroMirror.NoSymbol) None + else { + if (macroDebug) println("successfully loaded macro impl as (%s, %s)".format(implObj, implMethSymbol)) + Some((implObj, implMethSymbol)) + } } } + } catch { + case ex: ClassNotFoundException => + macroTrace("implementation class failed to load: ")(ex.toString) + None } - } catch { - case ex: ClassNotFoundException => - trace("implementation class failed to load: ")(ex.toString) - None + } + + val primary = loadMacroImpl(primaryMirror) + primary match { + case Some((implObj, implMethSymbol)) => + def runtime(args: List[Any]) = primaryMirror.invoke(implObj, implMethSymbol)(args: _*).asInstanceOf[Any] + Some(runtime) + case None => + if (settings.XmacroFallbackClasspath.value != "") { + if (macroDebug) println("trying to load macro implementation from the fallback mirror: %s".format(settings.XmacroFallbackClasspath.value)) + val fallback = loadMacroImpl(fallbackMirror) + fallback match { + case Some((implObj, implMethSymbol)) => + def runtime(args: List[Any]) = fallbackMirror.invoke(implObj, implMethSymbol)(args: _*).asInstanceOf[Any] + Some(runtime) + case None => + None + } + } else { + None + } } } - /** Return result of macro expansion. - * Or, if that fails, and the macro overrides a method return - * tree that calls this method instead of the macro. + /** Should become private again once we're done with migrating typetag generation from implicits */ + def macroContext(typer: Typer, prefixTree: Tree, expandeeTree: Tree): MacroContext { val mirror: global.type } = + new { + val mirror: global.type = global + val callsiteTyper: mirror.analyzer.Typer = typer.asInstanceOf[global.analyzer.Typer] + // todo. infer precise typetag for this Expr, namely the PrefixType member of the Context refinement + val prefix = Expr(prefixTree)(TypeTag.Nothing) + val expandee = expandeeTree + } with MacroContext { + override def toString = "MacroContext(%s@%s +%d)".format(expandee.symbol.name, expandee.pos, openMacros.length - 1 /* exclude myself */) + } + + /** Calculate the arguments to pass to a macro implementation when expanding the provided tree. + * + * This includes inferring the exact type and instance of the macro context to pass, and also + * allowing for missing parameter sections in macro implementation (see ``macroImplParamsss'' for more info). + * + * @return list of runtime objects to pass to the implementation obtained by ``macroRuntime'' */ - def macroExpand(tree: Tree, typer: Typer): Option[Any] = { - val trace = scala.tools.nsc.util.trace when settings.Ymacrodebug.value - trace("macroExpand: ")(tree) - - val macroDef = tree.symbol - macroImpl(macroDef) match { - case Some((receiver, rmeth)) => - val argss = List(global) :: macroArgs(tree) - val paramss = macroMeth(macroDef).paramss - trace("paramss: ")(paramss) - val rawArgss = for ((as, ps) <- argss zip paramss) yield { - if (isVarArgsList(ps)) as.take(ps.length - 1) :+ as.drop(ps.length - 1) - else as - } - val rawArgs: Seq[Any] = rawArgss.flatten - trace("rawArgs: ")(rawArgs) - val savedInfolevel = nodePrinters.infolevel + private def macroArgs(typer: Typer, expandee: Tree): Option[List[Any]] = { + var prefixTree: Tree = EmptyTree + var typeArgs = List[Tree]() + val exprArgs = new ListBuffer[List[Expr[_]]] + def collectMacroArgs(tree: Tree): Unit = tree match { + case Apply(fn, args) => + // todo. infer precise typetag for this Expr, namely the declared type of the corresponding macro impl argument + exprArgs.prepend(args map (Expr(_)(TypeTag.Nothing))) + collectMacroArgs(fn) + case TypeApply(fn, args) => + typeArgs = args + collectMacroArgs(fn) + case Select(qual, name) => + prefixTree = qual + case _ => + } + collectMacroArgs(expandee) + val context = macroContext(typer, prefixTree, expandee) + var argss: List[List[Any]] = List(context) :: exprArgs.toList + macroTrace("argss: ")(argss) + + val macroDef = expandee.symbol + val ann = macroDef.getAnnotation(MacroImplAnnotation).getOrElse(throw new Error("assertion failed. %s: %s".format(macroDef, macroDef.annotations))) + val macroImpl = ann.args(0).symbol + var paramss = macroImpl.paramss + val tparams = macroImpl.typeParams + macroTrace("paramss: ")(paramss) + + // we need to take care of all possible combos of nullary/empty-paramlist macro defs vs nullary/empty-arglist invocations + // nullary def + nullary invocation => paramss and argss match, everything is okay + // nullary def + empty-arglist invocation => illegal Scala code, impossible, everything is okay + // empty-paramlist def + nullary invocation => uh-oh, we need to append a List() to argss + // empty-paramlist def + empty-arglist invocation => paramss and argss match, everything is okay + // that's almost it, but we need to account for the fact that paramss might have context bounds that mask the empty last paramlist + val paramss_without_evidences = transformTypeTagEvidenceParams(paramss, (param, tparam) => None) + val isEmptyParamlistDef = paramss_without_evidences.length != 0 && paramss_without_evidences.last.isEmpty + val isEmptyArglistInvocation = argss.length != 0 && argss.last.isEmpty + if (isEmptyParamlistDef && !isEmptyArglistInvocation) { + if (macroDebug) println("isEmptyParamlistDef && !isEmptyArglistInvocation: appending a List() to argss") + argss = argss :+ Nil + } + + // nb! check partial application against paramss without evidences + val numParamLists = paramss_without_evidences.length + val numArgLists = argss.length + if (numParamLists != numArgLists) { + typer.context.error(expandee.pos, "macros cannot be partially applied") + return None + } + + // if paramss have typetag context bounds, add an arglist to argss if necessary and instantiate the corresponding evidences + // consider the following example: + // + // class D[T] { + // class C[U] { + // def foo[V] = macro Impls.foo[T, U, V] + // } + // } + // + // val outer1 = new D[Int] + // val outer2 = new outer1.C[String] + // outer2.foo[Boolean] + // + // then T and U need to be inferred from the lexical scope of the call using ``asSeenFrom'' + // whereas V won't be resolved by asSeenFrom and need to be loaded directly from ``expandee'' which needs to contain a TypeApply node + // also, macro implementation reference may contain a regular type as a type argument, then we pass it verbatim + paramss = transformTypeTagEvidenceParams(paramss, (param, tparam) => Some(tparam)) + if (paramss.lastOption map (params => !params.isEmpty && params.forall(_.isType)) getOrElse false) argss = argss :+ Nil + val evidences = paramss.last takeWhile (_.isType) map (tparam => { + val TypeApply(_, implRefTargs) = ann.args(0) + var implRefTarg = implRefTargs(tparam.paramPos).tpe.typeSymbol + val tpe = if (implRefTarg.isTypeParameterOrSkolem) { + if (implRefTarg.owner == macroDef) { + // [Eugene] doesn't work when macro def is compiled separately from its usages + // then implRefTarg is not a skolem and isn't equal to any of macroDef.typeParams +// val paramPos = implRefTarg.deSkolemize.paramPos + val paramPos = macroDef.typeParams.indexWhere(_.name == implRefTarg.name) + typeArgs(paramPos).tpe + } else + implRefTarg.tpe.asSeenFrom( + if (prefixTree == EmptyTree) macroDef.owner.tpe else prefixTree.tpe, + macroDef.owner) + } else + implRefTarg.tpe + if (macroDebug) println("resolved tparam %s as %s".format(tparam, tpe)) + tpe + }) map (tpe => { + val ttag = TypeTag(tpe) + if (ttag.isGround) ttag.toGround else ttag + }) + argss = argss.dropRight(1) :+ (evidences ++ argss.last) + + assert(argss.length == paramss.length, "argss: %s, paramss: %s".format(argss, paramss)) + val rawArgss = for ((as, ps) <- argss zip paramss) yield { + if (isVarArgsList(ps)) as.take(ps.length - 1) :+ as.drop(ps.length - 1) + else as + } + val rawArgs = rawArgss.flatten + macroTrace("rawArgs: ")(rawArgs) + Some(rawArgs) + } + + /** Keeps track of macros in-flight. + * See more informations in comments to ``openMacros'' in ``scala.reflect.makro.Context''. + */ + var openMacros = List[MacroContext]() + + /** Performs macro expansion: + * 1) Checks whether the expansion needs to be delayed (see ``mustDelayMacroExpansion'') + * 2) Loads macro implementation using ``macroMirror'' + * 3) Synthesizes invocation arguments for the macro implementation + * 4) Checks that the result is a tree bound to this universe + * 5) Typechecks the result against the return type of the macro definition + * + * If -Ymacro-debug is enabled, you will get detailed log of how exactly this function + * performs class loading and method resolution in order to load the macro implementation. + * The log will also include other non-trivial steps of macro expansion. + * + * If -Ymacro-copypaste is enabled along with -Ymacro-debug, you will get macro expansions + * logged in the form that can be copy/pasted verbatim into REPL (useful for debugging!). + * + * @return + * the expansion result if the expansion has been successful, + * the fallback method invocation if the expansion has been unsuccessful, but there is a fallback, + * the expandee unchanged if the expansion has been delayed, + * the expandee fully expanded if the expansion has been delayed before and has been expanded now, + * the expandee with an error marker set if the expansion has been cancelled due malformed arguments or implementation + * the expandee with an error marker set if there has been an error + */ + def macroExpand(typer: Typer, expandee: Tree, pt: Type): Tree = + macroExpand1(typer, expandee) match { + case Success(expanded) => try { - // @xeno.by: InfoLevel.Verbose examines and prints out infos of symbols - // by the means of this'es these symbols can climb up the lexical scope - // when these symbols will be examined by a node printer - // they will enumerate and analyze their children (ask for infos and tpes) - // if one of those children involves macro expansion, things might get nasty - // that's why I'm temporarily turning this behavior off - nodePrinters.infolevel = nodePrinters.InfoLevel.Quiet - val expanded = mirror.invoke(receiver, rmeth)(rawArgs: _*) - expanded match { - case expanded: Tree => - val expectedTpe = tree.tpe - val typed = typer.typed(expanded, EXPRmode, expectedTpe) - Some(typed) - case expanded if expanded.isInstanceOf[Tree] => - typer.context.unit.error(tree.pos, "macro must return a compiler-specific tree; returned value is Tree, but it doesn't belong to this compiler's universe") - None - case expanded => - typer.context.unit.error(tree.pos, "macro must return a compiler-specific tree; returned value is of class: " + expanded.getClass) - None + var expectedTpe = expandee.tpe + + // [Eugene] weird situation. what's the conventional way to deal with it? + val isNullaryInvocation = expandee match { + case TypeApply(Select(_, _), _) => true + case Select(_, _) => true + case _ => false } - } catch { - case ex => - val realex = ReflectionUtils.unwrapThrowable(ex) - val msg = if (settings.Ymacrodebug.value) { - val stacktrace = new java.io.StringWriter() - realex.printStackTrace(new java.io.PrintWriter(stacktrace)) - System.getProperty("line.separator") + stacktrace - } else { - realex.getMessage - } - typer.context.unit.error(tree.pos, "exception during macro expansion: " + msg) - None + if (isNullaryInvocation) expectedTpe match { + case MethodType(Nil, restpe) => + macroTrace("nullary invocation of a method with an empty parameter list. unwrapping expectedTpe from " + expectedTpe + " to:")(restpe) + expectedTpe = restpe + case _ => ; + } + + var typechecked = typer.context.withImplicitsEnabled(typer.typed(expanded, EXPRmode, expectedTpe)) + if (macroDebug) { + println("typechecked1:") + println(typechecked) + println(showRaw(typechecked)) + } + + typechecked = typer.context.withImplicitsEnabled(typer.typed(typechecked, EXPRmode, pt)) + if (macroDebug) { + println("typechecked2:") + println(typechecked) + println(showRaw(typechecked)) + } + + typechecked } finally { - nodePrinters.infolevel = savedInfolevel + openMacros = openMacros.tail } - case None => - def notFound() = { - typer.context.unit.error(tree.pos, "macro implementation not found: " + macroDef.name) - None - } - def fallBackToOverridden(tree: Tree): Option[Tree] = { - tree match { - case Select(qual, name) if (macroDef.isMacro) => - macroDef.allOverriddenSymbols match { - case first :: _ => - Some(Select(qual, name) setPos tree.pos setSymbol first) + case Fallback(fallback) => + typer.context.withImplicitsEnabled(typer.typed(fallback, EXPRmode, pt)) + case Other(result) => + result + } + + private sealed abstract class MacroExpansionResult extends Product with Serializable + private case class Success(expanded: Tree) extends MacroExpansionResult + private case class Fallback(fallback: Tree) extends MacroExpansionResult + private case class Other(result: Tree) extends MacroExpansionResult + private def Delay(expandee: Tree) = Other(expandee) + private def Skip(expanded: Tree) = Other(expanded) + private def Cancel(expandee: Tree) = Other(expandee) + private def Failure(expandee: Tree) = Other(expandee) + private def fail(typer: Typer, expandee: Tree, msg: String = null) = { + if (macroDebug || macroCopypaste) { + var msg1 = if (msg contains "exception during macro expansion") msg.split(EOL).drop(1).headOption.getOrElse("?") else msg + if (macroDebug) msg1 = msg + println("macro expansion has failed: %s".format(msg1)) + } + val pos = if (expandee.pos != NoPosition) expandee.pos else openMacros.find(c => c.expandee.pos != NoPosition).map(_.expandee.pos).getOrElse(NoPosition) + if (msg != null) typer.context.error(pos, msg) + typer.infer.setError(expandee) + Failure(expandee) + } + + /** Does the same as ``macroExpand'', but without typechecking the expansion + * Meant for internal use within the macro infrastructure, don't use it elsewhere. + */ + private def macroExpand1(typer: Typer, expandee: Tree): MacroExpansionResult = { + // if a macro implementation is incompatible or any of the arguments are erroneous + // there is no sense to expand the macro itself => it will only make matters worse + if (expandee.symbol.isErroneous || (expandee exists (_.isErroneous))) { + val reason = if (expandee.symbol.isErroneous) "incompatible macro implementation" else "erroneous arguments" + macroTrace("cancelled macro expansion because of %s: ".format(reason))(expandee) + return Cancel(typer.infer.setError(expandee)) + } + + if (!isDelayed(expandee)) { + if (macroDebug || macroCopypaste) println("typechecking macro expansion %s at %s".format(expandee, expandee.pos)) + + val undetparams = calculateUndetparams(expandee) + if (undetparams.size != 0) { + macroTrace("macro expansion is delayed: ")(expandee) + delayed += expandee -> (typer.context, undetparams) + Delay(expandee) + } else { + val macroDef = expandee.symbol + macroRuntime(macroDef) match { + case Some(runtime) => + val savedInfolevel = nodePrinters.infolevel + try { + // InfoLevel.Verbose examines and prints out infos of symbols + // by the means of this'es these symbols can climb up the lexical scope + // when these symbols will be examined by a node printer + // they will enumerate and analyze their children (ask for infos and tpes) + // if one of those children involves macro expansion, things might get nasty + // that's why I'm temporarily turning this behavior off + nodePrinters.infolevel = nodePrinters.InfoLevel.Quiet + val args = macroArgs(typer, expandee) + args match { + case Some(args) => + // adding stuff to openMacros is easy, but removing it is a nightmare + // it needs to be sprinkled over several different code locations + val (context: MacroContext) :: _ = args + openMacros = context :: openMacros + val expanded: MacroExpansionResult = try { + val prevNumErrors = reporter.ERROR.count + val expanded = runtime(args) + val currNumErrors = reporter.ERROR.count + if (currNumErrors != prevNumErrors) { + fail(typer, expandee) // errors have been reported by the macro itself + } else { + expanded match { + case expanded: Expr[_] => + if (macroDebug || macroCopypaste) { + if (macroDebug) println("original:") + println(expanded.tree) + println(showRaw(expanded.tree)) + } + + freeTerms(expanded.tree) foreach (fte => typer.context.error(expandee.pos, + ("macro expansion contains free term variable %s %s. "+ + "have you forgot to use eval when splicing this variable into a reifee? " + + "if you have troubles tracking free term variables, consider using -Xlog-free-terms").format(fte.name, fte.origin))) + freeTypes(expanded.tree) foreach (fty => typer.context.error(expandee.pos, + ("macro expansion contains free type variable %s %s. "+ + "have you forgot to use c.TypeTag annotation for this type parameter? " + + "if you have troubles tracking free type variables, consider using -Xlog-free-types").format(fty.name, fty.origin))) + + val currNumErrors = reporter.ERROR.count + if (currNumErrors != prevNumErrors) { + fail(typer, expandee) + } else { + // inherit the position from the first position-ful expandee in macro callstack + // this is essential for sane error messages + var tree = expanded.tree + var position = openMacros.find(c => c.expandee.pos != NoPosition).map(_.expandee.pos).getOrElse(NoPosition) + tree = atPos(position.focus)(tree) + + // now macro expansion gets typechecked against the macro definition return type + // however, this happens in macroExpand, not here in macroExpand1 + Success(tree) + } + case expanded if expanded.isInstanceOf[Expr[_]] => + val msg = "macro must return a compiler-specific expr; returned value is Expr, but it doesn't belong to this compiler's universe" + fail(typer, expandee, msg) + case expanded => + val msg = "macro must return a compiler-specific expr; returned value is of class: %s".format(expanded.getClass) + fail(typer, expandee, msg) + } + } + } catch { + case ex: Throwable => + openMacros = openMacros.tail + throw ex + } + if (!expanded.isInstanceOf[Success]) openMacros = openMacros.tail + expanded + case None => + fail(typer, expandee) // error has been reported by macroArgs + } + } catch { + case ex => + // [Eugene] any ideas about how to improve this one? + val realex = ReflectionUtils.unwrapThrowable(ex) + realex match { + case realex: reflect.makro.runtime.AbortMacroException => + if (macroDebug || macroCopypaste) println("macro expansion has failed: %s".format(realex.msg)) + fail(typer, expandee) // error has been reported by abort + case _ => + val message = { + try { + // the most reliable way of obtaining currently executing method + // http://stackoverflow.com/questions/442747/getting-the-name-of-the-current-executing-method + val currentMethodName = new Object(){}.getClass().getEnclosingMethod().getName + val relevancyThreshold = realex.getStackTrace().indexWhere(este => este.getMethodName == currentMethodName) + if (relevancyThreshold == -1) None + else { + var relevantElements = realex.getStackTrace().take(relevancyThreshold + 1) + var framesTillReflectiveInvocationOfMacroImpl = relevantElements.reverse.indexWhere(_.isNativeMethod) + 1 + relevantElements = relevantElements dropRight framesTillReflectiveInvocationOfMacroImpl + + realex.setStackTrace(relevantElements) + val message = new java.io.StringWriter() + realex.printStackTrace(new java.io.PrintWriter(message)) + Some(EOL + message) + } + } catch { + // if the magic above goes boom, just fall back to uninformative, but better than nothing, getMessage + case ex: Throwable => + None + } + } getOrElse realex.getMessage + fail(typer, expandee, "exception during macro expansion: " + message) + } + } finally { + nodePrinters.infolevel = savedInfolevel + } + case None => + def notFound() = { + typer.context.error(expandee.pos, "macro implementation not found: " + macroDef.name + " " + + "(the most common reason for that is that you cannot use macro implementations in the same compilation run that defines them)\n" + + "if you do need to define macro implementations along with the rest of your program, consider two-phase compilation with -Xmacro-fallback-classpath " + + "in the second phase pointing to the output of the first phase") + None + } + def fallBackToOverridden(tree: Tree): Option[Tree] = { + tree match { + case Select(qual, name) if (macroDef.isTermMacro) => + macroDef.allOverriddenSymbols match { + case first :: _ => + Some(Select(qual, name) setPos tree.pos setSymbol first) + case _ => + macroTrace("macro is not overridden: ")(tree) + notFound() + } + case Apply(fn, args) => + fallBackToOverridden(fn) match { + case Some(fn1) => Some(Apply(fn1, args) setPos tree.pos) + case _ => None + } + case TypeApply(fn, args) => + fallBackToOverridden(fn) match { + case Some(fn1) => Some(TypeApply(fn1, args) setPos tree.pos) + case _ => None + } case _ => - trace("macro is not overridden: ")(tree) + macroTrace("unexpected tree in fallback: ")(tree) notFound() } - case Apply(fn, args) => - fallBackToOverridden(fn) match { - case Some(fn1) => Some(Apply(fn1, args) setPos tree.pos) - case _ => None - } - case TypeApply(fn, args) => - fallBackToOverridden(fn) match { - case Some(fn1) => Some(TypeApply(fn1, args) setPos tree.pos) - case _ => None - } - case _ => - trace("unexpected tree in fallback: ")(tree) - notFound() - } - } - fallBackToOverridden(tree) match { - case Some(tree1) => - trace("falling back to ")(tree1) - currentRun.macroExpansionFailed = true - Some(tree1) - case None => - None + } + fallBackToOverridden(expandee) match { + case Some(tree1) => + macroTrace("falling back to ")(tree1) + currentRun.macroExpansionFailed = true + Fallback(tree1) + case None => + fail(typer, expandee) + } } + } + } else { + val undetparams = calculateUndetparams(expandee) + if (undetparams.size != 0) + Delay(expandee) + else + Skip(macroExpandAll(typer, expandee)) } } + + /** Without any restrictions on macro expansion, macro applications will expand at will, + * and when type inference is involved, expansions will end up using yet uninferred type params. + * + * For some macros this might be ok (thanks to TreeTypeSubstituter that replaces + * the occurrences of undetparams with their inferred values), but in general case this won't work. + * E.g. for reification simple substitution is not enough - we actually need to re-reify inferred types. + * + * Luckily, there exists a very simple way to fix the problem: delay macro expansion until everything is inferred. + * Here are the exact rules. Macro application gets delayed if any of its subtrees contain: + * 1) type vars (tpe.isInstanceOf[TypeVar]) // [Eugene] this check is disabled right now, because TypeVars seem to be created from undetparams anyways + * 2) undetparams (sym.isTypeParameter && !sym.isSkolem) + */ + var hasPendingMacroExpansions = false + private val delayed = perRunCaches.newWeakMap[Tree, (Context, collection.mutable.Set[Int])] + private def isDelayed(expandee: Tree) = delayed contains expandee + private def calculateUndetparams(expandee: Tree): collection.mutable.Set[Int] = + delayed.get(expandee).map(_._2).getOrElse { + val calculated = collection.mutable.Set[Int]() + expandee foreach (sub => { + def traverse(sym: Symbol) = if (sym != null && (undetparams contains sym.id)) calculated += sym.id + if (sub.symbol != null) traverse(sub.symbol) + if (sub.tpe != null) sub.tpe foreach (sub => traverse(sub.typeSymbol)) + }) + calculated + } + private val undetparams = perRunCaches.newSet[Int] + def notifyUndetparamsAdded(newUndets: List[Symbol]): Unit = undetparams ++= newUndets map (_.id) + def notifyUndetparamsInferred(undetNoMore: List[Symbol], inferreds: List[Type]): Unit = { + undetparams --= undetNoMore map (_.id) + if (!delayed.isEmpty) + delayed.toList foreach { + case (expandee, (_, undetparams)) if !undetparams.isEmpty => + undetparams --= undetNoMore map (_.id) + if (undetparams.isEmpty) { + hasPendingMacroExpansions = true + macroTrace("macro expansion is pending: ")(expandee) + } + case _ => + // do nothing + } + } + + /** Performs macro expansion on all subtrees of a given tree. + * Innermost macros are expanded first, outermost macros are expanded last. + * See the documentation for ``macroExpand'' for more information. + */ + def macroExpandAll(typer: Typer, expandee: Tree): Tree = + new Transformer { + override def transform(tree: Tree) = super.transform(tree match { + // todo. expansion should work from the inside out + case wannabe if (delayed contains wannabe) && calculateUndetparams(wannabe).isEmpty => + val (context, _) = delayed(wannabe) + delayed -= wannabe + macroExpand(newTyper(context), wannabe, WildcardType) + case _ => + tree + }) + }.transform(expandee) } diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index 3d8c2ea564..6382e5a847 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -28,6 +28,7 @@ trait MethodSynthesis { else DefDef(sym, body) def applyTypeInternal(manifests: List[M[_]]): Type = { + // [Eugene to Paul] needs review!! val symbols = manifests map manifestToSymbol val container :: args = symbols val tparams = container.typeConstructor.typeParams @@ -58,12 +59,13 @@ trait MethodSynthesis { def newMethodType[F](owner: Symbol)(implicit m: Manifest[F]): Type = { val fnSymbol = manifestToSymbol(m) - assert(fnSymbol isSubClass FunctionClass(m.typeArguments.size - 1), (owner, m)) - val symbols = m.typeArguments map (m => manifestToSymbol(m)) - val formals = symbols.init map (_.typeConstructor) + assert(fnSymbol isSubClass FunctionClass(m.tpe.typeArguments.size - 1), (owner, m)) + // [Eugene to Paul] needs review!! + // val symbols = m.typeArguments map (m => manifestToSymbol(m)) + // val formals = symbols.init map (_.typeConstructor) + val formals = manifestToType(m).typeArguments val params = owner newSyntheticValueParams formals - - MethodType(params, symbols.last.typeConstructor) + MethodType(params, formals.last) } } import synthesisUtil._ @@ -373,7 +375,7 @@ trait MethodSynthesis { case ExistentialType(_, _) => TypeTree() case tp => TypeTree(tp) } - tpt setPos focusPos(derivedSym.pos) + tpt setPos derivedSym.pos.focus // keep type tree of original abstract field if (mods.isDeferred) tpt setOriginal tree.tpt diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 2539091966..696952fe6a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -624,11 +624,6 @@ trait Namers extends MethodSynthesis { enterCopyMethodOrGetter(tree, tparams) else sym setInfo completerOf(tree, tparams) - - if (mods hasFlag MACRO) { - if (!(sym.owner.isClass && sym.owner.isStatic)) - context.error(tree.pos, "macro definition must appear in globally accessible class") - } } def enterClassDef(tree: ClassDef) { @@ -651,14 +646,6 @@ trait Namers extends MethodSynthesis { val m = ensureCompanionObject(tree) classAndNamerOfModule(m) = (tree, null) } - val hasMacro = impl.body exists { - case DefDef(mods, _, _, _, _, _) => mods hasFlag MACRO - case _ => false - } - if (hasMacro) { - val m = ensureCompanionObject(tree) - classOfModuleClass(m.moduleClass) = new WeakReference(tree) - } val owner = tree.symbol.owner if (owner.isPackageObjectClass) { context.unit.warning(tree.pos, @@ -809,7 +796,9 @@ trait Namers extends MethodSynthesis { */ private def assignTypeToTree(tree: ValOrDefDef, defnTyper: Typer, pt: Type): Type = { // compute result type from rhs - val typedBody = defnTyper.computeType(tree.rhs, pt) + val typedBody = + if (tree.symbol.isTermMacro) defnTyper.computeMacroDefType(tree, pt) + else defnTyper.computeType(tree.rhs, pt) val sym = if (owner.isMethod) owner else tree.symbol val typedDefn = widenIfNecessary(sym, typedBody, pt) assignTypeToTree(tree, typedDefn) @@ -871,10 +860,8 @@ trait Namers extends MethodSynthesis { Namers.this.classOfModuleClass get clazz foreach { cdefRef => val cdef = cdefRef() if (cdef.mods.isCase) addApplyUnapply(cdef, templateNamer) - if (settings.Xmacros.value) addMacroMethods(cdef.impl, templateNamer) classOfModuleClass -= clazz } - if (settings.Xmacros.value) addMacroMethods(templ, templateNamer) } // add the copy method to case classes; this needs to be done here, not in SyntheticMethods, because @@ -1029,12 +1016,20 @@ trait Namers extends MethodSynthesis { } addDefaultGetters(meth, vparamss, tparams, overriddenSymbol) + // macro defs need to be typechecked in advance + // because @macroImpl annotation only gets assigned during typechecking + // otherwise we might find ourselves in the situation when we specified -Xmacro-fallback-classpath + // but macros still don't expand + // that might happen because macro def doesn't have its link a macro impl yet + if (ddef.symbol.isTermMacro) { + val pt = resultPt.substSym(tparamSyms, tparams map (_.symbol)) + typer.computeMacroDefType(ddef, pt) + } + thisMethodType({ val rt = ( if (!tpt.isEmpty) { typer.typedType(tpt).tpe - } else if (meth.isMacro) { - assignTypeToTree(ddef, AnyClass.tpe) } else { // replace deSkolemized symbols with skolemized ones // (for resultPt computed by looking at overridden symbol, right?) diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 806ee480f0..ad727d4082 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -227,6 +227,8 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R * 1.8.1 M's type is a subtype of O's type, or * 1.8.2 M is of type []S, O is of type ()T and S <: T, or * 1.8.3 M is of type ()S, O is of type []T and S <: T, or + * 1.9. If M is a macro def, O cannot be deferred. + * 1.10. If M is not a macro def, O cannot be a macro def. * 2. Check that only abstract classes have deferred members * 3. Check that concrete classes do not have deferred definitions * that are not implemented in a subclass. @@ -416,6 +418,10 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R } else if (other.isValue && other.isLazy && !other.isSourceMethod && !other.isDeferred && member.isValue && !member.isLazy) { overrideError("must be declared lazy to override a concrete lazy value") + } else if (other.isDeferred && member.isTermMacro) { // (1.9) + overrideError("cannot override an abstract method") + } else if (other.isTermMacro && !member.isTermMacro) { // (1.10) + overrideError("cannot override a macro") } else { checkOverrideTypes() if (settings.warnNullaryOverride.value) { diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index 3233b7b07c..38c2c5f719 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -456,14 +456,20 @@ trait TypeDiagnostics { ex match { case CyclicReference(sym, info: TypeCompleter) => - val pos = info.tree match { - case Import(expr, _) => expr.pos - case _ => ex.pos + if (context0.owner.isTermMacro) { + // see comments to TypeSigError for an explanation of this special case + // [Eugene] is there a better way? + throw ex + } else { + val pos = info.tree match { + case Import(expr, _) => expr.pos + case _ => ex.pos + } + contextError(context0, pos, cyclicReferenceMessage(sym, info.tree) getOrElse ex.getMessage()) + + if (sym == ObjectClass) + throw new FatalError("cannot redefine root "+sym) } - contextError(context0, pos, cyclicReferenceMessage(sym, info.tree) getOrElse ex.getMessage()) - - if (sym == ObjectClass) - throw new FatalError("cannot redefine root "+sym) case _ => contextError(context0, ex.pos, ex) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index f558e0afc7..1b508a96fe 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -51,6 +51,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { transformed.clear() } + // [Eugene] shouldn't this be converted to resetAllAttrs? object UnTyper extends Traverser { override def traverse(tree: Tree) = { if (tree != EmptyTree) tree.tpe = null @@ -181,7 +182,10 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { case _ => def wrapImplicit(from: Type): Tree = { val result = inferImplicit(tree, functionType(List(from), to), reportAmbiguous, true, context, saveErrors) - if (result.subst != EmptyTreeTypeSubstituter) result.subst traverse tree + if (result.subst != EmptyTreeTypeSubstituter) { + result.subst traverse tree + notifyUndetparamsInferred(result.subst.from, result.subst.to) + } result.tree } val result = wrapImplicit(from) @@ -813,7 +817,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { case Block(_, tree1) => tree1.symbol case _ => tree.symbol } - if (!meth.isConstructor && !meth.isMacro && isFunctionType(pt)) { // (4.2) + if (!meth.isConstructor && !meth.isTermMacro && isFunctionType(pt)) { // (4.2) debuglog("eta-expanding " + tree + ":" + tree.tpe + " to " + pt) checkParamsConvertible(tree, tree.tpe) val tree0 = etaExpand(context.unit, tree) @@ -1008,12 +1012,13 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { else TypeApply(tree, tparams1 map (tparam => TypeTree(tparam.tpeHK) setPos tree.pos.focus)) setPos tree.pos //@M/tcpolyinfer: changed tparam.tpe to tparam.tpeHK context.undetparams ++= tparams1 + notifyUndetparamsAdded(tparams1) adapt(tree1 setType restpe.substSym(tparams, tparams1), mode, pt, original) case mt: MethodType if mt.isImplicit && ((mode & (EXPRmode | FUNmode | LHSmode)) == EXPRmode) => // (4.1) adaptToImplicitMethod(mt) case mt: MethodType if (((mode & (EXPRmode | FUNmode | LHSmode)) == EXPRmode) && - (context.undetparams.isEmpty || inPolyMode(mode))) => + (context.undetparams.isEmpty || inPolyMode(mode))) && !(tree.symbol != null && tree.symbol.isTermMacro) => instantiateToMethodType(mt) case _ => @@ -1026,13 +1031,10 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { } if (tree.isType) adaptType() - else if (inExprModeButNot(mode, FUNmode) && tree.symbol != null && tree.symbol.isMacro && !tree.isDef && !(tree exists (_.isErroneous))) - macroExpand(tree, this) match { - case Some(expanded: Tree) => - typed(expanded, mode, pt) - case None => - setError(tree) // error already reported - } + else if (context.macrosEnabled && // when macros are enabled + inExprModeButNot(mode, FUNmode) && !tree.isDef && // and typechecking application + tree.symbol != null && tree.symbol.isTermMacro) // of a term macro + macroExpand(this, tree, pt) else if ((mode & (PATTERNmode | FUNmode)) == (PATTERNmode | FUNmode)) adaptConstrPattern() else if (inAllModes(mode, EXPRmode | FUNmode) && @@ -1906,8 +1908,10 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { meth.owner.isAnonOrRefinementClass)) InvalidConstructorDefError(ddef) typed(ddef.rhs) - } else if (meth.isMacro) { - EmptyTree + } else if (meth.isTermMacro) { + // typechecking macro bodies is sort of unconventional + // that's why we employ our custom typing scheme orchestrated outside of the typer + transformedOr(ddef.rhs, typedMacroBody(this, ddef)) } else { transformedOrTyped(ddef.rhs, EXPRmode, tpt1.tpe) } @@ -2212,7 +2216,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { (ps, sel) case Some((vparams, sel)) => val newParamSyms = (vparams, formals).zipped map {(p, tp) => - methodSym.newValueParameter(p.name, focusPos(p.pos), SYNTHETIC) setInfo tp + methodSym.newValueParameter(p.name, p.pos.focus, SYNTHETIC) setInfo tp } (newParamSyms, sel.duplicate) @@ -2267,7 +2271,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { else { // applyOrElse's default parameter: val B1 = methodSym newTypeParameter(newTypeName("B1")) setInfo TypeBounds.empty //lower(resTp) - val default = methodSym newValueParameter(newTermName("default"), focusPos(tree.pos), SYNTHETIC) setInfo functionType(List(A1.tpe), B1.tpe) + val default = methodSym newValueParameter(newTermName("default"), tree.pos.focus, SYNTHETIC) setInfo functionType(List(A1.tpe), B1.tpe) val paramSyms = List(x, default) methodSym setInfoAndEnter polyType(List(A1, B1), MethodType(paramSyms, B1.tpe)) @@ -2489,7 +2493,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { var e1 = scope.lookupNextEntry(e) while ((e1 ne null) && e1.owner == scope) { if (!accesses(e.sym, e1.sym) && !accesses(e1.sym, e.sym) && - (e.sym.isType || inBlock || (e.sym.tpe matches e1.sym.tpe) || e.sym.isMacro && e1.sym.isMacro)) + (e.sym.isType || inBlock || (e.sym.tpe matches e1.sym.tpe))) // default getters are defined twice when multiple overloads have defaults. an // error for this is issued in RefChecks.checkDefaultsInOverloaded if (!e.sym.isErroneous && !e1.sym.isErroneous && !e.sym.hasDefault && @@ -2575,7 +2579,8 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { else if (isByNameParamType(formals.head)) 0 else BYVALmode ) - val tree = typedArg(args.head, mode, typedMode, adapted.head) + var tree = typedArg(args.head, mode, typedMode, adapted.head) + if (hasPendingMacroExpansions) tree = macroExpandAll(this, tree) // formals may be empty, so don't call tail tree :: loop(args.tail, formals drop 1, adapted.tail) } @@ -2737,6 +2742,11 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { def tryNamesDefaults: Tree = { val lencmp = compareLengths(args, formals) + def checkNotMacro() = { + if (fun.symbol != null && fun.symbol.filter(sym => sym != null && sym.isTermMacro) != NoSymbol) + duplErrorTree(NamedAndDefaultArgumentsNotSupportedForMacros(tree, fun)) + } + if (mt.isErroneous) duplErrTree else if (inPatternMode(mode)) { // #2064 @@ -2755,8 +2765,10 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { else if (isIdentity(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) } @@ -2764,6 +2776,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { // 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() val fun1 = transformNamedApplication(Typer.this, mode, pt)(fun, x => x) if (fun1.isErroneous) duplErrTree else { @@ -3111,7 +3124,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { } if (hasError) annotationError - else AnnotationInfo(annType, List(), nvPairs map {p => (p._1, p._2.get)}).setOriginal(ann).setPos(ann.pos) + else AnnotationInfo(annType, List(), nvPairs map {p => (p._1, p._2.get)}).setOriginal(Apply(typedFun, args)).setPos(ann.pos) } } else if (requireJava) { reportAnnotationError(NestedAnnotationError(ann, annType)) @@ -3151,7 +3164,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { def annInfo(t: Tree): AnnotationInfo = t match { case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => - AnnotationInfo(annType, args, List()).setOriginal(ann).setPos(t.pos) + AnnotationInfo(annType, args, List()).setOriginal(typedAnn).setPos(t.pos) case Block(stats, expr) => context.warning(t.pos, "Usage of named or default arguments transformed this annotation\n"+ @@ -3437,7 +3450,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { println(s) } - protected def typed1(tree: Tree, mode: Int, pt: Type): Tree = { + def typed1(tree: Tree, mode: Int, pt: Type): Tree = { def isPatternMode = inPatternMode(mode) //Console.println("typed1("+tree.getClass()+","+Integer.toHexString(mode)+","+pt+")") @@ -3451,10 +3464,27 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { } def typedAnnotated(ann: Tree, arg1: Tree): Tree = { - def mkTypeTree(tpe: Type) = TypeTree(tpe) setOriginal tree setPos tree.pos.focus /** mode for typing the annotation itself */ val annotMode = mode & ~TYPEmode | EXPRmode + def resultingTypeTree(tpe: Type) = { + // we need symbol-ful originals for reification + // hence we go the extra mile to hand-craft tis guy + val original = + if (arg1.isType) + (tree, arg1) match { + case (Annotated(annot, arg), tt @ TypeTree()) => Annotated(annot, tt.original) + // this clause is needed to correctly compile stuff like "new C @D" or "@(inline @getter)" + case (Annotated(annot, arg), _) => Annotated(annot, arg1) + case _ => throw new Error("unexpected trees in typedAnnotated: tree = %s, arg1 = %s".format(showRaw(tree), showRaw(arg1))) + } + else + tree + original setType ann.tpe + original setPos tree.pos.focus + TypeTree(tpe) setOriginal original setPos tree.pos.focus + } + if (arg1.isType) { // make sure the annotation is only typechecked once if (ann.tpe == null) { @@ -3497,11 +3527,11 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { arg1 // simply drop erroneous annotations else { ann.tpe = atype - mkTypeTree(atype) + resultingTypeTree(atype) } } else { // the annotation was typechecked before - mkTypeTree(ann.tpe) + resultingTypeTree(ann.tpe) } } else { @@ -3510,7 +3540,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { ann.tpe = arg1.tpe.withAnnotation(annotInfo) } val atype = ann.tpe - Typed(arg1, mkTypeTree(atype)) setPos tree.pos setType atype + Typed(arg1, resultingTypeTree(atype)) setPos tree.pos.focus setType atype } } @@ -3676,6 +3706,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { if (checkStablePrefixClassType(tpt0)) if (tpt0.hasSymbol && !tpt0.symbol.typeParams.isEmpty) { context.undetparams = cloneSymbols(tpt0.symbol.typeParams) + notifyUndetparamsAdded(context.undetparams) TypeTree().setOriginal(tpt0) .setType(appliedType(tpt0.tpe, context.undetparams map (_.tpeHK))) // @PP: tpeHK! #3343, #4018, #4347. } else tpt0 @@ -4534,7 +4565,18 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { typedNew(tpt) case Typed(expr, Function(List(), EmptyTree)) => - typedEta(checkDead(typed1(expr, mode, pt))) + // find out whether the programmer is trying to eta-expand a macro def + // to do that we need to typecheck the tree first (we need a symbol of the eta-expandee) + // that typecheck must not trigger macro expansions, so we explicitly prohibit them + // Q: "but, " - you may ask - ", `typed1` doesn't call adapt, which does macro expansion, so why explicit check?" + // A: solely for robustness reasons. this mechanism might change in the future, which might break unprotected code + val expr1 = context.withMacrosDisabled(typed1(expr, mode, pt)) + expr1 match { + case macroDef if macroDef.symbol.isTermMacro => + MacroEtaError(expr1) + case _ => + typedEta(checkDead(expr1)) + } case Typed(expr0, tpt @ Ident(tpnme.WILDCARD_STAR)) => val expr = typed(expr0, onlyStickyModes(mode), WildcardType) @@ -4608,18 +4650,17 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { tpt.tpe.typeSymbol == ArrayClass && args.length == 1 && erasure.GenericArray.unapply(tpt.tpe).isDefined) => // !!! todo simplify by using extractor - // convert new Array[T](len) to evidence[ClassManifest[T]].newArray(len) - // convert new Array^N[T](len) for N > 1 to evidence[ClassManifest[T]].newArrayN(len) - val Some((level, manifType)) = erasure.GenericArray.unapply(tpt.tpe) - if (level > MaxArrayDims) - MultiDimensionalArrayError(tree) - else { - val newArrayApp = atPos(tree.pos) { - val manif = getManifestTree(tree, manifType, false) - new ApplyToImplicitArgs(Select(manif, if (level == 1) "newArray" else "newArray"+level), args) - } - typed(newArrayApp, mode, pt) + // convert new Array[T](len) to evidence[ClassTag[T]].newArray(len) + // convert new Array^N[T](len) for N > 1 to evidence[ClassTag[Array[...Array[T]...]]].newArray(len), where Array HK gets applied (N-1) times + // [Eugene] no more MaxArrayDims. ClassTags are flexible enough to allow creation of arrays of arbitrary dimensionality (w.r.t JVM restrictions) + val Some((level, componentType)) = erasure.GenericArray.unapply(tpt.tpe) + val tagType = List.iterate(componentType, level)(tpe => appliedType(ArrayClass.asType, List(tpe))).last + val newArrayApp = atPos(tree.pos) { + val tag = resolveClassTag(tree, tagType) + if (tag.isEmpty) MissingClassTagError(tree, tagType) + else new ApplyToImplicitArgs(Select(tag, nme.newArray), args) } + typed(newArrayApp, mode, pt) case tree1 => tree1 } @@ -4679,7 +4720,10 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { case ReferenceToBoxed(idt @ Ident(_)) => val id1 = typed1(idt, mode, pt) match { case id: Ident => id } - treeCopy.ReferenceToBoxed(tree, id1) setType AnyRefClass.tpe + // [Eugene] am I doing it right? + val erasedTypes = phaseId(currentPeriod) >= currentRun.erasurePhase.id + val tpe = capturedVariableType(idt.symbol, erasedTypes = erasedTypes) + treeCopy.ReferenceToBoxed(tree, id1) setType tpe case Literal(value) => tree setType ( @@ -4916,31 +4960,75 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { def typedTypeConstructor(tree: Tree): Tree = typedTypeConstructor(tree, NOmode) def computeType(tree: Tree, pt: Type): Type = { + // macros employ different logic of `computeType` + assert(!context.owner.isTermMacro, context.owner) val tree1 = typed(tree, pt) transformed(tree) = tree1 packedType(tree1, context.owner) } + def computeMacroDefType(tree: Tree, pt: Type): Type = { + assert(context.owner.isTermMacro, context.owner) + assert(tree.symbol.isTermMacro, tree.symbol) + assert(tree.isInstanceOf[DefDef], tree.getClass) + val ddef = tree.asInstanceOf[DefDef] + + val tree1 = + if (transformed contains ddef.rhs) { + // macro defs are typechecked in `methodSig` (by calling this method) in order to establish their link to macro implementation asap + // if a macro def doesn't have explicitly specified return type, this method will be called again by `assignTypeToTree` + // here we guard against this case + transformed(ddef.rhs) + } else { + val tree1 = typedMacroBody(this, ddef) + transformed(ddef.rhs) = tree1 + tree1 + } + + val isMacroBodyOkay = !tree.symbol.isErroneous && !(tree1 exists (_.isErroneous)) + if (isMacroBodyOkay) computeMacroDefTypeFromMacroImpl(ddef, tree.symbol, tree1.symbol) else AnyClass.tpe + } + + def transformedOr(tree: Tree, op: => Tree): Tree = transformed.get(tree) match { + case Some(tree1) => transformed -= tree; tree1 + case None => op + } + def transformedOrTyped(tree: Tree, mode: Int, pt: Type): Tree = transformed.get(tree) match { case Some(tree1) => transformed -= tree; tree1 case None => typed(tree, mode, pt) } - def findManifest(tp: Type, full: Boolean) = beforeTyper { + // `tree` is only necessary here for its position + // but that's invaluable for error reporting, so I decided to include it into this method's contract + // before passing EmptyTree, please, consider passing something meaningful first + def resolveClassTag(tree: Tree, tp: Type): Tree = beforeTyper { inferImplicit( EmptyTree, - appliedType((if (full) FullManifestClass else PartialManifestClass).typeConstructor, List(tp)), - true, false, context) + appliedType(ClassTagClass.typeConstructor, List(tp)), + /*reportAmbiguous =*/ true, + /*isView =*/ false, + /*context =*/ context, + /*saveAmbiguousDivergent =*/ true, + /*pos =*/ tree.pos + ).tree } - def getManifestTree(tree: Tree, tp: Type, full: Boolean): Tree = { - val manifestOpt = findManifest(tp, full) - if (manifestOpt.tree.isEmpty) { - MissingManifestError(tree, full, tp) - } else { - manifestOpt.tree - } + // `tree` is only necessary here for its position + // but that's invaluable for error reporting, so I decided to include it into this method's contract + // before passing EmptyTree, please, consider passing something meaningful first + def resolveTypeTag(tree: Tree, pre: Type, tp: Type, full: Boolean): Tree = beforeTyper { + inferImplicit( + EmptyTree, + appliedType(singleType(pre, pre member (if (full) GroundTypeTagClass else TypeTagClass).name), List(tp)), + /*reportAmbiguous =*/ true, + /*isView =*/ false, + /*context =*/ context, + /*saveAmbiguousDivergent =*/ true, + /*pos =*/ tree.pos + ).tree } + /* def convertToTypeTree(tree: Tree): Tree = tree match { case TypeTree() => tree |