diff options
Diffstat (limited to 'src')
27 files changed, 629 insertions, 450 deletions
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 2b0f082051..c60a3c941f 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -220,11 +220,11 @@ class Global(var currentSettings: Settings, var reporter: Reporter) // not deprecated yet, but a method called "error" imported into // nearly every trait really must go. For now using globalError. - def error(msg: String) = globalError(msg) - def globalError(msg: String) = reporter.error(NoPosition, msg) - def inform(msg: String) = reporter.echo(msg) - def warning(msg: String) = - if (opt.fatalWarnings) globalError(msg) + def error(msg: String) = globalError(msg) + def inform(msg: String) = reporter.echo(msg) + override def globalError(msg: String) = reporter.error(NoPosition, msg) + override def warning(msg: String) = + if (settings.fatalWarnings.value) globalError(msg) else reporter.warning(NoPosition, msg) // Getting in front of Predef's asserts to supplement with more info. diff --git a/src/compiler/scala/tools/nsc/ast/Printers.scala b/src/compiler/scala/tools/nsc/ast/Printers.scala index 8b92f0acd6..885fc3f518 100644 --- a/src/compiler/scala/tools/nsc/ast/Printers.scala +++ b/src/compiler/scala/tools/nsc/ast/Printers.scala @@ -278,6 +278,7 @@ trait Printers extends reflect.internal.Printers { this: Global => def asString(t: Tree): String = render(t, newStandardTreePrinter, settings.printtypes.value, settings.uniqid.value, settings.Yshowsymkinds.value) def asCompactString(t: Tree): String = render(t, newCompactTreePrinter, settings.printtypes.value, settings.uniqid.value, settings.Yshowsymkinds.value) + def asCompactDebugString(t: Tree): String = render(t, newCompactTreePrinter, true, true, true) def newStandardTreePrinter(writer: PrintWriter): TreePrinter = new TreePrinter(writer) def newStandardTreePrinter(stream: OutputStream): TreePrinter = newStandardTreePrinter(new PrintWriter(stream)) diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index 720b1347ef..dbc34bd7b3 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -111,6 +111,12 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) "only use it if you haven't defined usecase for implicitly inherited members." ) + val docImplicitsHide = MultiStringSetting ( + "-implicits-hide", + "implicit(s)", + "Hide the members inherited by the given comma separated, fully qualified implicit conversions. Add dot (.) to include default conversions." + ) + val docDiagrams = BooleanSetting ( "-diagrams", "Create inheritance diagrams for classes, traits and packages." @@ -203,7 +209,7 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) docformat, doctitle, docfooter, docversion, docUncompilable, docsourceurl, docgenerator, docRootContent, useStupidTypes, docDiagrams, docDiagramsDebug, docDiagramsDotPath, docDiagramsDotTimeout, docDiagramsDotRestart, - docImplicits, docImplicitsDebug, docImplicitsShowAll, + docImplicits, docImplicitsDebug, docImplicitsShowAll, docImplicitsHide, docDiagramsMaxNormalClasses, docDiagramsMaxImplicitClasses, docNoPrefixes, docNoLinkWarnings, docRawOutput, docSkipPackages, docExpandAllTypes, docGroups @@ -224,6 +230,14 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) def skipPackage(qname: String) = skipPackageNames(qname.toLowerCase) + lazy val hiddenImplicits: Set[String] = { + if (docImplicitsHide.value.isEmpty) hardcoded.commonConversionTargets + else docImplicitsHide.value.toSet flatMap { name: String => + if(name == ".") hardcoded.commonConversionTargets + else Set(name) + } + } + /** * This is the hardcoded area of Scaladoc. This is where "undesirable" stuff gets eliminated. I know it's not pretty, * but ultimately scaladoc has to be useful. :) @@ -264,7 +278,7 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) } /** Common conversion targets that affect any class in Scala */ - val commonConversionTargets = List( + val commonConversionTargets = Set( "scala.Predef.any2stringfmt", "scala.Predef.any2stringadd", "scala.Predef.any2ArrowAssoc", diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index 5977acc0c2..1f68781777 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -148,8 +148,13 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp <div id="ancestors"> <span class="filtertype">Implicitly<br/> </span> - <ol id="implicits"> - { tpl.conversions.map(conv => <li class="in" name={ conv.conversionQualifiedName }><span>{ "by " + conv.conversionShortName }</span></li>) } + <ol id="implicits"> { + tpl.conversions.map { conv => + val name = conv.conversionQualifiedName + val hide = universe.settings.hiddenImplicits(name) + <li class="in" name={ name } data-hidden={ hide.toString }><span>{ "by " + conv.conversionShortName }</span></li> + } + } </ol> </div> else NodeSeq.Empty diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js index afd0293fe1..5920fdfdb2 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js @@ -4,12 +4,11 @@ $(document).ready(function(){ var isHiddenClass = function (name) { return name == 'scala.Any' || - name == 'scala.AnyRef' || - name == 'scala.Predef.any2stringfmt' || - name == 'scala.Predef.any2stringadd' || - name == 'scala.Predef.any2ArrowAssoc' || - name == 'scala.Predef.any2Ensuring' || - name == 'scala.collection.TraversableOnce.alternateImplicit' + name == 'scala.AnyRef'; + }; + + var isHidden = function (elem) { + return $(elem).attr("data-hidden") == 'true'; }; $("#linearization li:gt(0)").filter(function(){ @@ -17,7 +16,7 @@ $(document).ready(function(){ }).removeClass("in").addClass("out"); $("#implicits li").filter(function(){ - return isHiddenClass($(this).attr("name")); + return isHidden(this); }).removeClass("in").addClass("out"); // Pre-filter members @@ -113,7 +112,7 @@ $(document).ready(function(){ var filteredImplicits = $("#implicits li.out").filter(function() { - return ! isHiddenClass($(this).attr("name")); + return ! isHidden(this); }); filteredImplicits.removeClass("out").addClass("in"); diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index 46b2a11d4a..6d193c30f7 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -530,8 +530,8 @@ trait ImplicitConversion { /** The members inherited by this implicit conversion */ def members: List[MemberEntity] - /** Is this a common implicit conversion (aka conversion that affects all classes, in Predef?) */ - def isCommonConversion: Boolean + /** Is this a hidden implicit conversion (as specified in the settings) */ + def isHiddenConversion: Boolean } /** Shadowing captures the information that the member is shadowed by some other members diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index a962ec4007..ed8541f692 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -1075,7 +1075,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def classExcluded(clazz: TemplateEntity): Boolean = settings.hardcoded.isExcluded(clazz.qualifiedName) // the implicit conversions that are excluded from the pages should not appear in the diagram - def implicitExcluded(convertorMethod: String): Boolean = settings.hardcoded.commonConversionTargets.contains(convertorMethod) + def implicitExcluded(convertorMethod: String): Boolean = settings.hiddenImplicits(convertorMethod) // whether or not to create a page for an {abstract,alias} type def typeShouldDocument(bSym: Symbol, inTpl: DocTemplateImpl) = diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index 5a0cc602e5..327436ed20 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -113,9 +113,9 @@ trait ModelFactoryImplicitSupport { conversions = conversions.filter((ic: ImplicitConversionImpl) => hardcoded.valueClassFilter(sym.nameString, ic.conversionQualifiedName)) - // Put the class-specific conversions in front + // Put the visible conversions in front val (ownConversions, commonConversions) = - conversions.partition(!_.isCommonConversion) + conversions.partition(!_.isHiddenConversion) ownConversions ::: commonConversions } @@ -416,7 +416,7 @@ trait ModelFactoryImplicitSupport { lazy val members: List[MemberEntity] = memberImpls - def isCommonConversion = hardcoded.commonConversionTargets.contains(conversionQualifiedName) + def isHiddenConversion = settings.hiddenImplicits(conversionQualifiedName) override def toString = "Implcit conversion from " + sym.tpe + " to " + toType + " done by " + convSym } diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index 8e928dc9e6..a06c3f78ea 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -497,7 +497,10 @@ abstract class ExplicitOuter extends InfoTransform else atPos(tree.pos)(outerPath(outerValue, currentClass.outerClass, sym)) // (5) case Select(qual, name) => - if (currentClass != sym.owner) + // make not private symbol acessed from inner classes, as well as + // symbols accessed from @inline methods + if (currentClass != sym.owner || + (sym.owner.enclMethod hasAnnotation ScalaInlineClass)) sym.makeNotPrivate(sym.owner) val qsym = qual.tpe.widen.typeSymbol diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index 10a946c318..db97308f41 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -452,7 +452,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { def survivingParams(params: List[Symbol], env: TypeEnv) = params filter { p => - !p.isSpecialized || + !p.isSpecialized || !env.contains(p) || !isPrimitiveValueType(env(p)) } @@ -506,16 +506,16 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { * was both already used for a map and mucho long. So "sClass" is the * specialized subclass of "clazz" throughout this file. */ - + // SI-5545: Eliminate classes with the same name loaded from the bytecode already present - all we need to do is // to force .info on them, as their lazy type will be evaluated and the symbols will be eliminated. Unfortunately // evaluating the info after creating the specialized class will mess the specialized class signature, so we'd - // better evaluate it before creating the new class symbol + // better evaluate it before creating the new class symbol val clazzName = specializedName(clazz, env0).toTypeName - val bytecodeClazz = clazz.owner.info.decl(clazzName) + val bytecodeClazz = clazz.owner.info.decl(clazzName) // debuglog("Specializing " + clazz + ", but found " + bytecodeClazz + " already there") bytecodeClazz.info - + val sClass = clazz.owner.newClass(clazzName, clazz.pos, (clazz.flags | SPECIALIZED) & ~CASE) def cloneInSpecializedClass(member: Symbol, flagFn: Long => Long, newName: Name = null) = @@ -652,11 +652,10 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { info(specMember) = Implementation(original) typeEnv(specMember) = env ++ typeEnv(m) - } - else debuglog({ + } else { val om = forwardToOverload(m) - "normalizedMember " + m + " om: " + om + " " + pp(typeEnv(om)) - }) + debuglog("normalizedMember " + m + " om: " + om + " " + pp(typeEnv(om))) + } } else debuglog("conflicting env for " + m + " env: " + env) @@ -762,7 +761,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } } } - + val subclasses = specializations(clazz.info.typeParams) filter satisfiable subclasses foreach { env => @@ -1006,7 +1005,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { * Fails if such an environment cannot be found. * * If `strict` is true, a UnifyError is thrown if unification is impossible. - * + * * If `tparams` is true, then the methods tries to unify over type params in polytypes as well. */ private def unify(tp1: Type, tp2: Type, env: TypeEnv, strict: Boolean, tparams: Boolean = false): TypeEnv = (tp1, tp2) match { @@ -1185,7 +1184,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { || specializedTypeVars(t1).nonEmpty || specializedTypeVars(t2).nonEmpty) } - + env forall { case (tvar, tpe) => matches(tvar.info.bounds.lo, tpe) && matches(tpe, tvar.info.bounds.hi) || { if (warnings) @@ -1201,7 +1200,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } } } - + def satisfiabilityConstraints(env: TypeEnv): Option[TypeEnv] = { val noconstraints = Some(emptyEnv) def matches(tpe1: Type, tpe2: Type): Option[TypeEnv] = { @@ -1232,7 +1231,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } with typechecker.Duplicators { private val (castfrom, castto) = casts.unzip private object CastMap extends SubstTypeMap(castfrom.toList, castto.toList) - + class BodyDuplicator(_context: Context) extends super.BodyDuplicator(_context) { override def castType(tree: Tree, pt: Type): Tree = { // log(" expected type: " + pt) @@ -1249,9 +1248,9 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { ntree } } - + protected override def newBodyDuplicator(context: Context) = new BodyDuplicator(context) - + } /** A tree symbol substituter that substitutes on type skolems. @@ -1359,7 +1358,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } } } - + def reportError[T](body: =>T)(handler: TypeError => T): T = try body catch { @@ -1396,7 +1395,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { else None } else None } - + curTree = tree tree match { case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => @@ -1570,7 +1569,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { }) debuglog("created special overload tree " + t) debuglog("created " + t) - reportError { + reportError { localTyper.typed(t) } { _ => super.transform(tree) @@ -1629,9 +1628,9 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { super.transform(tree) } } - + /** Duplicate the body of the given method `tree` to the new symbol `source`. - * + * * Knowing that the method can be invoked only in the `castmap` type environment, * this method will insert casts for all the expressions of types mappend in the * `castmap`. diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 7c5d458fee..c8bf70e9e0 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -11,6 +11,7 @@ import reflect.internal.util.Statistics import scala.reflect.macros.util._ import java.lang.{Class => jClass} import java.lang.reflect.{Array => jArray, Method => jMethod} +import scala.reflect.internal.util.Collections._ /** * Code to deal with macros, namely with: @@ -48,6 +49,171 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { val globalMacroCache = collection.mutable.Map[Any, Any]() val perRunMacroCache = perRunCaches.newMap[Symbol, collection.mutable.Map[Any, Any]] + /** `MacroImplBinding` and its companion module are responsible for + * serialization/deserialization of macro def -> impl bindings. + * + * The first officially released version of macros persisted these bindings across compilation runs + * using a neat trick. The right-hand side of a macro definition (which contains a reference to a macro impl) + * was typechecked and then put verbatim into an annotation on the macro definition. + * + * This solution is very simple, but unfortunately it's also lacking. If we use it, then + * signatures of macro defs become transitively dependent on scala-reflect.jar + * (because they refer to macro impls, and macro impls refer to scala.reflect.macros.Context defined in scala-reflect.jar). + * More details can be found in comments to https://issues.scala-lang.org/browse/SI-5940. + * + * Therefore we have to avoid putting macro impls into binding pickles and come up with our own serialization format. + * Situation is further complicated by the fact that it's not enough to just pickle macro impl's class name and method name, + * because macro expansion needs some knowledge about the shape of macro impl's signature (which we can't pickle). + * Hence we precompute necessary stuff (e.g. the layout of type parameters) when compiling macro defs. + */ + + /** Represents all the information that a macro definition needs to know about its implementation. + * Includes a path to load the implementation via Java reflection, + * and various accounting information necessary when composing an argument list for the reflective invocation. + */ + private case class MacroImplBinding( + // Java class name of the class that contains the macro implementation + // is used to load the corresponding object with Java reflection + val className: String, + // method name of the macro implementation + // `className` and `methName` are all we need to reflectively invoke a macro implementation + // because macro implementations cannot be overloaded + val methName: String, + // flattens the macro impl's parameter lists having symbols replaced with metadata + // currently metadata is an index of the type parameter corresponding to that type tag (if applicable) + // f.ex. for: def impl[T: AbsTypeTag, U: AbsTypeTag, V](c: Context)(x: c.Expr[T]): (U, V) = ??? + // `signature` will be equal to List(-1, -1, 0, 1) + val signature: List[Int], + // type arguments part of a macro impl ref (the right-hand side of a macro definition) + // these trees don't refer to a macro impl, so we can pickle them as is + val targs: List[Tree]) + + /** Macro def -> macro impl bindings are serialized into a `macroImpl` annotation + * with synthetic content that carries the payload described in `MacroImplBinding`. + * + * For example, for a pair of macro definition and macro implementation: + * def impl(c: scala.reflect.macros.Context): c.Expr[Unit] = c.literalUnit; + * def foo: Unit = macro impl + * + * We will have the following annotation added on the macro definition `foo`: + * + * @scala.reflect.macros.internal.macroImpl( + * `macro`( + * "signature" = List(-1), + * "methodName" = "impl", + * "versionFormat" = 1, + * "className" = "Macros$")) + */ + private object MacroImplBinding { + val versionFormat = 1 + + def pickleAtom(obj: Any): Tree = + obj match { + case list: List[_] => Apply(Ident(ListModule), list map pickleAtom) + case s: String => Literal(Constant(s)) + case i: Int => Literal(Constant(i)) + } + + def unpickleAtom(tree: Tree): Any = + tree match { + case Apply(list @ Ident(_), args) if list.symbol == ListModule => args map unpickleAtom + case Literal(Constant(s: String)) => s + case Literal(Constant(i: Int)) => i + } + + def pickle(macroImplRef: Tree): Tree = { + val macroImpl = macroImplRef.symbol + val paramss = macroImpl.paramss + + // this logic relies on the assumptions that were valid for the old macro prototype + // namely that macro implementations can only be defined in top-level classes and modules + // with the new prototype that materialized in a SIP, macros need to be statically accessible, which is different + // for example, a macro def could be defined in a trait that is implemented by an object + // there are some more clever cases when seemingly non-static method ends up being statically accessible + // however, the code below doesn't account for these guys, because it'd take a look of time to get it right + // for now I leave it as a todo and move along to more the important stuff + // [Eugene] relies on the fact that macro implementations can only be defined in static classes + // [Martin to Eugene++] There's similar logic buried in Symbol#flatname. Maybe we can refactor? + // [Eugene] we will refactor once I get my hands on https://issues.scala-lang.org/browse/SI-5498 + def className: String = { + def loop(sym: Symbol): String = sym match { + case sym if sym.owner.isPackageClass => + val suffix = if (sym.isModuleClass) "$" else "" + sym.fullName + suffix + case sym => + val separator = if (sym.owner.isModuleClass) "" else "$" + loop(sym.owner) + separator + sym.javaSimpleName.toString + } + + loop(macroImpl.owner.enclClass) + } + + def signature: List[Int] = { + val transformed = transformTypeTagEvidenceParams(paramss, (param, tparam) => tparam) + transformed.flatten map (p => if (p.isTerm) -1 else p.paramPos) + } + + val payload = List[(String, Any)]( + "versionFormat" -> versionFormat, + "className" -> className, + "methodName" -> macroImpl.name.toString, + "signature" -> signature + ) + + // the shape of the nucleus is chosen arbitrarily. it doesn't carry any payload. + // it's only necessary as a stub `fun` for an Apply node that carries metadata in its `args` + // so don't try to find a program element named "macro" that corresponds to the nucleus + // I just named it "macro", because it's macro-related, but I could as well name it "foobar" + val nucleus = Ident(newTermName("macro")) + val wrapped = Apply(nucleus, payload map { case (k, v) => Assign(pickleAtom(k), pickleAtom(v)) }) + val pickle = gen.mkTypeApply(wrapped, treeInfo.typeArguments(macroImplRef.duplicate)) + + // assign NoType to all freshly created AST nodes + // otherwise pickler will choke on tree.tpe being null + // there's another gotcha + // if you don't assign a ConstantType to a constant + // then pickling will crash + new Transformer { + override def transform(tree: Tree) = { + tree match { + case Literal(const @ Constant(x)) if tree.tpe == null => tree setType ConstantType(const) + case _ if tree.tpe == null => tree setType NoType + case _ => ; + } + super.transform(tree) + } + }.transform(pickle) + } + + def unpickle(pickle: Tree): MacroImplBinding = { + val (wrapped, targs) = + pickle match { + case TypeApply(wrapped, targs) => (wrapped, targs) + case wrapped => (wrapped, Nil) + } + val Apply(_, pickledPayload) = wrapped + val payload = pickledPayload.map{ case Assign(k, v) => (unpickleAtom(k), unpickleAtom(v)) }.toMap + + val pickleVersionFormat = payload("versionFormat").asInstanceOf[Int] + if (versionFormat != pickleVersionFormat) throw new Error("macro impl binding format mismatch: expected $versionFormat, actual $pickleVersionFormat") + + val className = payload("className").asInstanceOf[String] + val methodName = payload("methodName").asInstanceOf[String] + val signature = payload("signature").asInstanceOf[List[Int]] + MacroImplBinding(className, methodName, signature, targs) + } + } + + private def bindMacroImpl(macroDef: Symbol, macroImplRef: Tree): Unit = { + val pickle = MacroImplBinding.pickle(macroImplRef) + macroDef withAnnotation AnnotationInfo(MacroImplAnnotation.tpe, List(pickle), Nil) + } + + private def loadMacroImplBinding(macroDef: Symbol): MacroImplBinding = { + val Some(AnnotationInfo(_, List(pickle), _)) = macroDef.getAnnotation(MacroImplAnnotation) + MacroImplBinding.unpickle(pickle) + } + /** A list of compatible macro implementation signatures. * * In the example above: @@ -144,32 +310,22 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { macroTraceVerbose("macroImplSigs are: ")(paramsss, implRetTpe) } - private def transformTypeTagEvidenceParams(paramss: List[List[Symbol]], transform: (Symbol, Symbol) => Option[Symbol]): List[List[Symbol]] = { - if (paramss.length == 0) + private def transformTypeTagEvidenceParams(paramss: List[List[Symbol]], transform: (Symbol, Symbol) => Symbol): List[List[Symbol]] = { + import definitions.{ AbsTypeTagClass, MacroContextClass } + if (paramss.isEmpty || paramss.last.isEmpty) return paramss - val wannabe = if (paramss.head.length == 1) paramss.head.head else NoSymbol - val contextParam = if (wannabe != NoSymbol && wannabe.tpe <:< definitions.MacroContextClass.tpe) wannabe else NoSymbol - - val lastParamList0 = paramss.lastOption getOrElse Nil - val lastParamList = lastParamList0 flatMap (param => param.tpe match { - case TypeRef(SingleType(NoPrefix, contextParam), sym, List(tparam)) => - var wannabe = sym - while (wannabe.isAliasType) wannabe = wannabe.info.typeSymbol - if (wannabe != definitions.AbsTypeTagClass) - List(param) - else - transform(param, tparam.typeSymbol) map (_ :: Nil) getOrElse Nil - case _ => - List(param) - }) - - var result = paramss.dropRight(1) :+ lastParamList - if (lastParamList0.isEmpty ^ lastParamList.isEmpty) { - result = result dropRight 1 + val ContextParam = paramss.head match { + case p :: Nil => p filter (_.tpe <:< definitions.MacroContextClass.tpe) + case _ => NoSymbol } - - result + def isTag(sym: Symbol): Boolean = (sym == AbsTypeTagClass) || (sym.isAliasType && isTag(sym.info.typeSymbol)) + def transformTag(param: Symbol): Symbol = param.tpe match { + case TypeRef(SingleType(NoPrefix, ContextParam), sym, tp :: Nil) if isTag(sym) => transform(param, tp.typeSymbol) + case _ => param + } + val last = paramss.last map transformTag filterNot (_ eq NoSymbol) + if (last.isEmpty) paramss.init else paramss.init :+ last } /** As specified above, body of a macro definition must reference its implementation. @@ -186,6 +342,11 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { import typer.context macroLogVerbose("typechecking macro def %s at %s".format(ddef.symbol, ddef.pos)) + val macroDef = ddef.symbol + val defpos = macroDef.pos + val implpos = ddef.rhs.pos + assert(macroDef.isTermMacro, ddef) + if (fastTrack contains ddef.symbol) { macroLogVerbose("typecheck terminated unexpectedly: macro is hardwired") assert(!ddef.tpt.isEmpty, "hardwired macros must provide result type") @@ -207,17 +368,18 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } } - var hasErrors = false + var _hasError = false + def hasError = _hasError + def setError(): Unit = { + _hasError = true + macroDef setFlag IS_ERROR + } def reportError(pos: Position, msg: String) = { - hasErrors = true + setError() context.error(pos, msg) + macroDef setFlag IS_ERROR } - val macroDef = ddef.symbol - val defpos = macroDef.pos - val implpos = ddef.rhs.pos - assert(macroDef.isTermMacro, ddef) - def invalidBodyError() = reportError(defpos, "macro body has wrong shape:" + @@ -275,7 +437,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { val rhs = ddef.rhs validatePreTyper(rhs) - if (hasErrors) macroTraceVerbose("macro def failed to satisfy trivial preconditions: ")(macroDef) + if (hasError) macroTraceVerbose("macro def failed to satisfy trivial preconditions: ")(macroDef) // we use typed1 instead of typed, because otherwise adapt is going to mess us up // if adapt sees <qualifier>.<method>, it will want to perform eta-expansion and will fail @@ -284,7 +446,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { def typecheckRhs(rhs: Tree): Tree = { try { val prevNumErrors = reporter.ERROR.count // [Eugene] funnily enough, the isErroneous check is not enough - var rhs1 = if (hasErrors) EmptyTree else typer.typed1(rhs, EXPRmode, WildcardType) + var rhs1 = if (hasError) EmptyTree else typer.typed1(rhs, EXPRmode, WildcardType) def typecheckedWithErrors = (rhs1 exists (_.isErroneous)) || reporter.ERROR.count != prevNumErrors def rhsNeedsMacroExpansion = rhs1.symbol != null && rhs1.symbol.isTermMacro && !rhs1.symbol.isErroneous while (!typecheckedWithErrors && rhsNeedsMacroExpansion) { @@ -313,35 +475,41 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { val prevNumErrors = reporter.ERROR.count // funnily enough, the isErroneous check is not enough var rhs1 = typecheckRhs(rhs) - def typecheckedWithErrors = (rhs1 exists (_.isErroneous)) || reporter.ERROR.count != prevNumErrors - hasErrors = hasErrors || typecheckedWithErrors - if (typecheckedWithErrors) macroTraceVerbose("body of a macro def failed to typecheck: ")(ddef) - val macroImpl = rhs1.symbol - macroDef withAnnotation AnnotationInfo(MacroImplAnnotation.tpe, List(rhs1), Nil) - if (!hasErrors) { - if (macroImpl == null) { - invalidBodyError() - } else { - if (!macroImpl.isMethod) + def typecheckedWithErrors = (rhs1 exists (_.isErroneous)) || reporter.ERROR.count != prevNumErrors + if (typecheckedWithErrors) { + setError() + macroTraceVerbose("body of a macro def failed to typecheck: ")(ddef) + } else { + if (!hasError) { + if (macroImpl == null) { invalidBodyError() - if (macroImpl.isOverloaded) - reportError(implpos, "macro implementation cannot be overloaded") - if (!macroImpl.typeParams.isEmpty && (!rhs1.isInstanceOf[TypeApply])) - reportError(implpos, "macro implementation reference needs type arguments") - if (!hasErrors) - validatePostTyper(rhs1) + } else { + if (!macroImpl.isMethod) + invalidBodyError() + if (!macroImpl.isPublic) + reportError(implpos, "macro implementation must be public") + if (macroImpl.isOverloaded) + reportError(implpos, "macro implementation cannot be overloaded") + if (!macroImpl.typeParams.isEmpty && (!rhs1.isInstanceOf[TypeApply])) + reportError(implpos, "macro implementation reference needs type arguments") + if (!hasError) + validatePostTyper(rhs1) + } + if (hasError) + macroTraceVerbose("macro def failed to satisfy trivial preconditions: ")(macroDef) + } + if (!hasError) { + bindMacroImpl(macroDef, rhs1) // we must bind right over here, because return type inference needs this info } - if (hasErrors) - macroTraceVerbose("macro def failed to satisfy trivial preconditions: ")(macroDef) } - if (!hasErrors) { + if (!hasError) { def checkCompatibility(reqparamss: List[List[Symbol]], actparamss: List[List[Symbol]], reqres: Type, actres: Type): List[String] = { - var hasErrors = false + var hasError = false var errors = List[String]() def compatibilityError(msg: String) { - hasErrors = true + hasError = true errors :+= msg } @@ -364,7 +532,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } } - if (!hasErrors) { + if (!hasError) { try { for ((rparams, aparams) <- reqparamss zip actparamss) { if (rparams.length < aparams.length) @@ -374,7 +542,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } // if the implementation signature is already deemed to be incompatible, we bail out // otherwise, high-order type magic employed below might crash in weird ways - if (!hasErrors) { + if (!hasError) { for ((rparams, aparams) <- reqparamss zip actparamss) { for ((rparam, aparam) <- rparams zip aparams) { def isRepeated(param: Symbol) = param.tpe.typeSymbol == RepeatedParamClass @@ -387,7 +555,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { compatibilityError("types incompatible for parameter "+rparam.name+": corresponding is not a vararg parameter") if (!isRepeated(rparam) && isRepeated(aparam)) compatibilityError("types incompatible for parameter "+aparam.name+": corresponding is not a vararg parameter") - if (!hasErrors) { + if (!hasError) { var atpe = aparam.tpe.substSym(flatactparams, flatreqparams).instantiateTypeParams(tparams, tvars) atpe = atpe.dealias // SI-5706 // strip the { type PrefixType = ... } refinement off the Context or otherwise we get compatibility errors @@ -400,11 +568,11 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } } } - if (!hasErrors) { + if (!hasError) { val atpe = actres.substSym(flatactparams, flatreqparams).instantiateTypeParams(tparams, tvars) checkSubType("return type", atpe, reqres) } - if (!hasErrors) { + if (!hasError) { val targs = solvedTypes(tvars, tparams, tparams map varianceInType(actres), false, lubDepth(flatactparams map (_.tpe)) max lubDepth(flatreqparams map (_.tpe))) val boundsOk = typer.silent(_.infer.checkBounds(ddef, NoPrefix, NoSymbol, tparams, targs, "")) @@ -429,7 +597,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } var actparamss = macroImpl.paramss - actparamss = transformTypeTagEvidenceParams(actparamss, (param, tparam) => None) + actparamss = transformTypeTagEvidenceParams(actparamss, (param, tparam) => NoSymbol) val rettpe = if (!ddef.tpt.isEmpty) typer.typedType(ddef.tpt).tpe else computeMacroDefTypeFromMacroImpl(ddef, macroDef, macroImpl) val (reqparamsss0, reqres0) = macroImplSigs(macroDef, ddef.tparams, ddef.vparamss, rettpe) @@ -443,7 +611,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { macroTraceVerbose("macro def failed to satisfy trivial preconditions: ")(macroDef) } - if (!hasErrors) { + if (!hasError) { val reqres = reqres0 val actres = macroImpl.tpe.finalResultType def showMeth(pss: List[List[Symbol]], restpe: Type, abbreviate: Boolean) = { @@ -482,15 +650,6 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } } - // if this macro definition is erroneous, then there's no sense in expanding its usages - // in the previous prototype macro implementations were magically generated from macro definitions - // so macro definitions and its usages couldn't be compiled in the same compilation run - // however, now definitions and implementations are decoupled, so it's everything is possible - // hence, we now use IS_ERROR flag to serve as an indicator that given macro definition is broken - if (hasErrors) { - macroDef setFlag IS_ERROR - } - rhs1 } @@ -526,17 +685,9 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { // sym.paramPos is unreliable (see another case below) val tparams = macroImpl.typeParams map (_.deSkolemize) val paramPos = tparams indexOf sym.deSkolemize - val sym1 = if (paramPos == -1) sym else { - val ann = macroDef.getAnnotation(MacroImplAnnotation) - ann match { - case Some(ann) => - val TypeApply(_, implRefTargs) = ann.args(0) - val implRefTarg = implRefTargs(paramPos).tpe.typeSymbol - implRefTarg - case None => - sym - } - } + val sym1 = + if (paramPos == -1) sym + else loadMacroImplBinding(macroDef).targs(paramPos).tpe.typeSymbol TypeRef(pre, sym1, args) case tpe => tpe @@ -550,7 +701,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { // val defParamss = macroDef.paramss val defParamss = mmap(macroDdef.vparamss)(_.symbol) var implParamss = macroImpl.paramss - implParamss = transformTypeTagEvidenceParams(implParamss, (param, tparam) => None) + implParamss = transformTypeTagEvidenceParams(implParamss, (param, tparam) => NoSymbol) val implCtxParam = if (implParamss.length > 0 && implParamss(0).length > 0) implParamss(0)(0) else null def implParamToDefParam(implParam: Symbol): Symbol = { @@ -619,118 +770,42 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { * 3) Loads the companion of that enclosing class from the macro classloader. * 4) Resolves macro implementation within the loaded companion. * - * @return Some(runtime) if macro implementation can be loaded successfully from either of the mirrors, - * None otherwise. + * @return Requested runtime if macro implementation can be loaded successfully from either of the mirrors, + * null otherwise. */ type MacroRuntime = List[Any] => Any - private val macroRuntimesCache = perRunCaches.newWeakMap[Symbol, Option[MacroRuntime]] - private def macroRuntime(macroDef: Symbol): Option[MacroRuntime] = { + private val macroRuntimesCache = perRunCaches.newWeakMap[Symbol, MacroRuntime] + private def macroRuntime(macroDef: Symbol): MacroRuntime = { macroTraceVerbose("looking for macro implementation: ")(macroDef) if (fastTrack contains macroDef) { macroLogVerbose("macro expansion is serviced by a fast track") - Some(fastTrack(macroDef)) + fastTrack(macroDef) } else { macroRuntimesCache.getOrElseUpdate(macroDef, { - val runtime = { - macroTraceVerbose("macroDef is annotated with: ")(macroDef.annotations) - - val ann = macroDef.getAnnotation(MacroImplAnnotation) - if (ann == None) { macroTraceVerbose("@macroImpl annotation is missing (this means that macro definition failed to typecheck)")(macroDef); return None } - - val macroImpl = ann.get.args(0).symbol - if (macroImpl == NoSymbol) { macroTraceVerbose("@macroImpl annotation is malformed (this means that macro definition failed to typecheck)")(macroDef); return None } - macroLogVerbose("resolved implementation %s at %s".format(macroImpl, macroImpl.pos)) - if (macroImpl.isErroneous) { macroTraceVerbose("macro implementation is erroneous (this means that either macro body or macro implementation signature failed to typecheck)")(macroDef); return None } - - // [Eugene++] I don't use Scala reflection here, because it seems to interfere with JIT magic - // whenever you instantiate a mirror (and not do anything with in, just instantiate), performance drops by 15-20% - // I'm not sure what's the reason - for me it's pure voodoo - def loadMacroImpl(cl: ClassLoader): Option[(Object, jMethod)] = { - try { - // this logic relies on the assumptions that were valid for the old macro prototype - // namely that macro implementations can only be defined in top-level classes and modules - // with the new prototype that materialized in a SIP, macros need to be statically accessible, which is different - // for example, a macro def could be defined in a trait that is implemented by an object - // there are some more clever cases when seemingly non-static method ends up being statically accessible - // however, the code below doesn't account for these guys, because it'd take a look of time to get it right - // for now I leave it as a todo and move along to more the important stuff - - macroTraceVerbose("loading implementation class: ")(macroImpl.owner.fullName) - macroTraceVerbose("classloader is: ")(ReflectionUtils.show(cl)) - - // [Eugene] relies on the fact that macro implementations can only be defined in static classes - // [Martin to Eugene++] There's similar logic buried in Symbol#flatname. Maybe we can refactor? - def classfile(sym: Symbol): String = { - def recur(sym: Symbol): String = sym match { - case sym if sym.owner.isPackageClass => - val suffix = if (sym.isModuleClass) "$" else "" - sym.fullName + suffix - case sym => - val separator = if (sym.owner.isModuleClass) "" else "$" - recur(sym.owner) + separator + sym.javaSimpleName.toString - } - - if (sym.isClass || sym.isModule) recur(sym) - else recur(sym.enclClass) - } - - // [Eugene++] this doesn't work for inner classes - // neither does macroImpl.owner.javaClassName, so I had to roll my own implementation - //val receiverName = macroImpl.owner.fullName - val implClassName = classfile(macroImpl.owner) - val implObj = try { - val implObjClass = jClass.forName(implClassName, true, cl) - implObjClass getField "MODULE$" get null - } catch { - case ex: NoSuchFieldException => macroTraceVerbose("exception when loading implObj: ")(ex); null - case ex: NoClassDefFoundError => macroTraceVerbose("exception when loading implObj: ")(ex); null - case ex: ClassNotFoundException => macroTraceVerbose("exception when loading implObj: ")(ex); null - } - - if (implObj == null) None - else { - // [Eugene++] doh, it seems that I need to copy/paste Scala reflection logic - // see `JavaMirrors.methodToJava` or whatever it's called now - val implMeth = { - def typeToJavaClass(tpe: Type): jClass[_] = tpe match { - case ExistentialType(_, rtpe) => typeToJavaClass(rtpe) - case TypeRef(_, ArrayClass, List(elemtpe)) => jArray.newInstance(typeToJavaClass(elemtpe), 0).getClass - case TypeRef(_, sym: ClassSymbol, _) => jClass.forName(classfile(sym), true, cl) - case _ => throw new NoClassDefFoundError("no Java class corresponding to "+tpe+" found") - } - - val paramClasses = transformedType(macroImpl).paramTypes map typeToJavaClass - try implObj.getClass getDeclaredMethod (macroImpl.name.toString, paramClasses: _*) - catch { - case ex: NoSuchMethodException => - val expandedName = - if (macroImpl.isPrivate) nme.expandedName(macroImpl.name.toTermName, macroImpl.owner).toString - else macroImpl.name.toString - implObj.getClass getDeclaredMethod (expandedName, paramClasses: _*) - } - } - macroLogVerbose("successfully loaded macro impl as (%s, %s)".format(implObj, implMeth)) - Some((implObj, implMeth)) - } - } catch { - case ex: ClassNotFoundException => - macroTraceVerbose("implementation class failed to load: ")(ex.toString) - None - case ex: NoSuchMethodException => - macroTraceVerbose("implementation method failed to load: ")(ex.toString) - None - } - } - - loadMacroImpl(macroClassloader) map { - case (implObj, implMeth) => - def runtime(args: List[Any]) = implMeth.invoke(implObj, (args map (_.asInstanceOf[AnyRef])): _*).asInstanceOf[Any] - runtime _ - } + val binding = loadMacroImplBinding(macroDef) + val className = binding.className + val methName = binding.methName + macroLogVerbose(s"resolved implementation as $className.$methName") + + // [Eugene++] I don't use Scala reflection here, because it seems to interfere with JIT magic + // whenever you instantiate a mirror (and not do anything with in, just instantiate), performance drops by 15-20% + // I'm not sure what's the reason - for me it's pure voodoo + try { + macroTraceVerbose("loading implementation class: ")(className) + macroTraceVerbose("classloader is: ")(ReflectionUtils.show(macroClassloader)) + val implObj = ReflectionUtils.staticSingletonInstance(macroClassloader, className) + // relies on the fact that macro impls cannot be overloaded + // so every methName can resolve to at maximum one method + val implMeths = implObj.getClass.getDeclaredMethods.find(_.getName == methName) + val implMeth = implMeths getOrElse { throw new NoSuchMethodException(s"$className.$methName") } + macroLogVerbose("successfully loaded macro impl as (%s, %s)".format(implObj, implMeth)) + (args: List[Any]) => implMeth.invoke(implObj, (args map (_.asInstanceOf[AnyRef])): _*) + } catch { + case ex: Exception => + macroTraceVerbose(s"macro runtime failed to load: ")(ex.toString) + macroDef setFlag IS_ERROR + null } - - if (runtime == None) macroDef setFlag IS_ERROR - runtime }) } } @@ -755,7 +830,6 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { */ private def macroArgs(typer: Typer, expandee: Tree): Option[List[Any]] = { val macroDef = expandee.symbol - val runtime = macroRuntime(macroDef) orElse { return None } val prefixTree = expandee.collect{ case Select(qual, name) => qual }.headOption.getOrElse(EmptyTree) val context = expandee.attachments.get[MacroRuntimeAttachment].flatMap(_.macroContext).getOrElse(macroContext(typer, prefixTree, expandee)) var typeArgs = List[Tree]() @@ -771,6 +845,11 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { case _ => } collectMacroArgs(expandee) + + val argcDoesntMatch = macroDef.paramss.length != exprArgs.length + val nullaryArgsEmptyParams = exprArgs.isEmpty && macroDef.paramss == List(List()) + if (argcDoesntMatch && !nullaryArgsEmptyParams) { typer.TyperErrorGen.MacroPartialApplicationError(expandee); return None } + var argss: List[List[Any]] = List(context) :: exprArgs.toList macroTraceVerbose("argss: ")(argss) val rawArgss = @@ -787,33 +866,8 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { return None } } else { - val ann = macroDef.getAnnotation(MacroImplAnnotation).getOrElse(throw new Error("assertion failed. %s: %s".format(macroDef, macroDef.annotations))) - val macroImpl = ann.args(0).symbol - var paramss = macroImpl.paramss - val tparams = macroImpl.typeParams - macroTraceVerbose("paramss: ")(paramss) - - // we need to take care of all possible combos of nullary/empty-paramlist macro defs vs nullary/empty-arglist invocations - // nullary def + nullary invocation => paramss and argss match, everything is okay - // nullary def + empty-arglist invocation => illegal Scala code, impossible, everything is okay - // empty-paramlist def + nullary invocation => uh-oh, we need to append a List() to argss - // empty-paramlist def + empty-arglist invocation => paramss and argss match, everything is okay - // that's almost it, but we need to account for the fact that paramss might have context bounds that mask the empty last paramlist - val paramss_without_evidences = transformTypeTagEvidenceParams(paramss, (param, tparam) => None) - val isEmptyParamlistDef = paramss_without_evidences.nonEmpty && paramss_without_evidences.last.isEmpty - val isEmptyArglistInvocation = argss.nonEmpty && argss.last.isEmpty - if (isEmptyParamlistDef && !isEmptyArglistInvocation) { - macroLogVerbose("isEmptyParamlistDef && !isEmptyArglistInvocation: appending a List() to argss") - argss = argss :+ Nil - } - - // nb! check partial application against paramss without evidences - val numParamLists = paramss_without_evidences.length - val numArgLists = argss.length - if (numParamLists != numArgLists) { - typer.TyperErrorGen.MacroPartialApplicationError(expandee) - return None - } + val binding = loadMacroImplBinding(macroDef) + macroTraceVerbose("binding: ")(binding) // if paramss have typetag context bounds, add an arglist to argss if necessary and instantiate the corresponding evidences // consider the following example: @@ -831,43 +885,41 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { // then T and U need to be inferred from the lexical scope of the call using ``asSeenFrom'' // whereas V won't be resolved by asSeenFrom and need to be loaded directly from ``expandee'' which needs to contain a TypeApply node // also, macro implementation reference may contain a regular type as a type argument, then we pass it verbatim - val resolved = collection.mutable.Map[Symbol, Type]() - paramss = transformTypeTagEvidenceParams(paramss, (param, tparam) => { - val TypeApply(_, implRefTargs) = ann.args(0) - var implRefTarg = implRefTargs(tparam.paramPos).tpe.typeSymbol - val tpe = if (implRefTarg.isTypeParameterOrSkolem) { - if (implRefTarg.owner == macroDef) { + val tags = binding.signature filter (_ != -1) map (paramPos => { + val targ = binding.targs(paramPos).tpe.typeSymbol + val tpe = if (targ.isTypeParameterOrSkolem) { + if (targ.owner == macroDef) { // [Eugene] doesn't work when macro def is compiled separately from its usages - // then implRefTarg is not a skolem and isn't equal to any of macroDef.typeParams - // val paramPos = implRefTarg.deSkolemize.paramPos - val paramPos = macroDef.typeParams.indexWhere(_.name == implRefTarg.name) - typeArgs(paramPos).tpe + // then targ is not a skolem and isn't equal to any of macroDef.typeParams + // val argPos = targ.deSkolemize.paramPos + val argPos = macroDef.typeParams.indexWhere(_.name == targ.name) + typeArgs(argPos).tpe } else - implRefTarg.tpe.asSeenFrom( + targ.tpe.asSeenFrom( if (prefixTree == EmptyTree) macroDef.owner.tpe else prefixTree.tpe, macroDef.owner) } else - implRefTarg.tpe - macroLogVerbose("resolved tparam %s as %s".format(tparam, tpe)) - resolved(tparam) = tpe - param.tpe.typeSymbol match { - case definitions.AbsTypeTagClass => - // do nothing - case _ => - throw new Error("unsupported tpe: " + tpe) - } - Some(tparam) + targ.tpe + if (tpe.isConcrete) context.TypeTag(tpe) else context.AbsTypeTag(tpe) + }) + val hasImplicitParams = macroDef.paramss.flatten.lastOption exists (_.isImplicit) + argss = if (hasImplicitParams) argss.dropRight(1) :+ (tags ++ argss.last) else argss :+ tags + + // transforms argss taking into account varargness of paramss + // not all argument lists in argss map to macroDef.paramss, so we need to apply extra care + // namely: + // 1) the first argument list represents (c: Context) in macroImpl, so it doesn't have correspondence in macroDef + // 2) typetag context bounds are only declared on macroImpls, so this optional arglist also doesn't match macroDef + // nb! varargs can apply to any parameter section, not necessarily to the last one + mapWithIndex(argss)((as, i_argss) => { + val i_paramss = i_argss - 1 + val mapsToParamss = 0 <= i_paramss && i_paramss < macroDef.paramss.length + if (mapsToParamss) { + val ps = macroDef.paramss(i_paramss) + if (isVarArgsList(ps)) as.take(ps.length - 1) :+ as.drop(ps.length - 1) + else as + } else as }) - val tags = paramss.last takeWhile (_.isType) map (resolved(_)) map (tpe => if (tpe.isConcrete) context.TypeTag(tpe) else context.AbsTypeTag(tpe)) - if (paramss.lastOption map (params => !params.isEmpty && params.forall(_.isType)) getOrElse false) argss = argss :+ Nil - argss = argss.dropRight(1) :+ (tags ++ argss.last) // todo. add support for context bounds in argss - - assert(argss.length == paramss.length, "argss: %s, paramss: %s".format(argss, paramss)) - val rawArgss = for ((as, ps) <- argss zip paramss) yield { - if (isVarArgsList(ps)) as.take(ps.length - 1) :+ as.drop(ps.length - 1) - else as - } - rawArgss } val rawArgs = rawArgss.flatten macroTraceVerbose("rawArgs: ")(rawArgs) @@ -996,12 +1048,9 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { return Cancel(typer.infer.setError(expandee)) } - macroRuntime(expandee.symbol) match { - case Some(runtime) => - macroExpandWithRuntime(typer, expandee, runtime) - case None => - macroExpandWithoutRuntime(typer, expandee) - } + val runtime = macroRuntime(expandee.symbol) + if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime) + else macroExpandWithoutRuntime(typer, expandee) } /** Expands a macro when a runtime (i.e. the macro implementation) can be successfully loaded diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index efb96b173c..92d0d4e228 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -988,6 +988,15 @@ trait Namers extends MethodSynthesis { // (either "macro ???" as they used to or just "???" to maximally simplify their compilation) if (fastTrack contains ddef.symbol) ddef.symbol setFlag MACRO + // macro defs need to be typechecked in advance + // because @macroImpl annotation only gets assigned during typechecking + // otherwise macro defs wouldn't be able to robustly coexist with their clients + // because a client could be typechecked before a macro def that it uses + if (ddef.symbol.isTermMacro) { + val pt = resultPt.substSym(tparamSyms, tparams map (_.symbol)) + typer.computeMacroDefType(ddef, pt) + } + thisMethodType({ val rt = ( if (!tpt.isEmpty) { diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala index 8dc2cb34d6..a1a596456f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala @@ -67,10 +67,6 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL object exceeded extends Exception { val advice = s"(The analysis required more space than allowed. Please try with scalac -Dscalac.patmat.analysisBudget=${AnalysisBudget.max*2} or -Dscalac.patmat.analysisBudget=off.)" } - - object stackOverflow extends Exception { - val advice = "(There was a stack overflow. Please try increasing the stack available to the compiler using e.g., -Xss2m.)" - } } def newTransformer(unit: CompilationUnit): Transformer = @@ -521,7 +517,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // case Star(_) | ArrayValue => error("stone age pattern relics encountered!") case _ => - error("unsupported pattern: "+ patTree +"(a "+ patTree.getClass +")") + typer.context.unit.error(patTree.pos, s"unsupported pattern: $patTree (a ${patTree.getClass}).\n This is a scalac bug. Tree diagnostics: ${asCompactDebugString(patTree)}.") noFurtherSubPats() } @@ -1851,20 +1847,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL class Prop case class Eq(p: Var, q: Const) extends Prop - type Const <: AbsConst - trait AbsConst { - // when we know V = C, which other equalities must hold - // in general, equality to some type implies equality to its supertypes - // (this multi-valued kind of equality is necessary for unreachability) - // note that we use subtyping as a model for implication between instanceof tests - // i.e., when S <:< T we assume x.isInstanceOf[S] implies x.isInstanceOf[T] - // unfortunately this is not true in general (see e.g. SI-6022) - def implies(other: Const): Boolean - - // does V = C preclude V having value `other`? V = null is an exclusive assignment, - // but V = 1 does not preclude V = Int, or V = Any - def excludes(other: Const): Boolean - } + type Const type TypeConst <: Const def TypeConst: TypeConstExtractor @@ -1899,8 +1882,8 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL def propForEqualsTo(c: Const): Prop // populated by registerEquality - // once equalitySyms has been called, must not call registerEquality anymore - def equalitySyms: List[Sym] + // once implications has been called, must not call registerEquality anymore + def implications: List[(Sym, List[Sym], List[Sym])] } // would be nice to statically check whether a prop is equational or pure, @@ -1999,19 +1982,8 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL var eqAxioms: Prop = True @inline def addAxiom(p: Prop) = eqAxioms = And(eqAxioms, p) - case class ExcludedPair(a: Const, b: Const) { - override def equals(o: Any) = o match { - case ExcludedPair(aa, bb) => (a == aa && b == bb) || (a == bb && b == aa) - case _ => false - } - // make ExcludedPair(a, b).hashCode == ExcludedPair(b, a).hashCode - override def hashCode = a.hashCode ^ b.hashCode - } - patmatDebug("removeVarEq vars: "+ vars) vars.foreach { v => - val excludedPair = new collection.mutable.HashSet[ExcludedPair] - // if v.domainSyms.isEmpty, we must consider the domain to be infinite // otherwise, since the domain fully partitions the type of the value, // exactly one of the types (and whatever it implies, imposed separately) must be chosen @@ -2026,27 +1998,11 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL else addAxiom(symForStaticTp) } - val syms = v.equalitySyms - patmatDebug("eqSyms "+(v, syms)) - syms foreach { sym => - // if we've already excluded the pair at some point (-A \/ -B), then don't exclude the symmetric one (-B \/ -A) - // (nor the positive implications -B \/ A, or -A \/ B, which would entail the equality axioms falsifying the whole formula) - val todo = syms filterNot (b => (b.const == sym.const) || excludedPair(ExcludedPair(b.const, sym.const))) - val (excluded, notExcluded) = todo partition (b => sym.const.excludes(b.const)) - val implied = notExcluded filter (b => sym.const.implies(b.const)) - - patmatDebug("eq axioms for: "+ sym.const) - patmatDebug("excluded: "+ excluded) - patmatDebug("implied: "+ implied) - - // when this symbol is true, what must hold... - implied foreach (impliedSym => addAxiom(Or(Not(sym), impliedSym))) - + v.implications foreach { case (sym, implied, excluded) => + // when sym is true, what must hold... + implied foreach (impliedSym => addAxiom(Or(Not(sym), impliedSym))) // ... and what must not? - excluded foreach {excludedSym => - excludedPair += ExcludedPair(sym.const, excludedSym.const) - addAxiom(Or(Not(sym), Not(excludedSym))) - } + excluded foreach (excludedSym => addAxiom(Or(Not(sym), Not(excludedSym)))) } } @@ -2096,21 +2052,29 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL def Lit(sym: Sym, pos: Boolean = true): Lit // throws an AnalysisBudget.Exception when the prop results in a CNF that's too big + // TODO: be smarter/more efficient about this (http://lara.epfl.ch/w/sav09:tseitin_s_encoding) def eqFreePropToSolvable(p: Prop): Formula = { - // TODO: for now, reusing the normalization from DPLL - def negationNormalForm(p: Prop): Prop = p match { - case And(a, b) => And(negationNormalForm(a), negationNormalForm(b)) - case Or(a, b) => Or(negationNormalForm(a), negationNormalForm(b)) - case Not(And(a, b)) => negationNormalForm(Or(Not(a), Not(b))) - case Not(Or(a, b)) => negationNormalForm(And(Not(a), Not(b))) - case Not(Not(p)) => negationNormalForm(p) - case Not(True) => False - case Not(False) => True - case True - | False - | (_ : Sym) - | Not(_ : Sym) => p - } + def negationNormalFormNot(p: Prop, budget: Int = AnalysisBudget.max): Prop = + if (budget <= 0) throw AnalysisBudget.exceeded + else p match { + case And(a, b) => Or(negationNormalFormNot(a, budget - 1), negationNormalFormNot(b, budget - 1)) + case Or(a, b) => And(negationNormalFormNot(a, budget - 1), negationNormalFormNot(b, budget - 1)) + case Not(p) => negationNormalForm(p, budget - 1) + case True => False + case False => True + case s: Sym => Not(s) + } + + def negationNormalForm(p: Prop, budget: Int = AnalysisBudget.max): Prop = + if (budget <= 0) throw AnalysisBudget.exceeded + else p match { + case And(a, b) => And(negationNormalForm(a, budget - 1), negationNormalForm(b, budget - 1)) + case Or(a, b) => Or(negationNormalForm(a, budget - 1), negationNormalForm(b, budget - 1)) + case Not(negated) => negationNormalFormNot(negated, budget - 1) + case True + | False + | (_ : Sym) => p + } val TrueF = formula() val FalseF = formula(clause()) @@ -2153,12 +2117,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } val start = Statistics.startTimer(patmatCNF) - val res = - try { - conjunctiveNormalForm(negationNormalForm(p)) - } catch { case ex : StackOverflowError => - throw AnalysisBudget.stackOverflow - } + val res = conjunctiveNormalForm(negationNormalForm(p)) Statistics.stopTimer(patmatCNF, start) @@ -2342,22 +2301,121 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL observed; allConsts } - // accessing after calling registerNull will result in inconsistencies - lazy val domainSyms: Option[Set[Sym]] = domain map { _ map symForEqualsTo } - - lazy val symForStaticTp: Option[Sym] = symForEqualsTo.get(TypeConst(staticTpCheckable)) - // populate equalitySyms // don't care about the result, but want only one fresh symbol per distinct constant c def registerEquality(c: Const): Unit = {ensureCanModify; symForEqualsTo getOrElseUpdate(c, Sym(this, c))} - // don't access until all potential equalities have been registered using registerEquality - lazy val equalitySyms = {observed; symForEqualsTo.values.toList} - // return the symbol that represents this variable being equal to the constant `c`, if it exists, otherwise False (for robustness) // (registerEquality(c) must have been called prior, either when constructing the domain or from outside) def propForEqualsTo(c: Const): Prop = {observed; symForEqualsTo.getOrElse(c, False)} + // [implementation NOTE: don't access until all potential equalities have been registered using registerEquality]p + /** the information needed to construct the boolean proposition that encods the equality proposition (V = C) + * + * that models a type test pattern `_: C` or constant pattern `C`, where the type test gives rise to a TypeConst C, + * and the constant pattern yields a ValueConst C + * + * for exhaustivity, we really only need implication (e.g., V = 1 implies that V = 1 /\ V = Int, if both tests occur in the match, + * and thus in this variable's equality symbols), but reachability also requires us to model things like V = 1 precluding V = "1" + */ + lazy val implications = { + /** when we know V = C, which other equalities must hold + * + * in general, equality to some type implies equality to its supertypes + * (this multi-valued kind of equality is necessary for unreachability) + * note that we use subtyping as a model for implication between instanceof tests + * i.e., when S <:< T we assume x.isInstanceOf[S] implies x.isInstanceOf[T] + * unfortunately this is not true in general (see e.g. SI-6022) + */ + def implies(lower: Const, upper: Const): Boolean = + // values and null + lower == upper || + // type implication + (lower != NullConst && !upper.isValue && + instanceOfTpImplies(if (lower.isValue) lower.wideTp else lower.tp, upper.tp)) + + // if(r) patmatDebug("implies : "+(lower, lower.tp, upper, upper.tp)) + // else patmatDebug("NOT implies: "+(lower, upper)) + + + /** does V = C preclude V having value `other`? + (1) V = null is an exclusive assignment, + (2) V = A and V = B, for A and B value constants, are mutually exclusive unless A == B + we err on the safe side, for example: + - assume `val X = 1; val Y = 1`, then + (2: Int) match { case X => case Y => <falsely considered reachable> } + - V = 1 does not preclude V = Int, or V = Any, it could be said to preclude V = String, but we don't model that + + (3) for types we could try to do something fancy, but be conservative and just say no + */ + def excludes(a: Const, b: Const): Boolean = + a != b && ((a == NullConst || b == NullConst) || (a.isValue && b.isValue)) + + // if(r) patmatDebug("excludes : "+(a, a.tp, b, b.tp)) + // else patmatDebug("NOT excludes: "+(a, b)) + +/* +[ HALF BAKED FANCINESS: //!equalitySyms.exists(common => implies(common.const, a) && implies(common.const, b))) + when type tests are involved, we reason (conservatively) under a closed world assumption, + since we are really only trying to counter the effects of the symbols that we introduce to model type tests + we don't aim to model the whole subtyping hierarchy, simply to encode enough about subtyping to do unreachability properly + + consider the following hierarchy: + + trait A + trait B + trait C + trait AB extends B with A + + // two types are mutually exclusive if there is no equality symbol whose constant implies both + object Test extends App { + def foo(x: Any) = x match { + case _ : C => println("C") + case _ : AB => println("AB") + case _ : (A with B) => println("AB'") + case _ : B => println("B") + case _ : A => println("A") + } + + of course this kind of reasoning is not true in general, + but we can safely pretend types are mutually exclusive as long as there are no counter-examples in the match we're analyzing} +*/ + + val excludedPair = new collection.mutable.HashSet[ExcludedPair] + + case class ExcludedPair(a: Const, b: Const) { + override def equals(o: Any) = o match { + case ExcludedPair(aa, bb) => (a == aa && b == bb) || (a == bb && b == aa) + case _ => false + } + // make ExcludedPair(a, b).hashCode == ExcludedPair(b, a).hashCode + override def hashCode = a.hashCode ^ b.hashCode + } + + equalitySyms map { sym => + // if we've already excluded the pair at some point (-A \/ -B), then don't exclude the symmetric one (-B \/ -A) + // (nor the positive implications -B \/ A, or -A \/ B, which would entail the equality axioms falsifying the whole formula) + val todo = equalitySyms filterNot (b => (b.const == sym.const) || excludedPair(ExcludedPair(b.const, sym.const))) + val (excluded, notExcluded) = todo partition (b => excludes(sym.const, b.const)) + val implied = notExcluded filter (b => implies(sym.const, b.const)) + + patmatDebug("eq axioms for: "+ sym.const) + patmatDebug("excluded: "+ excluded) + patmatDebug("implied: "+ implied) + + excluded foreach { excludedSym => excludedPair += ExcludedPair(sym.const, excludedSym.const)} + + (sym, implied, excluded) + } + } + + // accessing after calling registerNull will result in inconsistencies + lazy val domainSyms: Option[Set[Sym]] = domain map { _ map symForEqualsTo } + + lazy val symForStaticTp: Option[Sym] = symForEqualsTo.get(TypeConst(staticTpCheckable)) + + // don't access until all potential equalities have been registered using registerEquality + private lazy val equalitySyms = {observed; symForEqualsTo.values.toList} // don't call until all equalities have been registered and registerNull has been called (if needed) def describe = toString + ": " + staticTp + domain.map(_.mkString(" ::= ", " | ", "// "+ symForEqualsTo.keys)).getOrElse(symForEqualsTo.keys.mkString(" ::= ", " | ", " | ...")) + " // = " + path @@ -2413,42 +2471,12 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } } - sealed abstract class Const extends AbsConst { + sealed abstract class Const { def tp: Type - protected def wideTp: Type + def wideTp: Type def isAny = wideTp.typeSymbol == AnyClass - - final def implies(other: Const): Boolean = { - val r = (this, other) match { - case (_: ValueConst, _: ValueConst) => this == other // hashconsed - case (_: ValueConst, _: TypeConst) => instanceOfTpImplies(tp, other.tp) - case (_: TypeConst, _) => instanceOfTpImplies(tp, other.tp) - case _ => false - } - // if(r) patmatDebug("implies : "+(this, other)) - // else patmatDebug("NOT implies: "+(this, other)) - r - } - - // does V = C preclude V having value `other`? V = null is an exclusive assignment, - // but V = 1 does not preclude V = Int, or V = Any - final def excludes(other: Const): Boolean = { - val r = (this, other) match { - case (_, NullConst) => true - case (NullConst, _) => true - // this causes false negative for unreachability, but that's ok: - // example: val X = 1; val Y = 1; (2: Int) match { case X => case Y => /* considered reachable */ } - case (_: ValueConst, _: ValueConst) => this != other - case (_: ValueConst, _: TypeConst) => !(instanceOfTpImplies(tp, other.tp) || instanceOfTpImplies(other.tp, wideTp)) - case (_: TypeConst, _: ValueConst) => !(instanceOfTpImplies(other.tp, tp) || instanceOfTpImplies(tp, other.wideTp)) - case (_: TypeConst, _: TypeConst) => !(instanceOfTpImplies(tp, other.tp) || instanceOfTpImplies(other.tp, tp)) - case _ => false - } - // if(r) patmatDebug("excludes : "+(this, this.tp, other, other.tp)) - // else patmatDebug("NOT excludes: "+(this, other)) - r - } + def isValue: Boolean //= tp.isStable // note: use reference equality on Const since they're hash-consed (doing type equality all the time is too expensive) // the equals inherited from AnyRef does just this @@ -2479,7 +2507,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL private[this] val id: Int = Const.nextTypeId val wideTp = widenToClass(tp) - + def isValue = false override def toString = tp.toString //+"#"+ id } @@ -2523,14 +2551,15 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } sealed class ValueConst(val tp: Type, val wideTp: Type, override val toString: String) extends Const { // patmatDebug("VC"+(tp, wideTp, toString)) - assert(!(tp =:= NullTp)) + assert(!(tp =:= NullTp)) // TODO: assert(!tp.isStable) private[this] val id: Int = Const.nextValueId + def isValue = true } lazy val NullTp = ConstantType(Constant(null)) case object NullConst extends Const { def tp = NullTp - protected def wideTp = NullTp + def wideTp = NullTp def isValue = true override def toString = "null" @@ -2601,7 +2630,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL var reachable = true var caseIndex = 0 - patmatDebug("reachability, vars:\n"+ ((propsCasesFail flatMap gatherVariables) map (_.describe) mkString ("\n"))) + patmatDebug("reachability, vars:\n"+ ((propsCasesFail flatMap gatherVariables).distinct map (_.describe) mkString ("\n"))) patmatDebug("equality axioms:\n"+ cnfString(eqAxiomsCNF)) // invariant (prefixRest.length == current.length) && (prefix.reverse ++ prefixRest == symbolicCasesFail) @@ -2616,10 +2645,10 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL current = current.tail val model = findModelFor(andFormula(eqFreePropToSolvable(current.head), prefix)) - // patmatDebug("trying to reach:\n"+ cnfString(current.head) +"\nunder prefix:\n"+ cnfString(prefix)) - // if (ok) patmatDebug("reached: "+ modelString(model)) + // patmatDebug("trying to reach:\n"+ cnfString(eqFreePropToSolvable(current.head)) +"\nunder prefix:\n"+ cnfString(prefix)) + // if (NoModel ne model) patmatDebug("reached: "+ modelString(model)) - reachable = model ne NoModel + reachable = NoModel ne model } } @@ -2909,7 +2938,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // node in the tree that describes how to construct a counter-example case class VariableAssignment(variable: Var, equalTo: List[Const], notEqualTo: List[Const], fields: collection.mutable.Map[Symbol, VariableAssignment]) { // need to prune since the model now incorporates all super types of a constant (needed for reachability) - private lazy val uniqueEqualTo = equalTo filterNot (subsumed => equalTo.exists(better => (better ne subsumed) && (better implies subsumed))) + private lazy val uniqueEqualTo = equalTo filterNot (subsumed => equalTo.exists(better => (better ne subsumed) && instanceOfTpImplies(better.tp, subsumed.tp))) private lazy val prunedEqualTo = uniqueEqualTo filterNot (subsumed => variable.staticTpCheckable <:< subsumed.tp) private lazy val ctor = (prunedEqualTo match { case List(TypeConst(tp)) => tp case _ => variable.staticTpCheckable }).typeSymbol.primaryConstructor private lazy val ctorParams = if (ctor == NoSymbol || ctor.paramss.isEmpty) Nil else ctor.paramss.head diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index d05368bd5f..a6d7fcda75 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2644,7 +2644,6 @@ trait Typers extends Modes with Adaptations with Tags { def includesTargetPos(tree: Tree) = tree.pos.isRange && context.unit.exists && (tree.pos includes context.unit.targetPos) val localTarget = stats exists includesTargetPos - val statsErrors = scala.collection.mutable.LinkedHashSet[AbsTypeError]() def typedStat(stat: Tree): Tree = { if (context.owner.isRefinementClass && !treeInfo.isDeclarationOrTypeDef(stat)) OnlyDeclarationsError(stat) @@ -2663,7 +2662,6 @@ trait Typers extends Modes with Adaptations with Tags { stat } else { val localTyper = if (inBlock || (stat.isDef && !stat.isInstanceOf[LabelDef])) { - context.flushBuffer() this } else newTyper(context.make(stat, exprOwner)) // XXX this creates a spurious dead code warning if an exception is thrown @@ -2680,7 +2678,6 @@ trait Typers extends Modes with Adaptations with Tags { "a pure expression does nothing in statement position; " + "you may be omitting necessary parentheses" ) - statsErrors ++= localTyper.context.errBuffer result } } @@ -2763,12 +2760,7 @@ trait Typers extends Modes with Adaptations with Tags { } } - val stats1 = withSavedContext(context) { - val result = stats mapConserve typedStat - context.flushBuffer() - result - } - context.updateBuffer(statsErrors) + val stats1 = stats mapConserve typedStat if (phase.erasedTypes) stats1 else { checkNoDoubleDefs(stats1) diff --git a/src/library/scala/collection/IterableLike.scala b/src/library/scala/collection/IterableLike.scala index 2e9599058f..ac6d754f9e 100644 --- a/src/library/scala/collection/IterableLike.scala +++ b/src/library/scala/collection/IterableLike.scala @@ -171,7 +171,7 @@ self => * fewer elements than size. */ def sliding(size: Int): Iterator[Repr] = sliding(size, 1) - + /** Groups elements in fixed size blocks by passing a "sliding window" * over them (as opposed to partitioning them, as is done in grouped.) * @see [[scala.collection.Iterator]], method `sliding` @@ -293,6 +293,7 @@ self => override /*TraversableLike*/ def view = new IterableView[A, Repr] { protected lazy val underlying = self.repr + override def isEmpty = self.isEmpty override def iterator = self.iterator } diff --git a/src/library/scala/collection/SeqLike.scala b/src/library/scala/collection/SeqLike.scala index d7418de9c3..f6f08398d3 100644 --- a/src/library/scala/collection/SeqLike.scala +++ b/src/library/scala/collection/SeqLike.scala @@ -627,6 +627,7 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ override def view = new SeqView[A, Repr] { protected lazy val underlying = self.repr + override def isEmpty = self.isEmpty override def iterator = self.iterator override def length = self.length override def apply(idx: Int) = self.apply(idx) diff --git a/src/library/scala/collection/TraversableLike.scala b/src/library/scala/collection/TraversableLike.scala index 641dd095da..0345f05355 100644 --- a/src/library/scala/collection/TraversableLike.scala +++ b/src/library/scala/collection/TraversableLike.scala @@ -86,7 +86,7 @@ trait TraversableLike[+A, +Repr] extends Any def repr: Repr = this.asInstanceOf[Repr] final def isTraversableAgain: Boolean = true - + /** The underlying collection seen as an instance of `$Coll`. * By default this is implemented as the current collection object itself, * but this can be overridden. @@ -174,7 +174,7 @@ trait TraversableLike[+A, +Repr] extends Any * * @usecase def ++:[B](that: TraversableOnce[B]): $Coll[B] * @inheritdoc - * + * * Example: * {{{ * scala> val x = List(1) @@ -655,6 +655,7 @@ trait TraversableLike[+A, +Repr] extends Any def view = new TraversableView[A, Repr] { protected lazy val underlying = self.repr override def foreach[U](f: A => U) = self foreach f + override def isEmpty = self.isEmpty } /** Creates a non-strict view of a slice of this $coll. diff --git a/src/library/scala/collection/TraversableViewLike.scala b/src/library/scala/collection/TraversableViewLike.scala index bf4f8205d6..7fbcf1374b 100644 --- a/src/library/scala/collection/TraversableViewLike.scala +++ b/src/library/scala/collection/TraversableViewLike.scala @@ -59,7 +59,7 @@ trait ViewMkString[+A] { * $viewInfo * * All views for traversable collections are defined by creating a new `foreach` method. - * + * * @author Martin Odersky * @version 2.8 * @since 2.8 @@ -162,7 +162,7 @@ trait TraversableViewLike[+A, // if (b.isInstanceOf[NoBuilder[_]]) newFlatMapped(f).asInstanceOf[That] // else super.flatMap[B, That](f)(bf) } - override def flatten[B](implicit asTraversable: A => /*<:<!!!*/ GenTraversableOnce[B]) = + override def flatten[B](implicit asTraversable: A => /*<:<!!!*/ GenTraversableOnce[B]) = newFlatMapped(asTraversable) private[this] implicit def asThis(xs: Transformed[A]): This = xs.asInstanceOf[This] @@ -193,6 +193,15 @@ trait TraversableViewLike[+A, override def span(p: A => Boolean): (This, This) = (newTakenWhile(p), newDroppedWhile(p)) override def splitAt(n: Int): (This, This) = (newTaken(n), newDropped(n)) + // Without this, isEmpty tests go back to the Traversable default, which + // involves starting a foreach, which can force the first element of the + // view. This is just a backstop - it's overridden at all the "def view" + // instantiation points in the collections where the Coll type is known. + override def isEmpty = underlying match { + case x: GenTraversableOnce[_] => x.isEmpty + case _ => super.isEmpty + } + override def scanLeft[B, That](z: B)(op: (B, A) => B)(implicit bf: CanBuildFrom[This, B, That]): That = newForced(thisSeq.scanLeft(z)(op)).asInstanceOf[That] diff --git a/src/library/scala/collection/immutable/Stream.scala b/src/library/scala/collection/immutable/Stream.scala index 9f5f98ddf4..1bf1e20694 100644 --- a/src/library/scala/collection/immutable/Stream.scala +++ b/src/library/scala/collection/immutable/Stream.scala @@ -916,6 +916,7 @@ self => override def view = new StreamView[A, Stream[A]] { protected lazy val underlying = self.repr + override def isEmpty = self.isEmpty override def iterator = self.iterator override def length = self.length override def apply(idx: Int) = self.apply(idx) diff --git a/src/library/scala/collection/mutable/IndexedSeqLike.scala b/src/library/scala/collection/mutable/IndexedSeqLike.scala index 2ff7ac8272..5d4b4de7b2 100644 --- a/src/library/scala/collection/mutable/IndexedSeqLike.scala +++ b/src/library/scala/collection/mutable/IndexedSeqLike.scala @@ -53,6 +53,7 @@ trait IndexedSeqLike[A, +Repr] extends scala.collection.IndexedSeqLike[A, Repr] */ override def view = new IndexedSeqView[A, Repr] { protected lazy val underlying = self.repr + override def isEmpty = self.isEmpty override def iterator = self.iterator override def length = self.length override def apply(idx: Int) = self.apply(idx) diff --git a/src/library/scala/collection/parallel/ParIterableLike.scala b/src/library/scala/collection/parallel/ParIterableLike.scala index 85758b29bc..26877a32b1 100644 --- a/src/library/scala/collection/parallel/ParIterableLike.scala +++ b/src/library/scala/collection/parallel/ParIterableLike.scala @@ -171,9 +171,9 @@ self: ParIterableLike[T, Repr, Sequential] => /** The task support object which is responsible for scheduling and * load-balancing tasks to processors. - * + * * @see [[scala.collection.parallel.TaskSupport]] - */ + */ def tasksupport = { val ts = _tasksupport if (ts eq null) { @@ -188,18 +188,18 @@ self: ParIterableLike[T, Repr, Sequential] => * A task support object can be changed in a parallel collection after it * has been created, but only during a quiescent period, i.e. while there * are no concurrent invocations to parallel collection methods. - * - * Here is a way to change the task support of a parallel collection: - * - * {{{ - * import scala.collection.parallel._ - * val pc = mutable.ParArray(1, 2, 3) - * pc.tasksupport = new ForkJoinTaskSupport( - * new scala.concurrent.forkjoin.ForkJoinPool(2)) - * }}} + * + * Here is a way to change the task support of a parallel collection: + * + * {{{ + * import scala.collection.parallel._ + * val pc = mutable.ParArray(1, 2, 3) + * pc.tasksupport = new ForkJoinTaskSupport( + * new scala.concurrent.forkjoin.ForkJoinPool(2)) + * }}} * * @see [[scala.collection.parallel.TaskSupport]] - */ + */ def tasksupport_=(ts: TaskSupport) = _tasksupport = ts def seq: Sequential @@ -848,6 +848,7 @@ self: ParIterableLike[T, Repr, Sequential] => override def seq = self.seq.view def splitter = self.splitter def size = splitter.remaining + override def isEmpty = size == 0 } override def toArray[U >: T: ClassTag]: Array[U] = { @@ -877,13 +878,13 @@ self: ParIterableLike[T, Repr, Sequential] => override def toSet[U >: T]: immutable.ParSet[U] = toParCollection[U, immutable.ParSet[U]](() => immutable.ParSet.newCombiner[U]) override def toMap[K, V](implicit ev: T <:< (K, V)): immutable.ParMap[K, V] = toParMap[K, V, immutable.ParMap[K, V]](() => immutable.ParMap.newCombiner[K, V]) - + override def toVector: Vector[T] = to[Vector] override def to[Col[_]](implicit cbf: CanBuildFrom[Nothing, T, Col[T @uncheckedVariance]]): Col[T @uncheckedVariance] = if (cbf().isCombiner) { toParCollection[T, Col[T]](() => cbf().asCombiner) } else seq.to(cbf) - + /* tasks */ protected trait StrictSplitterCheckTask[R, Tp] extends Task[R, Tp] { diff --git a/src/library/scala/collection/parallel/ParSeqLike.scala b/src/library/scala/collection/parallel/ParSeqLike.scala index be5ab03ba7..27e8eeb174 100644 --- a/src/library/scala/collection/parallel/ParSeqLike.scala +++ b/src/library/scala/collection/parallel/ParSeqLike.scala @@ -44,7 +44,7 @@ trait ParSeqLike[+T, +Repr <: ParSeq[T], +Sequential <: Seq[T] with SeqLike[T, S extends scala.collection.GenSeqLike[T, Repr] with ParIterableLike[T, Repr, Sequential] { self => - + type SuperParIterator = IterableSplitter[T] /** A more refined version of the iterator found in the `ParallelIterable` trait, @@ -330,6 +330,7 @@ self => def apply(idx: Int) = self(idx) override def seq = self.seq.view def splitter = self.splitter + override def isEmpty = size == 0 } /* tasks */ diff --git a/src/partest/scala/tools/partest/nest/CompileManager.scala b/src/partest/scala/tools/partest/nest/CompileManager.scala index 604e53e64c..0f2806214f 100644 --- a/src/partest/scala/tools/partest/nest/CompileManager.scala +++ b/src/partest/scala/tools/partest/nest/CompileManager.scala @@ -9,6 +9,7 @@ package scala.tools.partest package nest import scala.tools.nsc.{ Global, Settings, CompilerCommand, FatalError, io } +import scala.tools.nsc.io.{ File => SFile } import scala.tools.nsc.interactive.RangePositions import scala.tools.nsc.reporters.{ Reporter, ConsoleReporter } import scala.tools.nsc.util.{ ClassPath, FakePos } @@ -94,7 +95,17 @@ class DirectCompiler(val fileManager: FileManager) extends SimpleCompiler { val logFile = basename(log.getName) val flagsFileName = "%s.flags" format (logFile.substring(0, logFile.lastIndexOf("-"))) val argString = (io.File(log).parent / flagsFileName) ifFile (x => updatePluginPath(x.slurp())) getOrElse "" - val allOpts = fileManager.SCALAC_OPTS.toList ::: argString.split(' ').toList.filter(_.length > 0) + + // slurp local flags (e.g., "A_1.flags") + val fstFile = SFile(files(0)) + def isInGroup(num: Int) = fstFile.stripExtension endsWith ("_" + num) + val inGroup = (1 to 9) flatMap (group => if (isInGroup(group)) List(group) else List()) + val localFlagsList = if (inGroup.nonEmpty) { + val localArgString = (fstFile.parent / (fstFile.stripExtension + ".flags")) ifFile (x => updatePluginPath(x.slurp())) getOrElse "" + localArgString.split(' ').toList.filter(_.length > 0) + } else List() + + val allOpts = fileManager.SCALAC_OPTS.toList ::: argString.split(' ').toList.filter(_.length > 0) ::: localFlagsList val args = allOpts.toList NestUI.verbose("scalac options: "+allOpts) diff --git a/src/reflect/scala/reflect/internal/SymbolTable.scala b/src/reflect/scala/reflect/internal/SymbolTable.scala index c564a93b62..761dcc0534 100644 --- a/src/reflect/scala/reflect/internal/SymbolTable.scala +++ b/src/reflect/scala/reflect/internal/SymbolTable.scala @@ -43,7 +43,9 @@ abstract class SymbolTable extends macros.Universe lazy val treeBuild = gen def log(msg: => AnyRef): Unit - def abort(msg: String): Nothing = throw new FatalError(supplementErrorMessage(msg)) + def warning(msg: String): Unit = Console.err.println(msg) + def globalError(msg: String): Unit = abort(msg) + def abort(msg: String): Nothing = throw new FatalError(supplementErrorMessage(msg)) @deprecated("Give us a reason", "2.10.0") def abort(): Nothing = abort("unknown error") diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 5b99e2fdf0..ac8b254f83 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -390,6 +390,16 @@ trait Symbols extends api.Symbols { self: SymbolTable => case x: TermName => newErrorValue(x) } + /** Creates a placeholder symbol for when a name is encountered during + * unpickling for which there is no corresponding classfile. This defers + * failure to the point when that name is used for something, which is + * often to the point of never. + */ + def newStubSymbol(name: Name): Symbol = name match { + case n: TypeName => new StubClassSymbol(this, n) + case _ => new StubTermSymbol(this, name.toTermName) + } + @deprecated("Use the other signature", "2.10.0") def newClass(pos: Position, name: TypeName): Symbol = newClass(name, pos) @deprecated("Use the other signature", "2.10.0") @@ -2996,6 +3006,37 @@ trait Symbols extends api.Symbols { self: SymbolTable => || info.parents.exists(_.typeSymbol hasTransOwner sym) ) } + trait StubSymbol extends Symbol { + protected def stubWarning = { + val from = if (associatedFile == null) "" else s" - referenced from ${associatedFile.canonicalPath}" + s"$kindString $nameString$locationString$from (a classfile may be missing)" + } + private def fail[T](alt: T): T = { + // Avoid issuing lots of redundant errors + if (!hasFlag(IS_ERROR)) { + globalError(s"bad symbolic reference to " + stubWarning) + if (settings.debug.value) + (new Throwable).printStackTrace + + this setFlag IS_ERROR + } + alt + } + // This one doesn't call fail because SpecializeTypes winds up causing + // isMonomorphicType to be called, which calls this, which would fail us + // in all the scenarios we're trying to keep from failing. + override def originalInfo = NoType + override def associatedFile = owner.associatedFile + override def info = fail(NoType) + override def rawInfo = fail(NoType) + override def companionSymbol = fail(NoSymbol) + + locally { + debugwarn("creating stub symbol for " + stubWarning) + } + } + class StubClassSymbol(owner0: Symbol, name0: TypeName) extends ClassSymbol(owner0, owner0.pos, name0) with StubSymbol + class StubTermSymbol(owner0: Symbol, name0: TermName) extends TermSymbol(owner0, owner0.pos, name0) with StubSymbol trait FreeSymbol extends Symbol { def origin: String diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index 92a6156e54..19f264f60e 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -372,6 +372,13 @@ abstract class TreeInfo { case _ => EmptyTree } + /** If this tree represents a type application the type arguments. Otherwise Nil. + */ + def typeArguments(tree: Tree): List[Tree] = tree match { + case TypeApply(_, targs) => targs + case _ => Nil + } + /** If this tree has type parameters, those. Otherwise Nil. */ def typeParameters(tree: Tree): List[TypeDef] = tree match { diff --git a/src/reflect/scala/reflect/internal/pickling/UnPickler.scala b/src/reflect/scala/reflect/internal/pickling/UnPickler.scala index 55746f414b..9234ccca7b 100644 --- a/src/reflect/scala/reflect/internal/pickling/UnPickler.scala +++ b/src/reflect/scala/reflect/internal/pickling/UnPickler.scala @@ -230,9 +230,11 @@ abstract class UnPickler /*extends reflect.generic.UnPickler*/ { fromName(nme.expandedName(name.toTermName, owner)) orElse { // (3) Try as a nested object symbol. nestedObjectSymbol orElse { - // (4) Otherwise, fail. - //System.err.println("missing "+name+" in "+owner+"/"+owner.id+" "+owner.info.decls) - adjust(errorMissingRequirement(name, owner)) + // (4) Call the mirror's "missing" hook. + adjust(mirrorThatLoaded(owner).missingHook(owner, name)) orElse { + // (5) Create a stub symbol to defer hard failure a little longer. + owner.newStubSymbol(name) + } } } } |