summaryrefslogtreecommitdiff
path: root/src/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler')
-rw-r--r--src/compiler/scala/reflect/macros/compiler/Errors.scala4
-rw-r--r--src/compiler/scala/reflect/reify/codegen/GenTrees.scala27
-rw-r--r--src/compiler/scala/reflect/reify/codegen/GenUtils.scala3
-rw-r--r--src/compiler/scala/reflect/reify/package.scala2
-rw-r--r--src/compiler/scala/tools/ant/sabbus/Settings.scala14
-rw-r--r--src/compiler/scala/tools/ant/templates/tool-unix.tmpl2
-rw-r--r--src/compiler/scala/tools/cmd/Demo.scala85
-rw-r--r--src/compiler/scala/tools/cmd/Spec.scala2
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala21
-rw-r--r--src/compiler/scala/tools/nsc/ast/TreeGen.scala49
-rw-r--r--src/compiler/scala/tools/nsc/ast/Trees.scala77
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Parsers.scala54
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Scanners.scala33
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala49
-rw-r--r--src/compiler/scala/tools/nsc/backend/JavaPlatform.scala6
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/GenICode.scala3
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Members.scala8
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala3
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/CopyPropagation.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala1256
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeGlue.scala881
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala1329
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala844
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala727
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala401
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala991
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala4
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala203
-rw-r--r--src/compiler/scala/tools/nsc/backend/opt/Inliners.scala15
-rw-r--r--src/compiler/scala/tools/nsc/dependencies/Changes.scala4
-rw-r--r--src/compiler/scala/tools/nsc/io/Lexer.scala299
-rw-r--r--src/compiler/scala/tools/nsc/io/Pickler.scala377
-rw-r--r--src/compiler/scala/tools/nsc/io/PrettyWriter.scala41
-rw-r--r--src/compiler/scala/tools/nsc/io/Replayer.scala74
-rw-r--r--src/compiler/scala/tools/nsc/settings/MutableSettings.scala71
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaSettings.scala17
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaVersion.scala2
-rw-r--r--src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/CleanUp.scala21
-rw-r--r--src/compiler/scala/tools/nsc/transform/Constructors.scala6
-rw-r--r--src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala39
-rw-r--r--src/compiler/scala/tools/nsc/transform/LambdaLift.scala34
-rw-r--r--src/compiler/scala/tools/nsc/transform/Mixin.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala32
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala8
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala17
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Contexts.scala21
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Duplicators.scala4
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Macros.scala107
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala16
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Namers.scala15
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala26
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Unapplies.scala10
-rw-r--r--src/compiler/scala/tools/nsc/util/CharArrayReader.scala31
-rw-r--r--src/compiler/scala/tools/nsc/util/CommandLine.scala98
-rw-r--r--src/compiler/scala/tools/nsc/util/TreeSet.scala64
-rw-r--r--src/compiler/scala/tools/reflect/FastTrack.scala8
-rw-r--r--src/compiler/scala/tools/reflect/ToolBoxFactory.scala2
-rw-r--r--src/compiler/scala/tools/reflect/quasiquotes/Holes.scala187
-rw-r--r--src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala134
-rw-r--r--src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala123
-rw-r--r--src/compiler/scala/tools/reflect/quasiquotes/Quasiquotes.scala51
-rw-r--r--src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala290
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