diff options
Diffstat (limited to 'src/compiler')
63 files changed, 7934 insertions, 1394 deletions
diff --git a/src/compiler/scala/reflect/macros/compiler/Errors.scala b/src/compiler/scala/reflect/macros/compiler/Errors.scala index dd3142127e..a60a2c2306 100644 --- a/src/compiler/scala/reflect/macros/compiler/Errors.scala +++ b/src/compiler/scala/reflect/macros/compiler/Errors.scala @@ -60,8 +60,8 @@ trait Errors extends Traces { (rtpe, atpe) match { case _ if rtpe eq atpe => success() case (TypeRef(_, RepeatedParamClass, rtpe :: Nil), TypeRef(_, RepeatedParamClass, atpe :: Nil)) => check(rtpe, atpe) - case (ExprClassOf(_), TreeType()) => success() - case (TreeType(), ExprClassOf(_)) => success() + case (ExprClassOf(_), TreeType()) if rtpe.prefix =:= atpe.prefix => success() + case (SubtreeType(), ExprClassOf(_)) if rtpe.prefix =:= atpe.prefix => success() case _ => rtpe <:< atpe } } diff --git a/src/compiler/scala/reflect/reify/codegen/GenTrees.scala b/src/compiler/scala/reflect/reify/codegen/GenTrees.scala index 78bdf7e132..3507c2a173 100644 --- a/src/compiler/scala/reflect/reify/codegen/GenTrees.scala +++ b/src/compiler/scala/reflect/reify/codegen/GenTrees.scala @@ -42,12 +42,6 @@ trait GenTrees { // the second prototype reified external types, but avoided reifying local ones => this created an ugly irregularity // current approach is uniform and compact var rtree = tree match { - case global.EmptyTree => - reifyMirrorObject(EmptyTree) - case global.emptyValDef => - mirrorSelect(nme.emptyValDef) - case global.pendingSuperCall => - mirrorSelect(nme.pendingSuperCall) case FreeDef(_, _, _, _, _) => reifyNestedFreeDef(tree) case FreeRef(_, _) => @@ -56,12 +50,8 @@ trait GenTrees { reifyBoundTerm(tree) case BoundType(tree) => reifyBoundType(tree) - case Literal(const @ Constant(_)) => - mirrorCall(nme.Literal, reifyProduct(const)) - case Import(expr, selectors) => - mirrorCall(nme.Import, reify(expr), mkList(selectors map reifyProduct)) case _ => - reifyProduct(tree) + reifyTreeSyntactically(tree) } // usually we don't reify symbols/types, because they can be re-inferred during subsequent reflective compilation @@ -78,6 +68,21 @@ trait GenTrees { rtree } + def reifyTreeSyntactically(tree: Tree) = tree match { + case global.EmptyTree => + reifyMirrorObject(EmptyTree) + case global.emptyValDef => + mirrorSelect(nme.emptyValDef) + case global.pendingSuperCall => + mirrorSelect(nme.pendingSuperCall) + case Literal(const @ Constant(_)) => + mirrorCall(nme.Literal, reifyProduct(const)) + case Import(expr, selectors) => + mirrorCall(nme.Import, reify(expr), mkList(selectors map reifyProduct)) + case _ => + reifyProduct(tree) + } + def reifyModifiers(m: global.Modifiers) = mirrorFactoryCall(nme.Modifiers, mirrorBuildCall(nme.flagsFromBits, reify(m.flags)), reify(m.privateWithin), reify(m.annotations)) diff --git a/src/compiler/scala/reflect/reify/codegen/GenUtils.scala b/src/compiler/scala/reflect/reify/codegen/GenUtils.scala index e0570d61f2..de9fec0df5 100644 --- a/src/compiler/scala/reflect/reify/codegen/GenUtils.scala +++ b/src/compiler/scala/reflect/reify/codegen/GenUtils.scala @@ -42,6 +42,9 @@ trait GenUtils { def mirrorBuildCall(name: TermName, args: Tree*): Tree = call("" + nme.UNIVERSE_BUILD_PREFIX + name, args: _*) + def reifyBuildCall(name: TermName, args: Any*) = + mirrorBuildCall(name, args map reify: _*) + def mirrorMirrorCall(name: TermName, args: Tree*): Tree = call("" + nme.MIRROR_PREFIX + name, args: _*) diff --git a/src/compiler/scala/reflect/reify/package.scala b/src/compiler/scala/reflect/reify/package.scala index d3cae3d123..30cfec8e2a 100644 --- a/src/compiler/scala/reflect/reify/package.scala +++ b/src/compiler/scala/reflect/reify/package.scala @@ -32,7 +32,7 @@ package object reify { // If we're in the constructor of an object or others don't have easy access to `this`, we have no good way to grab // the class of that object. Instead, we construct an anonymous class and grab his class file, assuming // this is enough to get the correct class loadeer for the class we *want* a mirror for, the object itself. - rClassTree orElse Apply(Select(treeBuilder.makeAnonymousNew(Nil), sn.GetClass), Nil) + rClassTree orElse Apply(Select(gen.mkAnonymousNew(Nil), sn.GetClass), Nil) } // JavaUniverse is defined in scala-reflect.jar, so we must be very careful in case someone reifies stuff having only scala-library.jar on the classpath val isJavaUniverse = JavaUniverseClass != NoSymbol && universe.tpe <:< JavaUniverseClass.toTypeConstructor diff --git a/src/compiler/scala/tools/ant/sabbus/Settings.scala b/src/compiler/scala/tools/ant/sabbus/Settings.scala index d0fefdaa03..4cbc03d8d4 100644 --- a/src/compiler/scala/tools/ant/sabbus/Settings.scala +++ b/src/compiler/scala/tools/ant/sabbus/Settings.scala @@ -93,4 +93,18 @@ class Settings { case _ => false } + override lazy val hashCode: Int = Seq( + gBf, + uncheckedBf, + classpathBf, + sourcepathBf, + sourcedirBf, + bootclasspathBf, + extdirsBf, + dBf, + encodingBf, + targetBf, + optimiseBf, + extraParamsBf + ).## } diff --git a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl index 84ccaba749..abf9925ad9 100644 --- a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl +++ b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl @@ -115,7 +115,7 @@ if [[ -n "$cygwin$mingw" ]]; then case "$TERM" in rxvt* | xterm*) stty -icanon min 1 -echo - WINDOWS_OPT="-Djline.terminal=scala.tools.jline.UnixTerminal" + WINDOWS_OPT="-Djline.terminal=unix" ;; esac fi diff --git a/src/compiler/scala/tools/cmd/Demo.scala b/src/compiler/scala/tools/cmd/Demo.scala deleted file mode 100644 index fc90140f8f..0000000000 --- a/src/compiler/scala/tools/cmd/Demo.scala +++ /dev/null @@ -1,85 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2013 LAMP/EPFL - * @author Paul Phillips - */ - -package scala -package tools -package cmd - -/** A sample command specification for illustrative purposes. - * First take advantage of the meta-options: - * - * // this command creates an executable runner script "demo" - * % scala scala.tools.cmd.Demo --self-update demo - * - * // this one creates and sources a completion file - note backticks - * % `./demo --bash` - * - * // and now you have a runner with working completion - * % ./demo --<tab> - * --action --defint --int - * --bash --defstr --str - * --defenv --self-update --unary - * - * The normal option configuration is plausibly self-explanatory. - */ -trait DemoSpec extends Spec with Meta.StdOpts with Interpolation { - lazy val referenceSpec = DemoSpec - lazy val programInfo = Spec.Info("demo", "Usage: demo [<options>]", "scala.tools.cmd.Demo") - - help("""Usage: demo [<options>]""") - heading("Unary options:") - - val optIsUnary = "unary" / "a unary option" --? ; - ("action" / "a body which may be run") --> println("Hello, I am the --action body.") - - heading("Binary options:") - val optopt = "str" / "an optional String" --| - val optoptInt = ("int" / "an optional Int") . --^[Int] - val optEnv = "defenv" / "an optional String" defaultToEnv "PATH" - val optDefault = "defstr" / "an optional String" defaultTo "default" - val optDefaultInt = "defint" / "an optional Int" defaultTo -1 - val optExpand = "alias" / "an option which expands" expandTo ("--int", "15") -} - -object DemoSpec extends DemoSpec with Property { - lazy val propMapper = new PropertyMapper(DemoSpec) - - type ThisCommandLine = SpecCommandLine - def creator(args: List[String]) = - new SpecCommandLine(args) { - override def errorFn(msg: String) = { println("Error: " + msg) ; sys.exit(0) } - } -} - -class Demo(args: List[String]) extends { - val parsed = DemoSpec(args: _*) -} with DemoSpec with Instance { - import java.lang.reflect._ - - def helpMsg = DemoSpec.helpMsg - def demoSpecMethods = this.getClass.getMethods.toList - private def isDemo(m: Method) = (m.getName startsWith "opt") && !(m.getName contains "$") && (m.getParameterTypes.isEmpty) - - def demoString(ms: List[Method]) = { - val longest = ms map (_.getName.length) max - val formatStr = " %-" + longest + "s: %s" - val xs = ms map (m => formatStr.format(m.getName, m.invoke(this))) - - xs mkString ("Demo(\n ", "\n ", "\n)\n") - } - - override def toString = demoString(demoSpecMethods filter isDemo) -} - -object Demo { - def main(args: Array[String]): Unit = { - val runner = new Demo(args.toList) - - if (args.isEmpty) - println(runner.helpMsg) - - println(runner) - } -} diff --git a/src/compiler/scala/tools/cmd/Spec.scala b/src/compiler/scala/tools/cmd/Spec.scala index b761601167..a1cb31f911 100644 --- a/src/compiler/scala/tools/cmd/Spec.scala +++ b/src/compiler/scala/tools/cmd/Spec.scala @@ -15,7 +15,7 @@ trait Spec { def programInfo: Spec.Info protected def help(str: => String): Unit - protected def heading(str: => String): Unit = help("\n " + str) + protected def heading(str: => String): Unit = help(s"\n $str") type OptionMagic <: Opt.Implicit protected implicit def optionMagicAdditions(s: String): OptionMagic diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index eafe03d5cd..ea6543bb71 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -28,6 +28,7 @@ import transform.patmat.PatternMatching import transform._ import backend.icode.{ ICodes, GenICode, ICodeCheckers } import backend.{ ScalaPrimitives, Platform, JavaPlatform } +import backend.jvm.GenBCode import backend.jvm.GenASM import backend.opt.{ Inliners, InlineExceptionHandlers, ConstantOptimization, ClosureElimination, DeadCodeElimination } import backend.icode.analysis._ @@ -102,16 +103,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter) typer.typed(mkCast(tree, pt)) } - /** Trees fresh from the oven, mostly for use by the parser. */ - object treeBuilder extends { - val global: Global.this.type = Global.this - } with TreeBuilder { - def freshName(prefix: String): Name = freshTermName(prefix) - def freshTermName(prefix: String): TermName = currentUnit.freshTermName(prefix) - def freshTypeName(prefix: String): TypeName = currentUnit.freshTypeName(prefix) - def o2p(offset: Int): Position = new OffsetPosition(currentUnit.source, offset) - def r2p(start: Int, mid: Int, end: Int): Position = rangePos(currentUnit.source, start, mid, end) - } + /** A spare instance of TreeBuilder left for backwards compatibility. */ + lazy val treeBuilder: TreeBuilder { val global: Global.this.type } = new syntaxAnalyzer.ParserTreeBuilder /** Fold constants */ object constfold extends { @@ -619,6 +612,13 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val runsRightAfter = None } with GenASM + // phaseName = "bcode" + object genBCode extends { + val global: Global.this.type = Global.this + val runsAfter = List("dce") + val runsRightAfter = None + } with GenBCode + // phaseName = "terminal" object terminal extends { val global: Global.this.type = Global.this @@ -1057,6 +1057,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) @inline final def enteringMixin[T](op: => T): T = enteringPhase(currentRun.mixinPhase)(op) @inline final def enteringPickler[T](op: => T): T = enteringPhase(currentRun.picklerPhase)(op) @inline final def enteringRefchecks[T](op: => T): T = enteringPhase(currentRun.refchecksPhase)(op) + @inline final def enteringSpecialize[T](op: => T): T = enteringPhase(currentRun.specializePhase)(op) @inline final def enteringTyper[T](op: => T): T = enteringPhase(currentRun.typerPhase)(op) @inline final def enteringUncurry[T](op: => T): T = enteringPhase(currentRun.uncurryPhase)(op) diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index c28a6ba337..ad1977b9aa 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -112,7 +112,6 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { else AppliedTypeTree(Ident(clazz), targs map TypeTree) )) } - def mkSuperInitCall: Select = Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR) def wildcardStar(tree: Tree) = atPos(tree.pos) { Typed(tree, Ident(tpnme.WILDCARD_STAR)) } @@ -255,4 +254,52 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { attrThis, If(cond, Block(syncBody: _*), EmptyTree)) :: stats: _*) + + /** Creates a tree representing new Object { stats }. + * To make sure an anonymous subclass of Object is created, + * if there are no stats, a () is added. + */ + def mkAnonymousNew(stats: List[Tree]): Tree = { + val stats1 = if (stats.isEmpty) List(Literal(Constant(()))) else stats + mkNew(Nil, emptyValDef, stats1, NoPosition, NoPosition) + } + + /** Create positioned tree representing an object creation <new parents { stats } + * @param npos the position of the new + * @param cpos the position of the anonymous class starting with parents + */ + def mkNew(parents: List[Tree], self: ValDef, stats: List[Tree], + npos: Position, cpos: Position): Tree = + if (parents.isEmpty) + mkNew(List(scalaAnyRefConstr), self, stats, npos, cpos) + else if (parents.tail.isEmpty && stats.isEmpty) { + // `Parsers.template` no longer differentiates tpts and their argss + // e.g. `C()` will be represented as a single tree Apply(Ident(C), Nil) + // instead of parents = Ident(C), argss = Nil as before + // this change works great for things that are actually templates + // but in this degenerate case we need to perform postprocessing + val app = treeInfo.dissectApplied(parents.head) + atPos(npos union cpos) { New(app.callee, app.argss) } + } else { + val x = tpnme.ANON_CLASS_NAME + atPos(npos union cpos) { + Block( + List( + atPos(cpos) { + ClassDef( + Modifiers(FINAL), x, Nil, + mkTemplate(parents, self, NoMods, ListOfNil, stats, cpos.focus)) + }), + atPos(npos) { + New( + Ident(x) setPos npos.focus, + Nil) + } + ) + } + } + + def mkSyntheticParam(pname: TermName) = + ValDef(Modifiers(PARAM | SYNTHETIC), pname, TypeTree(), EmptyTree) + } diff --git a/src/compiler/scala/tools/nsc/ast/Trees.scala b/src/compiler/scala/tools/nsc/ast/Trees.scala index 41d89aa3b4..641ab9c279 100644 --- a/src/compiler/scala/tools/nsc/ast/Trees.scala +++ b/src/compiler/scala/tools/nsc/ast/Trees.scala @@ -54,77 +54,6 @@ trait Trees extends scala.reflect.internal.Trees { self: Global => case xs :: rest => rest.foldLeft(Apply(gen.mkSuperInitCall, xs): Tree)(Apply.apply) } - /** Generates a template with constructor corresponding to - * - * constrmods (vparams1_) ... (vparams_n) preSuper { presupers } - * extends superclass(args_1) ... (args_n) with mixins { self => body } - * - * This gets translated to - * - * extends superclass with mixins { self => - * presupers' // presupers without rhs - * vparamss // abstract fields corresponding to value parameters - * def <init>(vparamss) { - * presupers - * super.<init>(args) - * } - * body - * } - */ - def Template(parents: List[Tree], self: ValDef, constrMods: Modifiers, vparamss: List[List[ValDef]], body: List[Tree], superPos: Position): Template = { - /* Add constructor to template */ - - // create parameters for <init> as synthetic trees. - var vparamss1 = mmap(vparamss) { vd => - atPos(vd.pos.focus) { - val mods = Modifiers(vd.mods.flags & (IMPLICIT | DEFAULTPARAM | BYNAMEPARAM) | PARAM | PARAMACCESSOR) - ValDef(mods withAnnotations vd.mods.annotations, vd.name, vd.tpt.duplicate, vd.rhs.duplicate) - } - } - val (edefs, rest) = body span treeInfo.isEarlyDef - val (evdefs, etdefs) = edefs partition treeInfo.isEarlyValDef - val gvdefs = evdefs map { - case vdef @ ValDef(_, _, tpt, _) => - copyValDef(vdef)( - // atPos for the new tpt is necessary, since the original tpt might have no position - // (when missing type annotation for ValDef for example), so even though setOriginal modifies the - // position of TypeTree, it would still be NoPosition. That's what the author meant. - tpt = atPos(vdef.pos.focus)(TypeTree() setOriginal tpt setPos tpt.pos.focus), - rhs = EmptyTree - ) - } - val lvdefs = evdefs collect { case vdef: ValDef => copyValDef(vdef)(mods = vdef.mods | PRESUPER) } - - val constrs = { - if (constrMods hasFlag TRAIT) { - if (body forall treeInfo.isInterfaceMember) List() - else List( - atPos(wrappingPos(superPos, lvdefs)) ( - DefDef(NoMods, nme.MIXIN_CONSTRUCTOR, List(), ListOfNil, TypeTree(), Block(lvdefs, Literal(Constant(())))))) - } else { - // convert (implicit ... ) to ()(implicit ... ) if its the only parameter section - if (vparamss1.isEmpty || !vparamss1.head.isEmpty && vparamss1.head.head.mods.isImplicit) - vparamss1 = List() :: vparamss1 - val superCall = pendingSuperCall // we can't know in advance which of the parents will end up as a superclass - // this requires knowing which of the parents is a type macro and which is not - // and that's something that cannot be found out before typer - // (the type macros aren't in the trunk yet, but there is a plan for them to land there soon) - // this means that we don't know what will be the arguments of the super call - // therefore here we emit a dummy which gets populated when the template is named and typechecked - List( - // TODO: previously this was `wrappingPos(superPos, lvdefs ::: argss.flatten)` - // is it going to be a problem that we can no longer include the `argss`? - atPos(wrappingPos(superPos, lvdefs)) ( - DefDef(constrMods, nme.CONSTRUCTOR, List(), vparamss1, TypeTree(), Block(lvdefs ::: List(superCall), Literal(Constant(())))))) - } - } - constrs foreach (ensureNonOverlapping(_, parents ::: gvdefs, focus=false)) - // Field definitions for the class - remove defaults. - val fieldDefs = vparamss.flatten map (vd => copyValDef(vd)(mods = vd.mods &~ DEFAULTPARAM, rhs = EmptyTree)) - - Template(parents, self, gvdefs ::: fieldDefs ::: constrs ::: etdefs ::: rest) - } - /** Construct class definition with given class symbol, value parameters, * supercall arguments and template body. * @@ -143,9 +72,9 @@ trait Trees extends scala.reflect.internal.Trees { self: Global => ) ClassDef(sym, - Template(sym.info.parents map TypeTree, - if (sym.thisSym == sym || phase.erasedTypes) emptyValDef else ValDef(sym.thisSym), - constrMods, vparamss, body, superPos)) + gen.mkTemplate(sym.info.parents map TypeTree, + if (sym.thisSym == sym || phase.erasedTypes) emptyValDef else ValDef(sym.thisSym), + constrMods, vparamss, body, superPos)) } // --- subcomponents -------------------------------------------------- diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index ef5872986c..eb924a811b 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -26,13 +26,22 @@ import util.FreshNameCreator * the beginnings of a campaign against this latest incursion by Cutty * McPastington and his army of very similar soldiers. */ -trait ParsersCommon extends ScannersCommon { +trait ParsersCommon extends ScannersCommon { self => val global : Global import global._ def newLiteral(const: Any) = Literal(Constant(const)) def literalUnit = newLiteral(()) + class ParserTreeBuilder extends TreeBuilder { + val global: self.global.type = self.global + def freshName(prefix: String): Name = freshTermName(prefix) + def freshTermName(prefix: String): TermName = currentUnit.freshTermName(prefix) + def freshTypeName(prefix: String): TypeName = currentUnit.freshTypeName(prefix) + def o2p(offset: Int): Position = new OffsetPosition(currentUnit.source, offset) + def r2p(start: Int, mid: Int, end: Int): Position = rangePos(currentUnit.source, start, mid, end) + } + /** This is now an abstract class, only to work around the optimizer: * methods in traits are never inlined. */ @@ -147,6 +156,17 @@ self => def newScanner(): Scanner = new SourceFileScanner(source) + /** Scoping operator used to temporarily look into the future. + * Backs up scanner data before evaluating a block and restores it after. + */ + def lookingAhead[T](body: => T): T = { + val snapshot = (new ScannerData{}).copyFrom(in) + in.nextToken() + val res = body + in copyFrom snapshot + res + } + val in = newScanner() in.init() @@ -290,6 +310,7 @@ self => /** whether a non-continuable syntax error has been seen */ private var lastErrorOffset : Int = -1 + val treeBuilder = new ParserTreeBuilder import treeBuilder.{global => _, _} /** The types of the context bounds of type parameters of the surrounding class @@ -399,7 +420,7 @@ self => def mainParamType = AppliedTypeTree(Ident(tpnme.Array), List(Ident(tpnme.String))) def mainParameter = List(ValDef(Modifiers(Flags.PARAM), nme.argv, mainParamType, EmptyTree)) def mainSetArgv = List(ValDef(NoMods, nme.args, TypeTree(), Ident(nme.argv))) - def mainDef = DefDef(NoMods, nme.main, Nil, List(mainParameter), scalaDot(tpnme.Unit), Block(mainSetArgv, makeAnonymousNew(stmts))) + def mainDef = DefDef(NoMods, nme.main, Nil, List(mainParameter), scalaDot(tpnme.Unit), Block(mainSetArgv, gen.mkAnonymousNew(stmts))) // object Main def moduleName = newTermName(ScriptRunner scriptMain settings) @@ -604,6 +625,8 @@ self => case _ => false } + def isAnnotation: Boolean = in.token == AT + def isLocalModifier: Boolean = in.token match { case ABSTRACT | FINAL | SEALED | IMPLICIT | LAZY => true case _ => false @@ -731,7 +754,7 @@ self => } @inline final def commaSeparated[T](part: => T): List[T] = tokenSeparated(COMMA, sepFirst = false, part) @inline final def caseSeparated[T](part: => T): List[T] = tokenSeparated(CASE, sepFirst = true, part) - @inline final def readAnnots[T](part: => T): List[T] = tokenSeparated(AT, sepFirst = true, part) + def readAnnots(part: => Tree): List[Tree] = tokenSeparated(AT, sepFirst = true, part) /* --------- OPERAND/OPERATOR STACK --------------------------------------- */ @@ -1365,7 +1388,7 @@ self => } else { syntaxErrorOrIncomplete("`*' expected", skipIt = true) } - } else if (in.token == AT) { + } else if (isAnnotation) { t = (t /: annotations(skipNewLines = false))(makeAnnotated) } else { t = atPos(t.pos.startOrPoint, colonPos) { @@ -1501,7 +1524,7 @@ self => val pname = freshName("x$") in.nextToken() val id = atPos(start) (Ident(pname)) - val param = atPos(id.pos.focus){ makeSyntheticParam(pname.toTermName) } + val param = atPos(id.pos.focus){ gen.mkSyntheticParam(pname.toTermName) } placeholderParams = param :: placeholderParams id case LPAREN => @@ -1516,7 +1539,7 @@ self => val tstart = in.offset val (parents, self, stats) = template() val cpos = r2p(tstart, tstart, in.lastOffset max tstart) - makeNew(parents, self, stats, npos, cpos) + gen.mkNew(parents, self, stats, npos, cpos) case _ => syntaxErrorOrIncompleteAnd("illegal start of simple expression", skipIt = true)(errorTermTree) } @@ -1602,13 +1625,16 @@ self => */ def block(): Tree = makeBlock(blockStatSeq()) + def caseClause(): CaseDef = + atPos(in.offset)(makeCaseDef(pattern(), guard(), caseBlock())) + /** {{{ * CaseClauses ::= CaseClause {CaseClause} * CaseClause ::= case Pattern [Guard] `=>' Block * }}} */ def caseClauses(): List[CaseDef] = { - val cases = caseSeparated { atPos(in.offset)(makeCaseDef(pattern(), guard(), caseBlock())) } + val cases = caseSeparated { caseClause() } if (cases.isEmpty) // trigger error if there are no cases accept(CASE) @@ -2050,6 +2076,8 @@ self => /* -------- PARAMETERS ------------------------------------------- */ + def allowTypelessParams = false + /** {{{ * ParamClauses ::= {ParamClause} [[nl] `(' implicit Params `)'] * ParamClause ::= [nl] `(' [Params] `)' @@ -2086,7 +2114,7 @@ self => val name = ident() var bynamemod = 0 val tpt = - if (settings.YmethodInfer && !owner.isTypeName && in.token != COLON) { + if (((settings.YmethodInfer && !owner.isTypeName) || allowTypelessParams) && in.token != COLON) { TypeTree() } else { // XX-METHOD-INFER accept(COLON) @@ -2804,7 +2832,7 @@ self => if (inScalaRootPackage && ScalaValueClassNames.contains(name)) Template(parents0, self, anyvalConstructor :: body) else - Template(anyrefParents(), self, constrMods, vparamss, body, o2p(tstart)) + gen.mkTemplate(anyrefParents(), self, constrMods, vparamss, body, o2p(tstart)) } } @@ -2867,7 +2895,7 @@ self => case IMPORT => in.flushDoc importClause() - case x if x == AT || isTemplateIntro || isModifier => + case x if isAnnotation || isTemplateIntro || isModifier => joinComment(topLevelTmplDef :: Nil) case _ => if (isStatSep) Nil @@ -2923,11 +2951,11 @@ self => if (in.token == IMPORT) { in.flushDoc stats ++= importClause() + } else if (isDefIntro || isModifier || isAnnotation) { + stats ++= joinComment(nonLocalDefOrDcl) } else if (isExprIntro) { in.flushDoc stats += statement(InTemplate) - } else if (isDefIntro || isModifier || in.token == AT) { - stats ++= joinComment(nonLocalDefOrDcl) } else if (!isStatSep) { syntaxErrorOrIncomplete("illegal start of definition", skipIt = true) } @@ -3007,7 +3035,7 @@ self => stats += statement(InBlock) if (in.token != RBRACE && in.token != CASE) acceptStatSep() } - else if (isDefIntro || isLocalModifier || in.token == AT) { + else if (isDefIntro || isLocalModifier || isAnnotation) { if (in.token == IMPLICIT) { val start = in.skipToken() if (isIdent) stats += implicitClosure(start, InBlock) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index 2dca39f7a3..03cdead472 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -5,7 +5,7 @@ package scala.tools.nsc package ast.parser -import scala.tools.nsc.util.CharArrayReader +import scala.tools.nsc.util.{ CharArrayReader, CharArrayReaderData } import scala.reflect.internal.util._ import scala.reflect.internal.Chars._ import Tokens._ @@ -71,17 +71,37 @@ trait Scanners extends ScannersCommon { /** the base of a number */ var base: Int = 0 - def copyFrom(td: TokenData) = { + def copyFrom(td: TokenData): this.type = { this.token = td.token this.offset = td.offset this.lastOffset = td.lastOffset this.name = td.name this.strVal = td.strVal this.base = td.base + this } } - abstract class Scanner extends CharArrayReader with TokenData with ScannerCommon { + /** An interface to most of mutable data in Scanner defined in TokenData + * and CharArrayReader (+ next, prev fields) with copyFrom functionality + * to backup/restore data (used by quasiquotes' lookingAhead). + */ + trait ScannerData extends TokenData with CharArrayReaderData { + /** we need one token lookahead and one token history + */ + val next: TokenData = new TokenData{} + val prev: TokenData = new TokenData{} + + def copyFrom(sd: ScannerData): this.type = { + this.next copyFrom sd.next + this.prev copyFrom sd.prev + super[CharArrayReaderData].copyFrom(sd) + super[TokenData].copyFrom(sd) + this + } + } + + abstract class Scanner extends CharArrayReader with TokenData with ScannerData with ScannerCommon { private def isDigit(c: Char) = java.lang.Character isDigit c private var openComments = 0 @@ -194,13 +214,6 @@ trait Scanners extends ScannersCommon { cbuf.clear() } - private class TokenData0 extends TokenData - - /** we need one token lookahead and one token history - */ - val next : TokenData = new TokenData0 - val prev : TokenData = new TokenData0 - /** a stack of tokens which indicates whether line-ends can be statement separators * also used for keeping track of nesting levels. * We keep track of the closing symbol of a region. This can be diff --git a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala index 0ef71fa1b5..666f19851d 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala @@ -190,50 +190,6 @@ abstract class TreeBuilder { } } - /** Creates a tree representing new Object { stats }. - * To make sure an anonymous subclass of Object is created, - * if there are no stats, a () is added. - */ - def makeAnonymousNew(stats: List[Tree]): Tree = { - val stats1 = if (stats.isEmpty) List(Literal(Constant(()))) else stats - makeNew(Nil, emptyValDef, stats1, NoPosition, NoPosition) - } - - /** Create positioned tree representing an object creation <new parents { stats } - * @param npos the position of the new - * @param cpos the position of the anonymous class starting with parents - */ - def makeNew(parents: List[Tree], self: ValDef, stats: List[Tree], - npos: Position, cpos: Position): Tree = - if (parents.isEmpty) - makeNew(List(scalaAnyRefConstr), self, stats, npos, cpos) - else if (parents.tail.isEmpty && stats.isEmpty) { - // `Parsers.template` no longer differentiates tpts and their argss - // e.g. `C()` will be represented as a single tree Apply(Ident(C), Nil) - // instead of parents = Ident(C), argss = Nil as before - // this change works great for things that are actually templates - // but in this degenerate case we need to perform postprocessing - val app = treeInfo.dissectApplied(parents.head) - atPos(npos union cpos) { New(app.callee, app.argss) } - } else { - val x = tpnme.ANON_CLASS_NAME - atPos(npos union cpos) { - Block( - List( - atPos(cpos) { - ClassDef( - Modifiers(FINAL), x, Nil, - Template(parents, self, NoMods, ListOfNil, stats, cpos.focus)) - }), - atPos(npos) { - New( - Ident(x) setPos npos.focus, - Nil) - } - ) - } - } - /** Create a tree representing an assignment <lhs = rhs> */ def makeAssign(lhs: Tree, rhs: Tree): Tree = lhs match { case Apply(fn, args) => @@ -303,9 +259,6 @@ abstract class TreeBuilder { def makeParam(pname: TermName, tpe: Tree) = ValDef(Modifiers(PARAM), pname, tpe, EmptyTree) - def makeSyntheticParam(pname: TermName) = - ValDef(Modifiers(PARAM | SYNTHETIC), pname, TypeTree(), EmptyTree) - def makeSyntheticTypeParam(pname: TypeName, bounds: Tree) = TypeDef(Modifiers(DEFERRED | SYNTHETIC), pname, Nil, bounds) @@ -467,7 +420,7 @@ abstract class TreeBuilder { val x = freshTermName(prefix) val id = Ident(x) val sel = if (checkExhaustive) id else gen.mkUnchecked(id) - Function(List(makeSyntheticParam(x)), Match(sel, cases)) + Function(List(gen.mkSyntheticParam(x)), Match(sel, cases)) } /** Create tree for case definition <case pat if guard => rhs> */ diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala index 00f2933fab..c5fc12e3ec 100644 --- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala +++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala @@ -38,9 +38,13 @@ trait JavaPlatform extends Platform { // replaces the tighter abstract definition here. If we had DOT typing rules, the two // types would be conjoined and everything would work out. Yet another reason to push for DOT. + private def classEmitPhase = + if (settings.isBCodeActive) genBCode + else genASM + def platformPhases = List( flatten, // get rid of inner classes - genASM // generate .class files + classEmitPhase // generate .class files ) lazy val externalEquals = getDecl(BoxesRunTimeClass, nme.equals_) diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala index 7263a0d0b9..e6f21fc1e3 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala @@ -52,6 +52,7 @@ abstract class GenICode extends SubComponent { } override def apply(unit: CompilationUnit): Unit = { + if (settings.isBCodeActive) { return } this.unit = unit unit.icode.clear() informProgress("Generating icode for " + unit) @@ -1747,7 +1748,7 @@ abstract class GenICode extends SubComponent { /////////////////////// Context //////////////////////////////// - abstract class Cleanup(val value: AnyRef) { + sealed abstract class Cleanup(val value: AnyRef) { def contains(x: AnyRef) = value == x } case class MonitorRelease(m: Local) extends Cleanup(m) { } diff --git a/src/compiler/scala/tools/nsc/backend/icode/Members.scala b/src/compiler/scala/tools/nsc/backend/icode/Members.scala index 4389afb2b7..91bd39232e 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Members.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Members.scala @@ -108,6 +108,14 @@ trait Members { if (symbol eq other.symbol) 0 else if (symbol isLess other.symbol) -1 else 1 + + override def equals(other: Any): Boolean = + other match { + case other: IMember => (this compare other) == 0 + case _ => false + } + + override def hashCode = symbol.## } /** Represent a class in ICode */ diff --git a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala index 57a768d9cb..076f84ce7a 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala @@ -725,6 +725,8 @@ trait Opcodes { self: ICodes => /** Is this a static method call? */ def isStatic: Boolean = false + def isSuper: Boolean = false + /** Is this an instance method call? */ def hasInstance: Boolean = true @@ -758,6 +760,7 @@ trait Opcodes { self: ICodes => * On JVM, translated to `invokespecial`. */ case class SuperCall(mix: Name) extends InvokeStyle { + override def isSuper = true override def toString(): String = { "super(" + mix + ")" } } } diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/CopyPropagation.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/CopyPropagation.scala index 0c3f92f13f..9d48d7a0d3 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/analysis/CopyPropagation.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/analysis/CopyPropagation.scala @@ -27,7 +27,7 @@ abstract class CopyPropagation { case object This extends Location /** Values that can be on the stack. */ - abstract class Value { } + sealed abstract class Value { } case class Record(cls: Symbol, bindings: mutable.Map[Symbol, Value]) extends Value { } /** The value of some location in memory. */ case class Deref(l: Location) extends Value diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala new file mode 100644 index 0000000000..a7f43eefed --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -0,0 +1,1256 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + + +package scala +package tools.nsc +package backend +package jvm + +import scala.collection.{ mutable, immutable } +import scala.annotation.switch + +import scala.tools.asm + +/* + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ + * @version 1.0 + * + */ +abstract class BCodeBodyBuilder extends BCodeSkelBuilder { + import global._ + import definitions._ + + /* + * Functionality to build the body of ASM MethodNode, except for `synchronized` and `try` expressions. + */ + abstract class PlainBodyBuilder(cunit: CompilationUnit) extends PlainSkelBuilder(cunit) { + + import icodes.TestOp + import icodes.opcodes.InvokeStyle + + /* If the selector type has a member with the right name, + * it is the host class; otherwise the symbol's owner. + */ + def findHostClass(selector: Type, sym: Symbol) = selector member sym.name match { + case NoSymbol => log(s"Rejecting $selector as host class for $sym") ; sym.owner + case _ => selector.typeSymbol + } + + /* ---------------- helper utils for generating methods and code ---------------- */ + + def emit(opc: Int) { mnode.visitInsn(opc) } + def emit(i: asm.tree.AbstractInsnNode) { mnode.instructions.add(i) } + def emit(is: List[asm.tree.AbstractInsnNode]) { for(i <- is) { mnode.instructions.add(i) } } + + def emitZeroOf(tk: BType) { + (tk.sort: @switch) match { + case asm.Type.BOOLEAN => bc.boolconst(false) + case asm.Type.BYTE | + asm.Type.SHORT | + asm.Type.CHAR | + asm.Type.INT => bc.iconst(0) + case asm.Type.LONG => bc.lconst(0) + case asm.Type.FLOAT => bc.fconst(0) + case asm.Type.DOUBLE => bc.dconst(0) + case asm.Type.VOID => () + case _ => emit(asm.Opcodes.ACONST_NULL) + } + } + + /* + * Emits code that adds nothing to the operand stack. + * Two main cases: `tree` is an assignment, + * otherwise an `adapt()` to UNIT is performed if needed. + */ + def genStat(tree: Tree) { + lineNumber(tree) + tree match { + case Assign(lhs @ Select(_, _), rhs) => + val isStatic = lhs.symbol.isStaticMember + if (!isStatic) { genLoadQualifier(lhs) } + genLoad(rhs, symInfoTK(lhs.symbol)) + lineNumber(tree) + fieldStore(lhs.symbol) + + case Assign(lhs, rhs) => + val s = lhs.symbol + val Local(tk, _, idx, _) = locals.getOrMakeLocal(s) + genLoad(rhs, tk) + lineNumber(tree) + bc.store(idx, tk) + + case _ => + genLoad(tree, UNIT) + } + } + + def genThrow(expr: Tree): BType = { + val thrownKind = tpeTK(expr) + assert(exemplars.get(thrownKind).isSubtypeOf(ThrowableReference)) + genLoad(expr, thrownKind) + lineNumber(expr) + emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level. + + RT_NOTHING // always returns the same, the invoker should know :) + } + + /* Generate code for primitive arithmetic operations. */ + def genArithmeticOp(tree: Tree, code: Int): BType = { + val Apply(fun @ Select(larg, _), args) = tree + var resKind = tpeTK(larg) + + assert(resKind.isNumericType || (resKind == BOOL), + s"$resKind is not a numeric or boolean type [operation: ${fun.symbol}]") + + import scalaPrimitives._ + + args match { + // unary operation + case Nil => + genLoad(larg, resKind) + code match { + case POS => () // nothing + case NEG => bc.neg(resKind) + case NOT => bc.genPrimitiveArithmetic(icodes.NOT, resKind) + case _ => abort(s"Unknown unary operation: ${fun.symbol.fullName} code: $code") + } + + // binary operation + case rarg :: Nil => + resKind = maxType(tpeTK(larg), tpeTK(rarg)) + if (scalaPrimitives.isShiftOp(code) || scalaPrimitives.isBitwiseOp(code)) { + assert(resKind.isIntegralType || (resKind == BOOL), + s"$resKind incompatible with arithmetic modulo operation.") + } + + genLoad(larg, resKind) + genLoad(rarg, // check .NET size of shift arguments! + if (scalaPrimitives.isShiftOp(code)) INT else resKind) + + (code: @switch) match { + case ADD => bc add resKind + case SUB => bc sub resKind + case MUL => bc mul resKind + case DIV => bc div resKind + case MOD => bc rem resKind + + case OR | XOR | AND => bc.genPrimitiveLogical(code, resKind) + + case LSL | LSR | ASR => bc.genPrimitiveShift(code, resKind) + + case _ => abort(s"Unknown primitive: ${fun.symbol}[$code]") + } + + case _ => + abort(s"Too many arguments for primitive function: $tree") + } + lineNumber(tree) + resKind + } + + /* Generate primitive array operations. */ + def genArrayOp(tree: Tree, code: Int, expectedType: BType): BType = { + val Apply(Select(arrayObj, _), args) = tree + val k = tpeTK(arrayObj) + genLoad(arrayObj, k) + val elementType = typeOfArrayOp.getOrElse(code, abort(s"Unknown operation on arrays: $tree code: $code")) + + var generatedType = expectedType + + if (scalaPrimitives.isArrayGet(code)) { + // load argument on stack + assert(args.length == 1, s"Too many arguments for array get operation: $tree"); + genLoad(args.head, INT) + generatedType = k.getComponentType + bc.aload(elementType) + } + else if (scalaPrimitives.isArraySet(code)) { + args match { + case a1 :: a2 :: Nil => + genLoad(a1, INT) + genLoad(a2) + // the following line should really be here, but because of bugs in erasure + // we pretend we generate whatever type is expected from us. + //generatedType = UNIT + bc.astore(elementType) + case _ => + abort(s"Too many arguments for array set operation: $tree") + } + } + else { + generatedType = INT + emit(asm.Opcodes.ARRAYLENGTH) + } + lineNumber(tree) + + generatedType + } + + def genLoadIf(tree: If, expectedType: BType): BType = { + val If(condp, thenp, elsep) = tree + + val success = new asm.Label + val failure = new asm.Label + + val hasElse = !elsep.isEmpty + val postIf = if (hasElse) new asm.Label else failure + + genCond(condp, success, failure) + + val thenKind = tpeTK(thenp) + val elseKind = if (!hasElse) UNIT else tpeTK(elsep) + def hasUnitBranch = (thenKind == UNIT || elseKind == UNIT) + val resKind = if (hasUnitBranch) UNIT else tpeTK(tree) + + markProgramPoint(success) + genLoad(thenp, resKind) + if (hasElse) { bc goTo postIf } + markProgramPoint(failure) + if (hasElse) { + genLoad(elsep, resKind) + markProgramPoint(postIf) + } + + resKind + } + + def genPrimitiveOp(tree: Apply, expectedType: BType): BType = { + val sym = tree.symbol + val Apply(fun @ Select(receiver, _), _) = tree + val code = scalaPrimitives.getPrimitive(sym, receiver.tpe) + + import scalaPrimitives.{isArithmeticOp, isArrayOp, isLogicalOp, isComparisonOp} + + if (isArithmeticOp(code)) genArithmeticOp(tree, code) + else if (code == scalaPrimitives.CONCAT) genStringConcat(tree) + else if (code == scalaPrimitives.HASH) genScalaHash(receiver) + else if (isArrayOp(code)) genArrayOp(tree, code, expectedType) + else if (isLogicalOp(code) || isComparisonOp(code)) { + val success, failure, after = new asm.Label + genCond(tree, success, failure) + // success block + markProgramPoint(success) + bc boolconst true + bc goTo after + // failure block + markProgramPoint(failure) + bc boolconst false + // after + markProgramPoint(after) + + BOOL + } + else if (code == scalaPrimitives.SYNCHRONIZED) + genSynchronized(tree, expectedType) + else if (scalaPrimitives.isCoercion(code)) { + genLoad(receiver) + lineNumber(tree) + genCoercion(code) + coercionTo(code) + } + else abort( + s"Primitive operation not handled yet: ${sym.fullName}(${fun.symbol.simpleName}) at: ${tree.pos}" + ) + } + + def genLoad(tree: Tree) { + genLoad(tree, tpeTK(tree)) + } + + /* Generate code for trees that produce values on the stack */ + def genLoad(tree: Tree, expectedType: BType) { + var generatedType = expectedType + + lineNumber(tree) + + tree match { + case lblDf : LabelDef => genLabelDef(lblDf, expectedType) + + case ValDef(_, nme.THIS, _, _) => + debuglog("skipping trivial assign to _$this: " + tree) + + case ValDef(_, _, _, rhs) => + val sym = tree.symbol + /* most of the time, !locals.contains(sym), unless the current activation of genLoad() is being called + while duplicating a finalizer that contains this ValDef. */ + val Local(tk, _, idx, isSynth) = locals.getOrMakeLocal(sym) + if (rhs == EmptyTree) { emitZeroOf(tk) } + else { genLoad(rhs, tk) } + bc.store(idx, tk) + if (!isSynth) { // there are case <synthetic> ValDef's emitted by patmat + varsInScope ::= (sym -> currProgramPoint()) + } + generatedType = UNIT + + case t : If => + generatedType = genLoadIf(t, expectedType) + + case r : Return => + genReturn(r) + generatedType = expectedType + + case t : Try => + generatedType = genLoadTry(t) + + case Throw(expr) => + generatedType = genThrow(expr) + + case New(tpt) => + abort(s"Unexpected New(${tpt.summaryString}/$tpt) reached GenBCode.\n" + + " Call was genLoad" + ((tree, expectedType))) + + case app : Apply => + generatedType = genApply(app, expectedType) + + case ApplyDynamic(qual, args) => sys.error("No invokedynamic support yet.") + + case This(qual) => + val symIsModuleClass = tree.symbol.isModuleClass + assert(tree.symbol == claszSymbol || symIsModuleClass, + s"Trying to access the this of another class: tree.symbol = ${tree.symbol}, class symbol = $claszSymbol compilation unit: $cunit") + if (symIsModuleClass && tree.symbol != claszSymbol) { + generatedType = genLoadModule(tree) + } + else { + mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) + generatedType = + if (tree.symbol == ArrayClass) ObjectReference + else brefType(thisName) // inner class (if any) for claszSymbol already tracked. + } + + case Select(Ident(nme.EMPTY_PACKAGE_NAME), module) => + assert(tree.symbol.isModule, s"Selection of non-module from empty package: $tree sym: ${tree.symbol} at: ${tree.pos}") + genLoadModule(tree) + + case Select(qualifier, selector) => + val sym = tree.symbol + generatedType = symInfoTK(sym) + val hostClass = findHostClass(qualifier.tpe, sym) + log(s"Host class of $sym with qual $qualifier (${qualifier.tpe}) is $hostClass") + val qualSafeToElide = treeInfo isQualifierSafeToElide qualifier + + def genLoadQualUnlessElidable() { if (!qualSafeToElide) { genLoadQualifier(tree) } } + + if (sym.isModule) { + genLoadQualUnlessElidable() + genLoadModule(tree) + } + else if (sym.isStaticMember) { + genLoadQualUnlessElidable() + fieldLoad(sym, hostClass) + } + else { + genLoadQualifier(tree) + fieldLoad(sym, hostClass) + } + + case Ident(name) => + val sym = tree.symbol + if (!sym.isPackage) { + val tk = symInfoTK(sym) + if (sym.isModule) { genLoadModule(tree) } + else { locals.load(sym) } + generatedType = tk + } + + case Literal(value) => + if (value.tag != UnitTag) (value.tag, expectedType) match { + case (IntTag, LONG ) => bc.lconst(value.longValue); generatedType = LONG + case (FloatTag, DOUBLE) => bc.dconst(value.doubleValue); generatedType = DOUBLE + case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = RT_NULL + case _ => genConstant(value); generatedType = tpeTK(tree) + } + + case blck : Block => genBlock(blck, expectedType) + + case Typed(Super(_, _), _) => genLoad(This(claszSymbol), expectedType) + + case Typed(expr, _) => genLoad(expr, expectedType) + + case Assign(_, _) => + generatedType = UNIT + genStat(tree) + + case av : ArrayValue => + generatedType = genArrayValue(av) + + case mtch : Match => + generatedType = genMatch(mtch) + + case EmptyTree => if (expectedType != UNIT) { emitZeroOf(expectedType) } + + case _ => abort(s"Unexpected tree in genLoad: $tree/${tree.getClass} at: ${tree.pos}") + } + + // emit conversion + if (generatedType != expectedType) { + adapt(generatedType, expectedType) + } + + } // end of GenBCode.genLoad() + + // ---------------- field load and store ---------------- + + /* + * must-single-thread + */ + def fieldLoad( field: Symbol, hostClass: Symbol = null) { + fieldOp(field, isLoad = true, hostClass) + } + /* + * must-single-thread + */ + def fieldStore(field: Symbol, hostClass: Symbol = null) { + fieldOp(field, isLoad = false, hostClass) + } + + /* + * must-single-thread + */ + private def fieldOp(field: Symbol, isLoad: Boolean, hostClass: Symbol = null) { + // LOAD_FIELD.hostClass , CALL_METHOD.hostClass , and #4283 + val owner = + if (hostClass == null) internalName(field.owner) + else internalName(hostClass) + val fieldJName = field.javaSimpleName.toString + val fieldDescr = symInfoTK(field).getDescriptor + val isStatic = field.isStaticMember + val opc = + if (isLoad) { if (isStatic) asm.Opcodes.GETSTATIC else asm.Opcodes.GETFIELD } + else { if (isStatic) asm.Opcodes.PUTSTATIC else asm.Opcodes.PUTFIELD } + mnode.visitFieldInsn(opc, owner, fieldJName, fieldDescr) + + } + + // ---------------- emitting constant values ---------------- + + /* + * For const.tag in {ClazzTag, EnumTag} + * must-single-thread + * Otherwise it's safe to call from multiple threads. + */ + def genConstant(const: Constant) { + (const.tag: @switch) match { + + case BooleanTag => bc.boolconst(const.booleanValue) + + case ByteTag => bc.iconst(const.byteValue) + case ShortTag => bc.iconst(const.shortValue) + case CharTag => bc.iconst(const.charValue) + case IntTag => bc.iconst(const.intValue) + + case LongTag => bc.lconst(const.longValue) + case FloatTag => bc.fconst(const.floatValue) + case DoubleTag => bc.dconst(const.doubleValue) + + case UnitTag => () + + case StringTag => + assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` + mnode.visitLdcInsn(const.stringValue) // `stringValue` special-cases null, but not for a const with StringTag + + case NullTag => emit(asm.Opcodes.ACONST_NULL) + + case ClazzTag => + val toPush: BType = { + val kind = toTypeKind(const.typeValue) + if (kind.isValueType) classLiteral(kind) + else kind + } + mnode.visitLdcInsn(toPush.toASMType) + + case EnumTag => + val sym = const.symbolValue + val ownerName = internalName(sym.owner) + val fieldName = sym.javaSimpleName.toString + val fieldDesc = toTypeKind(sym.tpe.underlying).getDescriptor + mnode.visitFieldInsn( + asm.Opcodes.GETSTATIC, + ownerName, + fieldName, + fieldDesc + ) + + case _ => abort(s"Unknown constant value: $const") + } + } + + private def genLabelDef(lblDf: LabelDef, expectedType: BType) { + // duplication of LabelDefs contained in `finally`-clauses is handled when emitting RETURN. No bookkeeping for that required here. + // no need to call index() over lblDf.params, on first access that magic happens (moreover, no LocalVariableTable entries needed for them). + markProgramPoint(programPoint(lblDf.symbol)) + lineNumber(lblDf) + genLoad(lblDf.rhs, expectedType) + } + + private def genReturn(r: Return) { + val Return(expr) = r + val returnedKind = tpeTK(expr) + genLoad(expr, returnedKind) + adapt(returnedKind, returnType) + val saveReturnValue = (returnType != UNIT) + lineNumber(r) + + cleanups match { + case Nil => + // not an assertion: !shouldEmitCleanup (at least not yet, pendingCleanups() may still have to run, and reset `shouldEmitCleanup`. + bc emitRETURN returnType + case nextCleanup :: rest => + if (saveReturnValue) { + if (insideCleanupBlock) { + cunit.warning(r.pos, "Return statement found in finally-clause, discarding its return-value in favor of that of a more deeply nested return.") + bc drop returnType + } else { + // regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted. + if (earlyReturnVar == null) { + earlyReturnVar = locals.makeLocal(returnType, "earlyReturnVar") + } + locals.store(earlyReturnVar) + } + } + bc goTo nextCleanup + shouldEmitCleanup = true + } + + } // end of genReturn() + + private def genApply(app: Apply, expectedType: BType): BType = { + var generatedType = expectedType + lineNumber(app) + app match { + + case Apply(TypeApply(fun, targs), _) => + + val sym = fun.symbol + val cast = sym match { + case Object_isInstanceOf => false + case Object_asInstanceOf => true + case _ => abort(s"Unexpected type application $fun[sym: ${sym.fullName}] in: $app") + } + + val Select(obj, _) = fun + val l = tpeTK(obj) + val r = tpeTK(targs.head) + + def genTypeApply(): BType = { + genLoadQualifier(fun) + + if (l.isValueType && r.isValueType) + genConversion(l, r, cast) + else if (l.isValueType) { + bc drop l + if (cast) { + mnode.visitTypeInsn(asm.Opcodes.NEW, classCastExceptionReference.getInternalName) + bc dup ObjectReference + emit(asm.Opcodes.ATHROW) + } else { + bc boolconst false + } + } + else if (r.isValueType && cast) { + abort(s"Erasure should have added an unboxing operation to prevent this cast. Tree: $app") + } + else if (r.isValueType) { + bc isInstance classLiteral(r) + } + else { + genCast(r, cast) + } + + if (cast) r else BOOL + } // end of genTypeApply() + + generatedType = genTypeApply() + + // 'super' call: Note: since constructors are supposed to + // return an instance of what they construct, we have to take + // special care. On JVM they are 'void', and Scala forbids (syntactically) + // to call super constructors explicitly and/or use their 'returned' value. + // therefore, we can ignore this fact, and generate code that leaves nothing + // on the stack (contrary to what the type in the AST says). + case Apply(fun @ Select(Super(_, mix), _), args) => + val invokeStyle = icodes.opcodes.SuperCall(mix) + // if (fun.symbol.isConstructor) Static(true) else SuperCall(mix); + mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) + genLoadArguments(args, paramTKs(app)) + genCallMethod(fun.symbol, invokeStyle, pos = app.pos) + generatedType = asmMethodType(fun.symbol).getReturnType + + // 'new' constructor call: Note: since constructors are + // thought to return an instance of what they construct, + // we have to 'simulate' it by DUPlicating the freshly created + // instance (on JVM, <init> methods return VOID). + case Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) => + val ctor = fun.symbol + assert(ctor.isClassConstructor, s"'new' call to non-constructor: ${ctor.name}") + + generatedType = tpeTK(tpt) + assert(generatedType.isRefOrArrayType, s"Non reference type cannot be instantiated: $generatedType") + + generatedType match { + case arr if generatedType.isArray => + genLoadArguments(args, paramTKs(app)) + val dims = arr.getDimensions + var elemKind = arr.getElementType + val argsSize = args.length + if (argsSize > dims) { + cunit.error(app.pos, s"too many arguments for array constructor: found ${args.length} but array has only $dims dimension(s)") + } + if (argsSize < dims) { + /* In one step: + * elemKind = new BType(BType.ARRAY, arr.off + argsSize, arr.len - argsSize) + * however the above does not enter a TypeName for each nested arrays in chrs. + */ + for (i <- args.length until dims) elemKind = arrayOf(elemKind) + } + (argsSize : @switch) match { + case 1 => bc newarray elemKind + case _ => + val descr = ('[' * argsSize) + elemKind.getDescriptor // denotes the same as: arrayN(elemKind, argsSize).getDescriptor + mnode.visitMultiANewArrayInsn(descr, argsSize) + } + + case rt if generatedType.hasObjectSort => + assert(exemplar(ctor.owner).c == rt, s"Symbol ${ctor.owner.fullName} is different from $rt") + mnode.visitTypeInsn(asm.Opcodes.NEW, rt.getInternalName) + bc dup generatedType + genLoadArguments(args, paramTKs(app)) + genCallMethod(ctor, icodes.opcodes.Static(onInstance = true)) + + case _ => + abort(s"Cannot instantiate $tpt of kind: $generatedType") + } + + case Apply(fun @ _, List(expr)) if definitions.isBox(fun.symbol) => + val nativeKind = tpeTK(expr) + genLoad(expr, nativeKind) + val MethodNameAndType(mname, mdesc) = asmBoxTo(nativeKind) + bc.invokestatic(BoxesRunTime.getInternalName, mname, mdesc) + generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType) + + case Apply(fun @ _, List(expr)) if definitions.isUnbox(fun.symbol) => + genLoad(expr) + val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) + generatedType = boxType + val MethodNameAndType(mname, mdesc) = asmUnboxTo(boxType) + bc.invokestatic(BoxesRunTime.getInternalName, mname, mdesc) + + case app @ Apply(fun, args) => + val sym = fun.symbol + + if (sym.isLabel) { // jump to a label + genLoadLabelArguments(args, labelDef(sym), app.pos) + bc goTo programPoint(sym) + } else if (isPrimitive(sym)) { // primitive method call + generatedType = genPrimitiveOp(app, expectedType) + } else { // normal method call + + def genNormalMethodCall() { + + val invokeStyle = + if (sym.isStaticMember) icodes.opcodes.Static(onInstance = false) + else if (sym.isPrivate || sym.isClassConstructor) icodes.opcodes.Static(onInstance = true) + else icodes.opcodes.Dynamic; + + if (invokeStyle.hasInstance) { + genLoadQualifier(fun) + } + + genLoadArguments(args, paramTKs(app)) + + // In "a couple cases", squirrel away a extra information (hostClass, targetTypeKind). TODO Document what "in a couple cases" refers to. + var hostClass: Symbol = null + var targetTypeKind: BType = null + fun match { + case Select(qual, _) => + val qualSym = findHostClass(qual.tpe, sym) + if (qualSym == ArrayClass) { + targetTypeKind = tpeTK(qual) + log(s"Stored target type kind for ${sym.fullName} as $targetTypeKind") + } + else { + hostClass = qualSym + if (qual.tpe.typeSymbol != qualSym) { + log(s"Precisified host class for $sym from ${qual.tpe.typeSymbol.fullName} to ${qualSym.fullName}") + } + } + + case _ => + } + if ((targetTypeKind != null) && (sym == definitions.Array_clone) && invokeStyle.isDynamic) { + val target: String = targetTypeKind.getInternalName + bc.invokevirtual(target, "clone", "()Ljava/lang/Object;") + } + else { + genCallMethod(sym, invokeStyle, hostClass, app.pos) + } + + } // end of genNormalMethodCall() + + genNormalMethodCall() + + generatedType = asmMethodType(sym).getReturnType + } + + } + + generatedType + } // end of genApply() + + private def genArrayValue(av: ArrayValue): BType = { + val ArrayValue(tpt @ TypeTree(), elems) = av + + val elmKind = tpeTK(tpt) + val generatedType = arrayOf(elmKind) + + lineNumber(av) + bc iconst elems.length + bc newarray elmKind + + var i = 0 + var rest = elems + while (!rest.isEmpty) { + bc dup generatedType + bc iconst i + genLoad(rest.head, elmKind) + bc astore elmKind + rest = rest.tail + i = i + 1 + } + + generatedType + } + + /* + * A Match node contains one or more case clauses, + * each case clause lists one or more Int values to use as keys, and a code block. + * Except the "default" case clause which (if it exists) doesn't list any Int key. + * + * On a first pass over the case clauses, we flatten the keys and their targets (the latter represented with asm.Labels). + * That representation allows JCodeMethodV to emit a lookupswitch or a tableswitch. + * + * On a second pass, we emit the switch blocks, one for each different target. + */ + private def genMatch(tree: Match): BType = { + lineNumber(tree) + genLoad(tree.selector, INT) + val generatedType = tpeTK(tree) + + var flatKeys: List[Int] = Nil + var targets: List[asm.Label] = Nil + var default: asm.Label = null + var switchBlocks: List[Pair[asm.Label, Tree]] = Nil + + // collect switch blocks and their keys, but don't emit yet any switch-block. + for (caze @ CaseDef(pat, guard, body) <- tree.cases) { + assert(guard == EmptyTree, guard) + val switchBlockPoint = new asm.Label + switchBlocks ::= Pair(switchBlockPoint, body) + pat match { + case Literal(value) => + flatKeys ::= value.intValue + targets ::= switchBlockPoint + case Ident(nme.WILDCARD) => + assert(default == null, s"multiple default targets in a Match node, at ${tree.pos}") + default = switchBlockPoint + case Alternative(alts) => + alts foreach { + case Literal(value) => + flatKeys ::= value.intValue + targets ::= switchBlockPoint + case _ => + abort(s"Invalid alternative in alternative pattern in Match node: $tree at: ${tree.pos}") + } + case _ => + abort(s"Invalid pattern in Match node: $tree at: ${tree.pos}") + } + } + bc.emitSWITCH(mkArrayReverse(flatKeys), mkArray(targets.reverse), default, MIN_SWITCH_DENSITY) + + // emit switch-blocks. + val postMatch = new asm.Label + for (sb <- switchBlocks.reverse) { + val Pair(caseLabel, caseBody) = sb + markProgramPoint(caseLabel) + genLoad(caseBody, generatedType) + bc goTo postMatch + } + + markProgramPoint(postMatch) + generatedType + } + + def genBlock(tree: Block, expectedType: BType) { + val Block(stats, expr) = tree + val savedScope = varsInScope + varsInScope = Nil + stats foreach genStat + genLoad(expr, expectedType) + val end = currProgramPoint() + if (emitVars) { // add entries to LocalVariableTable JVM attribute + for (Pair(sym, start) <- varsInScope.reverse) { emitLocalVarScope(sym, start, end) } + } + varsInScope = savedScope + } + + def adapt(from: BType, to: BType) { + if (!conforms(from, to)) { + to match { + case UNIT => bc drop from + case _ => bc.emitT2T(from, to) + } + } else if (from.isNothingType) { + emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level. + } else if (from.isNullType) { + bc drop from + mnode.visitInsn(asm.Opcodes.ACONST_NULL) + } + else (from, to) match { + case (BYTE, LONG) | (SHORT, LONG) | (CHAR, LONG) | (INT, LONG) => bc.emitT2T(INT, LONG) + case _ => () + } + } + + /* Emit code to Load the qualifier of `tree` on top of the stack. */ + def genLoadQualifier(tree: Tree) { + lineNumber(tree) + tree match { + case Select(qualifier, _) => genLoad(qualifier) + case _ => abort(s"Unknown qualifier $tree") + } + } + + /* Generate code that loads args into label parameters. */ + def genLoadLabelArguments(args: List[Tree], lblDef: LabelDef, gotoPos: Position) { + assert(args forall { a => !a.hasSymbolField || a.hasSymbolWhich( s => !s.isLabel) }, s"SI-6089 at: $gotoPos") // SI-6089 + + val aps = { + val params: List[Symbol] = lblDef.params.map(_.symbol) + assert(args.length == params.length, s"Wrong number of arguments in call to label at: $gotoPos") + + def isTrivial(kv: (Tree, Symbol)) = kv match { + case (This(_), p) if p.name == nme.THIS => true + case (arg @ Ident(_), p) if arg.symbol == p => true + case _ => false + } + + (args zip params) filterNot isTrivial + } + + // first push *all* arguments. This makes sure muliple uses of the same labelDef-var will all denote the (previous) value. + aps foreach { case (arg, param) => genLoad(arg, locals(param).tk) } // `locals` is known to contain `param` because `genDefDef()` visited `labelDefsAtOrUnder` + + // second assign one by one to the LabelDef's variables. + aps.reverse foreach { + case (_, param) => + // TODO FIXME a "this" param results from tail-call xform. If so, the `else` branch seems perfectly fine. And the `then` branch must be wrong. + if (param.name == nme.THIS) mnode.visitVarInsn(asm.Opcodes.ASTORE, 0) + else locals.store(param) + } + + } + + def genLoadArguments(args: List[Tree], btpes: List[BType]) { + (args zip btpes) foreach { case (arg, btpe) => genLoad(arg, btpe) } + } + + def genLoadModule(tree: Tree): BType = { + // Working around SI-5604. Rather than failing the compile when we see a package here, check if there's a package object. + val module = ( + if (!tree.symbol.isPackageClass) tree.symbol + else tree.symbol.info.member(nme.PACKAGE) match { + case NoSymbol => abort(s"Cannot use package as value: $tree") ; NoSymbol + case s => devWarning("Bug: found package class where package object expected. Converting.") ; s.moduleClass + } + ) + lineNumber(tree) + genLoadModule(module) + symInfoTK(module) + } + + def genLoadModule(module: Symbol) { + if (claszSymbol == module.moduleClass && jMethodName != "readResolve") { + mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) + } else { + val mbt = symInfoTK(module) + mnode.visitFieldInsn( + asm.Opcodes.GETSTATIC, + mbt.getInternalName /* + "$" */ , + strMODULE_INSTANCE_FIELD, + mbt.getDescriptor // for nostalgics: toTypeKind(module.tpe).getDescriptor + ) + } + } + + def genConversion(from: BType, to: BType, cast: Boolean) { + if (cast) { bc.emitT2T(from, to) } + else { + bc drop from + bc boolconst (from == to) + } + } + + def genCast(to: BType, cast: Boolean) { + if (cast) { bc checkCast to } + else { bc isInstance to } + } + + /* Is the given symbol a primitive operation? */ + def isPrimitive(fun: Symbol): Boolean = scalaPrimitives.isPrimitive(fun) + + /* Generate coercion denoted by "code" */ + def genCoercion(code: Int) { + import scalaPrimitives._ + (code: @switch) match { + case B2B | S2S | C2C | I2I | L2L | F2F | D2D => () + case _ => + val from = coercionFrom(code) + val to = coercionTo(code) + bc.emitT2T(from, to) + } + } + + def genStringConcat(tree: Tree): BType = { + lineNumber(tree) + liftStringConcat(tree) match { + + // Optimization for expressions of the form "" + x. We can avoid the StringBuilder. + case List(Literal(Constant("")), arg) => + genLoad(arg, ObjectReference) + genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false)) + + case concatenations => + bc.genStartConcat + for (elem <- concatenations) { + val kind = tpeTK(elem) + genLoad(elem, kind) + bc.genStringConcat(kind) + } + bc.genEndConcat + + } + + StringReference + } + + def genCallMethod(method: Symbol, style: InvokeStyle, hostClass0: Symbol = null, pos: Position = NoPosition) { + + val siteSymbol = claszSymbol + val hostSymbol = if (hostClass0 == null) method.owner else hostClass0; + val methodOwner = method.owner + // info calls so that types are up to date; erasure may add lateINTERFACE to traits + hostSymbol.info ; methodOwner.info + + def needsInterfaceCall(sym: Symbol) = ( + sym.isInterface + || sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass) + ) + + def isAccessibleFrom(target: Symbol, site: Symbol): Boolean = { + target.isPublic || target.isProtected && { + (site.enclClass isSubClass target.enclClass) || + (site.enclosingPackage == target.privateWithin) + } + } + + // whether to reference the type of the receiver or + // the type of the method owner + val useMethodOwner = ( + style != icodes.opcodes.Dynamic + || hostSymbol.isBottomClass + || methodOwner == definitions.ObjectClass + ) + val receiver = if (useMethodOwner) methodOwner else hostSymbol + val bmOwner = asmClassType(receiver) + val jowner = bmOwner.getInternalName + val jname = method.javaSimpleName.toString + val bmType = asmMethodType(method) + val mdescr = bmType.getDescriptor + + def initModule() { + // we initialize the MODULE$ field immediately after the super ctor + if (!isModuleInitialized && + jMethodName == INSTANCE_CONSTRUCTOR_NAME && + jname == INSTANCE_CONSTRUCTOR_NAME && + isStaticModule(siteSymbol)) { + isModuleInitialized = true + mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) + mnode.visitFieldInsn( + asm.Opcodes.PUTSTATIC, + thisName, + strMODULE_INSTANCE_FIELD, + "L" + thisName + ";" + ) + } + } + + if (style.isStatic) { + if (style.hasInstance) { bc.invokespecial (jowner, jname, mdescr) } + else { bc.invokestatic (jowner, jname, mdescr) } + } + else if (style.isDynamic) { + if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr) } + else { bc.invokevirtual (jowner, jname, mdescr) } + } + else { + assert(style.isSuper, s"An unknown InvokeStyle: $style") + bc.invokespecial(jowner, jname, mdescr) + initModule() + } + + } // end of genCallMethod() + + /* Generate the scala ## method. */ + def genScalaHash(tree: Tree): BType = { + genLoadModule(ScalaRunTimeModule) // TODO why load ScalaRunTimeModule if ## has InvokeStyle of Static(false) ? + genLoad(tree, ObjectReference) + genCallMethod(hashMethodSym, icodes.opcodes.Static(onInstance = false)) + + INT + } + + /* + * Returns a list of trees that each should be concatenated, from left to right. + * It turns a chained call like "a".+("b").+("c") into a list of arguments. + */ + def liftStringConcat(tree: Tree): List[Tree] = tree match { + case Apply(fun @ Select(larg, method), rarg) => + if (isPrimitive(fun.symbol) && + scalaPrimitives.getPrimitive(fun.symbol) == scalaPrimitives.CONCAT) + liftStringConcat(larg) ::: rarg + else + tree :: Nil + case _ => + tree :: Nil + } + + /* Some useful equality helpers. */ + def isNull(t: Tree) = { + t match { + case Literal(Constant(null)) => true + case _ => false + } + } + + /* If l or r is constant null, returns the other ; otherwise null */ + def ifOneIsNull(l: Tree, r: Tree) = if (isNull(l)) r else if (isNull(r)) l else null + + /* Emit code to compare the two top-most stack values using the 'op' operator. */ + private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType) { + if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT + bc.emitIF_ICMP(op, success) + } else if (tk.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) + bc.emitIF_ACMP(op, success) + } else { + (tk: @unchecked) match { + case LONG => emit(asm.Opcodes.LCMP) + case FLOAT => + if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.FCMPG) + else emit(asm.Opcodes.FCMPL) + case DOUBLE => + if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.DCMPG) + else emit(asm.Opcodes.DCMPL) + } + bc.emitIF(op, success) + } + bc goTo failure + } + + /* Emits code to compare (and consume) stack-top and zero using the 'op' operator */ + private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType) { + if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT + bc.emitIF(op, success) + } else if (tk.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) + // @unchecked because references aren't compared with GT, GE, LT, LE. + (op : @unchecked) match { + case icodes.EQ => bc emitIFNULL success + case icodes.NE => bc emitIFNONNULL success + } + } else { + (tk: @unchecked) match { + case LONG => + emit(asm.Opcodes.LCONST_0) + emit(asm.Opcodes.LCMP) + case FLOAT => + emit(asm.Opcodes.FCONST_0) + if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.FCMPG) + else emit(asm.Opcodes.FCMPL) + case DOUBLE => + emit(asm.Opcodes.DCONST_0) + if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.DCMPG) + else emit(asm.Opcodes.DCMPL) + } + bc.emitIF(op, success) + } + bc goTo failure + } + + val testOpForPrimitive: Array[TestOp] = Array( + icodes.EQ, icodes.NE, icodes.EQ, icodes.NE, icodes.LT, icodes.LE, icodes.GE, icodes.GT + ) + + /* + * Generate code for conditional expressions. + * The jump targets success/failure of the test are `then-target` and `else-target` resp. + */ + private def genCond(tree: Tree, success: asm.Label, failure: asm.Label) { + + def genComparisonOp(l: Tree, r: Tree, code: Int) { + val op: TestOp = testOpForPrimitive(code - scalaPrimitives.ID) + // special-case reference (in)equality test for null (null eq x, x eq null) + var nonNullSide: Tree = null + if (scalaPrimitives.isReferenceEqualityOp(code) && + { nonNullSide = ifOneIsNull(l, r); nonNullSide != null } + ) { + genLoad(nonNullSide, ObjectReference) + genCZJUMP(success, failure, op, ObjectReference) + } + else { + val tk = maxType(tpeTK(l), tpeTK(r)) + genLoad(l, tk) + genLoad(r, tk) + genCJUMP(success, failure, op, tk) + } + } + + def default() = { + genLoad(tree, BOOL) + genCZJUMP(success, failure, icodes.NE, BOOL) + } + + lineNumber(tree) + tree match { + + case Apply(fun, args) if isPrimitive(fun.symbol) => + import scalaPrimitives.{ ZNOT, ZAND, ZOR, EQ, getPrimitive } + + // lhs and rhs of test + lazy val Select(lhs, _) = fun + val rhs = if (args.isEmpty) EmptyTree else args.head; // args.isEmpty only for ZNOT + + def genZandOrZor(and: Boolean) { // TODO WRONG + // reaching "keepGoing" indicates the rhs should be evaluated too (ie not short-circuited). + val keepGoing = new asm.Label + + if (and) genCond(lhs, keepGoing, failure) + else genCond(lhs, success, keepGoing) + + markProgramPoint(keepGoing) + genCond(rhs, success, failure) + } + + getPrimitive(fun.symbol) match { + case ZNOT => genCond(lhs, failure, success) + case ZAND => genZandOrZor(and = true) + case ZOR => genZandOrZor(and = false) + case code => + // TODO !!!!!!!!!! isReferenceType, in the sense of TypeKind? (ie non-array, non-boxed, non-nothing, may be null) + if (scalaPrimitives.isUniversalEqualityOp(code) && tpeTK(lhs).hasObjectSort) { + // `lhs` has reference type + if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure) + else genEqEqPrimitive(lhs, rhs, failure, success) + } + else if (scalaPrimitives.isComparisonOp(code)) + genComparisonOp(lhs, rhs, code) + else + default + } + + case _ => default + } + + } // end of genCond() + + /* + * Generate the "==" code for object references. It is equivalent of + * if (l eq null) r eq null else l.equals(r); + * + * @param l left-hand-side of the '==' + * @param r right-hand-side of the '==' + */ + def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label) { + + /* True if the equality comparison is between values that require the use of the rich equality + * comparator (scala.runtime.Comparator.equals). This is the case when either side of the + * comparison might have a run-time type subtype of java.lang.Number or java.lang.Character. + * When it is statically known that both sides are equal and subtypes of Number of Character, + * not using the rich equality is possible (their own equals method will do ok.) + */ + val mustUseAnyComparator: Boolean = { + val areSameFinals = l.tpe.isFinalType && r.tpe.isFinalType && (l.tpe =:= r.tpe) + + !areSameFinals && platform.isMaybeBoxed(l.tpe.typeSymbol) && platform.isMaybeBoxed(r.tpe.typeSymbol) + } + + if (mustUseAnyComparator) { + val equalsMethod = { + + def default = platform.externalEquals + + platform match { + case x: JavaPlatform => + import x._ + if (l.tpe <:< BoxedNumberClass.tpe) { + if (r.tpe <:< BoxedNumberClass.tpe) externalEqualsNumNum + else if (r.tpe <:< BoxedCharacterClass.tpe) externalEqualsNumChar + else externalEqualsNumObject + } + else default + + case _ => default + } + } + genLoad(l, ObjectReference) + genLoad(r, ObjectReference) + genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false)) + genCZJUMP(success, failure, icodes.NE, BOOL) + } + else { + if (isNull(l)) { + // null == expr -> expr eq null + genLoad(r, ObjectReference) + genCZJUMP(success, failure, icodes.EQ, ObjectReference) + } else if (isNull(r)) { + // expr == null -> expr eq null + genLoad(l, ObjectReference) + genCZJUMP(success, failure, icodes.EQ, ObjectReference) + } else { + // l == r -> if (l eq null) r eq null else l.equals(r) + val eqEqTempLocal = locals.makeLocal(AnyRefReference, nme.EQEQ_LOCAL_VAR.toString) + val lNull = new asm.Label + val lNonNull = new asm.Label + + genLoad(l, ObjectReference) + genLoad(r, ObjectReference) + locals.store(eqEqTempLocal) + bc dup ObjectReference + genCZJUMP(lNull, lNonNull, icodes.EQ, ObjectReference) + + markProgramPoint(lNull) + bc drop ObjectReference + locals.load(eqEqTempLocal) + genCZJUMP(success, failure, icodes.EQ, ObjectReference) + + markProgramPoint(lNonNull) + locals.load(eqEqTempLocal) + genCallMethod(Object_equals, icodes.opcodes.Dynamic) + genCZJUMP(success, failure, icodes.NE, BOOL) + } + } + } + + /* can-multi-thread */ + def getMaxType(ts: List[Type]): BType = { + ts map toTypeKind reduceLeft maxType + } + + def genSynchronized(tree: Apply, expectedType: BType): BType + def genLoadTry(tree: Try): BType + + } + +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeGlue.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeGlue.scala new file mode 100644 index 0000000000..f95ceef678 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeGlue.scala @@ -0,0 +1,881 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + +package scala +package tools.nsc +package backend.jvm + +import scala.tools.asm +import scala.annotation.switch +import scala.collection.{ immutable, mutable } + +/* + * Immutable representations of bytecode-level types. + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded + * @version 1.0 + * + */ +abstract class BCodeGlue extends SubComponent { + + import global._ + + object BType { + + import global.chrs + + // ------------- sorts ------------- + + val VOID : Int = 0 + val BOOLEAN: Int = 1 + val CHAR : Int = 2 + val BYTE : Int = 3 + val SHORT : Int = 4 + val INT : Int = 5 + val FLOAT : Int = 6 + val LONG : Int = 7 + val DOUBLE : Int = 8 + val ARRAY : Int = 9 + val OBJECT : Int = 10 + val METHOD : Int = 11 + + // ------------- primitive types ------------- + + val VOID_TYPE = new BType(VOID, ('V' << 24) | (5 << 16) | (0 << 8) | 0, 1) + val BOOLEAN_TYPE = new BType(BOOLEAN, ('Z' << 24) | (0 << 16) | (5 << 8) | 1, 1) + val CHAR_TYPE = new BType(CHAR, ('C' << 24) | (0 << 16) | (6 << 8) | 1, 1) + val BYTE_TYPE = new BType(BYTE, ('B' << 24) | (0 << 16) | (5 << 8) | 1, 1) + val SHORT_TYPE = new BType(SHORT, ('S' << 24) | (0 << 16) | (7 << 8) | 1, 1) + val INT_TYPE = new BType(INT, ('I' << 24) | (0 << 16) | (0 << 8) | 1, 1) + val FLOAT_TYPE = new BType(FLOAT, ('F' << 24) | (2 << 16) | (2 << 8) | 1, 1) + val LONG_TYPE = new BType(LONG, ('J' << 24) | (1 << 16) | (1 << 8) | 2, 1) + val DOUBLE_TYPE = new BType(DOUBLE, ('D' << 24) | (3 << 16) | (3 << 8) | 2, 1) + + /* + * Returns the Java type corresponding to the given type descriptor. + * + * @param off the offset of this descriptor in the chrs buffer. + * @return the Java type corresponding to the given type descriptor. + * + * can-multi-thread + */ + def getType(off: Int): BType = { + var len = 0 + chrs(off) match { + case 'V' => VOID_TYPE + case 'Z' => BOOLEAN_TYPE + case 'C' => CHAR_TYPE + case 'B' => BYTE_TYPE + case 'S' => SHORT_TYPE + case 'I' => INT_TYPE + case 'F' => FLOAT_TYPE + case 'J' => LONG_TYPE + case 'D' => DOUBLE_TYPE + case '[' => + len = 1 + while (chrs(off + len) == '[') { + len += 1 + } + if (chrs(off + len) == 'L') { + len += 1 + while (chrs(off + len) != ';') { + len += 1 + } + } + new BType(ARRAY, off, len + 1) + case 'L' => + len = 1 + while (chrs(off + len) != ';') { + len += 1 + } + new BType(OBJECT, off + 1, len - 1) + // case '(': + case _ => + assert(chrs(off) == '(') + var resPos = off + 1 + while (chrs(resPos) != ')') { resPos += 1 } + val resType = getType(resPos + 1) + val len = resPos - off + 1 + resType.len; + new BType( + METHOD, + off, + if (resType.hasObjectSort) { + len + 2 // "+ 2" accounts for the "L ... ;" in a descriptor for a non-array reference. + } else { + len + } + ) + } + } + + /* Params denote an internal name. + * can-multi-thread + */ + def getObjectType(index: Int, length: Int): BType = { + val sort = if (chrs(index) == '[') ARRAY else OBJECT; + new BType(sort, index, length) + } + + /* + * @param typeDescriptor a field or method type descriptor. + * + * must-single-thread + */ + def getType(typeDescriptor: String): BType = { + val n = global.newTypeName(typeDescriptor) + getType(n.start) + } + + /* + * @param methodDescriptor a method descriptor. + * + * must-single-thread + */ + def getMethodType(methodDescriptor: String): BType = { + val n = global.newTypeName(methodDescriptor) + new BType(BType.METHOD, n.start, n.length) // TODO assert isValidMethodDescriptor + } + + /* + * Returns the Java method type corresponding to the given argument and return types. + * + * @param returnType the return type of the method. + * @param argumentTypes the argument types of the method. + * @return the Java type corresponding to the given argument and return types. + * + * must-single-thread + */ + def getMethodType(returnType: BType, argumentTypes: Array[BType]): BType = { + val n = global.newTypeName(getMethodDescriptor(returnType, argumentTypes)) + new BType(BType.METHOD, n.start, n.length) + } + + /* + * Returns the Java types corresponding to the argument types of method descriptor whose first argument starts at idx0. + * + * @param idx0 index into chrs of the first argument. + * @return the Java types corresponding to the argument types of the given method descriptor. + * + * can-multi-thread + */ + private def getArgumentTypes(idx0: Int): Array[BType] = { + assert(chrs(idx0 - 1) == '(', "doesn't look like a method descriptor.") + val args = new Array[BType](getArgumentCount(idx0)) + var off = idx0 + var size = 0 + while (chrs(off) != ')') { + args(size) = getType(off) + off += args(size).len + if (args(size).sort == OBJECT) { off += 2 } + // debug: assert("LVZBSCIJFD[)".contains(chrs(off))) + size += 1 + } + // debug: var check = 0; while (check < args.length) { assert(args(check) != null); check += 1 } + args + } + + /* + * Returns the Java types corresponding to the argument types of the given + * method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the Java types corresponding to the argument types of the given method descriptor. + * + * must-single-thread + */ + def getArgumentTypes(methodDescriptor: String): Array[BType] = { + val n = global.newTypeName(methodDescriptor) + getArgumentTypes(n.start + 1) + } + + /* + * Returns the number of argument types of this method type, whose first argument starts at idx0. + * + * @param idx0 index into chrs of the first argument. + * @return the number of argument types of this method type. + * + * can-multi-thread + */ + private def getArgumentCount(idx0: Int): Int = { + assert(chrs(idx0 - 1) == '(', "doesn't look like a method descriptor.") + var off = idx0 + var size = 0 + var keepGoing = true + while (keepGoing) { + val car = chrs(off) + off += 1 + if (car == ')') { + keepGoing = false + } else if (car == 'L') { + while (chrs(off) != ';') { off += 1 } + off += 1 + size += 1 + } else if (car != '[') { + size += 1 + } + } + + size + } + + /* + * Returns the Java type corresponding to the return type of the given + * method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the Java type corresponding to the return type of the given method descriptor. + * + * must-single-thread + */ + def getReturnType(methodDescriptor: String): BType = { + val n = global.newTypeName(methodDescriptor) + val delta = n.pos(')') // `delta` is relative to the Name's zero-based start position, not a valid index into chrs. + assert(delta < n.length, s"not a valid method descriptor: $methodDescriptor") + getType(n.start + delta + 1) + } + + /* + * Returns the descriptor corresponding to the given argument and return types. + * Note: no BType is created here for the resulting method descriptor, + * if that's desired the invoker is responsible for that. + * + * @param returnType the return type of the method. + * @param argumentTypes the argument types of the method. + * @return the descriptor corresponding to the given argument and return types. + * + * can-multi-thread + */ + def getMethodDescriptor( + returnType: BType, + argumentTypes: Array[BType]): String = + { + val buf = new StringBuffer() + buf.append('(') + var i = 0 + while (i < argumentTypes.length) { + argumentTypes(i).getDescriptor(buf) + i += 1 + } + buf.append(')') + returnType.getDescriptor(buf) + buf.toString() + } + + } // end of object BType + + /* + * Based on ASM's Type class. Namer's chrs is used in this class for the same purposes as the `buf` char array in asm.Type. + * + * All methods of this classs can-multi-thread + */ + final class BType(val sort: Int, val off: Int, val len: Int) { + + import global.chrs + + /* + * can-multi-thread + */ + def toASMType: scala.tools.asm.Type = { + import scala.tools.asm + // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" + (sort: @switch) match { + case asm.Type.VOID => asm.Type.VOID_TYPE + case asm.Type.BOOLEAN => asm.Type.BOOLEAN_TYPE + case asm.Type.CHAR => asm.Type.CHAR_TYPE + case asm.Type.BYTE => asm.Type.BYTE_TYPE + case asm.Type.SHORT => asm.Type.SHORT_TYPE + case asm.Type.INT => asm.Type.INT_TYPE + case asm.Type.FLOAT => asm.Type.FLOAT_TYPE + case asm.Type.LONG => asm.Type.LONG_TYPE + case asm.Type.DOUBLE => asm.Type.DOUBLE_TYPE + case asm.Type.ARRAY | + asm.Type.OBJECT => asm.Type.getObjectType(getInternalName) + case asm.Type.METHOD => asm.Type.getMethodType(getDescriptor) + } + } + + /* + * Unlike for ICode's REFERENCE, isBoxedType(t) implies isReferenceType(t) + * Also, `isReferenceType(RT_NOTHING) == true` , similarly for RT_NULL. + * Use isNullType() , isNothingType() to detect Nothing and Null. + * + * can-multi-thread + */ + def hasObjectSort = (sort == BType.OBJECT) + + /* + * Returns the number of dimensions of this array type. This method should + * only be used for an array type. + * + * @return the number of dimensions of this array type. + * + * can-multi-thread + */ + def getDimensions: Int = { + var i = 1 + while (chrs(off + i) == '[') { + i += 1 + } + i + } + + /* + * Returns the (ultimate) element type of this array type. + * This method should only be used for an array type. + * + * @return Returns the type of the elements of this array type. + * + * can-multi-thread + */ + def getElementType: BType = { + assert(isArray, s"Asked for the element type of a non-array type: $this") + BType.getType(off + getDimensions) + } + + /* + * Returns the internal name of the class corresponding to this object or + * array type. The internal name of a class is its fully qualified name (as + * returned by Class.getName(), where '.' are replaced by '/'. This method + * should only be used for an object or array type. + * + * @return the internal name of the class corresponding to this object type. + * + * can-multi-thread + */ + def getInternalName: String = { + new String(chrs, off, len) + } + + /* + * @return the prefix of the internal name until the last '/' (if '/' present), empty string otherwise. + * + * can-multi-thread + */ + def getRuntimePackage: String = { + assert(hasObjectSort, s"not of object sort: $toString") + val iname = getInternalName + val idx = iname.lastIndexOf('/') + if (idx == -1) "" + else iname.substring(0, idx) + } + + /* + * @return the suffix of the internal name until the last '/' (if '/' present), internal name otherwise. + * + * can-multi-thread + */ + def getSimpleName: String = { + assert(hasObjectSort, s"not of object sort: $toString") + val iname = getInternalName + val idx = iname.lastIndexOf('/') + if (idx == -1) iname + else iname.substring(idx + 1) + } + + /* + * Returns the argument types of methods of this type. + * This method should only be used for method types. + * + * @return the argument types of methods of this type. + * + * can-multi-thread + */ + def getArgumentTypes: Array[BType] = { + BType.getArgumentTypes(off + 1) + } + + /* + * Returns the number of arguments of methods of this type. + * This method should only be used for method types. + * + * @return the number of arguments of methods of this type. + * + * can-multi-thread + */ + def getArgumentCount: Int = { + BType.getArgumentCount(off + 1) + } + + /* + * Returns the return type of methods of this type. + * This method should only be used for method types. + * + * @return the return type of methods of this type. + * + * can-multi-thread + */ + def getReturnType: BType = { + assert(chrs(off) == '(', s"doesn't look like a method descriptor: $toString") + var resPos = off + 1 + while (chrs(resPos) != ')') { resPos += 1 } + BType.getType(resPos + 1) + } + + /* + * Given a zero-based formal-param-position, return its corresponding local-var-index, + * taking into account the JVM-type-sizes of preceding formal params. + */ + def convertFormalParamPosToLocalVarIdx(paramPos: Int, isInstanceMethod: Boolean): Int = { + assert(sort == asm.Type.METHOD) + val paramTypes = getArgumentTypes + var local = 0 + (0 until paramPos) foreach { argPos => local += paramTypes(argPos).getSize } + + local + (if (isInstanceMethod) 1 else 0) + } + + /* + * Given a local-var-index, return its corresponding zero-based formal-param-position, + * taking into account the JVM-type-sizes of preceding formal params. + */ + def convertLocalVarIdxToFormalParamPos(localIdx: Int, isInstanceMethod: Boolean): Int = { + assert(sort == asm.Type.METHOD) + val paramTypes = getArgumentTypes + var remaining = (if (isInstanceMethod) (localIdx - 1) else localIdx) + assert(remaining >= 0) + var result = 0 + while (remaining > 0) { + remaining -= paramTypes(result).getSize + result += 1 + } + assert(remaining == 0) + + result + } + + // ------------------------------------------------------------------------ + // Inspector methods + // ------------------------------------------------------------------------ + + def isPrimitiveOrVoid = (sort < BType.ARRAY) // can-multi-thread + def isValueType = (sort < BType.ARRAY) // can-multi-thread + def isArray = (sort == BType.ARRAY) // can-multi-thread + def isUnitType = (sort == BType.VOID) // can-multi-thread + + def isRefOrArrayType = { hasObjectSort || isArray } // can-multi-thread + def isNonUnitValueType = { isValueType && !isUnitType } // can-multi-thread + + def isNonSpecial = { !isValueType && !isArray && !isPhantomType } // can-multi-thread + def isNothingType = { (this == RT_NOTHING) || (this == CT_NOTHING) } // can-multi-thread + def isNullType = { (this == RT_NULL) || (this == CT_NULL) } // can-multi-thread + def isPhantomType = { isNothingType || isNullType } // can-multi-thread + + /* + * can-multi-thread + */ + def isBoxed = { + this match { + case BOXED_UNIT | BOXED_BOOLEAN | BOXED_CHAR | + BOXED_BYTE | BOXED_SHORT | BOXED_INT | + BOXED_FLOAT | BOXED_LONG | BOXED_DOUBLE + => true + case _ + => false + } + } + + /* On the JVM, + * BOOL, BYTE, CHAR, SHORT, and INT + * are like Ints for the purpose of lub calculation. + * + * can-multi-thread + */ + def isIntSizedType = { + (sort : @switch) match { + case BType.BOOLEAN | BType.CHAR | + BType.BYTE | BType.SHORT | BType.INT + => true + case _ + => false + } + } + + /* On the JVM, similar to isIntSizedType except that BOOL isn't integral while LONG is. + * + * can-multi-thread + */ + def isIntegralType = { + (sort : @switch) match { + case BType.CHAR | + BType.BYTE | BType.SHORT | BType.INT | + BType.LONG + => true + case _ + => false + } + } + + /* On the JVM, FLOAT and DOUBLE. + * + * can-multi-thread + */ + def isRealType = { (sort == BType.FLOAT ) || (sort == BType.DOUBLE) } + + def isNumericType = (isIntegralType || isRealType) // can-multi-thread + + /* Is this type a category 2 type in JVM terms? (ie, is it LONG or DOUBLE?) + * + * can-multi-thread + */ + def isWideType = (getSize == 2) + + def isCapturedCellRef: Boolean = { + this == srBooleanRef || this == srByteRef || + this == srCharRef || + this == srIntRef || + this == srLongRef || + this == srFloatRef || this == srDoubleRef + } + + /* + * Element vs. Component type of an array: + * Quoting from the JVMS, Sec. 2.4 "Reference Types and Values" + * + * An array type consists of a component type with a single dimension (whose + * length is not given by the type). The component type of an array type may itself be + * an array type. If, starting from any array type, one considers its component type, + * and then (if that is also an array type) the component type of that type, and so on, + * eventually one must reach a component type that is not an array type; this is called + * the element type of the array type. The element type of an array type is necessarily + * either a primitive type, or a class type, or an interface type. + * + */ + + /* The type of items this array holds. + * + * can-multi-thread + */ + def getComponentType: BType = { + assert(isArray, s"Asked for the component type of a non-array type: $this") + BType.getType(off + 1) + } + + // ------------------------------------------------------------------------ + // Conversion to type descriptors + // ------------------------------------------------------------------------ + + /* + * @return the descriptor corresponding to this Java type. + * + * can-multi-thread + */ + def getDescriptor: String = { + val buf = new StringBuffer() + getDescriptor(buf) + buf.toString() + } + + /* + * Appends the descriptor corresponding to this Java type to the given string buffer. + * + * @param buf the string buffer to which the descriptor must be appended. + * + * can-multi-thread + */ + private def getDescriptor(buf: StringBuffer) { + if (isPrimitiveOrVoid) { + // descriptor is in byte 3 of 'off' for primitive types (buf == null) + buf.append(((off & 0xFF000000) >>> 24).asInstanceOf[Char]) + } else if (sort == BType.OBJECT) { + buf.append('L') + buf.append(chrs, off, len) + buf.append(';') + } else { // sort == ARRAY || sort == METHOD + buf.append(chrs, off, len) + } + } + + // ------------------------------------------------------------------------ + // Corresponding size and opcodes + // ------------------------------------------------------------------------ + + /* + * Returns the size of values of this type. + * This method must not be used for method types. + * + * @return the size of values of this type, i.e., 2 for <tt>long</tt> and + * <tt>double</tt>, 0 for <tt>void</tt> and 1 otherwise. + * + * can-multi-thread + */ + def getSize: Int = { + // the size is in byte 0 of 'off' for primitive types (buf == null) + if (isPrimitiveOrVoid) (off & 0xFF) else 1 + } + + /* + * Returns a JVM instruction opcode adapted to this Java type. This method + * must not be used for method types. + * + * @param opcode a JVM instruction opcode. This opcode must be one of ILOAD, + * ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, ISHL, + * ISHR, IUSHR, IAND, IOR, IXOR and IRETURN. + * @return an opcode that is similar to the given opcode, but adapted to + * this Java type. For example, if this type is <tt>float</tt> and + * <tt>opcode</tt> is IRETURN, this method returns FRETURN. + * + * can-multi-thread + */ + def getOpcode(opcode: Int): Int = { + import scala.tools.asm.Opcodes + if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) { + // the offset for IALOAD or IASTORE is in byte 1 of 'off' for + // primitive types (buf == null) + opcode + (if (isPrimitiveOrVoid) (off & 0xFF00) >> 8 else 4) + } else { + // the offset for other instructions is in byte 2 of 'off' for + // primitive types (buf == null) + opcode + (if (isPrimitiveOrVoid) (off & 0xFF0000) >> 16 else 4) + } + } + + // ------------------------------------------------------------------------ + // Equals, hashCode and toString + // ------------------------------------------------------------------------ + + /* + * Tests if the given object is equal to this type. + * + * @param o the object to be compared to this type. + * @return <tt>true</tt> if the given object is equal to this type. + * + * can-multi-thread + */ + override def equals(o: Any): Boolean = { + if (!(o.isInstanceOf[BType])) { + return false + } + val t = o.asInstanceOf[BType] + if (this eq t) { + return true + } + if (sort != t.sort) { + return false + } + if (sort >= BType.ARRAY) { + if (len != t.len) { + return false + } + // sort checked already + if (off == t.off) { + return true + } + var i = 0 + while (i < len) { + if (chrs(off + i) != chrs(t.off + i)) { + return false + } + i += 1 + } + // If we reach here, we could update the largest of (this.off, t.off) to match the other, so as to simplify future == comparisons. + // But that would require a var rather than val. + } + true + } + + /* + * @return a hash code value for this type. + * + * can-multi-thread + */ + override def hashCode(): Int = { + var hc = 13 * sort; + if (sort >= BType.ARRAY) { + var i = off + val end = i + len + while (i < end) { + hc = 17 * (hc + chrs(i)) + i += 1 + } + } + hc + } + + /* + * @return the descriptor of this type. + * + * can-multi-thread + */ + override def toString: String = { getDescriptor } + + } + + /* + * Creates a TypeName and the BType token for it. + * This method does not add to `innerClassBufferASM`, use `internalName()` or `asmType()` or `toTypeKind()` for that. + * + * must-single-thread + */ + def brefType(iname: String): BType = { brefType(newTypeName(iname.toCharArray(), 0, iname.length())) } + + /* + * Creates a BType token for the TypeName received as argument. + * This method does not add to `innerClassBufferASM`, use `internalName()` or `asmType()` or `toTypeKind()` for that. + * + * can-multi-thread + */ + def brefType(iname: TypeName): BType = { BType.getObjectType(iname.start, iname.length) } + + // due to keyboard economy only + val UNIT = BType.VOID_TYPE + val BOOL = BType.BOOLEAN_TYPE + val CHAR = BType.CHAR_TYPE + val BYTE = BType.BYTE_TYPE + val SHORT = BType.SHORT_TYPE + val INT = BType.INT_TYPE + val LONG = BType.LONG_TYPE + val FLOAT = BType.FLOAT_TYPE + val DOUBLE = BType.DOUBLE_TYPE + + val BOXED_UNIT = brefType("java/lang/Void") + val BOXED_BOOLEAN = brefType("java/lang/Boolean") + val BOXED_BYTE = brefType("java/lang/Byte") + val BOXED_SHORT = brefType("java/lang/Short") + val BOXED_CHAR = brefType("java/lang/Character") + val BOXED_INT = brefType("java/lang/Integer") + val BOXED_LONG = brefType("java/lang/Long") + val BOXED_FLOAT = brefType("java/lang/Float") + val BOXED_DOUBLE = brefType("java/lang/Double") + + /* + * RT_NOTHING and RT_NULL exist at run-time only. + * They are the bytecode-level manifestation (in method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs. + * Therefore, when RT_NOTHING or RT_NULL are to be emitted, + * a mapping is needed: the internal names of NothingClass and NullClass can't be emitted as-is. + */ + val RT_NOTHING = brefType("scala/runtime/Nothing$") + val RT_NULL = brefType("scala/runtime/Null$") + val CT_NOTHING = brefType("scala/Nothing") // TODO needed? + val CT_NULL = brefType("scala/Null") // TODO needed? + + val srBooleanRef = brefType("scala/runtime/BooleanRef") + val srByteRef = brefType("scala/runtime/ByteRef") + val srCharRef = brefType("scala/runtime/CharRef") + val srIntRef = brefType("scala/runtime/IntRef") + val srLongRef = brefType("scala/runtime/LongRef") + val srFloatRef = brefType("scala/runtime/FloatRef") + val srDoubleRef = brefType("scala/runtime/DoubleRef") + + /* Map from type kinds to the Java reference types. + * Useful when pushing class literals onto the operand stack (ldc instruction taking a class literal). + * @see Predef.classOf + * @see genConstant() + */ + val classLiteral = immutable.Map[BType, BType]( + UNIT -> BOXED_UNIT, + BOOL -> BOXED_BOOLEAN, + BYTE -> BOXED_BYTE, + SHORT -> BOXED_SHORT, + CHAR -> BOXED_CHAR, + INT -> BOXED_INT, + LONG -> BOXED_LONG, + FLOAT -> BOXED_FLOAT, + DOUBLE -> BOXED_DOUBLE + ) + + case class MethodNameAndType(mname: String, mdesc: String) + + val asmBoxTo: Map[BType, MethodNameAndType] = { + Map( + BOOL -> MethodNameAndType("boxToBoolean", "(Z)Ljava/lang/Boolean;" ) , + BYTE -> MethodNameAndType("boxToByte", "(B)Ljava/lang/Byte;" ) , + CHAR -> MethodNameAndType("boxToCharacter", "(C)Ljava/lang/Character;") , + SHORT -> MethodNameAndType("boxToShort", "(S)Ljava/lang/Short;" ) , + INT -> MethodNameAndType("boxToInteger", "(I)Ljava/lang/Integer;" ) , + LONG -> MethodNameAndType("boxToLong", "(J)Ljava/lang/Long;" ) , + FLOAT -> MethodNameAndType("boxToFloat", "(F)Ljava/lang/Float;" ) , + DOUBLE -> MethodNameAndType("boxToDouble", "(D)Ljava/lang/Double;" ) + ) + } + + val asmUnboxTo: Map[BType, MethodNameAndType] = { + Map( + BOOL -> MethodNameAndType("unboxToBoolean", "(Ljava/lang/Object;)Z") , + BYTE -> MethodNameAndType("unboxToByte", "(Ljava/lang/Object;)B") , + CHAR -> MethodNameAndType("unboxToChar", "(Ljava/lang/Object;)C") , + SHORT -> MethodNameAndType("unboxToShort", "(Ljava/lang/Object;)S") , + INT -> MethodNameAndType("unboxToInt", "(Ljava/lang/Object;)I") , + LONG -> MethodNameAndType("unboxToLong", "(Ljava/lang/Object;)J") , + FLOAT -> MethodNameAndType("unboxToFloat", "(Ljava/lang/Object;)F") , + DOUBLE -> MethodNameAndType("unboxToDouble", "(Ljava/lang/Object;)D") + ) + } + + /* + * can-multi-thread + */ + def toBType(t: asm.Type): BType = { + (t.getSort: @switch) match { + case asm.Type.VOID => BType.VOID_TYPE + case asm.Type.BOOLEAN => BType.BOOLEAN_TYPE + case asm.Type.CHAR => BType.CHAR_TYPE + case asm.Type.BYTE => BType.BYTE_TYPE + case asm.Type.SHORT => BType.SHORT_TYPE + case asm.Type.INT => BType.INT_TYPE + case asm.Type.FLOAT => BType.FLOAT_TYPE + case asm.Type.LONG => BType.LONG_TYPE + case asm.Type.DOUBLE => BType.DOUBLE_TYPE + case asm.Type.ARRAY | + asm.Type.OBJECT | + asm.Type.METHOD => + // TODO confirm whether this also takes care of the phantom types. + val key = + if (t.getSort == asm.Type.METHOD) t.getDescriptor + else t.getInternalName + + val n = global.lookupTypeName(key.toCharArray) + new BType(t.getSort, n.start, n.length) + } + } + + /* + * ASM trees represent types as strings (internal names, descriptors). + * Given that we operate instead on BTypes, conversion is needed when visiting MethodNodes outside GenBCode. + * + * can-multi-thread + */ + def descrToBType(typeDescriptor: String): BType = { + val c: Char = typeDescriptor(0) + c match { + case 'V' => BType.VOID_TYPE + case 'Z' => BType.BOOLEAN_TYPE + case 'C' => BType.CHAR_TYPE + case 'B' => BType.BYTE_TYPE + case 'S' => BType.SHORT_TYPE + case 'I' => BType.INT_TYPE + case 'F' => BType.FLOAT_TYPE + case 'J' => BType.LONG_TYPE + case 'D' => BType.DOUBLE_TYPE + case 'L' => + val iname = typeDescriptor.substring(1, typeDescriptor.length() - 1) + val n = global.lookupTypeName(iname.toCharArray) + new BType(asm.Type.OBJECT, n.start, n.length) + case _ => + val n = global.lookupTypeName(typeDescriptor.toCharArray) + BType.getType(n.start) + } + } + + /* + * Use only to lookup reference types, otherwise use `descrToBType()` + * + * can-multi-thread + */ + def lookupRefBType(iname: String): BType = { + import global.chrs + val n = global.lookupTypeName(iname.toCharArray) + val sort = if (chrs(n.start) == '[') BType.ARRAY else BType.OBJECT; + new BType(sort, n.start, n.length) + } + + def lookupRefBTypeIfExisting(iname: String): BType = { + import global.chrs + val n = global.lookupTypeNameIfExisting(iname.toCharArray, false) + if (n == null) { return null } + val sort = if (chrs(n.start) == '[') BType.ARRAY else BType.OBJECT; + new BType(sort, n.start, n.length) + } + +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala new file mode 100644 index 0000000000..62270b7c0a --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -0,0 +1,1329 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + +package scala +package tools.nsc +package backend.jvm + +import scala.tools.asm +import scala.annotation.switch +import scala.collection.{ immutable, mutable } +import scala.tools.nsc.io.AbstractFile + +/* + * Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes. + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded + * @version 1.0 + * + */ +abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { + + import global._ + + /* + * must-single-thread + */ + def getFileForClassfile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = { + getFile(base, clsName, suffix) + } + + /* + * must-single-thread + */ + def getOutFolder(csym: Symbol, cName: String, cunit: CompilationUnit): _root_.scala.tools.nsc.io.AbstractFile = { + try { + outputDirectory(csym) + } catch { + case ex: Throwable => + cunit.error(cunit.body.pos, s"Couldn't create file for class $cName\n${ex.getMessage}") + null + } + } + + var pickledBytes = 0 // statistics + + // ----------------------------------------------------------------------------------------- + // finding the least upper bound in agreement with the bytecode verifier (given two internal names handed by ASM) + // Background: + // http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf + // http://comments.gmane.org/gmane.comp.java.vm.languages/2293 + // https://issues.scala-lang.org/browse/SI-3872 + // ----------------------------------------------------------------------------------------- + + /* + * can-multi-thread + */ + def firstCommonSuffix(as: List[Tracked], bs: List[Tracked]): BType = { + var chainA = as + var chainB = bs + var fcs: Tracked = null + do { + if (chainB contains chainA.head) fcs = chainA.head + else if (chainA contains chainB.head) fcs = chainB.head + else { + chainA = chainA.tail + chainB = chainB.tail + } + } while (fcs == null) + fcs.c + } + + /* An `asm.ClassWriter` that uses `jvmWiseLUB()` + * The internal name of the least common ancestor of the types given by inameA and inameB. + * It's what ASM needs to know in order to compute stack map frames, http://asm.ow2.org/doc/developer-guide.html#controlflow + */ + final class CClassWriter(flags: Int) extends asm.ClassWriter(flags) { + + /* + * This method is thread re-entrant because chrs never grows during its operation (that's because all TypeNames being looked up have already been entered). + * To stress this point, rather than using `newTypeName()` we use `lookupTypeName()` + * + * can-multi-thread + */ + override def getCommonSuperClass(inameA: String, inameB: String): String = { + val a = brefType(lookupTypeName(inameA.toCharArray)) + val b = brefType(lookupTypeName(inameB.toCharArray)) + val lca = jvmWiseLUB(a, b) + val lcaName = lca.getInternalName // don't call javaName because that side-effects innerClassBuffer. + assert(lcaName != "scala/Any") + + lcaName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things. + } + + } + + /* + * Finding the least upper bound in agreement with the bytecode verifier (given two internal names handed out by ASM) + * Background: + * http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf + * http://comments.gmane.org/gmane.comp.java.vm.languages/2293 + * https://issues.scala-lang.org/browse/SI-3872 + * + * can-multi-thread + */ + def jvmWiseLUB(a: BType, b: BType): BType = { + + assert(a.isNonSpecial, s"jvmWiseLUB() received a non-plain-class $a") + assert(b.isNonSpecial, s"jvmWiseLUB() received a non-plain-class $b") + + val ta = exemplars.get(a) + val tb = exemplars.get(b) + + val res = Pair(ta.isInterface, tb.isInterface) match { + case (true, true) => + // exercised by test/files/run/t4761.scala + if (tb.isSubtypeOf(ta.c)) ta.c + else if (ta.isSubtypeOf(tb.c)) tb.c + else ObjectReference + case (true, false) => + if (tb.isSubtypeOf(a)) a else ObjectReference + case (false, true) => + if (ta.isSubtypeOf(b)) b else ObjectReference + case _ => + firstCommonSuffix(ta :: ta.superClasses, tb :: tb.superClasses) + } + assert(res.isNonSpecial, "jvmWiseLUB() returned a non-plain-class.") + res + } + + /* + * must-single-thread + */ + object isJavaEntryPoint { + + /* + * must-single-thread + */ + def apply(sym: Symbol, csymCompUnit: CompilationUnit): Boolean = { + def fail(msg: String, pos: Position = sym.pos) = { + csymCompUnit.warning(sym.pos, + sym.name + + s" has a main method with parameter type Array[String], but ${sym.fullName('.')} will not be a runnable program.\n Reason: $msg" + // TODO: make this next claim true, if possible + // by generating valid main methods as static in module classes + // not sure what the jvm allows here + // + " You can still run the program by calling it as " + sym.javaSimpleName + " instead." + ) + false + } + def failNoForwarder(msg: String) = { + fail(s"$msg, which means no static forwarder can be generated.\n") + } + val possibles = if (sym.hasModuleFlag) (sym.tpe nonPrivateMember nme.main).alternatives else Nil + val hasApproximate = possibles exists { m => + m.info match { + case MethodType(p :: Nil, _) => p.tpe.typeSymbol == definitions.ArrayClass + case _ => false + } + } + // At this point it's a module with a main-looking method, so either succeed or warn that it isn't. + hasApproximate && { + // Before erasure so we can identify generic mains. + enteringErasure { + val companion = sym.linkedClassOfClass + + if (definitions.hasJavaMainMethod(companion)) + failNoForwarder("companion contains its own main method") + else if (companion.tpe.member(nme.main) != NoSymbol) + // this is only because forwarders aren't smart enough yet + failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)") + else if (companion.isTrait) + failNoForwarder("companion is a trait") + // Now either succeeed, or issue some additional warnings for things which look like + // attempts to be java main methods. + else (possibles exists definitions.isJavaMainMethod) || { + possibles exists { m => + m.info match { + case PolyType(_, _) => + fail("main methods cannot be generic.") + case MethodType(params, res) => + if (res.typeSymbol :: params exists (_.isAbstractType)) + fail("main methods cannot refer to type parameters or abstract types.", m.pos) + else + definitions.isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos) + case tp => + fail(s"don't know what this is: $tp", m.pos) + } + } + } + } + } + } + + } + + /* + * must-single-thread + */ + def initBytecodeWriter(entryPoints: List[Symbol]): BytecodeWriter = { + settings.outputDirs.getSingleOutput match { + case Some(f) if f hasExtension "jar" => + // If no main class was specified, see if there's only one + // entry point among the classes going into the jar. + if (settings.mainClass.isDefault) { + entryPoints map (_.fullName('.')) match { + case Nil => + log("No Main-Class designated or discovered.") + case name :: Nil => + log(s"Unique entry point: setting Main-Class to $name") + settings.mainClass.value = name + case names => + log(s"No Main-Class due to multiple entry points:\n ${names.mkString("\n ")}") + } + } + else log(s"Main-Class was specified: ${settings.mainClass.value}") + + new DirectToJarfileWriter(f.file) + + case _ => factoryNonJarBytecodeWriter() + } + } + + /* + * must-single-thread + */ + def fieldSymbols(cls: Symbol): List[Symbol] = { + for (f <- cls.info.decls.toList ; + if !f.isMethod && f.isTerm && !f.isModule + ) yield f; + } + + /* + * can-multi-thread + */ + def methodSymbols(cd: ClassDef): List[Symbol] = { + cd.impl.body collect { case dd: DefDef => dd.symbol } + } + + /* + * Populates the InnerClasses JVM attribute with `refedInnerClasses`. + * In addition to inner classes mentioned somewhere in `jclass` (where `jclass` is a class file being emitted) + * `refedInnerClasses` should contain those inner classes defined as direct member classes of `jclass` + * but otherwise not mentioned in `jclass`. + * + * `refedInnerClasses` may contain duplicates, + * need not contain the enclosing inner classes of each inner class it lists (those are looked up for consistency). + * + * This method serializes in the InnerClasses JVM attribute in an appropriate order, + * not necessarily that given by `refedInnerClasses`. + * + * can-multi-thread + */ + final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: Iterable[BType]) { + // used to detect duplicates. + val seen = mutable.Map.empty[String, String] + // result without duplicates, not yet sorted. + val result = mutable.Set.empty[InnerClassEntry] + + for(s: BType <- refedInnerClasses; + e: InnerClassEntry <- exemplars.get(s).innersChain) { + + assert(e.name != null, "saveInnerClassesFor() is broken.") // documentation + val doAdd = seen.get(e.name) match { + // TODO is it ok for prevOName to be null? (Someone should really document the invariants of the InnerClasses bytecode attribute) + case Some(prevOName) => + // this occurs e.g. when innerClassBuffer contains both class Thread$State, object Thread$State, + // i.e. for them it must be the case that oname == java/lang/Thread + assert(prevOName == e.outerName, "duplicate") + false + case None => true + } + + if (doAdd) { + seen += (e.name -> e.outerName) + result += e + } + + } + // sorting ensures inner classes are listed after their enclosing class thus satisfying the Eclipse Java compiler + for(e <- result.toList sortBy (_.name.toString)) { + jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.access) + } + + } // end of method addInnerClassesASM() + + /* + * Custom attribute (JVMS 4.7.1) "ScalaSig" used as marker only + * i.e., the pickle is contained in a custom annotation, see: + * (1) `addAnnotations()`, + * (2) SID # 10 (draft) - Storage of pickled Scala signatures in class files, http://www.scala-lang.org/sid/10 + * (3) SID # 5 - Internals of Scala Annotations, http://www.scala-lang.org/sid/5 + * That annotation in turn is not related to the "java-generic-signature" (JVMS 4.7.9) + * other than both ending up encoded as attributes (JVMS 4.7) + * (with the caveat that the "ScalaSig" attribute is associated to some classes, + * while the "Signature" attribute can be associated to classes, methods, and fields.) + * + */ + trait BCPickles { + + import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer } + + val versionPickle = { + val vp = new PickleBuffer(new Array[Byte](16), -1, 0) + assert(vp.writeIndex == 0, vp) + vp writeNat PickleFormat.MajorVersion + vp writeNat PickleFormat.MinorVersion + vp writeNat 0 + vp + } + + /* + * can-multi-thread + */ + def createJAttribute(name: String, b: Array[Byte], offset: Int, len: Int): asm.Attribute = { + val dest = new Array[Byte](len); + System.arraycopy(b, offset, dest, 0, len); + new asm.CustomAttr(name, dest) + } + + /* + * can-multi-thread + */ + def pickleMarkerLocal = { + createJAttribute(tpnme.ScalaSignatureATTR.toString, versionPickle.bytes, 0, versionPickle.writeIndex) + } + + /* + * can-multi-thread + */ + def pickleMarkerForeign = { + createJAttribute(tpnme.ScalaATTR.toString, new Array[Byte](0), 0, 0) + } + + /* Returns a ScalaSignature annotation if it must be added to this class, none otherwise. + * This annotation must be added to the class' annotations list when generating them. + * + * Depending on whether the returned option is defined, it adds to `jclass` one of: + * (a) the ScalaSig marker attribute + * (indicating that a scala-signature-annotation aka pickle is present in this class); or + * (b) the Scala marker attribute + * (indicating that a scala-signature-annotation aka pickle is to be found in another file). + * + * + * @param jclassName The class file that is being readied. + * @param sym The symbol for which the signature has been entered in the symData map. + * This is different than the symbol + * that is being generated in the case of a mirror class. + * @return An option that is: + * - defined and contains an AnnotationInfo of the ScalaSignature type, + * instantiated with the pickle signature for sym. + * - empty if the jclass/sym pair must not contain a pickle. + * + * must-single-thread + */ + def getAnnotPickle(jclassName: String, sym: Symbol): Option[AnnotationInfo] = { + currentRun.symData get sym match { + case Some(pickle) if !nme.isModuleName(newTermName(jclassName)) => + val scalaAnnot = { + val sigBytes = ScalaSigBytes(pickle.bytes.take(pickle.writeIndex)) + AnnotationInfo(sigBytes.sigAnnot, Nil, (nme.bytes, sigBytes) :: Nil) + } + pickledBytes += pickle.writeIndex + currentRun.symData -= sym + currentRun.symData -= sym.companionSymbol + Some(scalaAnnot) + case _ => + None + } + } + + } // end of trait BCPickles + + trait BCInnerClassGen { + + def debugLevel = settings.debuginfo.indexOfChoice + + val emitSource = debugLevel >= 1 + val emitLines = debugLevel >= 2 + val emitVars = debugLevel >= 3 + + /* + * Contains class-symbols that: + * (a) are known to denote inner classes + * (b) are mentioned somewhere in the class being generated. + * + * In other words, the lifetime of `innerClassBufferASM` is associated to "the class being generated". + */ + val innerClassBufferASM = mutable.Set.empty[BType] + + /* + * Tracks (if needed) the inner class given by `sym`. + * + * must-single-thread + */ + final def internalName(sym: Symbol): String = { asmClassType(sym).getInternalName } + + /* + * Tracks (if needed) the inner class given by `sym`. + * + * must-single-thread + */ + final def asmClassType(sym: Symbol): BType = { + assert( + hasInternalName(sym), + { + val msg0 = if (sym.isAbstractType) "An AbstractTypeSymbol (SI-7122) " else "A symbol "; + msg0 + s"has reached the bytecode emitter, for which no JVM-level internal name can be found: ${sym.fullName}" + } + ) + val phantOpt = phantomTypeMap.get(sym) + if (phantOpt.isDefined) { + return phantOpt.get + } + val tracked = exemplar(sym) + val tk = tracked.c + if (tracked.isInnerClass) { + innerClassBufferASM += tk + } + + tk + } + + /* + * Returns the BType for the given type. + * Tracks (if needed) the inner class given by `t`. + * + * must-single-thread + */ + final def toTypeKind(t: Type): BType = { + + /* Interfaces have to be handled delicately to avoid introducing spurious errors, + * but if we treat them all as AnyRef we lose too much information. + */ + def newReference(sym0: Symbol): BType = { + assert(!primitiveTypeMap.contains(sym0), "Use primitiveTypeMap instead.") + assert(sym0 != definitions.ArrayClass, "Use arrayOf() instead.") + + if (sym0 == definitions.NullClass) return RT_NULL; + if (sym0 == definitions.NothingClass) return RT_NOTHING; + + // Working around SI-5604. Rather than failing the compile when we see + // a package here, check if there's a package object. + val sym = ( + if (!sym0.isPackageClass) sym0 + else sym0.info.member(nme.PACKAGE) match { + case NoSymbol => abort(s"Cannot use package as value: ${sym0.fullName}") + case s => devWarning("Bug: found package class where package object expected. Converting.") ; s.moduleClass + } + ) + + // Can't call .toInterface (at this phase) or we trip an assertion. + // See PackratParser#grow for a method which fails with an apparent mismatch + // between "object PackratParsers$class" and "trait PackratParsers" + if (sym.isImplClass) { + // pos/spec-List.scala is the sole failure if we don't check for NoSymbol + val traitSym = sym.owner.info.decl(tpnme.interfaceName(sym.name)) + if (traitSym != NoSymbol) { + // this tracks the inner class in innerClassBufferASM, if needed. + return asmClassType(traitSym) + } + } + + assert(hasInternalName(sym), s"Invoked for a symbol lacking JVM internal name: ${sym.fullName}") + assert(!phantomTypeMap.contains(sym), "phantom types not supposed to reach here.") + + val tracked = exemplar(sym) + val tk = tracked.c + if (tracked.isInnerClass) { + innerClassBufferASM += tk + } + + tk + } + + def primitiveOrRefType(sym: Symbol): BType = { + assert(sym != definitions.ArrayClass, "Use primitiveOrArrayOrRefType() instead.") + + primitiveTypeMap.getOrElse(sym, newReference(sym)) + } + + def primitiveOrRefType2(sym: Symbol): BType = { + primitiveTypeMap.get(sym) match { + case Some(pt) => pt + case None => + sym match { + case definitions.NullClass => RT_NULL + case definitions.NothingClass => RT_NOTHING + case _ if sym.isClass => newReference(sym) + case _ => + assert(sym.isType, sym) // it must be compiling Array[a] + ObjectReference + } + } + } + + import definitions.ArrayClass + + // Call to .normalize fixes #3003 (follow type aliases). Otherwise, primitiveOrArrayOrRefType() would return ObjectReference. + t.normalize match { + + case ThisType(sym) => + if (sym == ArrayClass) ObjectReference + else phantomTypeMap.getOrElse(sym, exemplar(sym).c) + + case SingleType(_, sym) => primitiveOrRefType(sym) + + case _: ConstantType => toTypeKind(t.underlying) + + case TypeRef(_, sym, args) => + if (sym == ArrayClass) arrayOf(toTypeKind(args.head)) + else primitiveOrRefType2(sym) + + case ClassInfoType(_, _, sym) => + assert(sym != ArrayClass, "ClassInfoType to ArrayClass!") + primitiveOrRefType(sym) + + // !!! Iulian says types which make no sense after erasure should not reach here, which includes the ExistentialType, AnnotatedType, RefinedType. + case ExistentialType(_, t) => toTypeKind(t) // TODO shouldn't get here but the following does: akka-actor/src/main/scala/akka/util/WildcardTree.scala + case AnnotatedType(_, w, _) => toTypeKind(w) // TODO test/files/jvm/annotations.scala causes an AnnotatedType to reach here. + case RefinedType(parents, _) => parents map toTypeKind reduceLeft jvmWiseLUB + + // For sure WildcardTypes shouldn't reach here either, but when debugging such situations this may come in handy. + // case WildcardType => REFERENCE(ObjectClass) + case norm => abort( + s"Unknown type: $t, $norm [${t.getClass}, ${norm.getClass}] TypeRef? ${t.isInstanceOf[TypeRef]}" + ) + } + + } // end of method toTypeKind() + + /* + * must-single-thread + */ + def asmMethodType(msym: Symbol): BType = { + assert(msym.isMethod, s"not a method-symbol: $msym") + val resT: BType = + if (msym.isClassConstructor || msym.isConstructor) BType.VOID_TYPE + else toTypeKind(msym.tpe.resultType); + BType.getMethodType( resT, mkArray(msym.tpe.paramTypes map toTypeKind) ) + } + + /* + * Returns all direct member inner classes of `csym`, + * thus making sure they get entries in the InnerClasses JVM attribute + * even if otherwise not mentioned in the class being built. + * + * must-single-thread + */ + final def trackMemberClasses(csym: Symbol, lateClosuresBTs: List[BType]): List[BType] = { + val lateInnerClasses = exitingErasure { + for (sym <- List(csym, csym.linkedClassOfClass); memberc <- sym.info.decls.map(innerClassSymbolFor) if memberc.isClass) + yield memberc + } + // as a precaution, do the following outside the above `exitingErasure` otherwise funny internal names might be computed. + val result = for(memberc <- lateInnerClasses) yield { + val tracked = exemplar(memberc) + val memberCTK = tracked.c + assert(tracked.isInnerClass, s"saveInnerClassesFor() says this was no inner-class after all: ${memberc.fullName}") + + memberCTK + } + + exemplar(csym).directMemberClasses = (result ::: lateClosuresBTs) + + result + } + + /* + * Tracks (if needed) the inner class given by `t`. + * + * must-single-thread + */ + final def descriptor(t: Type): String = { toTypeKind(t).getDescriptor } + + /* + * Tracks (if needed) the inner class given by `sym`. + * + * must-single-thread + */ + final def descriptor(sym: Symbol): String = { asmClassType(sym).getDescriptor } + + } // end of trait BCInnerClassGen + + trait BCAnnotGen extends BCInnerClassGen { + + /* + * can-multi-thread + */ + def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { + val ca = new Array[Char](bytes.length) + var idx = 0 + while (idx < bytes.length) { + val b: Byte = bytes(idx) + assert((b & ~0x7f) == 0) + ca(idx) = b.asInstanceOf[Char] + idx += 1 + } + + ca + } + + /* + * can-multi-thread + */ + private def arrEncode(sb: ScalaSigBytes): Array[String] = { + var strs: List[String] = Nil + val bSeven: Array[Byte] = sb.sevenBitsMayBeZero + // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure) + var prevOffset = 0 + var offset = 0 + var encLength = 0 + while (offset < bSeven.size) { + val deltaEncLength = (if (bSeven(offset) == 0) 2 else 1) + val newEncLength = encLength.toLong + deltaEncLength + if (newEncLength >= 65535) { + val ba = bSeven.slice(prevOffset, offset) + strs ::= new java.lang.String(ubytesToCharArray(ba)) + encLength = 0 + prevOffset = offset + } else { + encLength += deltaEncLength + offset += 1 + } + } + if (prevOffset < offset) { + assert(offset == bSeven.length) + val ba = bSeven.slice(prevOffset, offset) + strs ::= new java.lang.String(ubytesToCharArray(ba)) + } + assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict? + mkArrayReverse(strs) + } + + /* + * can-multi-thread + */ + private def strEncode(sb: ScalaSigBytes): String = { + val ca = ubytesToCharArray(sb.sevenBitsMayBeZero) + new java.lang.String(ca) + // debug val bvA = new asm.ByteVector; bvA.putUTF8(s) + // debug val enc: Array[Byte] = scala.reflect.internal.pickling.ByteCodecs.encode(bytes) + // debug assert(enc(idx) == bvA.getByte(idx + 2)) + // debug assert(bvA.getLength == enc.size + 2) + } + + /* + * For arg a LiteralAnnotArg(constt) with const.tag in {ClazzTag, EnumTag} + * as well as for arg a NestedAnnotArg + * must-single-thread + * Otherwise it's safe to call from multiple threads. + */ + def emitArgument(av: asm.AnnotationVisitor, + name: String, + arg: ClassfileAnnotArg) { + arg match { + + case LiteralAnnotArg(const) => + if (const.isNonUnitAnyVal) { av.visit(name, const.value) } + else { + const.tag match { + case StringTag => + assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` + av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag + case ClazzTag => av.visit(name, toTypeKind(const.typeValue).toASMType) + case EnumTag => + val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class. + val evalue = const.symbolValue.name.toString // value the actual enumeration value. + av.visitEnum(name, edesc, evalue) + } + } + + case sb @ ScalaSigBytes(bytes) => + // see http://www.scala-lang.org/sid/10 (Storage of pickled Scala signatures in class files) + // also JVMS Sec. 4.7.16.1 The element_value structure and JVMS Sec. 4.4.7 The CONSTANT_Utf8_info Structure. + if (sb.fitsInOneString) { + av.visit(name, strEncode(sb)) + } else { + val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) + for(arg <- arrEncode(sb)) { arrAnnotV.visit(name, arg) } + arrAnnotV.visitEnd() + } // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape. + + case ArrayAnnotArg(args) => + val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) + for(arg <- args) { emitArgument(arrAnnotV, null, arg) } + arrAnnotV.visitEnd() + + case NestedAnnotArg(annInfo) => + val AnnotationInfo(typ, args, assocs) = annInfo + assert(args.isEmpty, args) + val desc = descriptor(typ) // the class descriptor of the nested annotation class + val nestedVisitor = av.visitAnnotation(name, desc) + emitAssocs(nestedVisitor, assocs) + } + } + + /* Whether an annotation should be emitted as a Java annotation + * .initialize: if 'annot' is read from pickle, atp might be un-initialized + * + * must-single-thread + */ + private def shouldEmitAnnotation(annot: AnnotationInfo) = + annot.symbol.initialize.isJavaDefined && + annot.matches(definitions.ClassfileAnnotationClass) && + annot.args.isEmpty && + !annot.matches(definitions.DeprecatedAttr) + + /* + * In general, + * must-single-thread + * but not necessarily always. + */ + def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, ClassfileAnnotArg)]) { + for ((name, value) <- assocs) { + emitArgument(av, name.toString(), value) + } + av.visitEnd() + } + + /* + * must-single-thread + */ + def emitAnnotations(cw: asm.ClassVisitor, annotations: List[AnnotationInfo]) { + for(annot <- annotations; if shouldEmitAnnotation(annot)) { + val AnnotationInfo(typ, args, assocs) = annot + assert(args.isEmpty, args) + val av = cw.visitAnnotation(descriptor(typ), true) + emitAssocs(av, assocs) + } + } + + /* + * must-single-thread + */ + def emitAnnotations(mw: asm.MethodVisitor, annotations: List[AnnotationInfo]) { + for(annot <- annotations; if shouldEmitAnnotation(annot)) { + val AnnotationInfo(typ, args, assocs) = annot + assert(args.isEmpty, args) + val av = mw.visitAnnotation(descriptor(typ), true) + emitAssocs(av, assocs) + } + } + + /* + * must-single-thread + */ + def emitAnnotations(fw: asm.FieldVisitor, annotations: List[AnnotationInfo]) { + for(annot <- annotations; if shouldEmitAnnotation(annot)) { + val AnnotationInfo(typ, args, assocs) = annot + assert(args.isEmpty, args) + val av = fw.visitAnnotation(descriptor(typ), true) + emitAssocs(av, assocs) + } + } + + /* + * must-single-thread + */ + def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[AnnotationInfo]]) { + val annotationss = pannotss map (_ filter shouldEmitAnnotation) + if (annotationss forall (_.isEmpty)) return + for (Pair(annots, idx) <- annotationss.zipWithIndex; + annot <- annots) { + val AnnotationInfo(typ, args, assocs) = annot + assert(args.isEmpty, args) + val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), true) + emitAssocs(pannVisitor, assocs) + } + } + + } // end of trait BCAnnotGen + + trait BCJGenSigGen { + + // @M don't generate java generics sigs for (members of) implementation + // classes, as they are monomorphic (TODO: ok?) + /* + * must-single-thread + */ + private def needsGenericSignature(sym: Symbol) = !( + // PP: This condition used to include sym.hasExpandedName, but this leads + // to the total loss of generic information if a private member is + // accessed from a closure: both the field and the accessor were generated + // without it. This is particularly bad because the availability of + // generic information could disappear as a consequence of a seemingly + // unrelated change. + settings.Ynogenericsig + || sym.isArtifact + || sym.isLiftedMethod + || sym.isBridge + || (sym.ownerChain exists (_.isImplClass)) + ) + + def getCurrentCUnit(): CompilationUnit + + /* @return + * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases). + * - otherwise the signature in question + * + * must-single-thread + */ + def getGenericSignature(sym: Symbol, owner: Symbol): String = { + + if (!needsGenericSignature(sym)) { return null } + + val memberTpe = enteringErasure(owner.thisType.memberInfo(sym)) + + val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe) + if (jsOpt.isEmpty) { return null } + + val sig = jsOpt.get + log(sig) // This seems useful enough in the general case. + + def wrap(op: => Unit) = { + try { op; true } + catch { case _: Throwable => false } + } + + if (settings.Xverify) { + // Run the signature parser to catch bogus signatures. + val isValidSignature = wrap { + // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser) + import scala.tools.asm.util.CheckClassAdapter + if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } + else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig } + else { CheckClassAdapter checkClassSignature sig } + } + + if (!isValidSignature) { + getCurrentCUnit().warning(sym.pos, + """|compiler bug: created invalid generic signature for %s in %s + |signature: %s + |if this is reproducible, please report bug at https://issues.scala-lang.org/ + """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig)) + return null + } + } + + if ((settings.check containsName phaseName)) { + val normalizedTpe = enteringErasure(erasure.prepareSigMap(memberTpe)) + val bytecodeTpe = owner.thisType.memberInfo(sym) + if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) { + getCurrentCUnit().warning(sym.pos, + """|compiler bug: created generic signature for %s in %s that does not conform to its erasure + |signature: %s + |original type: %s + |normalized type: %s + |erasure type: %s + |if this is reproducible, please report bug at http://issues.scala-lang.org/ + """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig, memberTpe, normalizedTpe, bytecodeTpe)) + return null + } + } + + sig + } + + } // end of trait BCJGenSigGen + + trait BCForwardersGen extends BCAnnotGen with BCJGenSigGen { + + // ----------------------------------------------------------------------------------------- + // Static forwarders (related to mirror classes but also present in + // a plain class lacking companion module, for details see `isCandidateForForwarders`). + // ----------------------------------------------------------------------------------------- + + val ExcludedForwarderFlags = { + import symtab.Flags._ + // Should include DEFERRED but this breaks findMember. + ( CASE | SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO ) + } + + /* Adds a @remote annotation, actual use unknown. + * + * Invoked from genMethod() and addForwarder(). + * + * must-single-thread + */ + def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) { + val needsAnnotation = ( + ( isRemoteClass || + isRemote(meth) && isJMethodPublic + ) && !(meth.throwsAnnotations contains definitions.RemoteExceptionClass) + ) + if (needsAnnotation) { + val c = Constant(definitions.RemoteExceptionClass.tpe) + val arg = Literal(c) setType c.tpe + meth.addAnnotation(appliedType(definitions.ThrowsClass, c.tpe), arg) + } + } + + /* Add a forwarder for method m. Used only from addForwarders(). + * + * must-single-thread + */ + private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) { + val moduleName = internalName(module) + val methodInfo = module.thisType.memberInfo(m) + val paramJavaTypes: List[BType] = methodInfo.paramTypes map toTypeKind + // val paramNames = 0 until paramJavaTypes.length map ("x_" + _) + + /* Forwarders must not be marked final, + * as the JVM will not allow redefinition of a final static method, + * and we don't know what classes might be subclassing the companion class. See SI-4827. + */ + // TODO: evaluate the other flags we might be dropping on the floor here. + // TODO: ACC_SYNTHETIC ? + val flags = PublicStatic | ( + if (m.isVarargsMethod) asm.Opcodes.ACC_VARARGS else 0 + ) + + // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize } + val jgensig = if (m.isDeferred) null else getGenericSignature(m, module); // only add generic signature if method concrete; bug #1745 + addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) + val (throws, others) = m.annotations partition (_.symbol == definitions.ThrowsClass) + val thrownExceptions: List[String] = getExceptions(throws) + + val jReturnType = toTypeKind(methodInfo.resultType) + val mdesc = BType.getMethodType(jReturnType, mkArray(paramJavaTypes)).getDescriptor + val mirrorMethodName = m.javaSimpleName.toString + val mirrorMethod: asm.MethodVisitor = jclass.visitMethod( + flags, + mirrorMethodName, + mdesc, + jgensig, + mkArray(thrownExceptions) + ) + + emitAnnotations(mirrorMethod, others) + emitParamAnnotations(mirrorMethod, m.info.params.map(_.annotations)) + + mirrorMethod.visitCode() + + mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module)) + + var index = 0 + for(jparamType <- paramJavaTypes) { + mirrorMethod.visitVarInsn(jparamType.getOpcode(asm.Opcodes.ILOAD), index) + assert(jparamType.sort != BType.METHOD, jparamType) + index += jparamType.getSize + } + + mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, asmMethodType(m).getDescriptor) + mirrorMethod.visitInsn(jReturnType.getOpcode(asm.Opcodes.IRETURN)) + + mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments + mirrorMethod.visitEnd() + + } + + /* Add forwarders for all methods defined in `module` that don't conflict + * with methods in the companion class of `module`. A conflict arises when + * a method with the same name is defined both in a class and its companion object: + * method signature is not taken into account. + * + * must-single-thread + */ + def addForwarders(isRemoteClass: Boolean, jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol) { + assert(moduleClass.isModuleClass, moduleClass) + debuglog(s"Dumping mirror class for object: $moduleClass") + + val linkedClass = moduleClass.companionClass + lazy val conflictingNames: Set[Name] = { + (linkedClass.info.members collect { case sym if sym.name.isTermName => sym.name }).toSet + } + debuglog(s"Potentially conflicting names for forwarders: $conflictingNames") + + for (m <- moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, symtab.Flags.METHOD)) { + if (m.isType || m.isDeferred || (m.owner eq definitions.ObjectClass) || m.isConstructor) + debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'") + else if (conflictingNames(m.name)) + log(s"No forwarder for $m due to conflict with ${linkedClass.info.member(m.name)}") + else if (m.hasAccessBoundary) + log(s"No forwarder for non-public member $m") + else { + log(s"Adding static forwarder for '$m' from $jclassName to '$moduleClass'") + addForwarder(isRemoteClass, jclass, moduleClass, m) + } + } + } + + /* + * Quoting from JVMS 4.7.5 The Exceptions Attribute + * "The Exceptions attribute indicates which checked exceptions a method may throw. + * There may be at most one Exceptions attribute in each method_info structure." + * + * The contents of that attribute are determined by the `String[] exceptions` argument to ASM's ClassVisitor.visitMethod() + * This method returns such list of internal names. + * + * must-single-thread + */ + def getExceptions(excs: List[AnnotationInfo]): List[String] = { + for (ThrownException(exc) <- excs.distinct) + yield internalName(exc) + } + + } // end of trait BCForwardersGen + + trait BCClassGen extends BCInnerClassGen { + + // Used as threshold above which a tableswitch bytecode instruction is preferred over a lookupswitch. + // There's a space tradeoff between these multi-branch instructions (details in the JVM spec). + // The particular value in use for `MIN_SWITCH_DENSITY` reflects a heuristic. + val MIN_SWITCH_DENSITY = 0.7 + + /* + * must-single-thread + */ + def serialVUID(csym: Symbol): Option[Long] = csym getAnnotation definitions.SerialVersionUIDAttr collect { + case AnnotationInfo(_, Literal(const) :: _, _) => const.longValue + } + + /* + * Add public static final field serialVersionUID with value `id` + * + * can-multi-thread + */ + def addSerialVUID(id: Long, jclass: asm.ClassVisitor) { + // add static serialVersionUID field if `clasz` annotated with `@SerialVersionUID(uid: Long)` + jclass.visitField( + PublicStaticFinal, + "serialVersionUID", + "J", + null, // no java-generic-signature + new java.lang.Long(id) + ).visitEnd() + } + + /* + * @param owner internal name of the enclosing class of the class. + * + * @param name the name of the method that contains the class. + + * @param methodType the method that contains the class. + */ + case class EnclMethodEntry(owner: String, name: String, methodType: BType) + + /* + * @return null if the current class is not internal to a method + * + * Quoting from JVMS 4.7.7 The EnclosingMethod Attribute + * A class must have an EnclosingMethod attribute if and only if it is a local class or an anonymous class. + * A class may have no more than one EnclosingMethod attribute. + * + * must-single-thread + */ + def getEnclosingMethodAttribute(clazz: Symbol): EnclMethodEntry = { // JVMS 4.7.7 + + def newEEE(eClass: Symbol, m: Symbol) = { + EnclMethodEntry( + internalName(eClass), + m.javaSimpleName.toString, + asmMethodType(m) + ) + } + + var res: EnclMethodEntry = null + val sym = clazz.originalEnclosingMethod + if (sym.isMethod) { + debuglog(s"enclosing method for $clazz is $sym (in ${sym.enclClass})") + res = newEEE(sym.enclClass, sym) + } else if (clazz.isAnonymousClass) { + val enclClass = clazz.rawowner + assert(enclClass.isClass, enclClass) + val sym = enclClass.primaryConstructor + if (sym == NoSymbol) { + log(s"Ran out of room looking for an enclosing method for $clazz: no constructor here: $enclClass.") + } else { + debuglog(s"enclosing method for $clazz is $sym (in $enclClass)") + res = newEEE(enclClass, sym) + } + } + + res + } + + } // end of trait BCClassGen + + /* basic functionality for class file building of plain, mirror, and beaninfo classes. */ + abstract class JBuilder extends BCInnerClassGen { + + } // end of class JBuilder + + /* functionality for building plain and mirror classes */ + abstract class JCommonBuilder + extends JBuilder + with BCAnnotGen + with BCForwardersGen + with BCPickles { } + + /* builder of mirror classes */ + class JMirrorBuilder extends JCommonBuilder { + + private var cunit: CompilationUnit = _ + def getCurrentCUnit(): CompilationUnit = cunit; + + /* Generate a mirror class for a top-level module. A mirror class is a class + * containing only static methods that forward to the corresponding method + * on the MODULE instance of the given Scala object. It will only be + * generated if there is no companion class: if there is, an attempt will + * instead be made to add the forwarder methods to the companion class. + * + * must-single-thread + */ + def genMirrorClass(modsym: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = { + assert(modsym.companionClass == NoSymbol, modsym) + innerClassBufferASM.clear() + this.cunit = cunit + val moduleName = internalName(modsym) // + "$" + val mirrorName = moduleName.substring(0, moduleName.length() - 1) + + val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL) + val mirrorClass = new asm.tree.ClassNode + mirrorClass.visit( + classfileVersion, + flags, + mirrorName, + null /* no java-generic-signature */, + JAVA_LANG_OBJECT.getInternalName, + EMPTY_STRING_ARRAY + ) + + if (emitSource) { + mirrorClass.visitSource("" + cunit.source, + null /* SourceDebugExtension */) + } + + val ssa = getAnnotPickle(mirrorName, modsym.companionSymbol) + mirrorClass.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) + emitAnnotations(mirrorClass, modsym.annotations ++ ssa) + + addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym) + + innerClassBufferASM ++= trackMemberClasses(modsym, Nil /* TODO what about Late-Closure-Classes */ ) + addInnerClassesASM(mirrorClass, innerClassBufferASM.toList) + + mirrorClass.visitEnd() + + ("" + modsym.name) // this side-effect is necessary, really. + + mirrorClass + } + + } // end of class JMirrorBuilder + + /* builder of bean info classes */ + class JBeanInfoBuilder extends JBuilder { + + /* + * Generate a bean info class that describes the given class. + * + * @author Ross Judson (ross.judson@soletta.com) + * + * must-single-thread + */ + def genBeanInfoClass(cls: Symbol, cunit: CompilationUnit, fieldSymbols: List[Symbol], methodSymbols: List[Symbol]): asm.tree.ClassNode = { + + def javaSimpleName(s: Symbol): String = { s.javaSimpleName.toString } + + innerClassBufferASM.clear() + + val flags = mkFlags( + javaFlags(cls), + if (isDeprecated(cls)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + val beanInfoName = (internalName(cls) + "BeanInfo") + val beanInfoClass = new asm.tree.ClassNode + beanInfoClass.visit( + classfileVersion, + flags, + beanInfoName, + null, // no java-generic-signature + "scala/beans/ScalaBeanInfo", + EMPTY_STRING_ARRAY + ) + + beanInfoClass.visitSource( + cunit.source.toString, + null /* SourceDebugExtension */ + ) + + var fieldList = List[String]() + + for (f <- fieldSymbols if f.hasGetter; + g = f.getter(cls); + s = f.setter(cls); + if g.isPublic && !(f.name startsWith "$") + ) { + // inserting $outer breaks the bean + fieldList = javaSimpleName(f) :: javaSimpleName(g) :: (if (s != NoSymbol) javaSimpleName(s) else null) :: fieldList + } + + val methodList: List[String] = + for (m <- methodSymbols + if !m.isConstructor && + m.isPublic && + !(m.name startsWith "$") && + !m.isGetter && + !m.isSetter) + yield javaSimpleName(m) + + val constructor = beanInfoClass.visitMethod( + asm.Opcodes.ACC_PUBLIC, + INSTANCE_CONSTRUCTOR_NAME, + "()V", + null, // no java-generic-signature + EMPTY_STRING_ARRAY // no throwable exceptions + ) + + val stringArrayJType: BType = arrayOf(JAVA_LANG_STRING) + val conJType: BType = + BType.getMethodType( + BType.VOID_TYPE, + Array(exemplar(definitions.ClassClass).c, stringArrayJType, stringArrayJType) + ) + + def push(lst: List[String]) { + var fi = 0 + for (f <- lst) { + constructor.visitInsn(asm.Opcodes.DUP) + constructor.visitLdcInsn(new java.lang.Integer(fi)) + if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) } + else { constructor.visitLdcInsn(f) } + constructor.visitInsn(JAVA_LANG_STRING.getOpcode(asm.Opcodes.IASTORE)) + fi += 1 + } + } + + constructor.visitCode() + + constructor.visitVarInsn(asm.Opcodes.ALOAD, 0) + // push the class + constructor.visitLdcInsn(exemplar(cls).c) + + // push the string array of field information + constructor.visitLdcInsn(new java.lang.Integer(fieldList.length)) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName) + push(fieldList) + + // push the string array of method information + constructor.visitLdcInsn(new java.lang.Integer(methodList.length)) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName) + push(methodList) + + // invoke the superclass constructor, which will do the + // necessary java reflection and create Method objects. + constructor.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, "scala/beans/ScalaBeanInfo", INSTANCE_CONSTRUCTOR_NAME, conJType.getDescriptor) + constructor.visitInsn(asm.Opcodes.RETURN) + + constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments + constructor.visitEnd() + + innerClassBufferASM ++= trackMemberClasses(cls, Nil /* TODO what about Late-Closure-Classes */ ) + addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList) + + beanInfoClass.visitEnd() + + beanInfoClass + } + + } // end of class JBeanInfoBuilder + + trait JAndroidBuilder { + self: BCInnerClassGen => + + /* From the reference documentation of the Android SDK: + * The `Parcelable` interface identifies classes whose instances can be written to and restored from a `Parcel`. + * Classes implementing the `Parcelable` interface must also have a static field called `CREATOR`, + * which is an object implementing the `Parcelable.Creator` interface. + */ + val androidFieldName = newTermName("CREATOR") + + /* + * must-single-thread + */ + def isAndroidParcelableClass(sym: Symbol) = + (AndroidParcelableInterface != NoSymbol) && + (sym.parentSymbols contains AndroidParcelableInterface) + + /* + * must-single-thread + */ + def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String) { + // this tracks the inner class in innerClassBufferASM, if needed. + val androidCreatorType = asmClassType(AndroidCreatorClass) + val tdesc_creator = androidCreatorType.getDescriptor + + cnode.visitField( + PublicStaticFinal, + "CREATOR", + tdesc_creator, + null, // no java-generic-signature + null // no initial value + ).visitEnd() + + val moduleName = (thisName + "$") + + // GETSTATIC `moduleName`.MODULE$ : `moduleName`; + clinit.visitFieldInsn( + asm.Opcodes.GETSTATIC, + moduleName, + strMODULE_INSTANCE_FIELD, + "L" + moduleName + ";" + ) + + // INVOKEVIRTUAL `moduleName`.CREATOR() : android.os.Parcelable$Creator; + val bt = BType.getMethodType(androidCreatorType, Array.empty[BType]) + clinit.visitMethodInsn( + asm.Opcodes.INVOKEVIRTUAL, + moduleName, + "CREATOR", + bt.getDescriptor + ) + + // PUTSTATIC `thisName`.CREATOR; + clinit.visitFieldInsn( + asm.Opcodes.PUTSTATIC, + thisName, + "CREATOR", + tdesc_creator + ) + } + + } // end of trait JAndroidBuilder + +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala new file mode 100644 index 0000000000..eda17c6e32 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -0,0 +1,844 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + +package scala +package tools.nsc +package backend.jvm + +import scala.tools.asm +import scala.annotation.switch +import scala.collection.{ immutable, mutable } +import collection.convert.Wrappers.JListWrapper + +/* + * A high-level facade to the ASM API for bytecode generation. + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded + * @version 1.0 + * + */ +abstract class BCodeIdiomatic extends BCodeGlue { + + import global._ + + val classfileVersion: Int = settings.target.value match { + case "jvm-1.5" => asm.Opcodes.V1_5 + case "jvm-1.6" => asm.Opcodes.V1_6 + case "jvm-1.7" => asm.Opcodes.V1_7 + } + + val majorVersion: Int = (classfileVersion & 0xFF) + val emitStackMapFrame = (majorVersion >= 50) + + def mkFlags(args: Int*) = args.foldLeft(0)(_ | _) + + val extraProc: Int = mkFlags( + asm.ClassWriter.COMPUTE_MAXS, + if (emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0 + ) + + val StringBuilderClassName = "scala/collection/mutable/StringBuilder" + + val CLASS_CONSTRUCTOR_NAME = "<clinit>" + val INSTANCE_CONSTRUCTOR_NAME = "<init>" + + val ObjectReference = brefType("java/lang/Object") + val AnyRefReference = ObjectReference + val objArrayReference = arrayOf(ObjectReference) + + val JAVA_LANG_OBJECT = ObjectReference + val JAVA_LANG_STRING = brefType("java/lang/String") + + var StringBuilderReference: BType = null + + val EMPTY_STRING_ARRAY = Array.empty[String] + val EMPTY_INT_ARRAY = Array.empty[Int] + val EMPTY_LABEL_ARRAY = Array.empty[asm.Label] + val EMPTY_BTYPE_ARRAY = Array.empty[BType] + + /* can-multi-thread */ + final def mkArray(xs: List[BType]): Array[BType] = { + if (xs.isEmpty) { return EMPTY_BTYPE_ARRAY } + val a = new Array[BType](xs.size); xs.copyToArray(a); a + } + /* can-multi-thread */ + final def mkArray(xs: List[String]): Array[String] = { + if (xs.isEmpty) { return EMPTY_STRING_ARRAY } + val a = new Array[String](xs.size); xs.copyToArray(a); a + } + /* can-multi-thread */ + final def mkArray(xs: List[asm.Label]): Array[asm.Label] = { + if (xs.isEmpty) { return EMPTY_LABEL_ARRAY } + val a = new Array[asm.Label](xs.size); xs.copyToArray(a); a + } + /* can-multi-thread */ + final def mkArray(xs: List[Int]): Array[Int] = { + if (xs.isEmpty) { return EMPTY_INT_ARRAY } + val a = new Array[Int](xs.size); xs.copyToArray(a); a + } + + /* + * can-multi-thread + */ + final def mkArrayReverse(xs: List[String]): Array[String] = { + val len = xs.size + if (len == 0) { return EMPTY_STRING_ARRAY } + val a = new Array[String](len) + var i = len - 1 + var rest = xs + while (!rest.isEmpty) { + a(i) = rest.head + rest = rest.tail + i -= 1 + } + a + } + + /* + * can-multi-thread + */ + final def mkArrayReverse(xs: List[Int]): Array[Int] = { + val len = xs.size + if (len == 0) { return EMPTY_INT_ARRAY } + val a = new Array[Int](len) + var i = len - 1 + var rest = xs + while (!rest.isEmpty) { + a(i) = rest.head + rest = rest.tail + i -= 1 + } + a + } + + /* + * can-multi-thread + */ + final def mkArrayReverse(xs: List[asm.Label]): Array[asm.Label] = { + val len = xs.size + if (len == 0) { return EMPTY_LABEL_ARRAY } + val a = new Array[asm.Label](len) + var i = len - 1 + var rest = xs + while (!rest.isEmpty) { + a(i) = rest.head + rest = rest.tail + i -= 1 + } + a + } + + /* + * The type of 1-dimensional arrays of `elem` type. + * The invoker is responsible for tracking (if needed) the inner class given by the elem BType. + * + * must-single-thread + */ + final def arrayOf(elem: BType): BType = { + assert(!(elem.isUnitType), s"The element type of an array can't be: $elem") + brefType("[" + elem.getDescriptor) + } + + /* + * The type of N-dimensional arrays of `elem` type. + * The invoker is responsible for tracking (if needed) the inner class given by the elem BType. + * + * must-single-thread + */ + final def arrayN(elem: BType, dims: Int): BType = { + assert(dims > 0) + assert(!(elem.isUnitType) && !(elem.isPhantomType), + "The element type of an array type is necessarily either a primitive type, or a class type, or an interface type.") + val desc = ("[" * dims) + elem.getDescriptor + brefType(desc) + } + + /* Just a namespace for utilities that encapsulate MethodVisitor idioms. + * In the ASM world, org.objectweb.asm.commons.InstructionAdapter plays a similar role, + * but the methods here allow choosing when to transition from ICode to ASM types + * (including not at all, e.g. for performance). + */ + abstract class JCodeMethodN { + + def jmethod: asm.MethodVisitor + + import asm.Opcodes; + import icodes.opcodes.{ InvokeStyle, Static, Dynamic, SuperCall } + + final def emit(opc: Int) { jmethod.visitInsn(opc) } + + /* + * can-multi-thread + */ + final def genPrimitiveArithmetic(op: icodes.ArithmeticOp, kind: BType) { + + import icodes.{ ADD, SUB, MUL, DIV, REM, NOT } + + op match { + + case ADD => add(kind) + case SUB => sub(kind) + case MUL => mul(kind) + case DIV => div(kind) + case REM => rem(kind) + + case NOT => + if (kind.isIntSizedType) { + emit(Opcodes.ICONST_M1) + emit(Opcodes.IXOR) + } else if (kind == LONG) { + jmethod.visitLdcInsn(new java.lang.Long(-1)) + jmethod.visitInsn(Opcodes.LXOR) + } else { + abort(s"Impossible to negate an $kind") + } + + case _ => + abort(s"Unknown arithmetic primitive $op") + } + + } // end of method genPrimitiveArithmetic() + + /* + * can-multi-thread + */ + final def genPrimitiveLogical(op: /* LogicalOp */ Int, kind: BType) { + + import scalaPrimitives.{ AND, OR, XOR } + + ((op, kind): @unchecked) match { + case (AND, LONG) => emit(Opcodes.LAND) + case (AND, INT) => emit(Opcodes.IAND) + case (AND, _) => + emit(Opcodes.IAND) + if (kind != BOOL) { emitT2T(INT, kind) } + + case (OR, LONG) => emit(Opcodes.LOR) + case (OR, INT) => emit(Opcodes.IOR) + case (OR, _) => + emit(Opcodes.IOR) + if (kind != BOOL) { emitT2T(INT, kind) } + + case (XOR, LONG) => emit(Opcodes.LXOR) + case (XOR, INT) => emit(Opcodes.IXOR) + case (XOR, _) => + emit(Opcodes.IXOR) + if (kind != BOOL) { emitT2T(INT, kind) } + } + + } // end of method genPrimitiveLogical() + + /* + * can-multi-thread + */ + final def genPrimitiveShift(op: /* ShiftOp */ Int, kind: BType) { + + import scalaPrimitives.{ LSL, ASR, LSR } + + ((op, kind): @unchecked) match { + case (LSL, LONG) => emit(Opcodes.LSHL) + case (LSL, INT) => emit(Opcodes.ISHL) + case (LSL, _) => + emit(Opcodes.ISHL) + emitT2T(INT, kind) + + case (ASR, LONG) => emit(Opcodes.LSHR) + case (ASR, INT) => emit(Opcodes.ISHR) + case (ASR, _) => + emit(Opcodes.ISHR) + emitT2T(INT, kind) + + case (LSR, LONG) => emit(Opcodes.LUSHR) + case (LSR, INT) => emit(Opcodes.IUSHR) + case (LSR, _) => + emit(Opcodes.IUSHR) + emitT2T(INT, kind) + } + + } // end of method genPrimitiveShift() + + /* + * can-multi-thread + */ + final def genPrimitiveComparison(op: icodes.ComparisonOp, kind: BType) { + + import icodes.{ CMPL, CMP, CMPG } + + ((op, kind): @unchecked) match { + case (CMP, LONG) => emit(Opcodes.LCMP) + case (CMPL, FLOAT) => emit(Opcodes.FCMPL) + case (CMPG, FLOAT) => emit(Opcodes.FCMPG) + case (CMPL, DOUBLE) => emit(Opcodes.DCMPL) + case (CMPG, DOUBLE) => emit(Opcodes.DCMPL) // http://docs.oracle.com/javase/specs/jvms/se5.0/html/Instructions2.doc3.html + } + + } // end of method genPrimitiveComparison() + + /* + * can-multi-thread + */ + final def genStartConcat { + jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName) + jmethod.visitInsn(Opcodes.DUP) + invokespecial( + StringBuilderClassName, + INSTANCE_CONSTRUCTOR_NAME, + "()V" + ) + } + + /* + * can-multi-thread + */ + final def genStringConcat(el: BType) { + + val jtype = + if (el.isArray || el.hasObjectSort) JAVA_LANG_OBJECT + else el; + + val bt = BType.getMethodType(StringBuilderReference, Array(jtype)) + + invokevirtual(StringBuilderClassName, "append", bt.getDescriptor) + } + + /* + * can-multi-thread + */ + final def genEndConcat { + invokevirtual(StringBuilderClassName, "toString", "()Ljava/lang/String;") + } + + /* + * Emits one or more conversion instructions based on the types given as arguments. + * + * @param from The type of the value to be converted into another type. + * @param to The type the value will be converted into. + * + * can-multi-thread + */ + final def emitT2T(from: BType, to: BType) { + + assert( + from.isNonUnitValueType && to.isNonUnitValueType, + s"Cannot emit primitive conversion from $from to $to" + ) + + def pickOne(opcs: Array[Int]) { // TODO index on to.sort + val chosen = (to: @unchecked) match { + case BYTE => opcs(0) + case SHORT => opcs(1) + case CHAR => opcs(2) + case INT => opcs(3) + case LONG => opcs(4) + case FLOAT => opcs(5) + case DOUBLE => opcs(6) + } + if (chosen != -1) { emit(chosen) } + } + + if (from == to) { return } + // the only conversion involving BOOL that is allowed is (BOOL -> BOOL) + assert(from != BOOL && to != BOOL, s"inconvertible types : $from -> $to") + + // We're done with BOOL already + (from.sort: @switch) match { + + // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" + + case asm.Type.BYTE => pickOne(JCodeMethodN.fromByteT2T) + case asm.Type.SHORT => pickOne(JCodeMethodN.fromShortT2T) + case asm.Type.CHAR => pickOne(JCodeMethodN.fromCharT2T) + case asm.Type.INT => pickOne(JCodeMethodN.fromIntT2T) + + case asm.Type.FLOAT => + import asm.Opcodes.{ F2L, F2D, F2I } + (to.sort: @switch) match { + case asm.Type.LONG => emit(F2L) + case asm.Type.DOUBLE => emit(F2D) + case _ => emit(F2I); emitT2T(INT, to) + } + + case asm.Type.LONG => + import asm.Opcodes.{ L2F, L2D, L2I } + (to.sort: @switch) match { + case asm.Type.FLOAT => emit(L2F) + case asm.Type.DOUBLE => emit(L2D) + case _ => emit(L2I); emitT2T(INT, to) + } + + case asm.Type.DOUBLE => + import asm.Opcodes.{ D2L, D2F, D2I } + (to.sort: @switch) match { + case asm.Type.FLOAT => emit(D2F) + case asm.Type.LONG => emit(D2L) + case _ => emit(D2I); emitT2T(INT, to) + } + } + } // end of emitT2T() + + // can-multi-thread + final def aconst(cst: AnyRef) { + if (cst == null) { emit(Opcodes.ACONST_NULL) } + else { jmethod.visitLdcInsn(cst) } + } + + // can-multi-thread + final def boolconst(b: Boolean) { iconst(if (b) 1 else 0) } + + // can-multi-thread + final def iconst(cst: Int) { + if (cst >= -1 && cst <= 5) { + emit(Opcodes.ICONST_0 + cst) + } else if (cst >= java.lang.Byte.MIN_VALUE && cst <= java.lang.Byte.MAX_VALUE) { + jmethod.visitIntInsn(Opcodes.BIPUSH, cst) + } else if (cst >= java.lang.Short.MIN_VALUE && cst <= java.lang.Short.MAX_VALUE) { + jmethod.visitIntInsn(Opcodes.SIPUSH, cst) + } else { + jmethod.visitLdcInsn(new Integer(cst)) + } + } + + // can-multi-thread + final def lconst(cst: Long) { + if (cst == 0L || cst == 1L) { + emit(Opcodes.LCONST_0 + cst.asInstanceOf[Int]) + } else { + jmethod.visitLdcInsn(new java.lang.Long(cst)) + } + } + + // can-multi-thread + final def fconst(cst: Float) { + val bits: Int = java.lang.Float.floatToIntBits(cst) + if (bits == 0L || bits == 0x3f800000 || bits == 0x40000000) { // 0..2 + emit(Opcodes.FCONST_0 + cst.asInstanceOf[Int]) + } else { + jmethod.visitLdcInsn(new java.lang.Float(cst)) + } + } + + // can-multi-thread + final def dconst(cst: Double) { + val bits: Long = java.lang.Double.doubleToLongBits(cst) + if (bits == 0L || bits == 0x3ff0000000000000L) { // +0.0d and 1.0d + emit(Opcodes.DCONST_0 + cst.asInstanceOf[Int]) + } else { + jmethod.visitLdcInsn(new java.lang.Double(cst)) + } + } + + // can-multi-thread + final def newarray(elem: BType) { + if (elem.isRefOrArrayType || elem.isPhantomType ) { + /* phantom type at play in `Array(null)`, SI-1513. On the other hand, Array(()) has element type `scala.runtime.BoxedUnit` which hasObjectSort. */ + jmethod.visitTypeInsn(Opcodes.ANEWARRAY, elem.getInternalName) + } else { + val rand = { + // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" + (elem.sort: @switch) match { + case asm.Type.BOOLEAN => Opcodes.T_BOOLEAN + case asm.Type.BYTE => Opcodes.T_BYTE + case asm.Type.SHORT => Opcodes.T_SHORT + case asm.Type.CHAR => Opcodes.T_CHAR + case asm.Type.INT => Opcodes.T_INT + case asm.Type.LONG => Opcodes.T_LONG + case asm.Type.FLOAT => Opcodes.T_FLOAT + case asm.Type.DOUBLE => Opcodes.T_DOUBLE + } + } + jmethod.visitIntInsn(Opcodes.NEWARRAY, rand) + } + } + + + final def load( idx: Int, tk: BType) { emitVarInsn(Opcodes.ILOAD, idx, tk) } // can-multi-thread + final def store(idx: Int, tk: BType) { emitVarInsn(Opcodes.ISTORE, idx, tk) } // can-multi-thread + + final def aload( tk: BType) { emitTypeBased(JCodeMethodN.aloadOpcodes, tk) } // can-multi-thread + final def astore(tk: BType) { emitTypeBased(JCodeMethodN.astoreOpcodes, tk) } // can-multi-thread + + final def neg(tk: BType) { emitPrimitive(JCodeMethodN.negOpcodes, tk) } // can-multi-thread + final def add(tk: BType) { emitPrimitive(JCodeMethodN.addOpcodes, tk) } // can-multi-thread + final def sub(tk: BType) { emitPrimitive(JCodeMethodN.subOpcodes, tk) } // can-multi-thread + final def mul(tk: BType) { emitPrimitive(JCodeMethodN.mulOpcodes, tk) } // can-multi-thread + final def div(tk: BType) { emitPrimitive(JCodeMethodN.divOpcodes, tk) } // can-multi-thread + final def rem(tk: BType) { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread + + // can-multi-thread + final def invokespecial(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc) + } + // can-multi-thread + final def invokestatic(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc) + } + // can-multi-thread + final def invokeinterface(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc) + } + // can-multi-thread + final def invokevirtual(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc) + } + + // can-multi-thread + final def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) } + // can-multi-thread + final def emitIF(cond: icodes.TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF, label) } + // can-multi-thread + final def emitIF_ICMP(cond: icodes.TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP, label) } + // can-multi-thread + final def emitIF_ACMP(cond: icodes.TestOp, label: asm.Label) { + assert((cond == icodes.EQ) || (cond == icodes.NE), cond) + val opc = (if (cond == icodes.EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE) + jmethod.visitJumpInsn(opc, label) + } + // can-multi-thread + final def emitIFNONNULL(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNONNULL, label) } + // can-multi-thread + final def emitIFNULL (label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNULL, label) } + + // can-multi-thread + final def emitRETURN(tk: BType) { + if (tk == UNIT) { emit(Opcodes.RETURN) } + else { emitTypeBased(JCodeMethodN.returnOpcodes, tk) } + } + + /* Emits one of tableswitch or lookoupswitch. + * + * can-multi-thread + */ + final def emitSWITCH(keys: Array[Int], branches: Array[asm.Label], defaultBranch: asm.Label, minDensity: Double) { + assert(keys.length == branches.length) + + // For empty keys, it makes sense emitting LOOKUPSWITCH with defaultBranch only. + // Similar to what javac emits for a switch statement consisting only of a default case. + if (keys.length == 0) { + jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches) + return + } + + // sort `keys` by increasing key, keeping `branches` in sync. TODO FIXME use quicksort + var i = 1 + while (i < keys.length) { + var j = 1 + while (j <= keys.length - i) { + if (keys(j) < keys(j - 1)) { + val tmp = keys(j) + keys(j) = keys(j - 1) + keys(j - 1) = tmp + val tmpL = branches(j) + branches(j) = branches(j - 1) + branches(j - 1) = tmpL + } + j += 1 + } + i += 1 + } + + // check for duplicate keys to avoid "VerifyError: unsorted lookupswitch" (SI-6011) + i = 1 + while (i < keys.length) { + if (keys(i-1) == keys(i)) { + abort("duplicate keys in SWITCH, can't pick arbitrarily one of them to evict, see SI-6011.") + } + i += 1 + } + + val keyMin = keys(0) + val keyMax = keys(keys.length - 1) + + val isDenseEnough: Boolean = { + /* Calculate in long to guard against overflow. TODO what overflow? */ + val keyRangeD: Double = (keyMax.asInstanceOf[Long] - keyMin + 1).asInstanceOf[Double] + val klenD: Double = keys.length + val kdensity: Double = (klenD / keyRangeD) + + kdensity >= minDensity + } + + if (isDenseEnough) { + // use a table in which holes are filled with defaultBranch. + val keyRange = (keyMax - keyMin + 1) + val newBranches = new Array[asm.Label](keyRange) + var oldPos = 0 + var i = 0 + while (i < keyRange) { + val key = keyMin + i; + if (keys(oldPos) == key) { + newBranches(i) = branches(oldPos) + oldPos += 1 + } else { + newBranches(i) = defaultBranch + } + i += 1 + } + assert(oldPos == keys.length, "emitSWITCH") + jmethod.visitTableSwitchInsn(keyMin, keyMax, defaultBranch, newBranches: _*) + } else { + jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches) + } + } + + // internal helpers -- not part of the public API of `jcode` + // don't make private otherwise inlining will suffer + + // can-multi-thread + final def emitVarInsn(opc: Int, idx: Int, tk: BType) { + assert((opc == Opcodes.ILOAD) || (opc == Opcodes.ISTORE), opc) + jmethod.visitVarInsn(tk.getOpcode(opc), idx) + } + + // ---------------- array load and store ---------------- + + // can-multi-thread + final def emitTypeBased(opcs: Array[Int], tk: BType) { + assert(tk != UNIT, tk) + val opc = { + if (tk.isRefOrArrayType) { opcs(0) } + else if (tk.isIntSizedType) { + (tk: @unchecked) match { + case BOOL | BYTE => opcs(1) + case SHORT => opcs(2) + case CHAR => opcs(3) + case INT => opcs(4) + } + } else { + (tk: @unchecked) match { + case LONG => opcs(5) + case FLOAT => opcs(6) + case DOUBLE => opcs(7) + } + } + } + emit(opc) + } + + // ---------------- primitive operations ---------------- + + // can-multi-thread + final def emitPrimitive(opcs: Array[Int], tk: BType) { + val opc = { + // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" + (tk.sort: @switch) match { + case asm.Type.LONG => opcs(1) + case asm.Type.FLOAT => opcs(2) + case asm.Type.DOUBLE => opcs(3) + case _ => opcs(0) + } + } + emit(opc) + } + + // can-multi-thread + final def drop(tk: BType) { emit(if (tk.isWideType) Opcodes.POP2 else Opcodes.POP) } + + // can-multi-thread + final def dup(tk: BType) { emit(if (tk.isWideType) Opcodes.DUP2 else Opcodes.DUP) } + + // ---------------- type checks and casts ---------------- + + // can-multi-thread + final def isInstance(tk: BType) { + jmethod.visitTypeInsn(Opcodes.INSTANCEOF, tk.getInternalName) + } + + // can-multi-thread + final def checkCast(tk: BType) { + assert(tk.isRefOrArrayType, s"checkcast on primitive type: $tk") + // TODO ICode also requires: but that's too much, right? assert(!isBoxedType(tk), "checkcast on boxed type: " + tk) + jmethod.visitTypeInsn(Opcodes.CHECKCAST, tk.getInternalName) + } + + } // end of class JCodeMethodN + + /* Constant-valued val-members of JCodeMethodN at the companion object, so as to avoid re-initializing them multiple times. */ + object JCodeMethodN { + + import asm.Opcodes._ + + // ---------------- conversions ---------------- + + val fromByteT2T = { Array( -1, -1, I2C, -1, I2L, I2F, I2D) } // do nothing for (BYTE -> SHORT) and for (BYTE -> INT) + val fromCharT2T = { Array(I2B, I2S, -1, -1, I2L, I2F, I2D) } // for (CHAR -> INT) do nothing + val fromShortT2T = { Array(I2B, -1, I2C, -1, I2L, I2F, I2D) } // for (SHORT -> INT) do nothing + val fromIntT2T = { Array(I2B, I2S, I2C, -1, I2L, I2F, I2D) } + + // ---------------- array load and store ---------------- + + val aloadOpcodes = { Array(AALOAD, BALOAD, SALOAD, CALOAD, IALOAD, LALOAD, FALOAD, DALOAD) } + val astoreOpcodes = { Array(AASTORE, BASTORE, SASTORE, CASTORE, IASTORE, LASTORE, FASTORE, DASTORE) } + val returnOpcodes = { Array(ARETURN, IRETURN, IRETURN, IRETURN, IRETURN, LRETURN, FRETURN, DRETURN) } + + // ---------------- primitive operations ---------------- + + val negOpcodes: Array[Int] = { Array(INEG, LNEG, FNEG, DNEG) } + val addOpcodes: Array[Int] = { Array(IADD, LADD, FADD, DADD) } + val subOpcodes: Array[Int] = { Array(ISUB, LSUB, FSUB, DSUB) } + val mulOpcodes: Array[Int] = { Array(IMUL, LMUL, FMUL, DMUL) } + val divOpcodes: Array[Int] = { Array(IDIV, LDIV, FDIV, DDIV) } + val remOpcodes: Array[Int] = { Array(IREM, LREM, FREM, DREM) } + + } // end of object JCodeMethodN + + // ---------------- adapted from scalaPrimitives ---------------- + + /* Given `code` reports the src TypeKind of the coercion indicated by `code`. + * To find the dst TypeKind, `ScalaPrimitives.generatedKind(code)` can be used. + * + * can-multi-thread + */ + final def coercionFrom(code: Int): BType = { + import scalaPrimitives._ + (code: @switch) match { + case B2B | B2C | B2S | B2I | B2L | B2F | B2D => BYTE + case S2B | S2S | S2C | S2I | S2L | S2F | S2D => SHORT + case C2B | C2S | C2C | C2I | C2L | C2F | C2D => CHAR + case I2B | I2S | I2C | I2I | I2L | I2F | I2D => INT + case L2B | L2S | L2C | L2I | L2L | L2F | L2D => LONG + case F2B | F2S | F2C | F2I | F2L | F2F | F2D => FLOAT + case D2B | D2S | D2C | D2I | D2L | D2F | D2D => DOUBLE + } + } + + /* If code is a coercion primitive, the result type. + * + * can-multi-thread + */ + final def coercionTo(code: Int): BType = { + import scalaPrimitives._ + (code: @scala.annotation.switch) match { + case B2B | C2B | S2B | I2B | L2B | F2B | D2B => BYTE + case B2C | C2C | S2C | I2C | L2C | F2C | D2C => CHAR + case B2S | C2S | S2S | I2S | L2S | F2S | D2S => SHORT + case B2I | C2I | S2I | I2I | L2I | F2I | D2I => INT + case B2L | C2L | S2L | I2L | L2L | F2L | D2L => LONG + case B2F | C2F | S2F | I2F | L2F | F2F | D2F => FLOAT + case B2D | C2D | S2D | I2D | L2D | F2D | D2D => DOUBLE + } + } + + final val typeOfArrayOp: Map[Int, BType] = { + import scalaPrimitives._ + Map( + (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++ + (List(BARRAY_LENGTH, BARRAY_GET, BARRAY_SET) map (_ -> BYTE)) ++ + (List(SARRAY_LENGTH, SARRAY_GET, SARRAY_SET) map (_ -> SHORT)) ++ + (List(CARRAY_LENGTH, CARRAY_GET, CARRAY_SET) map (_ -> CHAR)) ++ + (List(IARRAY_LENGTH, IARRAY_GET, IARRAY_SET) map (_ -> INT)) ++ + (List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++ + (List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++ + (List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++ + (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectReference)) : _* + ) + } + + /* + * Collects (in `result`) all LabelDef nodes enclosed (directly or not) by each node it visits. + * + * In other words, this traverser prepares a map giving + * all labelDefs (the entry-value) having a Tree node (the entry-key) as ancestor. + * The entry-value for a LabelDef entry-key always contains the entry-key. + * + */ + class LabelDefsFinder extends Traverser { + val result = mutable.Map.empty[Tree, List[LabelDef]] + var acc: List[LabelDef] = Nil + + /* + * can-multi-thread + */ + override def traverse(tree: Tree) { + val saved = acc + acc = Nil + super.traverse(tree) + // acc contains all LabelDefs found under (but not at) `tree` + tree match { + case lblDf: LabelDef => acc ::= lblDf + case _ => () + } + if (acc.isEmpty) { + acc = saved + } else { + result += (tree -> acc) + acc = acc ::: saved + } + } + } + + implicit class MethodIterClassNode(cnode: asm.tree.ClassNode) { + + @inline final def foreachMethod(f: (asm.tree.MethodNode) => Unit) { toMethodList.foreach(f) } + + @inline final def toMethodList: List[asm.tree.MethodNode] = { JListWrapper(cnode.methods).toList } + + @inline final def toFieldList: List[asm.tree.FieldNode] = { JListWrapper(cnode.fields).toList } + + } + + implicit class InsnIterMethodNode(mnode: asm.tree.MethodNode) { + + @inline final def foreachInsn(f: (asm.tree.AbstractInsnNode) => Unit) { mnode.instructions.foreachInsn(f) } + + @inline final def toList: List[asm.tree.AbstractInsnNode] = { mnode.instructions.toList } + + } + + implicit class InsnIterInsnList(lst: asm.tree.InsnList) { + + @inline final def foreachInsn(f: (asm.tree.AbstractInsnNode) => Unit) { + val insnIter = lst.iterator() + while (insnIter.hasNext) { + f(insnIter.next()) + } + } + + @inline final def toList: List[asm.tree.AbstractInsnNode] = { + var result: List[asm.tree.AbstractInsnNode] = Nil + lst foreachInsn { insn => if (insn != null) { result ::= insn } } + result.reverse + } + + } + + /* + * Upon finding a name already seen among previous List elements, adds a numeric postfix to make it unique. + */ + def uniquify(names: List[String]): List[String] = { + val seen = mutable.Set.empty[String] + + @scala.annotation.tailrec def uniquified(current: String, attempt: Int): String = { + if (seen contains current) { + val currentBis = (current + "$" + attempt.toString) + if (seen contains currentBis) { + uniquified(current, attempt + 1) + } else currentBis + } else current + } + + var rest = names + var result: List[String] = Nil + while (rest.nonEmpty) { + val u = uniquified(rest.head.trim, 1) + seen += u + result ::= u + rest = rest.tail + } + + result.reverse + } + + def allDifferent[ElemType](xs: Iterable[ElemType]): Boolean = { + val seen = mutable.Set.empty[ElemType] + val iter = xs.iterator + while (iter.hasNext) { + val nxt = iter.next() + if (seen contains nxt) { return false } + seen += nxt + } + true + } + +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala new file mode 100644 index 0000000000..8b6b4ab9ce --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -0,0 +1,727 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + + +package scala +package tools.nsc +package backend +package jvm + +import scala.collection.{ mutable, immutable } +import scala.tools.nsc.symtab._ +import scala.annotation.switch + +import scala.tools.asm + +/* + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ + * @version 1.0 + * + */ +abstract class BCodeSkelBuilder extends BCodeHelpers { + import global._ + import definitions._ + + /* + * There's a dedicated PlainClassBuilder for each CompilationUnit, + * which simplifies the initialization of per-class data structures in `genPlainClass()` which in turn delegates to `initJClass()` + * + * The entry-point to emitting bytecode instructions is `genDefDef()` where the per-method data structures are initialized, + * including `resetMethodBookkeeping()` and `initJMethod()`. + * Once that's been done, and assuming the method being visited isn't abstract, `emitNormalMethodBody()` populates + * the ASM MethodNode instance with ASM AbstractInsnNodes. + * + * Given that CleanUp delivers trees that produce values on the stack, + * the entry-point to all-things instruction-emit is `genLoad()`. + * There, an operation taking N arguments results in recursively emitting instructions to lead each of them, + * followed by emitting instructions to process those arguments (to be found at run-time on the operand-stack). + * + * In a few cases the above recipe deserves more details, as provided in the documentation for: + * - `genLoadTry()` + * - `genSynchronized() + * - `jumpDest` , `cleanups` , `labelDefsAtOrUnder` + */ + abstract class PlainSkelBuilder(cunit: CompilationUnit) + extends BCClassGen + with BCAnnotGen + with BCInnerClassGen + with JAndroidBuilder + with BCForwardersGen + with BCPickles + with BCJGenSigGen { + + // Strangely I can't find this in the asm code 255, but reserving 1 for "this" + final val MaximumJvmParameters = 254 + + // current class + var cnode: asm.tree.ClassNode = null + var thisName: String = null // the internal name of the class being emitted + + var claszSymbol: Symbol = null + var isCZParcelable = false + var isCZStaticModule = false + var isCZRemote = false + + /* ---------------- idiomatic way to ask questions to typer ---------------- */ + + def paramTKs(app: Apply): List[BType] = { + val Apply(fun, _) = app + val funSym = fun.symbol + (funSym.info.paramTypes map toTypeKind) // this tracks mentioned inner classes (in innerClassBufferASM) + } + + def symInfoTK(sym: Symbol): BType = { + toTypeKind(sym.info) // this tracks mentioned inner classes (in innerClassBufferASM) + } + + def tpeTK(tree: Tree): BType = { toTypeKind(tree.tpe) } + + def log(msg: => AnyRef) { + global synchronized { global.log(msg) } + } + + override def getCurrentCUnit(): CompilationUnit = { cunit } + + /* ---------------- helper utils for generating classes and fiels ---------------- */ + + def genPlainClass(cd: ClassDef) { + assert(cnode == null, "GenBCode detected nested methods.") + innerClassBufferASM.clear() + + claszSymbol = cd.symbol + isCZParcelable = isAndroidParcelableClass(claszSymbol) + isCZStaticModule = isStaticModule(claszSymbol) + isCZRemote = isRemote(claszSymbol) + thisName = internalName(claszSymbol) + + cnode = new asm.tree.ClassNode() + + initJClass(cnode) + + val hasStaticCtor = methodSymbols(cd) exists (_.isStaticConstructor) + if (!hasStaticCtor) { + // but needs one ... + if (isCZStaticModule || isCZParcelable) { + fabricateStaticInit() + } + } + + val optSerial: Option[Long] = serialVUID(claszSymbol) + if (optSerial.isDefined) { addSerialVUID(optSerial.get, cnode)} + + addClassFields() + + innerClassBufferASM ++= trackMemberClasses(claszSymbol, Nil) + + gen(cd.impl) + + assert(cd.symbol == claszSymbol, "Someone messed up BCodePhase.claszSymbol during genPlainClass().") + + } // end of method genPlainClass() + + /* + * must-single-thread + */ + private def initJClass(jclass: asm.ClassVisitor) { + + val ps = claszSymbol.info.parents + val superClass: String = if (ps.isEmpty) JAVA_LANG_OBJECT.getInternalName else internalName(ps.head.typeSymbol); + val ifaces: Array[String] = { + val arrIfacesTr: Array[Tracked] = exemplar(claszSymbol).ifaces + val arrIfaces = new Array[String](arrIfacesTr.length) + var i = 0 + while (i < arrIfacesTr.length) { + val ifaceTr = arrIfacesTr(i) + val bt = ifaceTr.c + if (ifaceTr.isInnerClass) { innerClassBufferASM += bt } + arrIfaces(i) = bt.getInternalName + i += 1 + } + arrIfaces + } + // `internalName()` tracks inner classes. + + val flags = mkFlags( + javaFlags(claszSymbol), + if (isDeprecated(claszSymbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + val thisSignature = getGenericSignature(claszSymbol, claszSymbol.owner) + cnode.visit(classfileVersion, flags, + thisName, thisSignature, + superClass, ifaces) + + if (emitSource) { + cnode.visitSource(cunit.source.toString, null /* SourceDebugExtension */) + } + + val enclM = getEnclosingMethodAttribute(claszSymbol) + if (enclM != null) { + val EnclMethodEntry(className, methodName, methodType) = enclM + cnode.visitOuterClass(className, methodName, methodType.getDescriptor) + } + + val ssa = getAnnotPickle(thisName, claszSymbol) + cnode.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) + emitAnnotations(cnode, claszSymbol.annotations ++ ssa) + + if (isCZStaticModule || isCZParcelable) { + + if (isCZStaticModule) { addModuleInstanceField() } + + } else { + + val skipStaticForwarders = (claszSymbol.isInterface || settings.noForwarders) + if (!skipStaticForwarders) { + val lmoc = claszSymbol.companionModule + // add static forwarders if there are no name conflicts; see bugs #363 and #1735 + if (lmoc != NoSymbol) { + // it must be a top level class (name contains no $s) + val isCandidateForForwarders = { + exitingPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass } + } + if (isCandidateForForwarders) { + log(s"Adding static forwarders from '$claszSymbol' to implementations in '$lmoc'") + addForwarders(isRemote(claszSymbol), cnode, thisName, lmoc.moduleClass) + } + } + } + + } + + // the invoker is responsible for adding a class-static constructor. + + } // end of method initJClass + + /* + * can-multi-thread + */ + private def addModuleInstanceField() { + val fv = + cnode.visitField(PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED + strMODULE_INSTANCE_FIELD, + "L" + thisName + ";", + null, // no java-generic-signature + null // no initial value + ) + + fv.visitEnd() + } + + /* + * must-single-thread + */ + private def fabricateStaticInit() { + + val clinit: asm.MethodVisitor = cnode.visitMethod( + PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED + CLASS_CONSTRUCTOR_NAME, + "()V", + null, // no java-generic-signature + null // no throwable exceptions + ) + clinit.visitCode() + + /* "legacy static initialization" */ + if (isCZStaticModule) { + clinit.visitTypeInsn(asm.Opcodes.NEW, thisName) + clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, + thisName, INSTANCE_CONSTRUCTOR_NAME, "()V") + } + if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisName) } + clinit.visitInsn(asm.Opcodes.RETURN) + + clinit.visitMaxs(0, 0) // just to follow protocol, dummy arguments + clinit.visitEnd() + } + + def addClassFields() { + /* Non-method term members are fields, except for module members. Module + * members can only happen on .NET (no flatten) for inner traits. There, + * a module symbol is generated (transformInfo in mixin) which is used + * as owner for the members of the implementation class (so that the + * backend emits them as static). + * No code is needed for this module symbol. + */ + for (f <- fieldSymbols(claszSymbol)) { + val javagensig = getGenericSignature(f, claszSymbol) + val flags = mkFlags( + javaFieldFlags(f), + if (isDeprecated(f)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + val jfield = new asm.tree.FieldNode( + flags, + f.javaSimpleName.toString, + symInfoTK(f).getDescriptor, + javagensig, + null // no initial value + ) + cnode.fields.add(jfield) + emitAnnotations(jfield, f.annotations) + } + + } // end of method addClassFields() + + // current method + var mnode: asm.tree.MethodNode = null + var jMethodName: String = null + var isMethSymStaticCtor = false + var isMethSymBridge = false + var returnType: BType = null + var methSymbol: Symbol = null + // in GenASM this is local to genCode(), ie should get false whenever a new method is emitted (including fabricated ones eg addStaticInit()) + var isModuleInitialized = false + // used by genLoadTry() and genSynchronized() + var earlyReturnVar: Symbol = null + var shouldEmitCleanup = false + var insideCleanupBlock = false + // line numbers + var lastEmittedLineNr = -1 + + object bc extends JCodeMethodN { + override def jmethod = PlainSkelBuilder.this.mnode + } + + /* ---------------- Part 1 of program points, ie Labels in the ASM world ---------------- */ + + /* + * A jump is represented as an Apply node whose symbol denotes a LabelDef, the target of the jump. + * The `jumpDest` map is used to: + * (a) find the asm.Label for the target, given an Apply node's symbol; + * (b) anchor an asm.Label in the instruction stream, given a LabelDef node. + * In other words, (a) is necessary when visiting a jump-source, and (b) when visiting a jump-target. + * A related map is `labelDef`: it has the same keys as `jumpDest` but its values are LabelDef nodes not asm.Labels. + * + */ + var jumpDest: immutable.Map[ /* LabelDef */ Symbol, asm.Label ] = null + def programPoint(labelSym: Symbol): asm.Label = { + assert(labelSym.isLabel, s"trying to map a non-label symbol to an asm.Label, at: ${labelSym.pos}") + jumpDest.getOrElse(labelSym, { + val pp = new asm.Label + jumpDest += (labelSym -> pp) + pp + }) + } + + /* + * A program point may be lexically nested (at some depth) + * (a) in the try-clause of a try-with-finally expression + * (b) in a synchronized block. + * Each of the constructs above establishes a "cleanup block" to execute upon + * both normal-exit, early-return, and abrupt-termination of the instructions it encloses. + * + * The `cleanups` LIFO queue represents the nesting of active (for the current program point) + * pending cleanups. For each such cleanup an asm.Label indicates the start of its cleanup-block. + * At any given time during traversal of the method body, + * the head of `cleanups` denotes the cleanup-block for the closest enclosing try-with-finally or synchronized-expression. + * + * `cleanups` is used: + * + * (1) upon visiting a Return statement. + * In case of pending cleanups, we can't just emit a RETURN instruction, but must instead: + * - store the result (if any) in `earlyReturnVar`, and + * - jump to the next pending cleanup. + * See `genReturn()` + * + * (2) upon emitting a try-with-finally or a synchronized-expr, + * In these cases, the targets of the above jumps are emitted, + * provided an early exit was actually encountered somewhere in the protected clauses. + * See `genLoadTry()` and `genSynchronized()` + * + * The code thus emitted for jumps and targets covers the early-return case. + * The case of abrupt (ie exceptional) termination is covered by exception handlers + * emitted for that purpose as described in `genLoadTry()` and `genSynchronized()`. + */ + var cleanups: List[asm.Label] = Nil + def registerCleanup(finCleanup: asm.Label) { + if (finCleanup != null) { cleanups = finCleanup :: cleanups } + } + def unregisterCleanup(finCleanup: asm.Label) { + if (finCleanup != null) { + assert(cleanups.head eq finCleanup, + s"Bad nesting of cleanup operations: $cleanups trying to unregister: $finCleanup") + cleanups = cleanups.tail + } + } + + /* ---------------- local variables and params ---------------- */ + + case class Local(tk: BType, name: String, idx: Int, isSynth: Boolean) + + /* + * Bookkeeping for method-local vars and method-params. + */ + object locals { + + private val slots = mutable.Map.empty[Symbol, Local] // (local-or-param-sym -> Local(BType, name, idx, isSynth)) + + private var nxtIdx = -1 // next available index for local-var + + def reset(isStaticMethod: Boolean) { + slots.clear() + nxtIdx = if (isStaticMethod) 0 else 1 + } + + def contains(locSym: Symbol): Boolean = { slots.contains(locSym) } + + def apply(locSym: Symbol): Local = { slots.apply(locSym) } + + /* Make a fresh local variable, ensuring a unique name. + * The invoker must make sure inner classes are tracked for the sym's tpe. + */ + def makeLocal(tk: BType, name: String): Symbol = { + val locSym = methSymbol.newVariable(cunit.freshTermName(name), NoPosition, Flags.SYNTHETIC) // setInfo tpe + makeLocal(locSym, tk) + locSym + } + + def makeLocal(locSym: Symbol): Local = { + makeLocal(locSym, symInfoTK(locSym)) + } + + def getOrMakeLocal(locSym: Symbol): Local = { + // `getOrElse` below has the same effect as `getOrElseUpdate` because `makeLocal()` adds an entry to the `locals` map. + slots.getOrElse(locSym, makeLocal(locSym)) + } + + private def makeLocal(sym: Symbol, tk: BType): Local = { + assert(!slots.contains(sym), "attempt to create duplicate local var.") + assert(nxtIdx != -1, "not a valid start index") + val loc = Local(tk, sym.javaSimpleName.toString, nxtIdx, sym.isSynthetic) + slots += (sym -> loc) + assert(tk.getSize > 0, "makeLocal called for a symbol whose type is Unit.") + nxtIdx += tk.getSize + loc + } + + // not to be confused with `fieldStore` and `fieldLoad` which also take a symbol but a field-symbol. + def store(locSym: Symbol) { + val Local(tk, _, idx, _) = slots(locSym) + bc.store(idx, tk) + } + + def load(locSym: Symbol) { + val Local(tk, _, idx, _) = slots(locSym) + bc.load(idx, tk) + } + + } + + /* ---------------- Part 2 of program points, ie Labels in the ASM world ---------------- */ + + /* + * The semantics of try-with-finally and synchronized-expr require their cleanup code + * to be present in three forms in the emitted bytecode: + * (a) as normal-exit code, reached via fall-through from the last program point being protected, + * (b) as code reached upon early-return from an enclosed return statement. + * The only difference between (a) and (b) is their next program-point: + * the former must continue with fall-through while + * the latter must continue to the next early-return cleanup (if any, otherwise return from the method). + * Otherwise they are identical. + * (c) as exception-handler, reached via exceptional control flow, + * which rethrows the caught exception once it's done with the cleanup code. + * + * A particular cleanup may in general contain LabelDefs. Care is needed when duplicating such jump-targets, + * so as to preserve agreement wit the (also duplicated) jump-sources. + * This is achieved based on the bookkeeping provided by two maps: + * - `labelDefsAtOrUnder` lists all LabelDefs enclosed by a given Tree node (the key) + * - `labelDef` provides the LabelDef node whose symbol is used as key. + * As a sidenote, a related map is `jumpDest`: it has the same keys as `labelDef` but its values are asm.Labels not LabelDef nodes. + * + * Details in `emitFinalizer()`, which is invoked from `genLoadTry()` and `genSynchronized()`. + */ + var labelDefsAtOrUnder: scala.collection.Map[Tree, List[LabelDef]] = null + var labelDef: scala.collection.Map[Symbol, LabelDef] = null// (LabelDef-sym -> LabelDef) + + // bookkeeping the scopes of non-synthetic local vars, to emit debug info (`emitVars`). + var varsInScope: List[Pair[Symbol, asm.Label]] = null // (local-var-sym -> start-of-scope) + + // helpers around program-points. + def lastInsn: asm.tree.AbstractInsnNode = { + mnode.instructions.getLast + } + def currProgramPoint(): asm.Label = { + lastInsn match { + case labnode: asm.tree.LabelNode => labnode.getLabel + case _ => + val pp = new asm.Label + mnode visitLabel pp + pp + } + } + def markProgramPoint(lbl: asm.Label) { + val skip = (lbl == null) || isAtProgramPoint(lbl) + if (!skip) { mnode visitLabel lbl } + } + def isAtProgramPoint(lbl: asm.Label): Boolean = { + (lastInsn match { case labnode: asm.tree.LabelNode => (labnode.getLabel == lbl); case _ => false } ) + } + def lineNumber(tree: Tree) { + if (!emitLines || !tree.pos.isDefined) return; + val nr = tree.pos.line + if (nr != lastEmittedLineNr) { + lastEmittedLineNr = nr + lastInsn match { + case lnn: asm.tree.LineNumberNode => + // overwrite previous landmark as no instructions have been emitted for it + lnn.line = nr + case _ => + mnode.visitLineNumber(nr, currProgramPoint()) + } + } + } + + // on entering a method + def resetMethodBookkeeping(dd: DefDef) { + locals.reset(isStaticMethod = methSymbol.isStaticMember) + jumpDest = immutable.Map.empty[ /* LabelDef */ Symbol, asm.Label ] + // populate labelDefsAtOrUnder + val ldf = new LabelDefsFinder + ldf.traverse(dd.rhs) + labelDefsAtOrUnder = ldf.result.withDefaultValue(Nil) + labelDef = labelDefsAtOrUnder(dd.rhs).map(ld => (ld.symbol -> ld)).toMap + // check previous invocation of genDefDef exited as many varsInScope as it entered. + assert(varsInScope == null, "Unbalanced entering/exiting of GenBCode's genBlock().") + // check previous invocation of genDefDef unregistered as many cleanups as it registered. + assert(cleanups == Nil, "Previous invocation of genDefDef didn't unregister as many cleanups as it registered.") + isModuleInitialized = false + earlyReturnVar = null + shouldEmitCleanup = false + + lastEmittedLineNr = -1 + } + + /* ---------------- top-down traversal invoking ASM Tree API along the way ---------------- */ + + def gen(tree: Tree) { + tree match { + case EmptyTree => () + + case _: ModuleDef => abort(s"Modules should have been eliminated by refchecks: $tree") + + case ValDef(mods, name, tpt, rhs) => () // fields are added in `genPlainClass()`, via `addClassFields()` + + case dd : DefDef => genDefDef(dd) + + case Template(_, _, body) => body foreach gen + + case _ => abort(s"Illegal tree in gen: $tree") + } + } + + /* + * must-single-thread + */ + def initJMethod(flags: Int, paramAnnotations: List[List[AnnotationInfo]]) { + + val jgensig = getGenericSignature(methSymbol, claszSymbol) + addRemoteExceptionAnnot(isCZRemote, hasPublicBitSet(flags), methSymbol) + val (excs, others) = methSymbol.annotations partition (_.symbol == definitions.ThrowsClass) + val thrownExceptions: List[String] = getExceptions(excs) + + val bytecodeName = + if (isMethSymStaticCtor) CLASS_CONSTRUCTOR_NAME + else jMethodName + + val mdesc = asmMethodType(methSymbol).getDescriptor + mnode = cnode.visitMethod( + flags, + bytecodeName, + mdesc, + jgensig, + mkArray(thrownExceptions) + ).asInstanceOf[asm.tree.MethodNode] + + // TODO param names: (m.params map (p => javaName(p.sym))) + + emitAnnotations(mnode, others) + emitParamAnnotations(mnode, paramAnnotations) + + } // end of method initJMethod + + + def genDefDef(dd: DefDef) { + // the only method whose implementation is not emitted: getClass() + if (definitions.isGetClass(dd.symbol)) { return } + assert(mnode == null, "GenBCode detected nested method.") + + methSymbol = dd.symbol + jMethodName = methSymbol.javaSimpleName.toString + returnType = asmMethodType(dd.symbol).getReturnType + isMethSymStaticCtor = methSymbol.isStaticConstructor + isMethSymBridge = methSymbol.isBridge + + resetMethodBookkeeping(dd) + + // add method-local vars for params + val DefDef(_, _, _, vparamss, _, rhs) = dd + assert(vparamss.isEmpty || vparamss.tail.isEmpty, s"Malformed parameter list: $vparamss") + val params = if (vparamss.isEmpty) Nil else vparamss.head + for (p <- params) { locals.makeLocal(p.symbol) } + // debug assert((params.map(p => locals(p.symbol).tk)) == asmMethodType(methSymbol).getArgumentTypes.toList, "debug") + + if (params.size > MaximumJvmParameters) { + // SI-7324 + cunit.error(methSymbol.pos, s"Platform restriction: a parameter list's length cannot exceed $MaximumJvmParameters.") + return + } + + val isNative = methSymbol.hasAnnotation(definitions.NativeAttr) + val isAbstractMethod = (methSymbol.isDeferred || methSymbol.owner.isInterface) + val flags = mkFlags( + javaFlags(methSymbol), + if (claszSymbol.isInterface) asm.Opcodes.ACC_ABSTRACT else 0, + if (methSymbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0, + if (isNative) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes + if (isDeprecated(methSymbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize } + initJMethod(flags, params.map(p => p.symbol.annotations)) + + /* Add method-local vars for LabelDef-params. + * + * This makes sure that: + * (1) upon visiting any "forward-jumping" Apply (ie visited before its target LabelDef), and after + * (2) grabbing the corresponding param symbols, + * those param-symbols can be used to access method-local vars. + * + * When duplicating a finally-contained LabelDef, another program-point is needed for the copy (each such copy has its own asm.Label), + * but the same vars (given by the LabelDef's params) can be reused, + * because no LabelDef ends up nested within itself after such duplication. + */ + for(ld <- labelDefsAtOrUnder(dd.rhs); ldp <- ld.params; if !locals.contains(ldp.symbol)) { + // the tail-calls xform results in symbols shared btw method-params and labelDef-params, thus the guard above. + locals.makeLocal(ldp.symbol) + } + + if (!isAbstractMethod && !isNative) { + + def emitNormalMethodBody() { + val veryFirstProgramPoint = currProgramPoint() + genLoad(rhs, returnType) + + rhs match { + case Block(_, Return(_)) => () + case Return(_) => () + case EmptyTree => + globalError("Concrete method has no definition: " + dd + ( + if (settings.debug) "(found: " + methSymbol.owner.info.decls.toList.mkString(", ") + ")" + else "") + ) + case _ => + bc emitRETURN returnType + } + if (emitVars) { + // add entries to LocalVariableTable JVM attribute + val onePastLastProgramPoint = currProgramPoint() + val hasStaticBitSet = ((flags & asm.Opcodes.ACC_STATIC) != 0) + if (!hasStaticBitSet) { + mnode.visitLocalVariable( + "this", + "L" + thisName + ";", + null, + veryFirstProgramPoint, + onePastLastProgramPoint, + 0 + ) + } + for (p <- params) { emitLocalVarScope(p.symbol, veryFirstProgramPoint, onePastLastProgramPoint, force = true) } + } + + if (isMethSymStaticCtor) { appendToStaticCtor(dd) } + } // end of emitNormalMethodBody() + + lineNumber(rhs) + emitNormalMethodBody() + + // Note we don't invoke visitMax, thus there are no FrameNode among mnode.instructions. + // The only non-instruction nodes to be found are LabelNode and LineNumberNode. + } + mnode = null + } // end of method genDefDef() + + /* + * must-single-thread + * + * TODO document, explain interplay with `fabricateStaticInit()` + */ + private def appendToStaticCtor(dd: DefDef) { + + def insertBefore( + location: asm.tree.AbstractInsnNode, + i0: asm.tree.AbstractInsnNode, + i1: asm.tree.AbstractInsnNode) { + if (i0 != null) { + mnode.instructions.insertBefore(location, i0.clone(null)) + mnode.instructions.insertBefore(location, i1.clone(null)) + } + } + + // collect all return instructions + var rets: List[asm.tree.AbstractInsnNode] = Nil + mnode foreachInsn { i => if (i.getOpcode() == asm.Opcodes.RETURN) { rets ::= i } } + if (rets.isEmpty) { return } + + var insnModA: asm.tree.AbstractInsnNode = null + var insnModB: asm.tree.AbstractInsnNode = null + // call object's private ctor from static ctor + if (isCZStaticModule) { + // NEW `moduleName` + val className = internalName(methSymbol.enclClass) + insnModA = new asm.tree.TypeInsnNode(asm.Opcodes.NEW, className) + // INVOKESPECIAL <init> + val callee = methSymbol.enclClass.primaryConstructor + val jname = callee.javaSimpleName.toString + val jowner = internalName(callee.owner) + val jtype = asmMethodType(callee).getDescriptor + insnModB = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESPECIAL, jowner, jname, jtype) + } + + var insnParcA: asm.tree.AbstractInsnNode = null + var insnParcB: asm.tree.AbstractInsnNode = null + // android creator code + if (isCZParcelable) { + // add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator + val andrFieldDescr = asmClassType(AndroidCreatorClass).getDescriptor + cnode.visitField( + asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL, + "CREATOR", + andrFieldDescr, + null, + null + ) + // INVOKESTATIC CREATOR(): android.os.Parcelable$Creator; -- TODO where does this Android method come from? + val callee = definitions.getMember(claszSymbol.companionModule, androidFieldName) + val jowner = internalName(callee.owner) + val jname = callee.javaSimpleName.toString + val jtype = asmMethodType(callee).getDescriptor + insnParcA = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESTATIC, jowner, jname, jtype) + // PUTSTATIC `thisName`.CREATOR; + insnParcB = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisName, "CREATOR", andrFieldDescr) + } + + // insert a few instructions for initialization before each return instruction + for(r <- rets) { + insertBefore(r, insnModA, insnModB) + insertBefore(r, insnParcA, insnParcB) + } + + } + + def emitLocalVarScope(sym: Symbol, start: asm.Label, end: asm.Label, force: Boolean = false) { + val Local(tk, name, idx, isSynth) = locals(sym) + if (force || !isSynth) { + mnode.visitLocalVariable(name, tk.getDescriptor, null, start, end, idx) + } + } + + def genLoad(tree: Tree, expectedType: BType) + + } // end of class PlainSkelBuilder + +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala new file mode 100644 index 0000000000..439be77b31 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala @@ -0,0 +1,401 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + + +package scala +package tools.nsc +package backend +package jvm + +import scala.collection.{ mutable, immutable } +import scala.annotation.switch + +import scala.tools.asm + +/* + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ + * @version 1.0 + * + */ +abstract class BCodeSyncAndTry extends BCodeBodyBuilder { + import global._ + + + /* + * Functionality to lower `synchronized` and `try` expressions. + */ + abstract class SyncAndTryBuilder(cunit: CompilationUnit) extends PlainBodyBuilder(cunit) { + + def genSynchronized(tree: Apply, expectedType: BType): BType = { + val Apply(fun, args) = tree + val monitor = locals.makeLocal(ObjectReference, "monitor") + val monCleanup = new asm.Label + + // if the synchronized block returns a result, store it in a local variable. + // Just leaving it on the stack is not valid in MSIL (stack is cleaned when leaving try-blocks). + val hasResult = (expectedType != UNIT) + val monitorResult: Symbol = if (hasResult) locals.makeLocal(tpeTK(args.head), "monitorResult") else null; + + /* ------ (1) pushing and entering the monitor, also keeping a reference to it in a local var. ------ */ + genLoadQualifier(fun) + bc dup ObjectReference + locals.store(monitor) + emit(asm.Opcodes.MONITORENTER) + + /* ------ (2) Synchronized block. + * Reached by fall-through from (1). + * Protected by: + * (2.a) the EH-version of the monitor-exit, and + * (2.b) whatever protects the whole synchronized expression. + * ------ + */ + val startProtected = currProgramPoint() + registerCleanup(monCleanup) + genLoad(args.head, expectedType /* toTypeKind(tree.tpe.resultType) */) + unregisterCleanup(monCleanup) + if (hasResult) { locals.store(monitorResult) } + nopIfNeeded(startProtected) + val endProtected = currProgramPoint() + + /* ------ (3) monitor-exit after normal, non-early-return, termination of (2). + * Reached by fall-through from (2). + * Protected by whatever protects the whole synchronized expression. + * ------ + */ + locals.load(monitor) + emit(asm.Opcodes.MONITOREXIT) + if (hasResult) { locals.load(monitorResult) } + val postHandler = new asm.Label + bc goTo postHandler + + /* ------ (4) exception-handler version of monitor-exit code. + * Reached upon abrupt termination of (2). + * Protected by whatever protects the whole synchronized expression. + * ------ + */ + protect(startProtected, endProtected, currProgramPoint(), ThrowableReference) + locals.load(monitor) + emit(asm.Opcodes.MONITOREXIT) + emit(asm.Opcodes.ATHROW) + + /* ------ (5) cleanup version of monitor-exit code. + * Reached upon early-return from (2). + * Protected by whatever protects the whole synchronized expression. + * ------ + */ + if (shouldEmitCleanup) { + markProgramPoint(monCleanup) + locals.load(monitor) + emit(asm.Opcodes.MONITOREXIT) + pendingCleanups() + } + + /* ------ (6) normal exit of the synchronized expression. + * Reached after normal, non-early-return, termination of (3). + * Protected by whatever protects the whole synchronized expression. + * ------ + */ + mnode visitLabel postHandler + + lineNumber(tree) + + expectedType + } + + /* + * Detects whether no instructions have been emitted since label `lbl` and if so emits a NOP. + * Useful to avoid emitting an empty try-block being protected by exception handlers, + * which results in "java.lang.ClassFormatError: Illegal exception table range". See SI-6102. + */ + def nopIfNeeded(lbl: asm.Label) { + val noInstructionEmitted = isAtProgramPoint(lbl) + if (noInstructionEmitted) { emit(asm.Opcodes.NOP) } + } + + /* + * Emitting try-catch is easy, emitting try-catch-finally not quite so. + * A finally-block (which always has type Unit, thus leaving the operand stack unchanged) + * affects control-transfer from protected regions, as follows: + * + * (a) `return` statement: + * + * First, the value to return (if any) is evaluated. + * Afterwards, all enclosing finally-blocks are run, from innermost to outermost. + * Only then is the return value (if any) returned. + * + * Some terminology: + * (a.1) Executing a return statement that is protected + * by one or more finally-blocks is called "early return" + * (a.2) the chain of code sections (a code section for each enclosing finally-block) + * to run upon early returns is called "cleanup chain" + * + * As an additional spin, consider a return statement in a finally-block. + * In this case, the value to return depends on how control arrived at that statement: + * in case it arrived via a previous return, the previous return enjoys priority: + * the value to return is given by that statement. + * + * (b) A finally-block protects both the try-clause and the catch-clauses. + * + * Sidenote: + * A try-clause may contain an empty block. On CLR, a finally-block has special semantics + * regarding Abort interruptions; but on the JVM it's safe to elide an exception-handler + * that protects an "empty" range ("empty" as in "containing NOPs only", + * see `asm.optimiz.DanglingExcHandlers` and SI-6720). + * + * This means a finally-block indicates instructions that can be reached: + * (b.1) Upon normal (non-early-returning) completion of the try-clause or a catch-clause + * In this case, the next-program-point is that following the try-catch-finally expression. + * (b.2) Upon early-return initiated in the try-clause or a catch-clause + * In this case, the next-program-point is the enclosing cleanup section (if any), otherwise return. + * (b.3) Upon abrupt termination (due to unhandled exception) of the try-clause or a catch-clause + * In this case, the unhandled exception must be re-thrown after running the finally-block. + * + * (c) finally-blocks are implicit to `synchronized` (a finally-block is added to just release the lock) + * that's why `genSynchronized()` too emits cleanup-sections. + * + * A number of code patterns can be emitted to realize the intended semantics. + * + * A popular alternative (GenICode, javac) consists in duplicating the cleanup-chain at each early-return position. + * The principle at work being that once control is transferred to a cleanup-section, + * control will always stay within the cleanup-chain. + * That is, barring an exception being thrown in a cleanup-section, in which case the enclosing try-block + * (reached via abrupt termination) takes over. + * + * The observations above hint at another code layout, less verbose, for the cleanup-chain. + * + * The code layout that GenBCode emits takes into account that once a cleanup section has been reached, + * jumping to the next cleanup-section (and so on, until the outermost one) realizes the correct semantics. + * + * There is still code duplication in that two cleanup-chains are needed (but this is unavoidable, anyway): + * one for normal control flow and another chain consisting of exception handlers. + * The in-line comments below refer to them as + * - "early-return-cleanups" and + * - "exception-handler-version-of-finally-block" respectively. + * + */ + def genLoadTry(tree: Try): BType = { + + val Try(block, catches, finalizer) = tree + val kind = tpeTK(tree) + + val caseHandlers: List[EHClause] = + for (CaseDef(pat, _, caseBody) <- catches) yield { + pat match { + case Typed(Ident(nme.WILDCARD), tpt) => NamelessEH(tpeTK(tpt), caseBody) + case Ident(nme.WILDCARD) => NamelessEH(ThrowableReference, caseBody) + case Bind(_, _) => BoundEH (pat.symbol, caseBody) + } + } + + // ------ (0) locals used later ------ + + /* + * `postHandlers` is a program point denoting: + * (a) the finally-clause conceptually reached via fall-through from try-catch-finally + * (in case a finally-block is present); or + * (b) the program point right after the try-catch + * (in case there's no finally-block). + * The name choice emphasizes that the code section lies "after all exception handlers", + * where "all exception handlers" includes those derived from catch-clauses as well as from finally-blocks. + */ + val postHandlers = new asm.Label + + val hasFinally = (finalizer != EmptyTree) + + /* + * used in the finally-clause reached via fall-through from try-catch, if any. + */ + val guardResult = hasFinally && (kind != UNIT) && mayCleanStack(finalizer) + + /* + * please notice `tmp` has type tree.tpe, while `earlyReturnVar` has the method return type. + * Because those two types can be different, dedicated vars are needed. + */ + val tmp = if (guardResult) locals.makeLocal(tpeTK(tree), "tmp") else null; + + /* + * upon early return from the try-body or one of its EHs (but not the EH-version of the finally-clause) + * AND hasFinally, a cleanup is needed. + */ + val finCleanup = if (hasFinally) new asm.Label else null + + /* ------ (1) try-block, protected by: + * (1.a) the EHs due to case-clauses, emitted in (2), + * (1.b) the EH due to finally-clause, emitted in (3.A) + * (1.c) whatever protects the whole try-catch-finally expression. + * ------ + */ + + val startTryBody = currProgramPoint() + registerCleanup(finCleanup) + genLoad(block, kind) + unregisterCleanup(finCleanup) + nopIfNeeded(startTryBody) + val endTryBody = currProgramPoint() + bc goTo postHandlers + + /* ------ (2) One EH for each case-clause (this does not include the EH-version of the finally-clause) + * An EH in (2) is reached upon abrupt termination of (1). + * An EH in (2) is protected by: + * (2.a) the EH-version of the finally-clause, if any. + * (2.b) whatever protects the whole try-catch-finally expression. + * ------ + */ + + for (ch <- caseHandlers) { + + // (2.a) emit case clause proper + val startHandler = currProgramPoint() + var endHandler: asm.Label = null + var excType: BType = null + registerCleanup(finCleanup) + ch match { + case NamelessEH(typeToDrop, caseBody) => + bc drop typeToDrop + genLoad(caseBody, kind) // adapts caseBody to `kind`, thus it can be stored, if `guardResult`, in `tmp`. + nopIfNeeded(startHandler) + endHandler = currProgramPoint() + excType = typeToDrop + + case BoundEH (patSymbol, caseBody) => + // test/files/run/contrib674.scala , a local-var already exists for patSymbol. + // rather than creating on first-access, we do it right away to emit debug-info for the created local var. + val Local(patTK, _, patIdx, _) = locals.getOrMakeLocal(patSymbol) + bc.store(patIdx, patTK) + genLoad(caseBody, kind) + nopIfNeeded(startHandler) + endHandler = currProgramPoint() + emitLocalVarScope(patSymbol, startHandler, endHandler) + excType = patTK + } + unregisterCleanup(finCleanup) + // (2.b) mark the try-body as protected by this case clause. + protect(startTryBody, endTryBody, startHandler, excType) + // (2.c) emit jump to the program point where the finally-clause-for-normal-exit starts, or in effect `after` if no finally-clause was given. + bc goTo postHandlers + + } + + /* ------ (3.A) The exception-handler-version of the finally-clause. + * Reached upon abrupt termination of (1) or one of the EHs in (2). + * Protected only by whatever protects the whole try-catch-finally expression. + * ------ + */ + + // a note on terminology: this is not "postHandlers", despite appearences. + // "postHandlers" as in the source-code view. And from that perspective, both (3.A) and (3.B) are invisible implementation artifacts. + if (hasFinally) { + nopIfNeeded(startTryBody) + val finalHandler = currProgramPoint() // version of the finally-clause reached via unhandled exception. + protect(startTryBody, finalHandler, finalHandler, null) + val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(ThrowableReference, "exc")) + bc.store(eIdx, eTK) + emitFinalizer(finalizer, null, isDuplicate = true) + bc.load(eIdx, eTK) + emit(asm.Opcodes.ATHROW) + } + + /* ------ (3.B) Cleanup-version of the finally-clause. + * Reached upon early RETURN from (1) or upon early RETURN from one of the EHs in (2) + * (and only from there, ie reached only upon early RETURN from + * program regions bracketed by registerCleanup/unregisterCleanup). + * Protected only by whatever protects the whole try-catch-finally expression. + * + * Given that control arrives to a cleanup section only upon early RETURN, + * the value to return (if any) is always available. Therefore, a further RETURN + * found in a cleanup section is always ignored (a warning is displayed, @see `genReturn()`). + * In order for `genReturn()` to know whether the return statement is enclosed in a cleanup section, + * the variable `insideCleanupBlock` is used. + * ------ + */ + + // this is not "postHandlers" either. + // `shouldEmitCleanup` can be set, and at the same time this try expression may lack a finally-clause. + // In other words, all combinations of (hasFinally, shouldEmitCleanup) are valid. + if (hasFinally && shouldEmitCleanup) { + val savedInsideCleanup = insideCleanupBlock + insideCleanupBlock = true + markProgramPoint(finCleanup) + // regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted. + emitFinalizer(finalizer, null, isDuplicate = true) + pendingCleanups() + insideCleanupBlock = savedInsideCleanup + } + + /* ------ (4) finally-clause-for-normal-nonEarlyReturn-exit + * Reached upon normal, non-early-return termination of (1) or of an EH in (2). + * Protected only by whatever protects the whole try-catch-finally expression. + * TODO explain what happens upon RETURN contained in (4) + * ------ + */ + + markProgramPoint(postHandlers) + if (hasFinally) { + emitFinalizer(finalizer, tmp, isDuplicate = false) // the only invocation of emitFinalizer with `isDuplicate == false` + } + + kind + } // end of genLoadTry() + + /* if no more pending cleanups, all that remains to do is return. Otherwise jump to the next (outer) pending cleanup. */ + private def pendingCleanups() { + cleanups match { + case Nil => + if (earlyReturnVar != null) { + locals.load(earlyReturnVar) + bc.emitRETURN(locals(earlyReturnVar).tk) + } else { + bc emitRETURN UNIT + } + shouldEmitCleanup = false + + case nextCleanup :: _ => + bc goTo nextCleanup + } + } + + def protect(start: asm.Label, end: asm.Label, handler: asm.Label, excType: BType) { + val excInternalName: String = + if (excType == null) null + else excType.getInternalName + assert(start != end, "protecting a range of zero instructions leads to illegal class format. Solution: add a NOP to that range.") + mnode.visitTryCatchBlock(start, end, handler, excInternalName) + } + + /* `tmp` (if non-null) is the symbol of the local-var used to preserve the result of the try-body, see `guardResult` */ + def emitFinalizer(finalizer: Tree, tmp: Symbol, isDuplicate: Boolean) { + var saved: immutable.Map[ /* LabelDef */ Symbol, asm.Label ] = null + if (isDuplicate) { + saved = jumpDest + for(ldef <- labelDefsAtOrUnder(finalizer)) { + jumpDest -= ldef.symbol + } + } + // when duplicating, the above guarantees new asm.Labels are used for LabelDefs contained in the finalizer (their vars are reused, that's ok) + if (tmp != null) { locals.store(tmp) } + genLoad(finalizer, UNIT) + if (tmp != null) { locals.load(tmp) } + if (isDuplicate) { + jumpDest = saved + } + } + + /* Does this tree have a try-catch block? */ + def mayCleanStack(tree: Tree): Boolean = tree exists { t => t.isInstanceOf[Try] } + + abstract class Cleanup(val value: AnyRef) { + def contains(x: AnyRef) = value == x + } + case class MonitorRelease(v: Symbol) extends Cleanup(v) { } + case class Finalizer(f: Tree) extends Cleanup (f) { } + + trait EHClause + case class NamelessEH(typeToDrop: BType, caseBody: Tree) extends EHClause + case class BoundEH (patSymbol: Symbol, caseBody: Tree) extends EHClause + + } + +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala new file mode 100644 index 0000000000..542a90fa85 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala @@ -0,0 +1,991 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + +package scala +package tools.nsc +package backend.jvm + +import scala.tools.asm +import scala.collection.{ immutable, mutable } + +/* + * Utilities to mediate between types as represented in Scala ASTs and ASM trees. + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded + * @version 1.0 + * + */ +abstract class BCodeTypes extends BCodeIdiomatic { + + import global._ + + // when compiling the Scala library, some assertions don't hold (e.g., scala.Boolean has null superClass although it's not an interface) + val isCompilingStdLib = !(settings.sourcepath.isDefault) + + val srBoxedUnit = brefType("scala/runtime/BoxedUnit") + + // special names + var StringReference : BType = null + var ThrowableReference : BType = null + var jlCloneableReference : BType = null // java/lang/Cloneable + var jlNPEReference : BType = null // java/lang/NullPointerException + var jioSerializableReference : BType = null // java/io/Serializable + var scalaSerializableReference : BType = null // scala/Serializable + var classCastExceptionReference : BType = null // java/lang/ClassCastException + + var lateClosureInterfaces: Array[Tracked] = null // the only interface a Late-Closure-Class implements is scala.Serializable + + /* A map from scala primitive type-symbols to BTypes */ + var primitiveTypeMap: Map[Symbol, BType] = null + /* A map from scala type-symbols for Nothing and Null to (runtime version) BTypes */ + var phantomTypeMap: Map[Symbol, BType] = null + /* Maps the method symbol for a box method to the boxed type of the result. + * For example, the method symbol for `Byte.box()`) is mapped to the BType `Ljava/lang/Integer;`. */ + var boxResultType: Map[Symbol, BType] = null + /* Maps the method symbol for an unbox method to the primitive type of the result. + * For example, the method symbol for `Byte.unbox()`) is mapped to the BType BYTE. */ + var unboxResultType: Map[Symbol, BType] = null + + var hashMethodSym: Symbol = null // scala.runtime.ScalaRunTime.hash + + var AndroidParcelableInterface: Symbol = null + var AndroidCreatorClass : Symbol = null // this is an inner class, use asmType() to get hold of its BType while tracking in innerClassBufferASM + var androidCreatorType : BType = null + + var BeanInfoAttr: Symbol = null + + /* The Object => String overload. */ + var String_valueOf: Symbol = null + + var ArrayInterfaces: Set[Tracked] = null + + // scala.FunctionX and scala.runtim.AbstractFunctionX + val FunctionReference = new Array[Tracked](definitions.MaxFunctionArity + 1) + val AbstractFunctionReference = new Array[Tracked](definitions.MaxFunctionArity + 1) + val abstractFunctionArityMap = mutable.Map.empty[BType, Int] + + var PartialFunctionReference: BType = null // scala.PartialFunction + var AbstractPartialFunctionReference: BType = null // scala.runtime.AbstractPartialFunction + + var BoxesRunTime: BType = null + + /* + * must-single-thread + */ + def initBCodeTypes() { + + import definitions._ + + primitiveTypeMap = + Map( + UnitClass -> UNIT, + BooleanClass -> BOOL, + CharClass -> CHAR, + ByteClass -> BYTE, + ShortClass -> SHORT, + IntClass -> INT, + LongClass -> LONG, + FloatClass -> FLOAT, + DoubleClass -> DOUBLE + ) + + phantomTypeMap = + Map( + NothingClass -> RT_NOTHING, + NullClass -> RT_NULL, + NothingClass -> RT_NOTHING, // we map on purpose to RT_NOTHING, getting rid of the distinction compile-time vs. runtime for NullClass. + NullClass -> RT_NULL // ditto. + ) + + boxResultType = + for(Pair(csym, msym) <- definitions.boxMethod) + yield (msym -> classLiteral(primitiveTypeMap(csym))) + + unboxResultType = + for(Pair(csym, msym) <- definitions.unboxMethod) + yield (msym -> primitiveTypeMap(csym)) + + // boxed classes are looked up in the `exemplars` map by jvmWiseLUB(). + // Other than that, they aren't needed there (e.g., `isSubtypeOf()` special-cases boxed classes, similarly for others). + val boxedClasses = List(BoxedBooleanClass, BoxedCharacterClass, BoxedByteClass, BoxedShortClass, BoxedIntClass, BoxedLongClass, BoxedFloatClass, BoxedDoubleClass) + for(csym <- boxedClasses) { + val key = brefType(csym.javaBinaryName.toTypeName) + val tr = buildExemplar(key, csym) + symExemplars.put(csym, tr) + exemplars.put(tr.c, tr) + } + + // reversePrimitiveMap = (primitiveTypeMap map { case (s, pt) => (s.tpe, pt) } map (_.swap)).toMap + + hashMethodSym = getMember(ScalaRunTimeModule, nme.hash_) + + // TODO avoiding going through through missingHook for every line in the REPL: https://github.com/scala/scala/commit/8d962ed4ddd310cc784121c426a2e3f56a112540 + AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable") + AndroidCreatorClass = rootMirror.getClassIfDefined("android.os.Parcelable$Creator") + + // the following couldn't be an eager vals in Phase constructors: + // that might cause cycles before Global has finished initialization. + BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo") + + String_valueOf = { + getMember(StringModule, nme.valueOf) filter (sym => + sym.info.paramTypes match { + case List(pt) => pt.typeSymbol == ObjectClass + case _ => false + } + ) + } + + ArrayInterfaces = Set(JavaCloneableClass, JavaSerializableClass) map exemplar + + StringReference = exemplar(StringClass).c + StringBuilderReference = exemplar(StringBuilderClass).c + ThrowableReference = exemplar(ThrowableClass).c + jlCloneableReference = exemplar(JavaCloneableClass).c + jlNPEReference = exemplar(NullPointerExceptionClass).c + jioSerializableReference = exemplar(JavaSerializableClass).c + scalaSerializableReference = exemplar(SerializableClass).c + classCastExceptionReference = exemplar(ClassCastExceptionClass).c + + lateClosureInterfaces = Array(exemplar(SerializableClass)) + + /* + * The bytecode emitter special-cases String concatenation, in that three methods of `JCodeMethodN` + * ( `genStartConcat()` , `genStringConcat()` , and `genEndConcat()` ) + * don't obtain the method descriptor of the callee via `asmMethodType()` (as normally done) + * but directly emit callsites on StringBuilder using literal constant for method descriptors. + * In order to make sure those method descriptors are available as BTypes, they are initialized here. + */ + BType.getMethodType("()V") // necessary for JCodeMethodN.genStartConcat + BType.getMethodType("()Ljava/lang/String;") // necessary for JCodeMethodN.genEndConcat + + PartialFunctionReference = exemplar(PartialFunctionClass).c + for(idx <- 0 to definitions.MaxFunctionArity) { + FunctionReference(idx) = exemplar(FunctionClass(idx)) + AbstractFunctionReference(idx) = exemplar(AbstractFunctionClass(idx)) + abstractFunctionArityMap += (AbstractFunctionReference(idx).c -> idx) + AbstractPartialFunctionReference = exemplar(AbstractPartialFunctionClass).c + } + + // later a few analyses (e.g. refreshInnerClasses) will look up BTypes based on descriptors in instructions + // we make sure those BTypes can be found via lookup as opposed to creating them on the fly. + BoxesRunTime = brefType("scala/runtime/BoxesRunTime") + asmBoxTo.values foreach { mnat: MethodNameAndType => BType.getMethodType(mnat.mdesc) } + asmUnboxTo.values foreach { mnat: MethodNameAndType => BType.getMethodType(mnat.mdesc) } + + } + + /* + * must-single-thread + */ + def clearBCodeTypes() { + symExemplars.clear() + exemplars.clear() + } + + val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC + val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL + + val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString + + // ------------------------------------------------ + // accessory maps tracking the isInterface, innerClasses, superClass, and supportedInterfaces relations, + // allowing answering `conforms()` without resorting to typer. + // ------------------------------------------------ + + val exemplars = new java.util.concurrent.ConcurrentHashMap[BType, Tracked] + val symExemplars = new java.util.concurrent.ConcurrentHashMap[Symbol, Tracked] + + /* + * Typically, a question about a BType can be answered only by using the BType as lookup key in one or more maps. + * A `Tracked` object saves time by holding together information required to answer those questions: + * + * - `sc` denotes the bytecode-level superclass if any, null otherwise + * + * - `ifaces` denotes the interfaces explicitly declared. + * Not included are those transitively supported, but the utility method `allLeafIfaces()` can be used for that. + * + * - `innersChain` denotes the containing classes for a non-package-level class `c`, null otherwise. + * Note: the optimizer may inline anonymous closures, thus eliding those inner classes + * (no physical class file is emitted for elided classes). + * Before committing `innersChain` to bytecode, cross-check with the list of elided classes (SI-6546). + * + * All methods of this class can-multi-thread + */ + case class Tracked(c: BType, flags: Int, sc: Tracked, ifaces: Array[Tracked], innersChain: Array[InnerClassEntry]) { + + // not a case-field because we initialize it only for JVM classes we emit. + private var _directMemberClasses: List[BType] = null + + def directMemberClasses: List[BType] = { + assert(_directMemberClasses != null, s"getter directMemberClasses() invoked too early for $c") + _directMemberClasses + } + + def directMemberClasses_=(bs: List[BType]) { + if (_directMemberClasses != null) { + // TODO we enter here when both mirror class and plain class are emitted for the same ModuleClassSymbol. + assert(_directMemberClasses == bs.sortBy(_.off)) + } + _directMemberClasses = bs.sortBy(_.off) + } + + /* `isCompilingStdLib` saves the day when compiling: + * (1) scala.Nothing (the test `c.isNonSpecial` fails for it) + * (2) scala.Boolean (it has null superClass and is not an interface) + */ + assert(c.isNonSpecial || isCompilingStdLib /*(1)*/, s"non well-formed plain-type: $this") + assert( + if (sc == null) { (c == ObjectReference) || isInterface || isCompilingStdLib /*(2)*/ } + else { (c != ObjectReference) && !sc.isInterface } + , "non well-formed plain-type: " + this + ) + assert(ifaces.forall(i => i.c.isNonSpecial && i.isInterface), s"non well-formed plain-type: $this") + + import asm.Opcodes._ + def hasFlags(mask: Int) = (flags & mask) != 0 + def isPrivate = hasFlags(ACC_PRIVATE) + def isPublic = hasFlags(ACC_PUBLIC) + def isAbstract = hasFlags(ACC_ABSTRACT) + def isInterface = hasFlags(ACC_INTERFACE) + def isFinal = hasFlags(ACC_FINAL) + def isSynthetic = hasFlags(ACC_SYNTHETIC) + def isSuper = hasFlags(ACC_SUPER) + def isDeprecated = hasFlags(ACC_DEPRECATED) + def isInnerClass = { innersChain != null } + def isTraditionalClosureClass = { + isInnerClass && isFinal && (c.getSimpleName.contains(tpnme.ANON_FUN_NAME.toString)) && isFunctionType(c) + } + def isLambda = { + // ie isLCC || isTraditionalClosureClass + isFinal && (c.getSimpleName.contains(tpnme.ANON_FUN_NAME.toString)) && isFunctionType(c) + } + def isSerializable = { isSubtypeOf(jioSerializableReference) } + + /* can-multi-thread */ + def superClasses: List[Tracked] = { + if (sc == null) Nil else sc :: sc.superClasses + } + + /* can-multi-thread */ + def isSubtypeOf(other: BType): Boolean = { + assert(other.isNonSpecial, "so called special cases have to be handled in BCodeTypes.conforms()") + + if (c == other) return true; + + val otherIsIface = exemplars.get(other).isInterface + + if (this.isInterface) { + if (other == ObjectReference) return true; + if (!otherIsIface) return false; + } + else { + if (sc != null && sc.isSubtypeOf(other)) return true; + if (!otherIsIface) return false; + } + + var idx = 0 + while (idx < ifaces.length) { + if (ifaces(idx).isSubtypeOf(other)) return true; + idx += 1 + } + + false + } + + /* + * The `ifaces` field lists only those interfaces declared by `c` + * From the set of all supported interfaces, this method discards those which are supertypes of others in the set. + */ + def allLeafIfaces: Set[Tracked] = { + if (sc == null) { ifaces.toSet } + else { minimizeInterfaces(ifaces.toSet ++ sc.allLeafIfaces) } + } + + /* + * This type may not support in its entirety the interface given by the argument, however it may support some of its super-interfaces. + * We visualize each such supported subset of the argument's functionality as a "branch". This method returns all such branches. + * + * In other words, let Ri be a branch supported by `ib`, + * this method returns all Ri such that this <:< Ri, where each Ri is maximally deep. + */ + def supportedBranches(ib: Tracked): Set[Tracked] = { + assert(ib.isInterface, s"Non-interface argument: $ib") + + val result: Set[Tracked] = + if (this.isSubtypeOf(ib.c)) { Set(ib) } + else { ib.ifaces.toSet[Tracked].flatMap( bi => supportedBranches(bi) ) } + + checkAllInterfaces(result) + + result + } + + override def toString = { c.toString } + + } + + /* must-single-thread */ + final def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) } + + /* must-single-thread */ + final def hasInternalName(sym: Symbol) = { sym.isClass || (sym.isModule && !sym.isMethod) } + + /* must-single-thread */ + def getSuperInterfaces(csym: Symbol): List[Symbol] = { + + // Additional interface parents based on annotations and other cues + def newParentForAttr(ann: AnnotationInfo): Symbol = ann.symbol match { + case definitions.RemoteAttr => definitions.RemoteInterfaceClass + case _ => NoSymbol + } + + /* Drop redundant interfaces (which are implemented by some other parent) from the immediate parents. + * In other words, no two interfaces in the result are related by subtyping. + * This method works on Symbols, a similar one (not duplicate) works on Tracked instances. + */ + def minimizeInterfaces(lstIfaces: List[Symbol]): List[Symbol] = { + var rest = lstIfaces + var leaves = List.empty[Symbol] + while (!rest.isEmpty) { + val candidate = rest.head + val nonLeaf = leaves exists { lsym => lsym isSubClass candidate } + if (!nonLeaf) { + leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym }) + } + rest = rest.tail + } + + leaves + } + + val superInterfaces0: List[Symbol] = csym.mixinClasses + val superInterfaces = existingSymbols(superInterfaces0 ++ csym.annotations.map(newParentForAttr)).distinct + + assert(!superInterfaces.contains(NoSymbol), s"found NoSymbol among: ${superInterfaces.mkString}") + assert(superInterfaces.forall(s => s.isInterface || s.isTrait), s"found non-interface among: ${superInterfaces.mkString}") + + minimizeInterfaces(superInterfaces) + } + + final def exemplarIfExisting(iname: String): Tracked = { + val bt = lookupRefBTypeIfExisting(iname) + if (bt != null) exemplars.get(bt) + else null + } + + final def lookupExemplar(iname: String) = { + exemplars.get(lookupRefBType(iname)) + } + + /* + * Records the superClass and supportedInterfaces relations, + * so that afterwards queries can be answered without resorting to typer. + * This method does not add to `innerClassBufferASM`, use `internalName()` or `asmType()` or `toTypeKind()` for that. + * On the other hand, this method does record the inner-class status of the argument, via `buildExemplar()`. + * + * must-single-thread + */ + final def exemplar(csym0: Symbol): Tracked = { + assert(csym0 != NoSymbol, "NoSymbol can't be tracked") + + val csym = { + if (csym0.isJavaDefined && csym0.isModuleClass) csym0.linkedClassOfClass + else if (csym0.isModule) csym0.moduleClass + else csym0 // we track only module-classes and plain-classes + } + + assert(!primitiveTypeMap.contains(csym) || isCompilingStdLib, s"primitive types not tracked here: ${csym.fullName}") + assert(!phantomTypeMap.contains(csym), s"phantom types not tracked here: ${csym.fullName}") + + val opt = symExemplars.get(csym) + if (opt != null) { + return opt + } + + val key = brefType(csym.javaBinaryName.toTypeName) + assert(key.isNonSpecial || isCompilingStdLib, s"Not a class to track: ${csym.fullName}") + + // TODO accomodate the fix for SI-5031 of https://github.com/scala/scala/commit/0527b2549bcada2fda2201daa630369b377d0877 + // TODO Weaken this assertion? buildExemplar() needs to be updated, too. In the meantime, pos/t5031_3 has been moved to test/disabled/pos. + val whatWasInExemplars = exemplars.get(key) + assert(whatWasInExemplars == null, "Maps `symExemplars` and `exemplars` got out of synch.") + val tr = buildExemplar(key, csym) + symExemplars.put(csym, tr) + if (csym != csym0) { symExemplars.put(csym0, tr) } + exemplars.put(tr.c, tr) // tr.c is the hash-consed, internalized, canonical representative for csym's key. + tr + } + + val EMPTY_TRACKED_SET = Set.empty[Tracked] + + val EMPTY_TRACKED_ARRAY = Array.empty[Tracked] + val EMPTY_InnerClassEntry_ARRAY = Array.empty[InnerClassEntry] + + /* + * must-single-thread + */ + private def buildExemplar(key: BType, csym: Symbol): Tracked = { + val sc = + if (csym.isImplClass) definitions.ObjectClass + else csym.superClass + assert( + if (csym == definitions.ObjectClass) + sc == NoSymbol + else if (csym.isInterface) + sc == definitions.ObjectClass + else + ((sc != NoSymbol) && !sc.isInterface) || isCompilingStdLib, + "superClass out of order" + ) + val ifaces = getSuperInterfaces(csym) map exemplar; + val ifacesArr = + if (ifaces.isEmpty) EMPTY_TRACKED_ARRAY + else { + val arr = new Array[Tracked](ifaces.size) + ifaces.copyToArray(arr) + arr + } + + val flags = mkFlags( + javaFlags(csym), + if (isDeprecated(csym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + val tsc = if (sc == NoSymbol) null else exemplar(sc) + + val innersChain = saveInnerClassesFor(csym, key) + + Tracked(key, flags, tsc, ifacesArr, innersChain) + } + + /* can-multi-thread */ + final def mkArray(xs: List[Tracked]): Array[Tracked] = { + if (xs.isEmpty) { return EMPTY_TRACKED_ARRAY } + val a = new Array[Tracked](xs.size); xs.copyToArray(a); a + } + + // ---------------- utilities around interfaces represented by Tracked instances. ---------------- + + /* Drop redundant interfaces (those which are implemented by some other). + * In other words, no two interfaces in the result are related by subtyping. + * This method works on Tracked elements, a similar one (not duplicate) works on Symbols. + */ + def minimizeInterfaces(lstIfaces: Set[Tracked]): Set[Tracked] = { + checkAllInterfaces(lstIfaces) + var rest = lstIfaces.toList + var leaves = List.empty[Tracked] + while (!rest.isEmpty) { + val candidate = rest.head + val nonLeaf = leaves exists { leaf => leaf.isSubtypeOf(candidate.c) } + if (!nonLeaf) { + leaves = candidate :: (leaves filterNot { leaf => candidate.isSubtypeOf(leaf.c) }) + } + rest = rest.tail + } + + leaves.toSet + } + + def allInterfaces(is: Iterable[Tracked]): Boolean = { is forall { i => i.isInterface } } + def nonInterfaces(is: Iterable[Tracked]): Iterable[Tracked] = { is filterNot { i => i.isInterface } } + + def checkAllInterfaces(ifaces: Iterable[Tracked]) { + assert(allInterfaces(ifaces), s"Non-interfaces: ${nonInterfaces(ifaces).mkString}") + } + + /* + * Returns the intersection of two sets of interfaces. + */ + def intersection(ifacesA: Set[Tracked], ifacesB: Set[Tracked]): Set[Tracked] = { + var acc: Set[Tracked] = Set() + for(ia <- ifacesA; ib <- ifacesB) { + val ab = ia.supportedBranches(ib) + val ba = ib.supportedBranches(ia) + acc = minimizeInterfaces(acc ++ ab ++ ba) + } + checkAllInterfaces(acc) + + acc + } + + /* + * Subtype check `a <:< b` on BTypes that takes into account the JVM built-in numeric promotions (e.g. BYTE to INT). + * Its operation can be visualized more easily in terms of the Java bytecode type hierarchy. + * This method used to be called, in the ICode world, TypeKind.<:<() + * + * can-multi-thread + */ + final def conforms(a: BType, b: BType): Boolean = { + if (a.isArray) { // may be null + /* Array subtyping is covariant here, as in Java bytecode. Also necessary for Java interop. */ + if ((b == jlCloneableReference) || + (b == jioSerializableReference) || + (b == AnyRefReference)) { true } + else if (b.isArray) { conforms(a.getComponentType, b.getComponentType) } + else { false } + } + else if (a.isBoxed) { // may be null + if (b.isBoxed) { a == b } + else if (b == AnyRefReference) { true } + else if (!(b.hasObjectSort)) { false } + else { exemplars.get(a).isSubtypeOf(b) } // e.g., java/lang/Double conforms to java/lang/Number + } + else if (a.isNullType) { // known to be null + if (b.isNothingType) { false } + else if (b.isValueType) { false } + else { true } + } + else if (a.isNothingType) { // known to be Nothing + true + } + else if (a.isUnitType) { + b.isUnitType + } + else if (a.hasObjectSort) { // may be null + if (a.isNothingType) { true } + else if (b.hasObjectSort) { exemplars.get(a).isSubtypeOf(b) } + else if (b.isArray) { a.isNullType } // documentation only, because `if(a.isNullType)` (above) covers this case already. + else { false } + } + else { + + def msg = s"(a: $a, b: $b)" + + assert(a.isNonUnitValueType, s"a isn't a non-Unit value type. $msg") + assert(b.isValueType, s"b isn't a value type. $msg") + + (a eq b) || (a match { + case BOOL | BYTE | SHORT | CHAR => b == INT || b == LONG // TODO Actually, BOOL does NOT conform to LONG. Even with adapt(). + case _ => a == b + }) + } + } + + /* The maxValueType of (Char, Byte) and of (Char, Short) is Int, to encompass the negative values of Byte and Short. See ticket #2087. + * + * can-multi-thread + */ + def maxValueType(a: BType, other: BType): BType = { + assert(a.isValueType, "maxValueType() is defined only for 1st arg valuetypes (2nd arg doesn't matter).") + + def uncomparable: Nothing = { + abort(s"Uncomparable BTypes: $a with $other") + } + + if (a.isNothingType) return other; + if (other.isNothingType) return a; + if (a == other) return a; + + a match { + + case UNIT => uncomparable + case BOOL => uncomparable + + case BYTE => + if (other == CHAR) INT + else if (other.isNumericType) other + else uncomparable + + case SHORT => + other match { + case BYTE => SHORT + case CHAR => INT + case INT | LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case CHAR => + other match { + case BYTE | SHORT => INT + case INT | LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case INT => + other match { + case BYTE | SHORT | CHAR => INT + case LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case LONG => + if (other.isIntegralType) LONG + else if (other.isRealType) DOUBLE + else uncomparable + + case FLOAT => + if (other == DOUBLE) DOUBLE + else if (other.isNumericType) FLOAT + else uncomparable + + case DOUBLE => + if (other.isNumericType) DOUBLE + else uncomparable + + case _ => uncomparable + } + } + + /* Takes promotions of numeric primitives into account. + * + * can-multi-thread + */ + final def maxType(a: BType, other: BType): BType = { + if (a.isValueType) { maxValueType(a, other) } + else { + if (a.isNothingType) return other; + if (other.isNothingType) return a; + if (a == other) return a; + // Approximate `lub`. The common type of two references is always AnyRef. + // For 'real' least upper bound wrt to subclassing use method 'lub'. + assert(a.isArray || a.isBoxed || a.hasObjectSort, s"This is not a valuetype and it's not something else, what is it? $a") + // TODO For some reason, ICode thinks `REFERENCE(...).maxType(BOXED(whatever))` is `uncomparable`. Here, that has maxType AnyRefReference. + // BTW, when swapping arguments, ICode says BOXED(whatever).maxType(REFERENCE(...)) == AnyRefReference, so I guess the above was an oversight in REFERENCE.maxType() + if (other.isRefOrArrayType) { AnyRefReference } + else { abort(s"Uncomparable BTypes: $a with $other") } + } + } + + /* + * Whether the argument (the signature of a method) takes as argument + * one ore more Function or PartialFunction (in particular an anonymous closure). + * + * can-multi-thread + */ + final def isHigherOrderMethod(mtype: BType): Boolean = { + assert(mtype.sort == BType.METHOD) + + val ats = mtype.getArgumentTypes + var idx = 0 + while (idx < ats.length) { + val t = ats(idx) + if (isFunctionType(t) || isPartialFunctionType(t)) { + return true + } + idx += 1 + } + false + } + + /* + * Whether the argument is a subtype of + * scala.PartialFunction[-A, +B] extends (A => B) + * N.B.: this method returns true for a scala.runtime.AbstractPartialFunction + * + * can-multi-thread + */ + def isPartialFunctionType(t: BType): Boolean = { + (t.hasObjectSort) && exemplars.get(t).isSubtypeOf(PartialFunctionReference) + } + + /* + * Whether the argument is a subtype of + * scala.runtime.AbstractPartialFunction[-T1, +R] extends Function1[T1, R] with PartialFunction[T1, R] + * + * can-multi-thread + */ + def isAbstractPartialFunctionType(t: BType): Boolean = { + (t.hasObjectSort) && exemplars.get(t).isSubtypeOf(AbstractPartialFunctionReference) + } + + /* + * Whether the argument is a subtype of scala.FunctionX where 0 <= X <= definitions.MaxFunctionArity + * + * can-multi-thread + */ + def isFunctionType(t: BType): Boolean = { + if (!t.hasObjectSort) return false + var idx = 0 + val et: Tracked = exemplars.get(t) + while (idx <= definitions.MaxFunctionArity) { + if (et.isSubtypeOf(FunctionReference(idx).c)) { + return true + } + idx += 1 + } + false + } + + def isClosureClass(bt: BType): Boolean = { + val tr = exemplars.get(bt); (tr != null && tr.isLambda) + } + + /* + * Whether the argument is a subtype of scala.runtime.AbstractFunctionX where 0 <= X <= definitions.MaxFunctionArity + * + * can-multi-thread + */ + def isAbstractFunctionType(t: BType): Boolean = { + if (!t.hasObjectSort) return false + var idx = 0 + val et: Tracked = exemplars.get(t) + while (idx <= definitions.MaxFunctionArity) { + if (et.isSubtypeOf(AbstractFunctionReference(idx).c)) { + return true + } + idx += 1 + } + false + } + + /* + * For an argument of exactly one of the types + * scala.runtime.AbstractFunctionX where 0 <= X <= definitions.MaxFunctionArity + * returns the function arity, -1 otherwise. + * + * can-multi-thread + */ + def abstractFunctionArity(t: BType): Int = { + abstractFunctionArityMap.getOrElse(t, -1) + } + + /* + * must-single-thread + */ + def isTopLevelModule(sym: Symbol): Boolean = { + exitingPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass } + } + + /* + * must-single-thread + */ + def isStaticModule(sym: Symbol): Boolean = { + sym.isModuleClass && !sym.isImplClass && !sym.isLifted + } + + // --------------------------------------------------------------------- + // ---------------- InnerClasses attribute (JVMS 4.7.6) ---------------- + // --------------------------------------------------------------------- + + val INNER_CLASSES_FLAGS = + (asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED | + asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_INTERFACE | asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_FINAL) + + /* + * @param name the internal name of an inner class. + * @param outerName the internal name of the class to which the inner class belongs. + * May be `null` for non-member inner classes (ie for a Java local class or a Java anonymous class). + * @param innerName the (simple) name of the inner class inside its enclosing class. It's `null` for anonymous inner classes. + * @param access the access flags of the inner class as originally declared in the enclosing class. + */ + case class InnerClassEntry(name: String, outerName: String, innerName: String, access: Int) { + assert(name != null, "Null isn't good as class name in an InnerClassEntry.") + } + + /* For given symbol return a symbol corresponding to a class that should be declared as inner class. + * + * For example: + * class A { + * class B + * object C + * } + * + * then method will return: + * NoSymbol for A, + * the same symbol for A.B (corresponding to A$B class), and + * A$C$ symbol for A.C. + * + * must-single-thread + */ + def innerClassSymbolFor(s: Symbol): Symbol = + if (s.isClass) s else if (s.isModule) s.moduleClass else NoSymbol + + /* + * Computes the chain of inner-class (over the is-member-of relation) for the given argument. + * The resulting chain will be cached in `exemplars`. + * + * The chain thus cached is valid during this compiler run, see in contrast + * `innerClassBufferASM` for a cache that is valid only for the class being emitted. + * + * The argument can be any symbol, but given that this method is invoked only from `buildExemplar()`, + * in practice it has been vetted to be a class-symbol. + * + * Returns: + * + * - a non-empty array of entries for an inner-class argument. + * The array's first element is the outermost top-level class, + * the array's last element corresponds to csym. + * + * - null otherwise. + * + * This method does not add to `innerClassBufferASM`, use instead `exemplar()` for that. + * + * must-single-thread + */ + final def saveInnerClassesFor(csym: Symbol, csymTK: BType): Array[InnerClassEntry] = { + + val ics = innerClassSymbolFor(csym) + if (ics == NoSymbol) { + return null + } + assert(ics == csym, s"Disagreement between innerClassSymbolFor() and exemplar()'s tracked symbol for the same input: ${csym.fullName}") + + var chain: List[Symbol] = Nil + var x = ics + while (x ne NoSymbol) { + assert(x.isClass, s"not a class symbol: ${x.fullName}") + val isInner = !x.rawowner.isPackageClass + if (isInner) { + chain ::= x + x = innerClassSymbolFor(x.rawowner) + } else { + x = NoSymbol + } + } + + // now that we have all of `ics` , `csym` , and soon the inner-classes-chain, it's too tempting not to cache. + if (chain.isEmpty) { null } + else { + val arr = new Array[InnerClassEntry](chain.size) + (chain map toInnerClassEntry).copyToArray(arr) + + arr + } + } + + /* + * must-single-thread + */ + private def toInnerClassEntry(innerSym: Symbol): InnerClassEntry = { + + /* The outer name for this inner class. Note that it returns null + * when the inner class should not get an index in the constant pool. + * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS. + */ + def outerName(innerSym: Symbol): Name = { + if (innerSym.originalEnclosingMethod != NoSymbol) + null + else { + val outerName = innerSym.rawowner.javaBinaryName + if (isTopLevelModule(innerSym.rawowner)) nme.stripModuleSuffix(outerName) + else outerName + } + } + + def innerName(innerSym: Symbol): String = { + if (innerSym.isAnonymousClass || innerSym.isAnonymousFunction) + null + else + innerSym.rawname + innerSym.moduleSuffix + } + + val flagsWithFinal: Int = mkFlags( + if (innerSym.rawowner.hasModuleFlag) asm.Opcodes.ACC_STATIC else 0, + javaFlags(innerSym), + if (isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag + ) & (INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED) + val flags = if (innerSym.isModuleClass) flagsWithFinal & ~asm.Opcodes.ACC_FINAL else flagsWithFinal // For SI-5676, object overriding. + + val jname = innerSym.javaBinaryName.toString // never null + val oname = { // null when method-enclosed + val on = outerName(innerSym) + if (on == null) null else on.toString + } + val iname = { // null for anonymous inner class + val in = innerName(innerSym) + if (in == null) null else in.toString + } + + InnerClassEntry(jname, oname, iname, flags) + } + + // -------------------------------------------- + // ---------------- Java flags ---------------- + // -------------------------------------------- + + /* + * can-multi-thread + */ + final def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0) + + /* + * must-single-thread + */ + final def isRemote(s: Symbol) = (s hasAnnotation definitions.RemoteAttr) + + /* + * Return the Java modifiers for the given symbol. + * Java modifiers for classes: + * - public, abstract, final, strictfp (not used) + * for interfaces: + * - the same as for classes, without 'final' + * for fields: + * - public, private (*) + * - static, final + * for methods: + * - the same as for fields, plus: + * - abstract, synchronized (not used), strictfp (not used), native (not used) + * + * (*) protected cannot be used, since inner classes 'see' protected members, + * and they would fail verification after lifted. + * + * must-single-thread + */ + def javaFlags(sym: Symbol): Int = { + // constructors of module classes should be private + // PP: why are they only being marked private at this stage and not earlier? + val privateFlag = + sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModule(sym.owner)) + + // Final: the only fields which can receive ACC_FINAL are eager vals. + // Neither vars nor lazy vals can, because: + // + // Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3 + // "Another problem is that the specification allows aggressive + // optimization of final fields. Within a thread, it is permissible to + // reorder reads of a final field with those modifications of a final + // field that do not take place in the constructor." + // + // A var or lazy val which is marked final still has meaning to the + // scala compiler. The word final is heavily overloaded unfortunately; + // for us it means "not overridable". At present you can't override + // vars regardless; this may change. + // + // The logic does not check .isFinal (which checks flags for the FINAL flag, + // and includes symbols marked lateFINAL) instead inspecting rawflags so + // we can exclude lateFINAL. Such symbols are eligible for inlining, but to + // avoid breaking proxy software which depends on subclassing, we do not + // emit ACC_FINAL. + // Nested objects won't receive ACC_FINAL in order to allow for their overriding. + + val finalFlag = ( + (((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModule(sym)) + && !sym.enclClass.isInterface + && !sym.isClassConstructor + && !sym.isMutable // lazy vals and vars both + ) + + // Primitives are "abstract final" to prohibit instantiation + // without having to provide any implementations, but that is an + // illegal combination of modifiers at the bytecode level so + // suppress final if abstract if present. + import asm.Opcodes._ + mkFlags( + if (privateFlag) ACC_PRIVATE else ACC_PUBLIC, + if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0, + if (sym.isInterface) ACC_INTERFACE else 0, + if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0, + if (sym.isStaticMember) ACC_STATIC else 0, + if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, + if (sym.isArtifact) ACC_SYNTHETIC else 0, + if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, + if (sym.isVarargsMethod) ACC_VARARGS else 0, + if (sym.hasFlag(symtab.Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0 + ) + } + + /* + * must-single-thread + */ + def javaFieldFlags(sym: Symbol) = { + javaFlags(sym) | mkFlags( + if (sym hasAnnotation definitions.TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0, + if (sym hasAnnotation definitions.VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0, + if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL + ) + } + +} // end of class BCodeTypes diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala index 0df828393d..8e6c09213f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala @@ -22,13 +22,13 @@ trait BytecodeWriters { val global: Global import global._ - private def outputDirectory(sym: Symbol): AbstractFile = + def outputDirectory(sym: Symbol): AbstractFile = settings.outputDirs outputDirFor enteringFlatten(sym.sourceFile) /** * @param clsName cls.getName */ - private def getFile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = { + def getFile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = { def ensureDirectory(dir: AbstractFile): AbstractFile = if (dir.isDirectory) dir else throw new FileConflictException(s"${base.path}/$clsName$suffix: ${dir.path} is not a directory", dir) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala new file mode 100644 index 0000000000..e55a3baed0 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -0,0 +1,203 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + + +package scala +package tools.nsc +package backend +package jvm + +import scala.collection.{ mutable, immutable } +import scala.annotation.switch + +import scala.tools.asm + +/* + * Prepare in-memory representations of classfiles using the ASM Tree API, and serialize them to disk. + * + * `BCodePhase.apply(CompilationUnit)` is invoked by some external force and that sets in motion: + * - visiting each ClassDef contained in that CompilationUnit + * - lowering the ClassDef into: + * (a) an optional mirror class, + * (b) a plain class, and + * (c) an optional bean class. + * - each of the ClassNodes above is lowered into a byte-array (ie into a classfile) and serialized. + * + * Plain, mirror, and bean classes are built respectively by PlainClassBuilder, JMirrorBuilder, and JBeanInfoBuilder. + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ + * @version 1.0 + * + */ +abstract class GenBCode extends BCodeSyncAndTry { + import global._ + + val phaseName = "jvm" + + override def newPhase(prev: Phase) = new BCodePhase(prev) + + final class PlainClassBuilder(cunit: CompilationUnit) extends SyncAndTryBuilder(cunit) + + class BCodePhase(prev: Phase) extends StdPhase(prev) { + + override def name = phaseName + override def description = "Generate bytecode from ASTs using the ASM library" + override def erasedTypes = true + + private var bytecodeWriter : BytecodeWriter = null + private var mirrorCodeGen : JMirrorBuilder = null + private var beanInfoCodeGen : JBeanInfoBuilder = null + + private var needsOutFolder = false // whether getOutFolder(claszSymbol) should be invoked for each claszSymbol + + val caseInsensitively = mutable.Map.empty[String, Symbol] + + /* + * Checks for duplicate internal names case-insensitively, + * builds ASM ClassNodes for mirror, plain, and bean classes. + * + */ + def visit(arrivalPos: Int, cd: ClassDef, cunit: CompilationUnit) { + val claszSymbol = cd.symbol + + // GenASM checks this before classfiles are emitted, https://github.com/scala/scala/commit/e4d1d930693ac75d8eb64c2c3c69f2fc22bec739 + val lowercaseJavaClassName = claszSymbol.javaClassName.toLowerCase + caseInsensitively.get(lowercaseJavaClassName) match { + case None => + caseInsensitively.put(lowercaseJavaClassName, claszSymbol) + case Some(dupClassSym) => + cunit.warning( + claszSymbol.pos, + s"Class ${claszSymbol.javaClassName} differs only in case from ${dupClassSym.javaClassName}. " + + "Such classes will overwrite one another on case-insensitive filesystems." + ) + } + + // -------------- mirror class, if needed -------------- + val mirrorC = + if (isStaticModule(claszSymbol) && isTopLevelModule(claszSymbol)) { + if (claszSymbol.companionClass == NoSymbol) { + mirrorCodeGen.genMirrorClass(claszSymbol, cunit) + } else { + log(s"No mirror class for module with linked class: ${claszSymbol.fullName}"); + null + } + } else null + + // -------------- "plain" class -------------- + val pcb = new PlainClassBuilder(cunit) + pcb.genPlainClass(cd) + val outF = if (needsOutFolder) getOutFolder(claszSymbol, pcb.thisName, cunit) else null; + val plainC = pcb.cnode + + // -------------- bean info class, if needed -------------- + val beanC = + if (claszSymbol hasAnnotation BeanInfoAttr) { + beanInfoCodeGen.genBeanInfoClass( + claszSymbol, cunit, + fieldSymbols(claszSymbol), + methodSymbols(cd) + ) + } else null + + // ----------- serialize classfiles to disk + + def getByteArray(cn: asm.tree.ClassNode): Array[Byte] = { + val cw = new CClassWriter(extraProc) + cn.accept(cw) + cw.toByteArray + } + + if (mirrorC != null) { + sendToDisk(mirrorC.name, getByteArray(mirrorC), outF) + } + sendToDisk(plainC.name, getByteArray(plainC), outF) + if (beanC != null) { + sendToDisk(beanC.name, getByteArray(beanC), outF) + } + + } // end of method visit() + + var arrivalPos = 0 + + /* + * A run of the BCodePhase phase comprises: + * + * (a) set-up steps (most notably supporting maps in `BCodeTypes`, + * but also "the" writer where class files in byte-array form go) + * + * (b) building of ASM ClassNodes, their optimization and serialization. + * + * (c) tear down (closing the classfile-writer and clearing maps) + * + */ + override def run() { + + arrivalPos = 0 // just in case + scalaPrimitives.init + initBCodeTypes() + + // initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated. + bytecodeWriter = initBytecodeWriter(cleanup.getEntryPoints) + mirrorCodeGen = new JMirrorBuilder + beanInfoCodeGen = new JBeanInfoBuilder + + needsOutFolder = bytecodeWriter.isInstanceOf[ClassBytecodeWriter] + + super.run() + + // closing output files. + bytecodeWriter.close() + + caseInsensitively.clear() + + /* TODO Bytecode can be verified (now that all classfiles have been written to disk) + * + * (1) asm.util.CheckAdapter.verify() + * public static void verify(ClassReader cr, ClassLoader loader, boolean dump, PrintWriter pw) + * passing a custom ClassLoader to verify inter-dependent classes. + * Alternatively, + * - an offline-bytecode verifier could be used (e.g. Maxine brings one as separate tool). + * - -Xverify:all + * + * (2) if requested, check-java-signatures, over and beyond the syntactic checks in `getGenericSignature()` + * + */ + + // clearing maps + clearBCodeTypes() + } + + def sendToDisk(jclassName: String, jclassBytes: Array[Byte], outFolder: _root_.scala.tools.nsc.io.AbstractFile) { + try { + val outFile = + if (outFolder == null) null + else getFileForClassfile(outFolder, jclassName, ".class") + bytecodeWriter.writeClass(jclassName, jclassName, jclassBytes, outFile) + } + catch { + case e: FileConflictException => + error(s"error writing $jclassName: ${e.getMessage}") + } + } + + override def apply(cunit: CompilationUnit): Unit = { + + def gen(tree: Tree) { + tree match { + case EmptyTree => () + case PackageDef(_, stats) => stats foreach gen + case cd: ClassDef => + visit(arrivalPos, cd, cunit) + arrivalPos += 1 + } + } + + gen(cunit.body) + } + + } // end of class BCodePhase + +} // end of class GenBCode diff --git a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala index a6eedbd07e..56191cc981 100644 --- a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala +++ b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala @@ -679,9 +679,18 @@ abstract class Inliners extends SubComponent { } */ - def checkField(f: Symbol) = check(f, f.isPrivate && !canMakePublic(f)) - def checkSuper(n: Symbol) = check(n, n.isPrivate || !n.isClassConstructor) - def checkMethod(n: Symbol) = check(n, n.isPrivate) + + def isPrivateForInlining(sym: Symbol): Boolean = { + if (sym.isJavaDefined) { + def check(sym: Symbol) = !(sym.isPublic || sym.isProtected) + check(sym) || check(sym.owner) // SI-7582 Must check the enclosing class *and* the symbol for Java. + } + else sym.isPrivate // Scala never emits package-private bytecode + } + + def checkField(f: Symbol) = check(f, isPrivateForInlining(f) && !canMakePublic(f)) + def checkSuper(n: Symbol) = check(n, isPrivateForInlining(n) || !n.isClassConstructor) + def checkMethod(n: Symbol) = check(n, isPrivateForInlining(n)) def getAccess(i: Instruction) = i match { case CALL_METHOD(n, SuperCall(_)) => checkSuper(n) diff --git a/src/compiler/scala/tools/nsc/dependencies/Changes.scala b/src/compiler/scala/tools/nsc/dependencies/Changes.scala index c341d33a62..81d64421b3 100644 --- a/src/compiler/scala/tools/nsc/dependencies/Changes.scala +++ b/src/compiler/scala/tools/nsc/dependencies/Changes.scala @@ -16,7 +16,7 @@ abstract class Changes { import compiler._ import symtab.Flags._ - abstract class Change + sealed abstract class Change private lazy val annotationsChecked = List(definitions.SpecializedClass) // Any others that should be checked? @@ -38,7 +38,7 @@ abstract class Changes { /** An entity in source code, either a class or a member definition. * Name is fully-qualified. */ - abstract class Entity + sealed abstract class Entity case class Class(name: String) extends Entity case class Definition(name: String) extends Entity diff --git a/src/compiler/scala/tools/nsc/io/Lexer.scala b/src/compiler/scala/tools/nsc/io/Lexer.scala deleted file mode 100644 index 1c926aff6b..0000000000 --- a/src/compiler/scala/tools/nsc/io/Lexer.scala +++ /dev/null @@ -1,299 +0,0 @@ -package scala.tools.nsc.io - -import java.io.Reader - -/** Companion object of class `Lexer` which defines tokens and some utility concepts - * used for tokens and lexers - */ -object Lexer { - - /** An exception raised if an input does not correspond to what's expected - * @param rdr the lexer from which the bad input is read - * @param msg the error message - */ - class MalformedInput(val rdr: Lexer, val msg: String) extends Exception("Malformed JSON input at "+rdr.tokenPos+": "+msg) - - /** The class of tokens, i.e. descriptions of input words (or: lexemes). - * @param str the characters making up this token - */ - class Token(val str: String) { - override def toString = str - } - - /** A subclass of `Token` representing single-character delimiters - * @param char the delimiter character making up this token - */ - case class Delim(char: Char) extends Token(s"'$char'") - - /** A subclass of token representing integer literals */ - case class IntLit(override val str: String) extends Token(str) - - /** A subclass of token representing floating point literals */ - case class FloatLit(override val str: String) extends Token(str) - - /** A subclass of token representing string literals */ - case class StringLit(override val str: String) extends Token(str) { - override def toString = quoted(str) - } - - /** The `true` token */ - val TrueLit = new Token("true") - - /** The `false` token */ - val FalseLit = new Token("false") - - /** The `null` token */ - val NullLit = new Token("null") - - /** The '`(`' token */ - val LParen = new Delim('(') - - /** The '`)`' token */ - val RParen = new Delim(')') - - /** The '`{`' token */ - val LBrace = new Delim('{') - - /** The '`}`' token */ - val RBrace = new Delim('}') - - /** The '`[`' token */ - val LBracket = new Delim('[') - - /** The '`]`' token */ - val RBracket = new Delim(']') - - /** The '`,`' token */ - val Comma = new Delim(',') - - /** The '`:`' token */ - val Colon = new Delim(':') - - /** The token representing end of input */ - val EOF = new Token("<end of input>") - - private def toUDigit(ch: Int): Char = { - val d = ch & 0xF - (if (d < 10) d + '0' else d - 10 + 'A').toChar - } - - private def addToStr(buf: StringBuilder, ch: Char) { - ch match { - case '"' => buf ++= "\\\"" - case '\b' => buf ++= "\\b" - case '\f' => buf ++= "\\f" - case '\n' => buf ++= "\\n" - case '\r' => buf ++= "\\r" - case '\t' => buf ++= "\\t" - case '\\' => buf ++= "\\\\" - case _ => - if (' ' <= ch && ch < 128) buf += ch - else buf ++= "\\u" += toUDigit(ch >>> 12) += toUDigit(ch >>> 8) += toUDigit(ch >>> 4) += toUDigit(ch.toInt) - } - } - - /** Returns given string enclosed in `"`-quotes with all string characters escaped - * so that they correspond to the JSON standard. - * Characters that escaped are: `"`, `\b`, `\f`, `\n`, `\r`, `\t`, `\`. - * Furthermore, every other character which is not in the ASCII range 32-127 is - * escaped as a four hex-digit unicode character of the form `\ u x x x x`. - * @param str the string to be quoted - */ - def quoted(str: String): String = { - val buf = new StringBuilder += '\"' - str foreach (addToStr(buf, _)) - buf += '\"' - buf.toString - } - - private val BUF_SIZE = 2 << 16 -} - -import Lexer._ - -/** A simple lexer for tokens as they are used in JSON, plus parens `(`, `)` - * Tokens understood are: - * - * `(`, `)`, `[`, `]`, `{`, `}`, `:`, `,`, `true`, `false`, `null`, - * strings (syntax as in JSON), - * integer numbers (syntax as in JSON: -?(0|\d+) - * floating point numbers (syntax as in JSON: -?(0|\d+)(\.\d+)?((e|E)(+|-)?\d+)?) - * The end of input is represented as its own token, EOF. - * Lexers can keep one token lookahead - * - * @param rd the reader from which characters are read. - */ -class Lexer(rd: Reader) { - - /** The last-read character */ - var ch: Char = 0 - - /** The number of characters read so far */ - var pos: Long = 0 - - /** The last-read token */ - var token: Token = _ - - /** The number of characters read before the start of the last-read token */ - var tokenPos: Long = 0 - - private var atEOF: Boolean = false - private val buf = new Array[Char](BUF_SIZE) - private var nread: Int = 0 - private var bp = 0 - - /** Reads next character into `ch` */ - def nextChar() { - assert(!atEOF) - if (bp == nread) { - nread = rd.read(buf) - bp = 0 - if (nread <= 0) { ch = 0; atEOF = true; return } - } - ch = buf(bp) - bp += 1 - pos += 1 - } - - /** If last-read character equals given character, reads next character, - * otherwise raises an error - * @param c the given character to compare with last-read character - * @throws MalformedInput if character does not match - */ - def acceptChar(c: Char) = if (ch == c) nextChar() else error("'"+c+"' expected") - - private val sb = new StringBuilder - - private def putChar() { - sb += ch; nextChar() - } - - private def putAcceptString(str: String) { - str foreach acceptChar - sb ++= str - } - - /** Skips whitespace and reads next lexeme into `token` - * @throws MalformedInput if lexeme not recognized as a valid token - */ - def nextToken() { - sb.clear() - while (!atEOF && ch <= ' ') nextChar() - tokenPos = pos - 1 - if (atEOF) token = EOF - else ch match { - case '(' => putChar(); token = LParen - case ')' => putChar(); token = RParen - case '{' => putChar(); token = LBrace - case '}' => putChar(); token = RBrace - case '[' => putChar(); token = LBracket - case ']' => putChar(); token = RBracket - case ',' => putChar(); token = Comma - case ':' => putChar(); token = Colon - case 't' => putAcceptString("true"); token = TrueLit - case 'f' => putAcceptString("false"); token = FalseLit - case 'n' => putAcceptString("null"); token = NullLit - case '"' => getString() - case '-' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => getNumber() - case _ => error("unrecoginezed start of token: '"+ch+"'") - } - //println("["+token+"]") - } - - /** Reads a string literal, and forms a `StringLit` token from it. - * Last-read input character `ch` must be opening `"`-quote. - * @throws MalformedInput if lexeme not recognized as a string literal. - */ - def getString() { - def udigit() = { - nextChar() - if ('0' <= ch && ch <= '9') ch - '9' - else if ('A' <= ch && ch <= 'F') ch - 'A' + 10 - else if ('a' <= ch && ch <= 'f') ch - 'a' + 10 - else error("illegal unicode escape character: '"+ch+"'") - } - val delim = ch - nextChar() - while (ch != delim && ch >= ' ') { - if (ch == '\\') { - nextChar() - ch match { - case '\'' => sb += '\'' - case '"' => sb += '"' - case '\\' => sb += '\\' - case '/' => sb += '/' - case 'b' => sb += '\b' - case 'f' => sb += '\f' - case 'n' => sb += '\n' - case 'r' => sb += '\r' - case 't' => sb += '\t' - case 'u' => sb += (udigit() << 12 | udigit() << 8 | udigit() << 4 | udigit()).toChar - case _ => error("illegal escape character: '"+ch+"'") - } - nextChar() - } else { - putChar() - } - } - acceptChar(delim) - token = StringLit(sb.toString) - } - - /** Reads a numeric literal, and forms an `IntLit` or `FloatLit` token from it. - * Last-read input character `ch` must be either `-` or a digit. - * @throws MalformedInput if lexeme not recognized as a numeric literal. - */ - def getNumber() { - def digit() = - if ('0' <= ch && ch <= '9') putChar() - else error("<digit> expected") - def digits() = - do { digit() } while ('0' <= ch && ch <= '9') - var isFloating = false - if (ch == '-') putChar() - if (ch == '0') digit() - else digits() - if (ch == '.') { - isFloating = true - putChar() - digits() - } - if (ch == 'e' || ch == 'E') { - isFloating = true - putChar() - if (ch == '+' || ch == '-') putChar() - digits() - } - token = if (isFloating) FloatLit(sb.toString) else IntLit(sb.toString) - } - - /** If current token equals given token, reads next token, otherwise raises an error. - * @param t the given token to compare current token with - * @throws MalformedInput if the two tokens do not match. - */ - def accept(t: Token) { - if (token == t) nextToken() - else error(t+" expected, but "+token+" found") - } - - /** The current token is a delimiter consisting of given character, reads next token, - * otherwise raises an error. - * @param ch the given delimiter character to compare current token with - * @throws MalformedInput if the current token `token` is not a delimiter, or - * consists of a character different from `c`. - */ - def accept(ch: Char) { - token match { - case Delim(`ch`) => nextToken() - case _ => accept(Delim(ch)) - } - } - - /** Always throws a `MalformedInput` exception with given error message. - * @param msg the error message - */ - def error(msg: String) = throw new MalformedInput(this, msg) - - nextChar() - nextToken() -} diff --git a/src/compiler/scala/tools/nsc/io/Pickler.scala b/src/compiler/scala/tools/nsc/io/Pickler.scala deleted file mode 100644 index 0e7da37c52..0000000000 --- a/src/compiler/scala/tools/nsc/io/Pickler.scala +++ /dev/null @@ -1,377 +0,0 @@ -package scala.tools.nsc.io - -import Lexer._ -import java.io.Writer -import scala.language.implicitConversions -import scala.reflect.ClassTag - -/** An abstract class for writing and reading Scala objects to and - * from a legible representation. The presesentation follows the following grammar: - * {{{ - * Pickled = `true` | `false` | `null` | NumericLit | StringLit | - * Labelled | Pickled `,` Pickled - * Labelled = StringLit `(` Pickled? `)` - * }}} - * - * All ...Lit classes are as in JSON. @see scala.tools.nsc.io.Lexer - * - * Subclasses of `Pickler` each can write and read individual classes - * of values. - * - * @tparam T the type of values handled by this pickler. - * - * These Picklers build on the work of Andrew Kennedy. They are most closely inspired by - * Iulian Dragos' picklers for Scala to XML. See: - * - * <a href="http://code.google.com/p/gdata-scala-client/wiki/DevelopersGuide"> - * http://code.google.com/p/gdata-scala-client/wiki/DevelopersGuide - * </a> - */ -abstract class Pickler[T] { - - import Pickler._ - - /** Writes value in pickled form - * @param wr the writer to which pickled form is written - * @param x the value to write - */ - def pickle(wr: Writer, x: T) - - /** Reads value from pickled form. - * - * @param rd the lexer from which lexemes are read - * @return An `UnpickleSuccess value if the current input corresponds to the - * kind of value that is unpickled by the current subclass of `Pickler`, - * an `UnpickleFailure` value otherwise. - * @throws `Lexer.MalformedInput` if input is invalid, or if - * an `Unpickle - */ - def unpickle(rd: Lexer): Unpickled[T] - - /** A pickler representing a `~`-pair of values as two consecutive pickled - * strings, separated by a comma. - * @param that the second pickler which together with the current pickler makes - * up the pair `this ~ that` to be pickled. - */ - def ~ [U] (that: => Pickler[U]): Pickler[T ~ U] = seqPickler(this, that) - - /** A pickler that adds a label to the current pickler, using the representation - * `label ( <current pickler> )` - * - * @label the string to be added as a label. - */ - def labelled(label: String): Pickler[T] = labelledPickler(label, this) - - /** A pickler obtained from the current pickler by a pair of transformer functions - * @param in the function that maps values handled by the current pickler to - * values handled by the wrapped pickler. - * @param out the function that maps values handled by the wrapped pickler to - * values handled by the current pickler. - */ - def wrapped [U] (in: T => U)(out: U => T): Pickler[U] = wrappedPickler(this)(in)(out) - - /** A conditional pickler obtained from the current pickler. - * @param p the condition to test to find out whether pickler can handle - * some Scala value. - */ - def cond(p: Any => Boolean): CondPickler[T] = conditionalPickler(this, p) - - /** A conditional pickler handling values of some Scala class. It adds the - * class name as a label to the representation of the current pickler and - * @param c the class of values handled by this pickler. - */ - def asClass[U <: T](c: Class[U]): CondPickler[T] = this.labelled(c.getName).cond(c isInstance _) -} - -object Pickler { - /** A base class representing unpickler result. It has two subclasses: - * `UnpickleSucess` for successful unpicklings and `UnpickleFailure` for failures, - * where a value of the given type `T` could not be unpickled from input. - * @tparam T the type of unpickled values in case of success. - */ - abstract class Unpickled[+T] { - /** Transforms success values to success values using given function, - * leaves failures alone - * @param f the function to apply. - */ - def map[U](f: T => U): Unpickled[U] = this match { - case UnpickleSuccess(x) => UnpickleSuccess(f(x)) - case f: UnpickleFailure => f - } - /** Transforms success values to successes or failures using given function, - * leaves failures alone. - * @param f the function to apply. - */ - def flatMap[U](f: T => Unpickled[U]): Unpickled[U] = this match { - case UnpickleSuccess(x) => f(x) - case f: UnpickleFailure => f - } - /** Tries alternate expression if current result is a failure - * @param alt the alternate expression to be tried in case of failure - */ - def orElse[U >: T](alt: => Unpickled[U]): Unpickled[U] = this match { - case UnpickleSuccess(x) => this - case f: UnpickleFailure => alt - } - - /** Transforms failures into thrown `MalformedInput` exceptions. - * @throws MalformedInput if current result is a failure - */ - def requireSuccess: UnpickleSuccess[T] = this match { - case s @ UnpickleSuccess(x) => s - case f: UnpickleFailure => - throw new MalformedInput(f.rd, "Unrecoverable unpickle failure:\n"+f.errMsg) - } - } - - /** A class representing successful unpicklings - * @tparam T the type of the unpickled value - * @param result the unpickled value - */ - case class UnpickleSuccess[+T](result: T) extends Unpickled[T] - - /** A class representing unpickle failures - * @param msg an error message describing what failed. - * @param rd the lexer unpickled values were read from (can be used to get - * error position, for instance). - */ - class UnpickleFailure(msg: => String, val rd: Lexer) extends Unpickled[Nothing] { - def errMsg = msg - override def toString = "Failure at "+rd.tokenPos+":\n"+msg - } - - private def errorExpected(rd: Lexer, msg: => String) = - new UnpickleFailure("expected: "+msg+"\n" + - "found : "+rd.token, - rd) - - private def nextSuccess[T](rd: Lexer, result: T) = { - rd.nextToken() - UnpickleSuccess(result) - } - - /** The implicit `Pickler` value for type `T`. Equivalent to `implicitly[Pickler[T]]`. - */ - def pkl[T: Pickler] = implicitly[Pickler[T]] - - /** A class represenenting `~`-pairs */ - case class ~[+S, +T](fst: S, snd: T) - - /** A wrapper class to be able to use `~` s an infix method */ - implicit class TildeDecorator[S](x: S) { - /** Infix method that forms a `~`-pair. */ - def ~ [T](y: T): S ~ T = new ~ (x, y) - } - - /** Same as `p.labelled(label)`. - */ - def labelledPickler[T](label: String, p: Pickler[T]): Pickler[T] = new Pickler[T] { - def pickle(wr: Writer, x: T) = { - wr.write(quoted(label)) - wr.write("(") - p.pickle(wr, x) - wr.write(")") - } - def unpickle(rd: Lexer): Unpickled[T] = - rd.token match { - case StringLit(`label`) => - rd.nextToken() - rd.accept('(') - val result = p.unpickle(rd).requireSuccess - rd.accept(')') - result - case _ => - errorExpected(rd, quoted(label)+"(...)") - } - } - - /** Same as `p.wrap(in)(out)` - */ - def wrappedPickler[S, T](p: Pickler[S])(in: S => T)(out: T => S) = new Pickler[T] { - def pickle(wr: Writer, x: T) = p.pickle(wr, out(x)) - def unpickle(rd: Lexer) = p.unpickle(rd) map in - } - - /** Same as `p.cond(condition)` - */ - def conditionalPickler[T](p: Pickler[T], condition: Any => Boolean) = new CondPickler[T](condition) { - def pickle(wr: Writer, x: T) = p.pickle(wr, x) - def unpickle(rd: Lexer) = p.unpickle(rd) - } - - /** Same as `p ~ q` - */ - def seqPickler[T, U](p: Pickler[T], q: => Pickler[U]) = new Pickler[T ~ U] { - lazy val qq = q - def pickle(wr: Writer, x: T ~ U) = { - p.pickle(wr, x.fst) - wr.write(',') - q.pickle(wr, x.snd) - } - def unpickle(rd: Lexer) = - for (x <- p.unpickle(rd); y <- { rd.accept(','); qq.unpickle(rd).requireSuccess }) - yield x ~ y - } - - /** Same as `p | q` - */ - def eitherPickler[T, U <: T, V <: T](p: CondPickler[U], q: => CondPickler[V]) = - new CondPickler[T](x => p.canPickle(x) || q.canPickle(x)) { - lazy val qq = q - override def tryPickle(wr: Writer, x: Any): Boolean = - p.tryPickle(wr, x) || qq.tryPickle(wr, x) - def pickle(wr: Writer, x: T) = - require(tryPickle(wr, x), - "no pickler found for "+x+" of class "+x.getClass.getName) - def unpickle(rd: Lexer) = p.unpickle(rd) orElse qq.unpickle(rd) - } - - /** A conditional pickler for singleton objects. It represents these - * with the object's underlying class as a label. - * Example: Object scala.None would be represented as `scala.None$()`. - */ - def singletonPickler[T <: AnyRef](x: T): CondPickler[T] = - unitPickler - .wrapped { _ => x } { x => () } - .labelled (x.getClass.getName) - .cond (x eq _.asInstanceOf[AnyRef]) - - /** A pickler the handles instances of classes that have an empty constructor. - * It represents than as `$new ( <name of class> )`. - * When unpickling, a new instance of the class is created using the empty - * constructor of the class via `Class.forName(<name of class>).newInstance()`. - */ - def javaInstancePickler[T <: AnyRef]: Pickler[T] = - (stringPickler labelled "$new") - .wrapped { name => Class.forName(name).newInstance().asInstanceOf[T] } { _.getClass.getName } - - /** A picklers that handles iterators. It pickles all values - * returned by an iterator separated by commas. - * When unpickling, it always returns an `UnpickleSuccess` containing an iterator. - * This iterator returns 0 or more values that are obtained by unpickling - * until a closing parenthesis, bracket or brace or the end of input is encountered. - * - * This means that iterator picklers should not be directly followed by `~` - * because the pickler would also read any values belonging to the second - * part of the `~`-pair. - * - * What's usually done instead is that the iterator pickler is wrapped and labelled - * to handle other kinds of sequences. - */ - implicit def iterPickler[T: Pickler]: Pickler[Iterator[T]] = new Pickler[Iterator[T]] { - lazy val p = pkl[T] - def pickle(wr: Writer, xs: Iterator[T]) { - var first = true - for (x <- xs) { - if (first) first = false else wr.write(',') - p.pickle(wr, x) - } - } - def unpickle(rd: Lexer): Unpickled[Iterator[T]] = UnpickleSuccess(new Iterator[T] { - var first = true - def hasNext = { - val t = rd.token - t != EOF && t != RParen && t != RBrace && t != RBracket - } - def next(): T = { - if (first) first = false else rd.accept(',') - p.unpickle(rd).requireSuccess.result - } - }) - } - - /** A pickler that handles values that can be represented as a single token. - * @param kind the kind of token representing the value, used in error messages - * for unpickling. - * @param matcher A partial function from tokens to handled values. Unpickling - * succeeds if the matcher function is defined on the current token. - */ - private def tokenPickler[T](kind: String)(matcher: PartialFunction[Token, T]) = new Pickler[T] { - def pickle(wr: Writer, x: T) = wr.write(x.toString) - def unpickle(rd: Lexer) = - if (matcher isDefinedAt rd.token) nextSuccess(rd, matcher(rd.token)) - else errorExpected(rd, kind) - } - - /** A pickler for values of type `Long`, represented as integer literals */ - implicit val longPickler: Pickler[Long] = - tokenPickler("integer literal") { case IntLit(s) => s.toLong } - - /** A pickler for values of type `Int`, represented as integer literals */ - implicit val intPickler: Pickler[Int] = longPickler.wrapped { _.toInt } { _.toLong } - - /** A conditional pickler for the boolean value `true` */ - private val truePickler = - tokenPickler("boolean literal") { case TrueLit => true } cond { _ == true } - - /** A conditional pickler for the boolean value `false` */ - private val falsePickler = - tokenPickler("boolean literal") { case FalseLit => false } cond { _ == false } - - /** A pickler for values of type `Boolean`, represented as the literals `true` or `false`. */ - implicit def booleanPickler: Pickler[Boolean] = truePickler | falsePickler - - /** A pickler for values of type `Unit`, represented by the empty character string */ - implicit val unitPickler: Pickler[Unit] = new Pickler[Unit] { - def pickle(wr: Writer, x: Unit) {} - def unpickle(rd: Lexer): Unpickled[Unit] = UnpickleSuccess(()) - } - - /** A pickler for values of type `String`, represented as string literals */ - implicit val stringPickler: Pickler[String] = new Pickler[String] { - def pickle(wr: Writer, x: String) = wr.write(if (x == null) "null" else quoted(x)) - def unpickle(rd: Lexer) = rd.token match { - case StringLit(s) => nextSuccess(rd, s) - case NullLit => nextSuccess(rd, null) - case _ => errorExpected(rd, "string literal") - } - } - - /** A pickler for pairs, represented as `~`-pairs */ - implicit def tuple2Pickler[T1: Pickler, T2: Pickler]: Pickler[(T1, T2)] = - (pkl[T1] ~ pkl[T2]) - .wrapped { case x1 ~ x2 => (x1, x2) } { case (x1, x2) => x1 ~ x2 } - .labelled ("tuple2") - - /** A pickler for 3-tuples, represented as `~`-tuples */ - implicit def tuple3Pickler[T1, T2, T3](implicit p1: Pickler[T1], p2: Pickler[T2], p3: Pickler[T3]): Pickler[(T1, T2, T3)] = - (p1 ~ p2 ~ p3) - .wrapped { case x1 ~ x2 ~ x3 => (x1, x2, x3) } { case (x1, x2, x3) => x1 ~ x2 ~ x3 } - .labelled ("tuple3") - - /** A pickler for list values */ - implicit def listPickler[T: Pickler]: Pickler[List[T]] = - iterPickler[T] .wrapped { _.toList } { _.iterator } .labelled ("scala.List") -} - -/** A subclass of Pickler can indicate whether a particular value can be pickled by instances - * of this class. - * @param canPickle The predicate that indicates whether a given value - * can be pickled by instances of this class. - */ -abstract class CondPickler[T](val canPickle: Any => Boolean) extends Pickler[T] { - import Pickler._ - - /** Pickles given value `x` if possible, as indicated by `canPickle(x)`. - */ - def tryPickle(wr: Writer, x: Any): Boolean = { - val result = canPickle(x) - if (result) pickle(wr, x.asInstanceOf[T]) - result - } - - /** A pickler obtained from this pickler and an alternative pickler. - * To pickle a value, this pickler is tried first. If it cannot handle - * the object (as indicated by its `canPickle` test), then the - * alternative pickler is tried. - * To unpickle a value, this unpickler is tried first. If it cannot read - * the input (as indicated by a `UnpickleFailure` result), then the - * alternative pickler is tried. - * @tparam V The handled type of the returned pickler. - * @tparam U The handled type of the alternative pickler. - * @param that The alternative pickler. - */ - def | [V >: T, U <: V] (that: => CondPickler[U]): CondPickler[V] = - eitherPickler[V, T, U](this, that) -} - diff --git a/src/compiler/scala/tools/nsc/io/PrettyWriter.scala b/src/compiler/scala/tools/nsc/io/PrettyWriter.scala deleted file mode 100644 index 11d3703983..0000000000 --- a/src/compiler/scala/tools/nsc/io/PrettyWriter.scala +++ /dev/null @@ -1,41 +0,0 @@ -package scala.tools.nsc.io - -import java.io.Writer - -class PrettyWriter(wr: Writer) extends Writer { - protected val indentStep = " " - private var indent = 0 - private def newLine() { - wr.write('\n') - wr.write(indentStep * indent) - } - def close() = wr.close() - def flush() = wr.flush() - def write(str: Array[Char], off: Int, len: Int): Unit = { - if (off < str.length && off < len) { - str(off) match { - case '{' | '[' | '(' => - indent += 1 - wr.write(str(off).toInt) - newLine() - wr.write(str, off + 1, len - 1) - case '}' | ']' | ')' => - wr.write(str, off, len) - indent -= 1 - case ',' => - wr.write(',') - newLine() - wr.write(str, off + 1, len - 1) - case ':' => - wr.write(':') - wr.write(' ') - wr.write(str, off + 1, len - 1) - case _ => - wr.write(str, off, len) - } - } else { - wr.write(str, off, len) - } - } - override def toString = wr.toString -} diff --git a/src/compiler/scala/tools/nsc/io/Replayer.scala b/src/compiler/scala/tools/nsc/io/Replayer.scala deleted file mode 100644 index e3dc8939a3..0000000000 --- a/src/compiler/scala/tools/nsc/io/Replayer.scala +++ /dev/null @@ -1,74 +0,0 @@ -package scala.tools.nsc.io - -import java.io.{Reader, Writer} - -import Pickler._ -import Lexer.EOF - -abstract class LogReplay { - def logreplay(event: String, x: => Boolean): Boolean - def logreplay[T: Pickler](event: String, x: => Option[T]): Option[T] - def close() - def flush() -} - -class Logger(wr0: Writer) extends LogReplay { - val wr = new PrettyWriter(wr0) - private var first = true - private def insertComma() = if (first) first = false else wr.write(",") - - def logreplay(event: String, x: => Boolean) = { - val xx = x - if (xx) { insertComma(); pkl[Unit].labelled(event).pickle(wr, ()) } - xx - } - def logreplay[T: Pickler](event: String, x: => Option[T]) = { - val xx = x - xx match { - case Some(y) => insertComma(); pkl[T].labelled(event).pickle(wr, y) - case None => - } - xx - } - def close() { wr.close() } - def flush() { wr.flush() } -} - -object NullLogger extends LogReplay { - def logreplay(event: String, x: => Boolean) = x - def logreplay[T: Pickler](event: String, x: => Option[T]) = x - def close() {} - def flush() {} -} - -class Replayer(raw: Reader) extends LogReplay { - private val rd = new Lexer(raw) - private var nextComma = false - - private def eatComma() = - if (nextComma) { rd.accept(','); nextComma = false } - - def logreplay(event: String, x: => Boolean) = - if (rd.token == EOF) NullLogger.logreplay(event, x) - else { - eatComma() - pkl[Unit].labelled(event).unpickle(rd) match { - case UnpickleSuccess(_) => nextComma = true; true - case _ => false - } - } - - def logreplay[T: Pickler](event: String, x: => Option[T]) = - if (rd.token == EOF) NullLogger.logreplay(event, x) - else { - eatComma() - pkl[T].labelled(event).unpickle(rd) match { - case UnpickleSuccess(y) => nextComma = true; Some(y) - case _ => None - } - } - - def close() { raw.close() } - def flush() {} -} - diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala index b5cc89c0c8..0536be92cf 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -9,8 +9,9 @@ package nsc package settings import io.{ AbstractFile, Jar, Path, PlainFile, VirtualDirectory } -import scala.reflect.internal.util.StringOps +import scala.collection.generic.Clearable import scala.io.Source +import scala.reflect.internal.util.StringOps import scala.reflect.{ ClassTag, classTag } /** A mutable Settings object. @@ -542,7 +543,7 @@ class MutableSettings(val errorFn: String => Unit) name: String, val arg: String, descr: String) - extends Setting(name, descr) { + extends Setting(name, descr) with Clearable { type T = List[String] protected var v: T = Nil def appendToValue(str: String) { value ++= List(str) } @@ -555,6 +556,7 @@ class MutableSettings(val errorFn: String => Unit) } override def tryToSetColon(args: List[String]) = tryToSet(args) override def tryToSetFromPropertyValue(s: String) = tryToSet(s.trim.split(',').toList) // used from ide + def clear(): Unit = (v = Nil) def unparse: List[String] = value map (name + ":" + _) withHelpSyntax(name + ":<" + arg + ">") @@ -608,44 +610,49 @@ class MutableSettings(val errorFn: String => Unit) name: String, descr: String, default: String - ) extends Setting(name, mkPhasesHelp(descr, default)) { + ) extends Setting(name, mkPhasesHelp(descr, default)) with Clearable { private[nsc] def this(name: String, descr: String) = this(name, descr, "") type T = List[String] - protected var v: T = Nil - override def value = if (v contains "all") List("all") else super.value - private lazy val (numericValues, stringValues) = - value filterNot (_ == "" ) partition (_ forall (ch => ch.isDigit || ch == '-')) - - /** A little ad-hoc parsing. If a string is not the name of a phase, it can also be: - * a phase id: 5 - * a phase id range: 5-10 (inclusive of both ends) - * a range with no start: -5 means up to and including 5 - * a range with no end: 10- means 10 until completion. - */ - private def stringToPhaseIdTest(s: String): Int => Boolean = (s indexOf '-') match { - case -1 => (_ == s.toInt) - case 0 => (_ <= s.tail.toInt) - case idx => - if (s.last == '-') (_ >= s.init.toInt) - else (s splitAt idx) match { - case (s1, s2) => (id => id >= s1.toInt && id <= s2.tail.toInt) - } - } - private lazy val phaseIdTest: Int => Boolean = - (numericValues map stringToPhaseIdTest) match { - case Nil => _ => false - case fns => fns.reduceLeft((f1, f2) => id => f1(id) || f2(id)) + private[this] var _v: T = Nil + private[this] var _numbs: List[(Int,Int)] = Nil + private[this] var _names: T = Nil + //protected var v: T = Nil + protected def v: T = _v + protected def v_=(t: T): Unit = { + // throws NumberFormat on bad range (like -5-6) + def asRange(s: String): (Int,Int) = (s indexOf '-') match { + case -1 => (s.toInt, s.toInt) + case 0 => (-1, s.tail.toInt) + case i if s.last == '-' => (s.init.toInt, Int.MaxValue) + case i => (s.take(i).toInt, s.drop(i+1).toInt) } + val numsAndStrs = t filter (_.nonEmpty) partition (_ forall (ch => ch.isDigit || ch == '-')) + _numbs = numsAndStrs._1 map asRange + _names = numsAndStrs._2 + _v = t + } + override def value = if (v contains "all") List("all") else super.value // i.e., v + private def numericValues = _numbs + private def stringValues = _names + private def phaseIdTest(i: Int): Boolean = numericValues exists (_ match { + case (min, max) => min <= i && i <= max + }) def tryToSet(args: List[String]) = if (default == "") errorAndValue("missing phase", None) - else { tryToSetColon(List(default)) ; Some(args) } + else tryToSetColon(List(default)) map (_ => args) + + override def tryToSetColon(args: List[String]) = try { + args match { + case Nil => if (default == "") errorAndValue("missing phase", None) + else tryToSetColon(List(default)) + case xs => value = (value ++ xs).distinct.sorted ; Some(Nil) + } + } catch { case _: NumberFormatException => None } + + def clear(): Unit = (v = Nil) - override def tryToSetColon(args: List[String]) = args match { - case Nil => if (default == "") errorAndValue("missing phase", None) else tryToSetColon(List(default)) - case xs => value = (value ++ xs).distinct.sorted ; Some(Nil) - } // we slightly abuse the usual meaning of "contains" here by returning // true if our phase list contains "all", regardless of the incoming argument def contains(phName: String) = doAllPhases || containsName(phName) diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index fe9165203f..993f735c72 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -39,7 +39,7 @@ trait ScalaSettings extends AbsScalaSettings protected def futureSettings = List[BooleanSetting]() /** Enabled under -optimise. */ - protected def optimiseSettings = List[BooleanSetting](inline, inlineHandlers, Xcloselim, Xdce, YconstOptimization) + def optimiseSettings = List[BooleanSetting](inline, inlineHandlers, Xcloselim, Xdce, YconstOptimization) /** Internal use - syntax enhancements. */ private class EnableSettings[T <: BooleanSetting](val s: T) { @@ -190,6 +190,7 @@ trait ScalaSettings extends AbsScalaSettings val Yreifydebug = BooleanSetting("-Yreify-debug", "Trace reification.") val Ytyperdebug = BooleanSetting("-Ytyper-debug", "Trace all type assignments.") val Ypatmatdebug = BooleanSetting("-Ypatmat-debug", "Trace pattern matching translation.") + val Yquasiquotedebug = BooleanSetting("-Yquasiquote-debug", "Trace quasiquote-related activities.") /** Groups of Settings. */ @@ -198,6 +199,12 @@ trait ScalaSettings extends AbsScalaSettings val nooptimise = BooleanSetting("-Ynooptimise", "Clears all the flags set by -optimise. Useful for testing optimizations in isolation.") withAbbreviation "-Ynooptimize" disabling optimise::optimiseSettings val Xexperimental = BooleanSetting("-Xexperimental", "Enable experimental extensions.") enabling experimentalSettings + /** + * Settings motivated by GenBCode + */ + val Ybackend = ChoiceSetting ("-Ybackend", "choice of bytecode emitter", "Choice of bytecode emitter.", + List("GenASM", "GenBCode"), + "GenASM") // Feature extensions val XmacroSettings = MultiStringSetting("-Xmacro-settings", "option", "Custom settings for macros.") @@ -220,4 +227,12 @@ trait ScalaSettings extends AbsScalaSettings /** Test whether this is scaladoc we're looking at */ def isScaladoc = false + + /** + * Helper utilities for use by checkConflictingSettings() + */ + def isBCodeActive = !isICodeAskedFor + def isBCodeAskedFor = (Ybackend.value != "GenASM") + def isICodeAskedFor = ((Ybackend.value == "GenASM") || optimiseSettings.exists(_.value) || writeICode.isSetByUser) + } diff --git a/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala b/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala index da1cc0c4cf..4f45043c5e 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala @@ -11,7 +11,7 @@ package tools.nsc.settings * Represents a single Scala version in a manner that * supports easy comparison and sorting. */ -abstract class ScalaVersion extends Ordered[ScalaVersion] { +sealed abstract class ScalaVersion extends Ordered[ScalaVersion] { def unparse: String } diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index cbfe5460f6..4c0c16690f 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -517,7 +517,7 @@ abstract class ClassfileParser { skipMembers() // methods if (!isScala) { clazz setFlag sflags - propagatePackageBoundary(jflags, clazz, staticModule) + propagatePackageBoundary(jflags, clazz, staticModule, staticModule.moduleClass) clazz setInfo classInfo moduleClass setInfo staticInfo staticModule setInfo moduleClass.tpe diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala index a37ef29355..b16ba91916 100644 --- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala +++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala @@ -20,6 +20,18 @@ abstract class CleanUp extends Transform with ast.TreeDSL { /** the following two members override abstract members in Transform */ val phaseName: String = "cleanup" + /* used in GenBCode: collects ClassDef symbols owning a main(Array[String]) method */ + private var entryPoints: List[Symbol] = null + def getEntryPoints: List[Symbol] = { + assert(settings.isBCodeActive, "Candidate Java entry points are collected here only when GenBCode in use.") + entryPoints sortBy ("" + _.fullName) // For predictably ordered error messages. + } + + override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = { + entryPoints = if (settings.isBCodeActive) Nil else null; + super.newPhase(prev) + } + protected def newTransformer(unit: CompilationUnit): Transformer = new CleanUpTransformer(unit) @@ -390,6 +402,15 @@ abstract class CleanUp extends Transform with ast.TreeDSL { override def transform(tree: Tree): Tree = tree match { + case _: ClassDef + if (entryPoints != null) && + genBCode.isJavaEntryPoint(tree.symbol, currentUnit) + => + // collecting symbols for entry points here (as opposed to GenBCode where they are used) + // has the advantage of saving an additional pass over all ClassDefs. + entryPoints ::= tree.symbol + super.transform(tree) + /* Transforms dynamic calls (i.e. calls to methods that are undefined * in the erased type space) to -- dynamically -- unsafe calls using * reflection. This is used for structural sub-typing of refinement diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 75fb043070..7dfa7cdf8d 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -9,7 +9,6 @@ package transform import scala.collection.{ mutable, immutable } import scala.collection.mutable.ListBuffer import symtab.Flags._ -import util.TreeSet /** This phase converts classes with parameters into Java-like classes with * fields, which are assigned to from constructors. @@ -239,7 +238,8 @@ abstract class Constructors extends Transform with ast.TreeDSL { // ----------- avoid making parameter-accessor fields for symbols accessed only within the primary constructor -------------- // A sorted set of symbols that are known to be accessed outside the primary constructor. - val accessedSyms = new TreeSet[Symbol]((x, y) => x isLess y) + val ord = Ordering.fromLessThan[Symbol](_ isLess _) + val accessedSyms = mutable.TreeSet.empty[Symbol](ord) // a list of outer accessor symbols and their bodies var outerAccessors: List[(Symbol, Tree)] = List() @@ -271,7 +271,7 @@ abstract class Constructors extends Transform with ast.TreeDSL { case Select(_, _) => if (!mustbeKept(tree.symbol)) { debuglog("accessedSyms += " + tree.symbol.fullName) - accessedSyms addEntry tree.symbol + accessedSyms += tree.symbol } super.traverse(tree) case _ => diff --git a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala index 672d9d232a..56ec49e962 100644 --- a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala +++ b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala @@ -208,6 +208,7 @@ abstract class ExtensionMethods extends Transform with TypingTransformers { companion.moduleClass.newMethod(extensionName, origMeth.pos, origMeth.flags & ~OVERRIDE & ~PROTECTED | FINAL) setAnnotations origMeth.annotations ) + origMeth.removeAnnotation(TailrecClass) // it's on the extension method, now. companion.info.decls.enter(extensionMeth) } @@ -221,15 +222,16 @@ abstract class ExtensionMethods extends Transform with TypingTransformers { val extensionParams = allParameters(extensionMono) val extensionThis = gen.mkAttributedStableRef(thiz setPos extensionMeth.pos) - val extensionBody = ( - rhs + val extensionBody: Tree = { + val tree = rhs .substituteSymbols(origTpeParams, extensionTpeParams) .substituteSymbols(origParams, extensionParams) .substituteThis(origThis, extensionThis) .changeOwner(origMeth -> extensionMeth) - ) + new SubstututeRecursion(origMeth, extensionMeth, unit).transform(tree) + } - // Record the extension method ( FIXME: because... ? ) + // Record the extension method. Later, in `Extender#transformStats`, these will be added to the companion object. extensionDefs(companion) += atPos(tree.pos)(DefDef(extensionMeth, extensionBody)) // These three lines are assembling Foo.bar$extension[T1, T2, ...]($this) @@ -264,4 +266,33 @@ abstract class ExtensionMethods extends Transform with TypingTransformers { stat } } + + final class SubstututeRecursion(origMeth: Symbol, extensionMeth: Symbol, + unit: CompilationUnit) extends TypingTransformer(unit) { + override def transform(tree: Tree): Tree = tree match { + // SI-6574 Rewrite recursive calls against the extension method so they can + // be tail call optimized later. The tailcalls phases comes before + // erasure, which performs this translation more generally at all call + // sites. + // + // // Source + // class C[C] { def meth[M](a: A) = { { <expr>: C[C'] }.meth[M'] } } + // + // // Translation + // class C[C] { def meth[M](a: A) = { { <expr>: C[C'] }.meth[M'](a1) } } + // object C { def meth$extension[M, C](this$: C[C], a: A) + // = { meth$extension[M', C']({ <expr>: C[C'] })(a1) } } + case treeInfo.Applied(sel @ Select(qual, _), targs, argss) if sel.symbol == origMeth => + import gen.CODE._ + localTyper.typedPos(tree.pos) { + val allArgss = List(qual) :: argss + val origThis = extensionMeth.owner.companionClass + val baseType = qual.tpe.baseType(origThis) + val allTargs = targs.map(_.tpe) ::: baseType.typeArgs + val fun = gen.mkAttributedTypeApply(THIS(extensionMeth.owner), extensionMeth, allTargs) + allArgss.foldLeft(fun)(Apply(_, _)) + } + case _ => super.transform(tree) + } + } } diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index 7888198531..ce495ca8ca 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -8,9 +8,8 @@ package transform import symtab._ import Flags._ -import util.TreeSet import scala.collection.{ mutable, immutable } -import scala.collection.mutable.{ LinkedHashMap, LinkedHashSet } +import scala.collection.mutable.{ LinkedHashMap, LinkedHashSet, TreeSet } abstract class LambdaLift extends InfoTransform { import global._ @@ -56,6 +55,8 @@ abstract class LambdaLift extends InfoTransform { class LambdaLifter(unit: CompilationUnit) extends explicitOuter.OuterPathTransformer(unit) { + private type SymSet = TreeSet[Symbol] + /** A map storing free variables of functions and classes */ private val free = new LinkedHashMap[Symbol, SymSet] @@ -68,6 +69,12 @@ abstract class LambdaLift extends InfoTransform { /** Symbols that are called from an inner class. */ private val calledFromInner = new LinkedHashSet[Symbol] + private val ord = Ordering.fromLessThan[Symbol](_ isLess _) + private def newSymSet = TreeSet.empty[Symbol](ord) + + private def symSet(f: LinkedHashMap[Symbol, SymSet], sym: Symbol): SymSet = + f.getOrElseUpdate(sym, newSymSet) + /** The set of symbols that need to be renamed. */ private val renamable = newSymSet @@ -107,13 +114,6 @@ abstract class LambdaLift extends InfoTransform { /** Buffers for lifted out classes and methods */ private val liftedDefs = new LinkedHashMap[Symbol, List[Tree]] - private type SymSet = TreeSet[Symbol] - - private def newSymSet = new TreeSet[Symbol](_ isLess _) - - private def symSet(f: LinkedHashMap[Symbol, SymSet], sym: Symbol): SymSet = - f.getOrElseUpdate(sym, newSymSet) - private def isSameOwnerEnclosure(sym: Symbol) = sym.owner.logicallyEnclosingMember == currentOwner.logicallyEnclosingMember @@ -155,8 +155,8 @@ abstract class LambdaLift extends InfoTransform { else { val ss = symSet(free, enclosure) if (!ss(sym)) { - ss addEntry sym - renamable addEntry sym + ss += sym + renamable += sym changedFreeVars = true debuglog("" + sym + " is free in " + enclosure) if (sym.isVariable) sym setFlag CAPTURED @@ -168,7 +168,7 @@ abstract class LambdaLift extends InfoTransform { private def markCalled(sym: Symbol, owner: Symbol) { debuglog("mark called: " + sym + " of " + sym.owner + " is called by " + owner) - symSet(called, owner) addEntry sym + symSet(called, owner) += sym if (sym.enclClass != owner.enclClass) calledFromInner += sym } @@ -195,17 +195,17 @@ abstract class LambdaLift extends InfoTransform { if (sym.isImplClass) localImplClasses((sym.owner, tpnme.interfaceName(sym.name))) = sym else { - renamable addEntry sym + renamable += sym if (sym.isTrait) localTraits((sym, sym.name)) = sym.owner } } case DefDef(_, _, _, _, _, _) => if (sym.isLocal) { - renamable addEntry sym + renamable += sym sym setFlag (PrivateLocal | FINAL) } else if (sym.isPrimaryConstructor) { - symSet(called, sym) addEntry sym.owner + symSet(called, sym) += sym.owner } case Ident(name) => if (sym == NoSymbol) { @@ -214,7 +214,7 @@ abstract class LambdaLift extends InfoTransform { val owner = currentOwner.logicallyEnclosingMember if (sym.isTerm && !sym.isMethod) markFree(sym, owner) else if (sym.isMethod) markCalled(sym, owner) - //symSet(called, owner) addEntry sym + //symSet(called, owner) += sym } case Select(_, _) => if (sym.isConstructor && sym.owner.isLocal) @@ -224,7 +224,7 @@ abstract class LambdaLift extends InfoTransform { super.traverse(tree) } catch {//debug case ex: Throwable => - Console.println("exception when traversing " + tree) + Console.println(s"$ex while traversing $tree") throw ex } } diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index e0b1d9ea80..1c44e86aca 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -119,7 +119,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * @param mixinClass The mixin class that produced the superaccessor */ private def rebindSuper(base: Symbol, member: Symbol, mixinClass: Symbol): Symbol = - exitingPickler { + exitingSpecialize { var bcs = base.info.baseClasses.dropWhile(mixinClass != _).tail var sym: Symbol = NoSymbol debuglog("starting rebindsuper " + base + " " + member + ":" + member.tpe + diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index d14fcb3eb1..4bc4e06fa7 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -1267,7 +1267,35 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } protected override def newBodyDuplicator(context: Context) = new BodyDuplicator(context) + } + /** Introduced to fix SI-7343: Phase ordering problem between Duplicators and Specialization. + * brief explanation: specialization rewires class parents during info transformation, and + * the new info then guides the tree changes. But if a symbol is created during duplication, + * which runs after specialization, its info is not visited and thus the corresponding tree + * is not specialized. One manifestation is the following: + * ``` + * object Test { + * class Parent[@specialized(Int) T] + * + * def spec_method[@specialized(Int) T](t: T, expectedXSuper: String) = { + * class X extends Parent[T]() + * // even in the specialized variant, the local X class + * // doesn't extend Parent$mcI$sp, since its symbol has + * // been created after specialization and was not seen + * // by specialzation's info transformer. + * ... + * } + * } + * ``` + * We fix this by forcing duplication to take place before specialization. + * + * Note: The constructors phase (which also uses duplication) comes after erasure and uses the + * post-erasure typer => we must protect it from the beforeSpecialization phase shifting. + */ + class SpecializationDuplicator(casts: Map[Symbol, Type]) extends Duplicator(casts) { + override def retyped(context: Context, tree: Tree, oldThis: Symbol, newThis: Symbol, env: scala.collection.Map[Symbol, Type]): Tree = + enteringSpecialize(super.retyped(context, tree, oldThis, newThis, env)) } /** A tree symbol substituter that substitutes on type skolems. @@ -1664,7 +1692,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { val tree1 = deriveValDef(tree)(_ => body(symbol.alias).duplicate) debuglog("now typing: " + tree1 + " in " + tree.symbol.owner.fullName) - val d = new Duplicator(emptyEnv) + val d = new SpecializationDuplicator(emptyEnv) val newValDef = d.retyped( localTyper.context1.asInstanceOf[d.Context], tree1, @@ -1723,7 +1751,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { val symbol = tree.symbol val meth = addBody(tree, source) - val d = new Duplicator(castmap) + val d = new SpecializationDuplicator(castmap) debuglog("-->d DUPLICATING: " + meth) d.retyped( localTyper.context1.asInstanceOf[d.Context], diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala index bce0a077fb..baccdcf544 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala @@ -79,7 +79,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { def chainBefore(next: Tree)(casegen: Casegen): Tree } - trait NoNewBinders extends TreeMaker { + sealed trait NoNewBinders extends TreeMaker { protected val localSubstitution: Substitution = EmptySubstitution } @@ -105,12 +105,12 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { override def toString = "S"+ localSubstitution } - abstract class FunTreeMaker extends TreeMaker { + sealed abstract class FunTreeMaker extends TreeMaker { val nextBinder: Symbol def pos = nextBinder.pos } - abstract class CondTreeMaker extends FunTreeMaker { + sealed abstract class CondTreeMaker extends FunTreeMaker { val prevBinder: Symbol val nextBinderTp: Type val cond: Tree @@ -126,7 +126,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { // unless we're optimizing, emit local variable bindings for all subpatterns of extractor/case class patterns protected val debugInfoEmitVars = !settings.optimise.value - trait PreserveSubPatBinders extends TreeMaker { + sealed trait PreserveSubPatBinders extends TreeMaker { val subPatBinders: List[Symbol] val subPatRefs: List[Tree] val ignoredSubPatBinders: Set[Symbol] diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 7fa199afaf..81f5545695 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -21,13 +21,13 @@ trait ContextErrors { import global._ import definitions._ - abstract class AbsTypeError extends Throwable { + sealed abstract class AbsTypeError extends Throwable { def errPos: Position def errMsg: String override def toString() = "[Type error at:" + errPos + "] " + errMsg } - abstract class TreeTypeError extends AbsTypeError { + sealed abstract class TreeTypeError extends AbsTypeError { def underlyingTree: Tree def errPos = underlyingTree.pos } @@ -697,7 +697,7 @@ trait ContextErrors { protected def macroExpansionError(expandee: Tree, msg: String, pos: Position = NoPosition) = { def msgForLog = if (msg != null && (msg contains "exception during macro expansion")) msg.split(EOL).drop(1).headOption.getOrElse("?") else msg macroLogLite("macro expansion has failed: %s".format(msgForLog)) - if (msg != null) context.error(pos, msg) // issueTypeError(PosAndMsgTypeError(..)) won't work => swallows positions + if (msg != null) context.error(if (pos.isDefined) pos else expandee.pos, msg) // issueTypeError(PosAndMsgTypeError(..)) won't work => swallows positions setError(expandee) throw MacroExpansionException } @@ -741,7 +741,7 @@ trait ContextErrors { try { // [Eugene] is there a better way? // [Paul] See Exceptional.scala and Origins.scala. - val relevancyThreshold = realex.getStackTrace().indexWhere(_.getMethodName endsWith "macroExpand1") + val relevancyThreshold = realex.getStackTrace().indexWhere(_.getMethodName endsWith "macroExpandWithRuntime") if (relevancyThreshold == -1) None else { var relevantElements = realex.getStackTrace().take(relevancyThreshold + 1) @@ -782,13 +782,16 @@ trait ContextErrors { } def MacroExpansionHasInvalidTypeError(expandee: Tree, expanded: Any) = { + def isUnaffiliatedExpr = expanded.isInstanceOf[scala.reflect.api.Exprs#Expr[_]] + def isUnaffiliatedTree = expanded.isInstanceOf[scala.reflect.api.Trees#TreeApi] val expected = "expr or tree" - val isPathMismatch = expanded != null && expanded.isInstanceOf[scala.reflect.api.Exprs#Expr[_]] + val actual = if (isUnaffiliatedExpr) "an expr" else if (isUnaffiliatedTree) "a tree" else "unexpected" + val isPathMismatch = expanded != null && (isUnaffiliatedExpr || isUnaffiliatedTree) macroExpansionError(expandee, s"macro must return a compiler-specific $expected; returned value is " + ( if (expanded == null) "null" - else if (isPathMismatch) s" $expected, but it doesn't belong to this compiler" - else " of " + expanded.getClass + else if (isPathMismatch) s"$actual, but it doesn't belong to this compiler's universe" + else "of " + expanded.getClass )) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 1f4ff7cc2d..1f8f13ae02 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -99,22 +99,13 @@ trait Contexts { self: Analyzer => // there must be a scala.xml package when xml literals were parsed in this unit if (unit.hasXml && ScalaXmlPackage == NoSymbol) - unit.error(unit.firstXmlPos, "XML literals may only be used if the package scala.xml is present in the compilation classpath.") - - // TODO: remove the def below and drop `|| predefDefinesDollarScope` in the condition for `contextWithXML` - // as soon as 2.11.0-M4 is released and used as STARR (and $scope is no longer defined in Predef) - // Until then, to allow compiling quick with pre-2.11.0-M4 STARR, - // which relied on Predef defining `val $scope`, we've left it in place. - // Since the new scheme also imports $scope (as an alias for scala.xml.TopScope), - // we must check whether it is still there and not import the alias to avoid ambiguity. - // (All of this is only necessary to compile the full quick stage with STARR. - // if using locker, Predef.$scope is no longer needed.) - def predefDefinesDollarScope = definitions.getMemberIfDefined(PredefModule, nme.dollarScope) != NoSymbol - - // hack for the old xml library (detected by looking for scala.xml.TopScope, which needs to be in scope as $scope) - // import scala.xml.{TopScope => $scope} + unit.error(unit.firstXmlPos, "To compile XML syntax, the scala.xml package must be on the classpath.\nPlease see https://github.com/scala/scala/wiki/Scala-2.11#xml.") + + // scala-xml needs `scala.xml.TopScope` to be in scope globally as `$scope` + // We detect `scala-xml` by looking for `scala.xml.TopScope` and + // inject the equivalent of `import scala.xml.{TopScope => $scope}` val contextWithXML = - if (!unit.hasXml || ScalaXmlTopScope == NoSymbol || predefDefinesDollarScope) rootImportsContext + if (!unit.hasXml || ScalaXmlTopScope == NoSymbol) rootImportsContext else rootImportsContext.make(gen.mkImport(ScalaXmlPackage, nme.TopScope, nme.dollarScope)) val c = contextWithXML.make(tree, unit = unit) diff --git a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala index 95b771a8a5..0a2628b482 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala @@ -32,6 +32,7 @@ abstract class Duplicators extends Analyzer { envSubstitution = new SubstSkolemsTypeMap(env.keysIterator.toList, env.valuesIterator.toList) debuglog("retyped with env: " + env) + newBodyDuplicator(context).typed(tree) } @@ -365,7 +366,8 @@ abstract class Duplicators extends Analyzer { tree.symbol = NoSymbol // maybe we can find a more specific member in a subclass of Any (see AnyVal members, like ==) } val ntree = castType(tree, pt) - super.typed(ntree, mode, pt) + val res = super.typed(ntree, mode, pt) + res } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 86ba3d2164..6b9537e27d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -142,7 +142,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { case Literal(Constant(s: String)) => s case Literal(Constant(d: Double)) => d case Literal(Constant(b: Boolean)) => b - case Literal(Constant(i: Int)) => new Fingerprint(i) + case Literal(Constant(i: Int)) => Fingerprint(i) } def pickle(macroImplRef: Tree): Tree = { @@ -464,9 +464,9 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { /** Describes the role that the macro expandee is performing. */ - type MacroRole = String - final def APPLY_ROLE: MacroRole = "APPLY_ROLE" - private val roleNames = Map(APPLY_ROLE -> "apply") + type MacroRole = scala.tools.nsc.typechecker.MacroRole + final def APPLY_ROLE = MacroRole.Apply + final def UNAPPLY_ROLE = MacroRole.Unapply /** Performs macro expansion: * @@ -482,9 +482,10 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { * ========= Macro expansion ========= * * First of all `macroExpandXXX`: - * 1) If necessary desugars the `expandee` to fit into `macroExpand1` + * 1) If necessary desugars the `expandee` to fit into the default expansion scheme + * that is understood by `macroExpandWithRuntime` / `macroExpandWithoutRuntime` * - * Then `macroExpand1`: + * Then `macroExpandWithRuntime`: * 2) Checks whether the expansion needs to be delayed * 3) Loads macro implementation using `macroMirror` * 4) Synthesizes invocation arguments for the macro implementation @@ -532,26 +533,41 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { def summary() = s"expander = $this, expandee = ${showDetailed(expandee)}, desugared = ${if (expandee == desugared) () else showDetailed(desugared)}" if (macroDebugVerbose) println(s"macroExpand: ${summary()}") assert(allowExpandee(expandee), summary()) + linkExpandeeAndDesugared(expandee, desugared, role) val start = if (Statistics.canEnable) Statistics.startTimer(macroExpandNanos) else null if (Statistics.canEnable) Statistics.incCounter(macroExpandCount) try { - linkExpandeeAndDesugared(expandee, desugared, role) - macroExpand1(typer, desugared) match { - case Success(expanded) => - if (allowExpanded(expanded)) { - // also see http://groups.google.com/group/scala-internals/browse_thread/thread/492560d941b315cc - val expanded1 = try onSuccess(duplicateAndKeepPositions(expanded)) finally popMacroContext() - if (!hasMacroExpansionAttachment(expanded1)) linkExpandeeAndExpanded(expandee, expanded1) - if (allowResult(expanded1)) expanded1 else onFailure(expanded) - } else { - typer.TyperErrorGen.MacroInvalidExpansionError(expandee, roleNames(role), allowedExpansions) - onFailure(expanded) + withInfoLevel(nodePrinters.InfoLevel.Quiet) { // verbose printing might cause recursive macro expansions + if (expandee.symbol.isErroneous || (expandee exists (_.isErroneous))) { + val reason = if (expandee.symbol.isErroneous) "not found or incompatible macro implementation" else "erroneous arguments" + macroLogVerbose(s"cancelled macro expansion because of $reason: $expandee") + onFailure(typer.infer.setError(expandee)) + } else try { + val expanded = { + val runtime = macroRuntime(expandee.symbol) + if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime) + else macroExpandWithoutRuntime(typer, expandee) + } + expanded match { + case Success(expanded) => + if (allowExpanded(expanded)) { + // also see http://groups.google.com/group/scala-internals/browse_thread/thread/492560d941b315cc + val expanded1 = try onSuccess(duplicateAndKeepPositions(expanded)) finally popMacroContext() + if (!hasMacroExpansionAttachment(expanded1)) linkExpandeeAndExpanded(expandee, expanded1) + if (allowResult(expanded1)) expanded1 else onFailure(expanded) + } else { + typer.TyperErrorGen.MacroInvalidExpansionError(expandee, role.name, allowedExpansions) + onFailure(expanded) + } + case Fallback(fallback) => onFallback(fallback) + case Delayed(delayed) => onDelayed(delayed) + case Skipped(skipped) => onSkipped(skipped) + case Failure(failure) => onFailure(failure) } - case Fallback(fallback) => onFallback(fallback) - case Delayed(delayed) => onDelayed(delayed) - case Skipped(skipped) => onSkipped(skipped) - case Failure(failure) => onFailure(failure) + } catch { + case typer.TyperErrorGen.MacroExpansionException => onFailure(expandee) + } } } finally { if (Statistics.canEnable) Statistics.stopTimer(macroExpandNanos, start) @@ -622,8 +638,21 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { expander(expandee) } - /** Captures statuses of macro expansions performed by `macroExpand1'. + /** Expands a term macro used in unapply role as `u.Quasiquote(StringContext("", "")).q.unapply(x)` in `case q"$x" => ...`. + * @see MacroExpander */ + def macroExpandUnapply(typer: Typer, original: Tree, fun: Tree, unapply: Symbol, args: List[Tree], mode: Mode, pt: Type) = { + val expandee = treeCopy.Apply(original, gen.mkAttributedSelect(fun, unapply), args) + object expander extends TermMacroExpander(UNAPPLY_ROLE, typer, expandee, mode, pt) { + override def allowedExpansions: String = "unapply trees" + override def allowExpandee(expandee: Tree) = expandee.isInstanceOf[Apply] + private def unsupported(what: String) = abort("unapply macros currently don't support " + what) + override def onFallback(fallback: Tree) = unsupported("fallback") + override def onDelayed(delayed: Tree) = unsupported("advanced interaction with type inference") + } + expander(original) + } + private sealed abstract class MacroStatus(val result: Tree) private case class Success(expanded: Tree) extends MacroStatus(expanded) private case class Fallback(fallback: Tree) extends MacroStatus(fallback) { currentRun.seenMacroExpansionsFallingBack = true } @@ -632,28 +661,6 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { private case class Failure(failure: Tree) extends MacroStatus(failure) private def Delay(expanded: Tree) = Delayed(expanded) private def Skip(expanded: Tree) = Skipped(expanded) - private def Cancel(expandee: Tree) = 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): MacroStatus = { - // verbose printing might cause recursive macro expansions, so I'm shutting it down here - withInfoLevel(nodePrinters.InfoLevel.Quiet) { - if (expandee.symbol.isErroneous || (expandee exists (_.isErroneous))) { - val reason = if (expandee.symbol.isErroneous) "not found or incompatible macro implementation" else "erroneous arguments" - macroLogVerbose(s"cancelled macro expansion because of $reason: $expandee") - Cancel(typer.infer.setError(expandee)) - } - else try { - val runtime = macroRuntime(expandee.symbol) - if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime) - else macroExpandWithoutRuntime(typer, expandee) - } catch { - case typer.TyperErrorGen.MacroExpansionException => Failure(expandee) - } - } - } /** Expands a macro when a runtime (i.e. the macro implementation) can be successfully loaded * Meant for internal use within the macro infrastructure, don't use it elsewhere. @@ -804,7 +811,7 @@ object MacrosStats { val macroExpandNanos = Statistics.newSubTimer("time spent in macroExpand", typerNanos) } -class Fingerprint(val value: Int) extends AnyVal { +class Fingerprint private[Fingerprint](val value: Int) extends AnyVal { def paramPos = { assert(isTag, this); value } def isTag = value >= 0 def isOther = this == Other @@ -819,8 +826,18 @@ class Fingerprint(val value: Int) extends AnyVal { } object Fingerprint { + def apply(value: Int) = new Fingerprint(value) def Tagged(tparamPos: Int) = new Fingerprint(tparamPos) val Other = new Fingerprint(-1) val LiftedTyped = new Fingerprint(-2) val LiftedUntyped = new Fingerprint(-3) } + +class MacroRole private[MacroRole](val name: String) extends AnyVal { + override def toString = name +} + +object MacroRole { + val Apply = new MacroRole("apply") + val Unapply = new MacroRole("unapply") +} diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index 646bf3a153..546186479f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -259,7 +259,7 @@ trait MethodSynthesis { * So it's important that creating an instance of Derived does not have a side effect, * or if it has a side effect, control that it is done only once. */ - trait Derived { + sealed trait Derived { /** The tree from which we are deriving a synthetic member. Typically, that's * given as an argument of the instance. */ @@ -288,7 +288,7 @@ trait MethodSynthesis { def derivedTree: Tree } - trait DerivedFromMemberDef extends Derived { + sealed trait DerivedFromMemberDef extends Derived { def tree: MemberDef def enclClass: Symbol @@ -297,12 +297,12 @@ trait MethodSynthesis { final def basisSym = tree.symbol } - trait DerivedFromClassDef extends DerivedFromMemberDef { + sealed trait DerivedFromClassDef extends DerivedFromMemberDef { def tree: ClassDef final def enclClass = basisSym.owner.enclClass } - trait DerivedFromValDef extends DerivedFromMemberDef { + sealed trait DerivedFromValDef extends DerivedFromMemberDef { def tree: ValDef final def enclClass = basisSym.enclClass @@ -341,10 +341,10 @@ trait MethodSynthesis { logDerived(derivedTree) } } - trait DerivedGetter extends DerivedFromValDef { + sealed trait DerivedGetter extends DerivedFromValDef { // TODO } - trait DerivedSetter extends DerivedFromValDef { + sealed trait DerivedSetter extends DerivedFromValDef { override def isSetter = true private def setterParam = derivedSym.paramss match { case (p :: Nil) :: _ => p @@ -378,7 +378,7 @@ trait MethodSynthesis { def name: TermName = tree.name.toTermName } - abstract class BaseGetter(tree: ValDef) extends DerivedGetter { + sealed abstract class BaseGetter(tree: ValDef) extends DerivedGetter { def name = tree.name def category = GetterTargetClass def flagsMask = GetterFlags @@ -510,7 +510,7 @@ trait MethodSynthesis { def flagsExtra = 0 override def derivedSym = enclClass.info decl name } - trait AnyBeanGetter extends BeanAccessor with DerivedGetter { + sealed trait AnyBeanGetter extends BeanAccessor with DerivedGetter { def category = BeanGetterTargetClass override def validate() { if (derivedSym == NoSymbol) { diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 0305aab844..1282cfb416 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -1408,11 +1408,20 @@ trait Namers extends MethodSynthesis { if (!annotated.isInitialized) tree match { case defn: MemberDef => val ainfos = defn.mods.annotations filterNot (_ eq null) map { ann => + val ctx = typer.context + val annCtx = ctx.make(ann) + annCtx.setReportErrors() // need to be lazy, #1782. beforeTyper to allow inferView in annotation args, SI-5892. AnnotationInfo lazily { - val context1 = typer.context.make(ann) - context1.setReportErrors() - enteringTyper(newTyper(context1) typedAnnotation ann) + if (typer.context ne ctx) + log(sm"""|The var `typer.context` in ${Namer.this} was mutated before the annotation ${ann} was forced. + | + |current value = ${typer.context} + |original value = $ctx + | + |This confirms the hypothesis for the cause of SI-7603. If you see this message, please comment on that ticket.""") + + enteringTyper(newTyper(annCtx) typedAnnotation ann) } } if (ainfos.nonEmpty) { diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index cb3a12b60d..1a9a30c2ad 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1431,8 +1431,8 @@ trait Typers extends Adaptations with Tags { implRestriction(tree, "nested object") //see https://issues.scala-lang.org/browse/SI-6444 //see https://issues.scala-lang.org/browse/SI-6463 - case _: ClassDef => - implRestriction(tree, "nested class") + case cd: ClassDef if !cd.symbol.isAnonymousClass => // Don't warn about partial functions, etc. SI-7571 + implRestriction(tree, "nested class") // avoiding Type Tests that might check the $outer pointer. case Select(sup @ Super(qual, mix), selector) if selector != nme.CONSTRUCTOR && qual.symbol == clazz && mix != tpnme.EMPTY => //see https://issues.scala-lang.org/browse/SI-6483 implRestriction(sup, "qualified super reference") @@ -2797,16 +2797,11 @@ trait Typers extends Adaptations with Tags { if (numVparams > definitions.MaxFunctionArity) return MaxFunctionArityError(fun) - def decompose(pt: Type): (Symbol, List[Type], Type) = - if ((isFunctionType(pt) || (pt.typeSymbol == PartialFunctionClass && numVparams == 1 && fun.body.isInstanceOf[Match])) && // see bug901 for a reason why next conditions are needed - ( pt.dealiasWiden.typeArgs.length - 1 == numVparams - || fun.vparams.exists(_.tpt.isEmpty) - )) - (pt.typeSymbol, pt.dealiasWiden.typeArgs.init, pt.dealiasWiden.typeArgs.last) - else - (FunctionClass(numVparams), fun.vparams map (x => NoType), WildcardType) - - val (clazz, argpts, respt) = decompose(pt) + val FunctionSymbol = FunctionClass(numVparams) + val (argpts, respt) = pt baseType FunctionSymbol match { + case TypeRef(_, FunctionSymbol, args :+ res) => (args, res) + case _ => (fun.vparams map (_ => NoType), WildcardType) + } if (argpts.lengthCompare(numVparams) != 0) WrongNumberOfParametersError(fun, argpts) else { @@ -2856,7 +2851,7 @@ trait Typers extends Adaptations with Tags { val formals = vparamSyms map (_.tpe) val body1 = typed(fun.body, respt) val restpe = packedType(body1, fun.symbol).deconst.resultType - val funtpe = appliedType(clazz, formals :+ restpe: _*) + val funtpe = appliedType(FunctionSymbol, formals :+ restpe: _*) treeCopy.Function(fun, vparams, body1) setType funtpe } @@ -3385,8 +3380,9 @@ trait Typers extends Adaptations with Tags { if (!tree.isErrorTyped) setError(tree) else tree // @H change to setError(treeCopy.Apply(tree, fun, args)) - case otpe if mode.inPatternMode && unapplyMember(otpe).exists => - doTypedUnapply(tree, fun0, fun, args, mode, pt) + case ExtractorType(unapply) if mode.inPatternMode => + if (unapply == QuasiquoteClass_api_unapply) macroExpandUnapply(this, tree, fun, unapply, args, mode, pt) + else doTypedUnapply(tree, fun0, fun, args, mode, pt) case _ => if (treeInfo.isMacroApplication(tree)) duplErrorTree(MacroTooManyArgumentListsError(tree, fun.symbol)) diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index af3f772f79..47c859bb5c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -51,6 +51,14 @@ trait Unapplies extends ast.TreeDSL case NoSymbol => tp member nme.unapplySeq case unapp => unapp } + + object ExtractorType { + def unapply(tp: Type): Option[Symbol] = { + val member = unapplyMember(tp) + if (member.exists) Some(member) else None + } + } + /** returns unapply member's parameter type. */ def unapplyParameterType(extractor: Symbol) = extractor.tpe.params match { case p :: Nil => p.tpe.typeSymbol @@ -142,7 +150,7 @@ trait Unapplies extends ast.TreeDSL ModuleDef( Modifiers(cdef.mods.flags & AccessFlags | SYNTHETIC, cdef.mods.privateWithin), cdef.name.toTermName, - Template(parents, emptyValDef, NoMods, Nil, body, cdef.impl.pos.focus)) + gen.mkTemplate(parents, emptyValDef, NoMods, Nil, body, cdef.impl.pos.focus)) } private val caseMods = Modifiers(SYNTHETIC | CASE) diff --git a/src/compiler/scala/tools/nsc/util/CharArrayReader.scala b/src/compiler/scala/tools/nsc/util/CharArrayReader.scala index 5c6f525c6f..f116e4af34 100644 --- a/src/compiler/scala/tools/nsc/util/CharArrayReader.scala +++ b/src/compiler/scala/tools/nsc/util/CharArrayReader.scala @@ -8,15 +8,7 @@ package util import scala.reflect.internal.Chars._ -abstract class CharArrayReader { self => - - val buf: Array[Char] - - def decodeUni: Boolean = true - - /** An error routine to call on bad unicode escapes \\uxxxx. */ - protected def error(offset: Int, msg: String): Unit - +trait CharArrayReaderData { /** the last read character */ var ch: Char = _ @@ -29,7 +21,26 @@ abstract class CharArrayReader { self => /** The start offset of the line before the current one */ var lastLineStartOffset: Int = 0 - private var lastUnicodeOffset = -1 + protected var lastUnicodeOffset = -1 + + def copyFrom(cd: CharArrayReaderData): this.type = { + this.ch = cd.ch + this.charOffset = cd.charOffset + this.lineStartOffset = cd.lineStartOffset + this.lastLineStartOffset = cd.lastLineStartOffset + this.lastUnicodeOffset = cd.lastUnicodeOffset + this + } +} + +abstract class CharArrayReader extends CharArrayReaderData { self => + + val buf: Array[Char] + + def decodeUni: Boolean = true + + /** An error routine to call on bad unicode escapes \\uxxxx. */ + protected def error(offset: Int, msg: String): Unit /** Is last character a unicode escape \\uxxxx? */ def isUnicodeEscape = charOffset == lastUnicodeOffset diff --git a/src/compiler/scala/tools/nsc/util/CommandLine.scala b/src/compiler/scala/tools/nsc/util/CommandLine.scala deleted file mode 100644 index ef28f6dc53..0000000000 --- a/src/compiler/scala/tools/nsc/util/CommandLine.scala +++ /dev/null @@ -1,98 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2013 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package nsc.util - -import scala.collection.mutable.ListBuffer - -/** - * XXX Note this has been completely obsolesced by scala.tools.cmd. - * I checked it back in as part of rolling partest back a month - * rather than go down the rabbit hole of unravelling dependencies. - */ -case class CommandLine( - args: List[String], - unaryArguments: List[String], - binaryArguments: List[String] -) { - def this(args: List[String]) = this(args, Nil, Nil) - def this(args: Array[String]) = this(args.toList, Nil, Nil) - def this(line: String) = this(cmd.CommandLineParser tokenize line, Nil, Nil) - - def withUnaryArgs(xs: List[String]) = copy(unaryArguments = xs) - def withBinaryArgs(xs: List[String]) = copy(binaryArguments = xs) - - def assumeBinary = true - def enforceArity = true - def onlyKnownOptions = false - - val Terminator = "--" - val ValueForUnaryOption = "true" // so if --opt is given, x(--opt) = true - - def mapForUnary(opt: String) = Map(opt -> ValueForUnaryOption) - def errorFn(msg: String) = println(msg) - - /** argMap is option -> argument (or "" if it is a unary argument) - * residualArgs are what is left after removing the options and their args. - */ - lazy val (argMap, residualArgs) = { - val residualBuffer = new ListBuffer[String] - - def stripQuotes(s: String) = { - def isQuotedBy(c: Char) = s.length > 0 && s.head == c && s.last == c - if (List('"', '\'') exists isQuotedBy) s.tail.init else s - } - - def isValidOption(s: String) = !onlyKnownOptions || (unaryArguments contains s) || (binaryArguments contains s) - def isOption(s: String) = (s startsWith "-") && (isValidOption(s) || { unknownOption(s) ; false }) - def isUnary(s: String) = isOption(s) && (unaryArguments contains s) - def isBinary(s: String) = isOption(s) && !isUnary(s) && (assumeBinary || (binaryArguments contains s)) - - def unknownOption(opt: String) = - errorFn("Option '%s' not recognized.".format(opt)) - def missingArg(opt: String, what: String) = - errorFn("Option '%s' requires argument, found %s instead.".format(opt, what)) - - def loop(args: List[String]): Map[String, String] = { - def residual(xs: List[String]) = { residualBuffer ++= xs ; Map[String, String]() } - if (args.isEmpty) return Map() - val hd :: rest = args - if (rest.isEmpty) { - if (isBinary(hd) && enforceArity) - missingArg(hd, "EOF") - - if (isOption(hd)) mapForUnary(hd) else residual(args) - } - else - if (hd == Terminator) residual(rest) - else { - val hd1 :: hd2 :: rest = args - - if (hd2 == Terminator) mapForUnary(hd1) ++ residual(rest) - else if (isUnary(hd1)) mapForUnary(hd1) ++ loop(hd2 :: rest) - else if (isBinary(hd1)) { - // Disabling this check so - // --scalacopts "-verbose" works. We can't tell if it's quoted, - // the shell does us in. - // - // if (isOption(hd2) && enforceArity) - // missingArg(hd1, hd2) - - Map(hd1 -> hd2) ++ loop(rest) - } - else { residual(List(hd1)) ++ loop(hd2 :: rest) } - } - } - - (loop(args), residualBuffer map stripQuotes toList) - } - - def isSet(arg: String) = args contains arg - def get(arg: String) = argMap get arg - def apply(arg: String) = argMap(arg) - - override def toString() = "CommandLine(\n%s)\n" format (args map (" " + _ + "\n") mkString) -} diff --git a/src/compiler/scala/tools/nsc/util/TreeSet.scala b/src/compiler/scala/tools/nsc/util/TreeSet.scala deleted file mode 100644 index d2e9238e8f..0000000000 --- a/src/compiler/scala/tools/nsc/util/TreeSet.scala +++ /dev/null @@ -1,64 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Martin Odersky - */ - -package scala.tools.nsc -package util - -/** Sets implemented as binary trees. - * - * @author Martin Odersky - * @version 1.0 - */ -class TreeSet[T >: Null <: AnyRef](less: (T, T) => Boolean) extends Set[T] { - - private class Tree(val elem: T) { - var l: Tree = null - var r: Tree = null - } - - private var tree: Tree = null - - def findEntry(x: T): T = { - def find(t: Tree): T = { - if (t eq null) null - else if (less(x, t.elem)) find(t.l) - else if (less(t.elem, x)) find(t.r) - else t.elem - } - find(tree) - } - - def addEntry(x: T) { - def add(t: Tree): Tree = { - if (t eq null) new Tree(x) - else if (less(x, t.elem)) { t.l = add(t.l); t } - else if (less(t.elem, x)) { t.r = add(t.r); t } - else t - } - tree = add(tree) - } - - def iterator = toList.iterator - - override def foreach[U](f: T => U) { - def loop(t: Tree) { - if (t ne null) { - loop(t.l) - f(t.elem) - loop(t.r) - } - } - loop(tree) - } - override def toList = { - val xs = scala.collection.mutable.ListBuffer[T]() - foreach(xs += _) - xs.toList - } - - override def toString(): String = { - if (tree eq null) "<empty>" else "(..." + tree.elem + "...)" - } -} diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala index 5a0ff4f6db..ad1d4c896b 100644 --- a/src/compiler/scala/tools/reflect/FastTrack.scala +++ b/src/compiler/scala/tools/reflect/FastTrack.scala @@ -5,7 +5,7 @@ import scala.reflect.reify.Taggers import scala.tools.nsc.typechecker.{ Analyzer, Macros } import scala.reflect.runtime.Macros.currentMirror import scala.reflect.api.Universe -import scala.reflect.macros.compiler.DefaultMacroCompiler +import scala.tools.reflect.quasiquotes.{ Quasiquotes => QuasiquoteImpls } /** Optimizes system macro expansions by hardwiring them directly to their implementations * bypassing standard reflective load and invoke to avoid the overhead of Java/Scala reflection. @@ -22,6 +22,8 @@ trait FastTrack { new { val c: c0.type = c0 } with Taggers private implicit def context2macroimplementations(c0: MacroContext): MacroImplementations { val c: c0.type } = new { val c: c0.type = c0 } with MacroImplementations + private implicit def context2quasiquote(c0: MacroContext): QuasiquoteImpls { val c: c0.type } = + new { val c: c0.type = c0 } with QuasiquoteImpls private def make(sym: Symbol)(pf: PartialFunction[Applied, MacroContext => Tree]) = sym -> new FastTrackEntry(pf) @@ -41,6 +43,8 @@ trait FastTrack { make( materializeTypeTag) { case Applied(_, ttag :: Nil, (u :: _) :: _) => _.materializeTypeTag(u, EmptyTree, ttag.tpe, concrete = true) }, make( ApiUniverseReify) { case Applied(_, ttag :: Nil, (expr :: _) :: _) => c => c.materializeExpr(c.prefix.tree, EmptyTree, expr) }, make( StringContext_f) { case Applied(Select(Apply(_, ps), _), _, args) => c => c.macro_StringInterpolation_f(ps, args.flatten, c.expandee.pos) }, - make(ReflectRuntimeCurrentMirror) { case _ => c => currentMirror(c).tree } + make(ReflectRuntimeCurrentMirror) { case _ => c => currentMirror(c).tree }, + make( QuasiquoteClass_api_apply) { case _ => _.expandQuasiquote }, + make(QuasiquoteClass_api_unapply) { case _ => _.expandQuasiquote } ) } diff --git a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala index c53d10bd87..afaca3396c 100644 --- a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala +++ b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala @@ -217,7 +217,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => val moduledef = ModuleDef( obj, - Template( + gen.mkTemplate( List(TypeTree(ObjectTpe)), emptyValDef, NoMods, diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala b/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala new file mode 100644 index 0000000000..9d171d52d2 --- /dev/null +++ b/src/compiler/scala/tools/reflect/quasiquotes/Holes.scala @@ -0,0 +1,187 @@ +package scala.tools.reflect +package quasiquotes + +import scala.collection.{immutable, mutable} +import scala.reflect.internal.Flags._ + +class Cardinality private[Cardinality](val value: Int) extends AnyVal { + def pred = { assert(value - 1 >= 0); new Cardinality(value - 1) } + def succ = new Cardinality(value + 1) + override def toString = if (value == 0) "no dots" else "." * (value + 1) +} + +object Cardinality { + val NoDot = new Cardinality(0) + val DotDot = new Cardinality(1) + val DotDotDot = new Cardinality(2) + object Dot { def unapply(card: Cardinality) = card != NoDot } + def parseDots(part: String) = { + if (part.endsWith("...")) (part.stripSuffix("..."), DotDotDot) + else if (part.endsWith("..")) (part.stripSuffix(".."), DotDot) + else (part, NoDot) + } +} + +/** Defines abstractions that provide support for splicing into Scala syntax. + */ +trait Holes { self: Quasiquotes => + import global._ + import Cardinality._ + import definitions._ + import universeTypes._ + + /** Location characterizes a kind of a non-terminal in Scala syntax where something is going to be spliced. + * A location is typically associated with a type of the things that can be spliced there. + * Associated type might be different from an actual tpe of a splicee due to lifting. + * This is the first pillar of modularity in the quasiquote reifier. + */ + sealed abstract class Location(val tpe: Type) + case object UnknownLocation extends Location(NoType) + case class TreeLocation(override val tpe: Type) extends Location(tpe) + case object NameLocation extends Location(nameType) + case object ModsLocation extends Location(modsType) + case object FlagsLocation extends Location(flagsType) + case object SymbolLocation extends Location(symbolType) + case class IterableLocation(card: Cardinality, sublocation: TreeLocation) extends Location(NoType) { + override val tpe = { + def loop(n: Cardinality, tpe: Type): Type = + if (n == NoDot) tpe + else appliedType(IterableClass.toType, List(loop(n.pred, tpe))) + loop(card, sublocation.tpe) + } + } + + /** Hole type describes location, cardinality and a pre-reification routine associated with a hole. + * An interesting thing about HoleType is that it can be completely inferred from the type of the splicee. + * This is the second pillar of modularity in the quasiquote reifier. + */ + case class HoleType(preprocessor: Tree => Tree, location: Location, cardinality: Cardinality) { + def makeHole(tree: Tree) = Hole(preprocessor(tree), location, cardinality) + } + object HoleType { + def unapply(tpe: Type): Option[HoleType] = tpe match { + case NativeType(holeTpe) => Some(holeTpe) + case LiftableType(holeTpe) => Some(holeTpe) + case IterableTreeType(holeTpe) => Some(holeTpe) + case IterableLiftableType(holeTpe) => Some(holeTpe) + case _ => None + } + + trait HoleTypeExtractor { + def unapply(tpe: Type): Option[HoleType] = { + for { + preprocessor <- this.preprocessor(tpe) + location <- this.location(tpe) + cardinality <- Some(this.cardinality(tpe)) + } yield HoleType(preprocessor, location, cardinality) + } + def preprocessor(tpe: Type): Option[Tree => Tree] + def location(tpe: Type): Option[Location] + def cardinality(tpe: Type): Cardinality = parseCardinality(tpe)._1 + + def lifter(tpe: Type): Option[Tree => Tree] = { + val lifterTpe = appliedType(LiftableClass.toType, List(tpe)) + val lifter = c.inferImplicitValue(lifterTpe, silent = true) + if (lifter != EmptyTree) Some(tree => { + val lifted = Apply(lifter, List(u, tree)) + val targetType = Select(u, tpnme.Tree) + atPos(tree.pos)(TypeApply(Select(lifted, nme.asInstanceOf_), List(targetType))) + }) else None + } + + def iterator(tpe: Type)(elementTransform: Tree => Tree): Option[Tree => Tree] = { + def reifyIterable(tree: Tree, n: Cardinality): Tree = { + def loop(tree: Tree, n: Cardinality) = + if (n == NoDot) elementTransform(tree) + else { + val x: TermName = c.freshName() + val wrapped = reifyIterable(Ident(x), n.pred) + val xToWrapped = Function(List(ValDef(Modifiers(PARAM), x, TypeTree(), EmptyTree)), wrapped) + Select(Apply(Select(tree, nme.map), List(xToWrapped)), nme.toList) + } + if (tree.tpe != null && (tree.tpe <:< listTreeType || tree.tpe <:< listListTreeType)) tree + else atPos(tree.pos)(loop(tree, n)) + } + val (card, elementTpe) = parseCardinality(tpe) + if (card != NoDot) Some(reifyIterable(_, card)) else None + } + } + + object NativeType extends HoleTypeExtractor { + def preprocessor(tpe: Type) = Some(identity) + def location(tpe: Type) = { + if (tpe <:< treeType) Some(TreeLocation(tpe)) + else if (tpe <:< nameType) Some(NameLocation) + else if (tpe <:< modsType) Some(ModsLocation) + else if (tpe <:< flagsType) Some(FlagsLocation) + else if (tpe <:< symbolType) Some(SymbolLocation) + else None + } + } + + object LiftableType extends HoleTypeExtractor { + def preprocessor(tpe: Type) = lifter(tpe) + def location(tpe: Type) = Some(TreeLocation(treeType)) + } + + object IterableTreeType extends HoleTypeExtractor { + def preprocessor(tpe: Type) = iterator(tpe)(identity) + def location(tpe: Type) = { + val (card, elementTpe) = parseCardinality(tpe) + if (card != NoDot && elementTpe <:< treeType) Some(IterableLocation(card, TreeLocation(elementTpe))) + else None + } + } + + object IterableLiftableType extends HoleTypeExtractor { + def preprocessor(tpe: Type) = { + val (_, elementTpe) = parseCardinality(tpe) + for { + lifter <- this.lifter(elementTpe) + iterator <- this.iterator(tpe)(lifter) + } yield iterator + } + def location(tpe: Type) = Some(IterableLocation(cardinality(tpe), TreeLocation(treeType))) + } + } + + /** Hole encapsulates information about splices in quasiquotes. + * It packs together a cardinality of a splice, a splicee (possibly preprocessed) + * and the description of the location in Scala syntax where the splicee can be spliced. + * This is the third pillar of modularity in the quasiquote reifier. + */ + case class Hole(tree: Tree, location: Location, cardinality: Cardinality) + + object Hole { + def apply(splicee: Tree, holeCard: Cardinality): Hole = { + if (splicee.tpe == null) return new Hole(splicee, UnknownLocation, holeCard) + val (spliceeCard, elementTpe) = parseCardinality(splicee.tpe) + def cantSplice() = { + val holeCardMsg = if (holeCard != NoDot) s" with $holeCard" else "" + val action = "splice " + splicee.tpe + holeCardMsg + val suggestCard = holeCard != spliceeCard || holeCard != NoDot + val spliceeCardMsg = if (holeCard != spliceeCard && spliceeCard != NoDot) s"using $spliceeCard" else "omitting the dots" + val cardSuggestion = if (suggestCard) spliceeCardMsg else "" + def canBeLifted(tpe: Type) = HoleType.LiftableType.unapply(tpe).nonEmpty + val suggestLifting = (holeCard == NoDot || spliceeCard != NoDot) && !(elementTpe <:< treeType) && !canBeLifted(elementTpe) + val liftedTpe = if (holeCard != NoDot) elementTpe else splicee.tpe + val liftSuggestion = if (suggestLifting) s"providing an implicit instance of Liftable[$liftedTpe]" else "" + val advice = List(cardSuggestion, liftSuggestion).filter(_ != "").mkString(" or ") + c.abort(splicee.pos, s"Can't $action, consider $advice") + } + val holeTpe = splicee.tpe match { + case _ if holeCard != spliceeCard => cantSplice() + case HoleType(holeTpe) => holeTpe + case _ => cantSplice() + } + holeTpe.makeHole(splicee) + } + } + + def parseCardinality(tpe: Type): (Cardinality, Type) = { + if (tpe != null && isIterableType(tpe)) { + val (card, innerTpe) = parseCardinality(tpe.typeArguments.head) + (card.succ, innerTpe) + } else (NoDot, tpe) + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala new file mode 100644 index 0000000000..9a6ba56c18 --- /dev/null +++ b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala @@ -0,0 +1,134 @@ +package scala.tools.reflect +package quasiquotes + +import scala.tools.nsc.ast.parser.{Parsers => ScalaParser} +import scala.tools.nsc.ast.parser.Tokens._ +import scala.compat.Platform.EOL +import scala.reflect.internal.util.{BatchSourceFile, SourceFile} +import scala.collection.mutable.ListBuffer + +/** Builds upon the vanilla Scala parser and teams up together with Placeholders.scala to emulate holes. + * A principled solution to splicing into Scala syntax would be a parser that natively supports holes. + * Unfortunately, that's outside of our reach in Scala 2.11, so we have to emulate. + */ +trait Parsers { self: Quasiquotes => + import global._ + + abstract class Parser extends { + val global: self.global.type = self.global + } with ScalaParser { + /** Wraps given code to obtain a desired parser mode. + * This way we can just re-use standard parser entry point. + */ + def wrapCode(code: String): String = + s"object wrapper { self => $EOL $code $EOL }" + + def unwrapTree(wrappedTree: Tree): Tree = { + val PackageDef(_, List(ModuleDef(_, _, Template(_, _, _ :: parsed)))) = wrappedTree + parsed match { + case tree :: Nil => tree + case stats :+ tree => Block(stats, tree) + } + } + + def parse(code: String): Tree = { + try { + val wrapped = wrapCode(code) + debug(s"wrapped code\n=${wrapped}\n") + val file = new BatchSourceFile(nme.QUASIQUOTE_FILE, wrapped) + val tree = new QuasiquoteParser(file).parse() + unwrapTree(tree) + } catch { + case mi: MalformedInput => c.abort(c.macroApplication.pos, s"syntax error: ${mi.msg}") + } + } + + class QuasiquoteParser(source0: SourceFile) extends SourceFileParser(source0) { + override val treeBuilder = new ParserTreeBuilder { + // q"(..$xs)" + override def makeTupleTerm(trees: List[Tree], flattenUnary: Boolean): Tree = + Apply(Ident(nme.QUASIQUOTE_TUPLE), trees) + + // tq"(..$xs)" + override def makeTupleType(trees: List[Tree], flattenUnary: Boolean): Tree = + AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), trees) + + // q"{ $x }" + override def makeBlock(stats: List[Tree]): Tree = stats match { + case (head @ Ident(name)) :: Nil if holeMap.contains(name) => Block(Nil, head) + case _ => super.makeBlock(stats) + } + } + import treeBuilder.{global => _, _} + + // q"def foo($x)" + override def allowTypelessParams = true + + // q"foo match { case $x }" + override def caseClause(): CaseDef = + if (isHole && lookingAhead { in.token == CASE || in.token == RBRACE || in.token == SEMI }) { + val c = makeCaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), List(Ident(ident()))), EmptyTree, EmptyTree) + while (in.token == SEMI) in.nextToken() + c + } else + super.caseClause() + + def isHole = isIdent && holeMap.contains(in.name) + + override def isAnnotation: Boolean = super.isAnnotation || (isHole && lookingAhead { isAnnotation }) + + override def isModifier: Boolean = super.isModifier || (isHole && lookingAhead { isModifier }) + + override def isLocalModifier: Boolean = super.isLocalModifier || (isHole && lookingAhead { isLocalModifier }) + + override def isTemplateIntro: Boolean = super.isTemplateIntro || (isHole && lookingAhead { isTemplateIntro }) + + override def isDclIntro: Boolean = super.isDclIntro || (isHole && lookingAhead { isDclIntro }) + + // $mods def foo + // $mods T + override def readAnnots(annot: => Tree): List[Tree] = in.token match { + case AT => + in.nextToken() + annot :: readAnnots(annot) + case _ if isHole && lookingAhead { in.token == AT || isModifier || isDefIntro || isIdent} => + val ann = Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(in.name.toString)))) + in.nextToken() + ann :: readAnnots(annot) + case _ => + Nil + } + } + } + + object TermParser extends Parser + + object CaseParser extends Parser { + override def wrapCode(code: String) = super.wrapCode("something match { case " + code + " }") + + override def unwrapTree(wrappedTree: Tree): Tree = { + val Match(_, head :: tail) = super.unwrapTree(wrappedTree) + if (tail.nonEmpty) + c.abort(c.macroApplication.pos, "Can't parse more than one casedef, consider generating a match tree instead") + head + } + } + + object PatternParser extends Parser { + override def wrapCode(code: String) = super.wrapCode("something match { case " + code + " => }") + + override def unwrapTree(wrappedTree: Tree): Tree = { + val Match(_, List(CaseDef(pat, _, _))) = super.unwrapTree(wrappedTree) + pat + } + } + + object TypeParser extends Parser { + override def wrapCode(code: String) = super.wrapCode("type T = " + code) + + override def unwrapTree(wrappedTree: Tree): Tree = { + val TypeDef(_, _, _, rhs) = super.unwrapTree(wrappedTree) + rhs + } + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala b/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala new file mode 100644 index 0000000000..b680c25f76 --- /dev/null +++ b/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala @@ -0,0 +1,123 @@ +package scala.tools.reflect +package quasiquotes + +import java.util.UUID.randomUUID +import scala.collection.{immutable, mutable} + +/** Emulates hole support (see Holes.scala) in the quasiquote parser (see Parsers.scala). + * A principled solution to splicing into Scala syntax would be a parser that natively supports holes. + * Unfortunately, that's outside of our reach in Scala 2.11, so we have to emulate. + * This trait stores knowledge of how to represent the holes as something understandable by the parser + * and how to recover holes from the results of parsing the produced representation. + */ +trait Placeholders { self: Quasiquotes => + import global._ + import Cardinality._ + + // Step 1: Transform Scala source with holes into vanilla Scala source + + lazy val holeMap = new HoleMap() + lazy val code = { + val sb = new StringBuilder() + val sessionSuffix = randomUUID().toString.replace("-", "").substring(0, 8) + "$" + + foreach2(args, parts.init) { (tree, p) => + val (part, cardinality) = parseDots(p) + val placeholderName = c.freshName(TermName(nme.QUASIQUOTE_PREFIX + sessionSuffix)) + sb.append(part) + sb.append(placeholderName) + holeMap(placeholderName) = Hole(tree, cardinality) + } + sb.append(parts.last) + + sb.toString + } + + class HoleMap { + private val underlying = mutable.ListMap[String, Hole]() + private val accessed = mutable.Set[String]() + def unused: Set[Name] = (underlying.keys.toSet -- accessed).map(TermName(_)) + def contains(key: Name) = underlying.contains(key.toString) + def apply(key: Name) = { + val s = key.toString + accessed += s + underlying(s) + } + def update(key: Name, hole: Hole) = { + underlying += key.toString -> hole + } + def get(key: Name) = { + val s = key.toString + accessed += s + underlying.get(s) + } + } + + // Step 2: Transform vanilla Scala AST into an AST with holes + + trait HolePlaceholder { + def matching: PartialFunction[Any, Name] + def unapply(scrutinee: Any): Option[(Tree, Location, Cardinality)] = { + val name = matching.lift(scrutinee) + name.flatMap { holeMap.get(_).map { case Hole(repr, loc, card) => (repr, loc, card) } } + } + } + + object Placeholder extends HolePlaceholder { + def matching = { + case name: Name => name + case Ident(name) => name + case Bind(name, Ident(nme.WILDCARD)) => name + case TypeDef(_, name, List(), TypeBoundsTree(EmptyTree, EmptyTree)) => name + case ValDef(_, name, TypeTree(), EmptyTree) => name + } + } + + object ModsPlaceholder extends HolePlaceholder { + def matching = { + case Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(s: String)))) => TermName(s) + } + } + + object AnnotPlaceholder { + def unapply(tree: Tree): Option[(Tree, Location, Cardinality, List[Tree])] = tree match { + case Apply(Select(New(Placeholder(tree, loc, card)), nme.CONSTRUCTOR), args) => Some(tree, loc, card, args) + case _ => None + } + } + + object TuplePlaceholder { + def unapply(tree: Tree): Option[List[Tree]] = tree match { + case Apply(Ident(nme.QUASIQUOTE_TUPLE), args) => Some(args) + case _ => None + } + } + + object TupleTypePlaceholder { + def unapply(tree: Tree): Option[List[Tree]] = tree match { + case AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), args) => Some(args) + case _ => None + } + } + + object SymbolPlaceholder { + def unapply(scrutinee: Any): Option[Tree] = scrutinee match { + case Placeholder(tree, SymbolLocation, _) => Some(tree) + case _ => None + } + } + + object CasePlaceholder { + def unapply(tree: Tree): Option[(Tree, Location, Cardinality)] = tree match { + case CaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), List(Placeholder(tree, location, card))), EmptyTree, EmptyTree) => Some((tree, location, card)) + case _ => None + } + } + + object ClassPlaceholder { + def unapply(tree: Tree): Option[Tree] = tree match { + case ClassDef(_, _, _, _) => Some(tree) + case _ => None + } + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala b/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala new file mode 100644 index 0000000000..fe954e0bfd --- /dev/null +++ b/src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala @@ -0,0 +1,51 @@ +package scala.tools.reflect +package quasiquotes + +import scala.reflect.macros.runtime.Context + +abstract class Quasiquotes extends Parsers + with Holes + with Placeholders + with Reifiers { + val c: Context + val global: c.universe.type = c.universe + import c.universe._ + + def debug(msg: String): Unit = + if (settings.Yquasiquotedebug.value) println(msg) + + lazy val (universe: Tree, args, parts, parse, reify) = c.macroApplication match { + case Apply(Select(Select(Apply(Select(universe0, _), List(Apply(_, parts0))), interpolator0), method0), args0) => + val parts1 = parts0.map { + case Literal(Constant(s: String)) => s + case part => c.abort(part.pos, "Quasiquotes can only be used with literal strings") + } + val reify0 = method0 match { + case nme.apply => new ApplyReifier().reifyFillingHoles(_) + case nme.unapply => new UnapplyReifier().reifyFillingHoles(_) + case other => global.abort(s"Unknown quasiquote api method: $other") + } + val parse0 = interpolator0 match { + case nme.q => TermParser.parse(_) + case nme.tq => TypeParser.parse(_) + case nme.cq => CaseParser.parse(_) + case nme.pq => PatternParser.parse(_) + case other => global.abort(s"Unknown quasiquote flavor: $other") + } + (universe0, args0, parts1, parse0, reify0) + case _ => + global.abort(s"Couldn't parse call prefix tree ${c.macroApplication}.") + } + + lazy val u = universe // shortcut + lazy val universeTypes = new definitions.UniverseDependentTypes(universe) + + def expandQuasiquote = { + debug(s"\ncode to parse=\n$code\n") + val tree = parse(code) + debug(s"parsed tree\n=${tree}\n=${showRaw(tree)}\n") + val reified = reify(tree) + debug(s"reified tree\n=${reified}\n=${showRaw(reified)}\n") + reified + } +} diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala new file mode 100644 index 0000000000..ec113036a3 --- /dev/null +++ b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala @@ -0,0 +1,290 @@ +package scala.tools.reflect +package quasiquotes + +import java.lang.UnsupportedOperationException +import scala.reflect.reify.{Reifier => ReflectReifier} +import scala.reflect.internal.Flags._ + +trait Reifiers { self: Quasiquotes => + import global._ + import global.build.SyntacticClassDef + import global.treeInfo._ + import global.definitions._ + import Cardinality._ + import universeTypes._ + + abstract class Reifier extends { + val global: self.global.type = self.global + } with ReflectReifier { + val reifee = EmptyTree + val universe = self.universe + val mirror = EmptyTree + val concrete = false + lazy val typer = throw new UnsupportedOperationException + + def isReifyingExpressions: Boolean + def isReifyingPatterns: Boolean = !isReifyingExpressions + def action = if (isReifyingExpressions) "splice" else "extract" + def holesHaveTypes = isReifyingExpressions + + def reifyFillingHoles(tree: Tree): Tree = { + val reified = reifyTree(tree) + holeMap.unused.foreach { hole => + c.abort(holeMap(hole).tree.pos, s"Don't know how to $action here") + } + reified + } + + override def reifyTree(tree: Tree): Tree = { + val reified = + reifyTreePlaceholder(tree) orElse + reifyTreeSyntactically(tree) + //println(s"reified ${showRaw(tree)} as $reified") + reified + } + + def reifyTreePlaceholder(tree: Tree): Tree = tree match { + case Placeholder(tree, TreeLocation(_), _) if isReifyingExpressions => tree + case Placeholder(tree, _, NoDot) if isReifyingPatterns => tree + case Placeholder(tree, _, card @ Dot()) => c.abort(tree.pos, s"Can't $action with $card here") + case TuplePlaceholder(args) => reifyTuple(args) + case TupleTypePlaceholder(args) => reifyTupleType(args) + case CasePlaceholder(tree, location, _) => reifyCase(tree, location) + case ClassPlaceholder(tree) => reifyClass(tree) + case _ => EmptyTree + } + + override def reifyName(name: Name): Tree = name match { + case Placeholder(tree, location, _) => + if (holesHaveTypes && !(location.tpe <:< nameType)) c.abort(tree.pos, s"$nameType expected but ${location.tpe} found") + tree + case _ => + super.reifyName(name) + } + + def reifyCase(tree: Tree, location: Location) = { + if (holesHaveTypes && !(location.tpe <:< caseDefType)) c.abort(tree.pos, s"$caseDefType expected but ${location.tpe} found") + tree + } + + def reifyTuple(args: List[Tree]) = args match { + case Nil => reify(Literal(Constant(()))) + case List(hole @ Placeholder(_, _, NoDot)) => reify(hole) + case List(Placeholder(_, _, _)) => reifyBuildCall(nme.TupleN, args) + // in a case we only have one element tuple without + // any cardinality annotations this means that this is + // just an expression wrapped in parentheses + case List(other) => reify(other) + case _ => reifyBuildCall(nme.TupleN, args) + } + + def reifyTupleType(args: List[Tree]) = args match { + case Nil => reify(Select(Ident(nme.scala_), tpnme.Unit)) + case List(hole @ Placeholder(_, _, NoDot)) => reify(hole) + case List(Placeholder(_, _, _)) => reifyBuildCall(nme.TupleTypeN, args) + case List(other) => reify(other) + case _ => reifyBuildCall(nme.TupleTypeN, args) + } + + def reifyClass(tree: Tree) = { + val SyntacticClassDef(mods, name, tparams, constrmods, argss, parents, selfval, body) = tree + reifyBuildCall(nme.SyntacticClassDef, mods, name, tparams, constrmods, argss, parents, selfval, body) + } + + /** Splits list into a list of groups where subsequent elements are considered + * similar by the corresponding function. + * + * Example: + * + * > group(List(1, 1, 0, 0, 1, 0)) { _ == _ } + * List(List(1, 1), List(0, 0), List(1), List(0)) + * + */ + def group[T](lst: List[T])(similar: (T, T) => Boolean) = lst.foldLeft[List[List[T]]](List()) { + case (Nil, el) => List(List(el)) + case (ll :+ (last @ (lastinit :+ lastel)), el) if similar(lastel, el) => ll :+ (last :+ el) + case (ll, el) => ll :+ List(el) + } + + /** Reifies list filling all the valid holeMap. + * + * Reification of non-trivial list is done in two steps: + * + * 1. split the list into groups where every placeholder is always + * put in a group of it's own and all subsquent non-holeMap are + * grouped together; element is considered to be a placeholder if it's + * in the domain of the fill function; + * + * 2. fold the groups into a sequence of lists added together with ++ using + * fill reification for holeMapĀ and fallback reification for non-holeMap. + * + * Example: + * + * reifyMultiCardinalityList(lst) { + * // first we define patterns that extract high-cardinality holeMap (currently ..) + * case Placeholder(CorrespondsTo(tree, tpe)) if tpe <:< iterableTreeType => tree + * } { + * // in the end we define how single elements are reified, typically with default reify call + * reify(_) + * } + * + * Sample execution of previous concrete list reifier: + * + * > val lst = List(foo, bar, qq$f3948f9s$1) + * > reifyMultiCardinalityList(lst) { ... } { ... } + * q"List($foo, $bar) ++ ${holeMap(qq$f3948f9s$1).tree}" + */ + def reifyMultiCardinalityList[T](xs: List[T])(fill: PartialFunction[T, Tree])(fallback: T => Tree): Tree + + /** Reifies arbitrary list filling ..$x and ...$y holeMap when they are put + * in the correct position. Fallbacks to regular reification for non-high cardinality + * elements. + */ + override def reifyList(xs: List[Any]): Tree = reifyMultiCardinalityList(xs) { + case Placeholder(tree, _, DotDot) => tree + case CasePlaceholder(tree, _, DotDot) => tree + case List(Placeholder(tree, _, DotDotDot)) => tree + } { + reify(_) + } + + def reifyAnnotList(annots: List[Tree]): Tree + + def ensureNoExplicitFlags(m: Modifiers, pos: Position) = + if ((m.flags & ExplicitFlags) != 0L) c.abort(pos, s"Can't $action modifiers together with flags, consider merging flags into modifiers") + + override def mirrorSelect(name: String): Tree = + Select(universe, TermName(name)) + + override def mirrorCall(name: TermName, args: Tree*): Tree = + Apply(Select(universe, name), args.toList) + + override def mirrorBuildCall(name: TermName, args: Tree*): Tree = + Apply(Select(Select(universe, nme.build), name), args.toList) + } + + class ApplyReifier extends Reifier { + def isReifyingExpressions = true + + override def reifyTreeSyntactically(tree: Tree): Tree = tree match { + case Block(stats, p @ Placeholder(_, _, _)) => reifyBuildCall(nme.Block, stats :+ p) + case Apply(f, List(Placeholder(argss, _, DotDotDot))) => reifyCallWithArgss(f, argss) + case RefTree(qual, SymbolPlaceholder(tree)) => mirrorBuildCall(nme.RefTree, reify(qual), tree) + case _ => super.reifyTreeSyntactically(tree) + } + + def reifyCallWithArgss(f: Tree, argss: Tree) = { + val f1 = reifyTree(f) + val foldLeftF1 = Apply(TypeApply(Select(argss, nme.foldLeft), List(Select(u, tpnme.Tree))), List(f1)) + val uDotApply = Function( + List(gen.mkSyntheticParam(nme.x_1), gen.mkSyntheticParam(nme.x_2)), + Apply(Select(u, nme.Apply), List(Ident(nme.x_1), Ident(nme.x_2)))) + Apply(foldLeftF1, List(uDotApply)) + } + + override def reifyMultiCardinalityList[T](xs: List[T])(fill: PartialFunction[T, Tree])(fallback: T => Tree): Tree = xs match { + case Nil => mkList(Nil) + case _ => + def reifyGroup(group: List[T]): Tree = group match { + case List(elem) if fill.isDefinedAt(elem) => fill(elem) + case elems => mkList(elems.map(fallback)) + } + val head :: tail = group(xs) { (a, b) => !fill.isDefinedAt(a) && !fill.isDefinedAt(b) } + tail.foldLeft[Tree](reifyGroup(head)) { (tree, lst) => Apply(Select(tree, nme.PLUSPLUS), List(reifyGroup(lst))) } + } + + override def reifyAnnotList(annots: List[Tree]): Tree = reifyMultiCardinalityList(annots) { + case AnnotPlaceholder(tree, _, DotDot, args) => + val x: TermName = c.freshName() + val xToAnnotationCtor = Function( + List(ValDef(Modifiers(PARAM), x, TypeTree(), EmptyTree)), + mirrorBuildCall(nme.mkAnnotationCtor, Ident(x), reify(args))) + Apply(Select(tree, nme.map), List(xToAnnotationCtor)) + } { + case AnnotPlaceholder(tree, _: TreeLocation, _, args) => + mirrorBuildCall(nme.mkAnnotationCtor, tree, reify(args)) + case other => reify(other) + } + + override def reifyModifiers(m: Modifiers) = { + val (modsPlaceholders, annots) = m.annotations.partition { + case ModsPlaceholder(_, _, _) => true + case _ => false + } + val (mods, flags) = modsPlaceholders.map { + case ModsPlaceholder(tree, location, card) => (tree, location) + }.partition { case (tree, location) => + location match { + case ModsLocation => true + case FlagsLocation => false + case _ => c.abort(tree.pos, s"$flagsType or $modsType expected but ${tree.tpe} found") + } + } + mods match { + case (tree, _) :: Nil => + if (flags.nonEmpty) c.abort(flags(0)._1.pos, "Can't splice flags together with modifiers, consider merging flags into modifiers") + if (annots.nonEmpty) c.abort(tree.pos, "Can't splice modifiers together with annotations, consider merging annotations into modifiers") + ensureNoExplicitFlags(m, tree.pos) + tree + case _ :: (second, _) :: Nil => + c.abort(second.pos, "Can't splice multiple modifiers, consider merging them into a single modifiers instance") + case _ => + val baseFlags = reifyBuildCall(nme.flagsFromBits, m.flags) + val reifiedFlags = flags.foldLeft[Tree](baseFlags) { case (flag, (tree, _)) => Apply(Select(flag, nme.OR), List(tree)) } + mirrorFactoryCall(nme.Modifiers, reifiedFlags, reify(m.privateWithin), reifyAnnotList(annots)) + } + } + } + + class UnapplyReifier extends Reifier { + def isReifyingExpressions = false + + override def reifyTreeSyntactically(tree: Tree): Tree = tree match { + case treeInfo.Applied(fun, Nil, argss) if fun != tree && !tree.isInstanceOf[AppliedTypeTree] => + reifyBuildCall(nme.Applied, fun, argss) + case treeInfo.Applied(fun, targs, argss) if fun != tree & !tree.isInstanceOf[AppliedTypeTree] => + mirrorBuildCall(nme.Applied, reifyBuildCall(nme.TypeApplied, fun, targs), reifyList(argss)) + case _ => + super.reifyTreeSyntactically(tree) + } + + override def scalaFactoryCall(name: String, args: Tree*): Tree = + call("scala." + name, args: _*) + + override def reifyMultiCardinalityList[T](xs: List[T])(fill: PartialFunction[T, Tree])(fallback: T => Tree) = xs match { + case init :+ last if fill.isDefinedAt(last) => + init.foldRight[Tree](fill(last)) { (el, rest) => + val cons = Select(Select(Select(Ident(nme.scala_), nme.collection), nme.immutable), nme.CONS) + Apply(cons, List(fallback(el), rest)) + } + case _ => + mkList(xs.map(fallback)) + } + + override def reifyAnnotList(annots: List[Tree]): Tree = reifyMultiCardinalityList(annots) { + case AnnotPlaceholder(tree, _, DotDot, Nil) => tree + } { + case AnnotPlaceholder(tree, _, NoDot, Nil) => tree + case AnnotPlaceholder(tree, _, NoDot, args) => + val selectCONSTRUCTOR = Apply(Select(u, nme.Select), List(Apply(Select(u, nme.New), List(tree)), Select(Select(u, nme.nmeNme), nme.nmeCONSTRUCTOR))) + Apply(Select(u, nme.Apply), List(selectCONSTRUCTOR, reify(args))) + case other => + reify(other) + } + + override def reifyModifiers(m: Modifiers) = { + val mods = m.annotations.collect { case ModsPlaceholder(tree, _, _) => tree } + mods match { + case tree :: Nil => + if (m.annotations.length != 1) c.abort(tree.pos, "Can't extract modifiers together with annotations, consider extracting just modifiers") + ensureNoExplicitFlags(m, tree.pos) + tree + case _ :: second :: rest => + c.abort(second.pos, "Can't extract multiple modifiers together, consider extracting a single modifiers instance") + case Nil => + mirrorFactoryCall(nme.Modifiers, reifyBuildCall(nme.FlagsAsBits, m.flags), + reify(m.privateWithin), reifyAnnotList(m.annotations)) + } + } + } +}
\ No newline at end of file |