summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--lib/.gitignore15
-rw-r--r--src/asm/scala/tools/asm/tree/MethodNode.java2
-rw-r--r--src/compiler/scala/tools/ant/sabbus/Settings.scala14
-rw-r--r--src/compiler/scala/tools/cmd/Spec.scala2
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala9
-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/settings/ScalaSettings.scala16
-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/LambdaLift.scala34
-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.scala4
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Duplicators.scala4
-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.scala4
-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/interactive/scala/tools/nsc/interactive/Global.scala2
-rw-r--r--src/interactive/scala/tools/nsc/interactive/Lexer.scala (renamed from src/compiler/scala/tools/nsc/io/Lexer.scala)2
-rw-r--r--src/interactive/scala/tools/nsc/interactive/Pickler.scala (renamed from src/compiler/scala/tools/nsc/io/Pickler.scala)2
-rw-r--r--src/interactive/scala/tools/nsc/interactive/Picklers.scala4
-rw-r--r--src/interactive/scala/tools/nsc/interactive/PrettyWriter.scala (renamed from src/compiler/scala/tools/nsc/io/PrettyWriter.scala)2
-rw-r--r--src/interactive/scala/tools/nsc/interactive/Replayer.scala (renamed from src/compiler/scala/tools/nsc/io/Replayer.scala)2
-rw-r--r--src/library/scala/concurrent/Future.scala177
-rw-r--r--src/library/scala/util/parsing/combinator/Parsers.scala4
-rw-r--r--src/library/scala/xml/dtd/Decl.scala10
-rw-r--r--src/library/scala/xml/dtd/ExternalID.scala2
-rw-r--r--src/partest/scala/tools/partest/PartestTask.scala5
-rw-r--r--src/partest/scala/tools/partest/TestKinds.scala3
-rw-r--r--src/partest/scala/tools/partest/nest/ConsoleRunner.scala68
-rw-r--r--src/partest/scala/tools/partest/nest/ConsoleRunnerSpec.scala54
-rw-r--r--src/partest/scala/tools/partest/nest/NestUI.scala30
-rw-r--r--src/partest/scala/tools/partest/nest/ReflectiveRunner.scala7
-rw-r--r--src/partest/scala/tools/partest/nest/Runner.scala11
-rw-r--r--src/reflect/scala/reflect/internal/AnnotationInfos.scala2
-rw-r--r--src/reflect/scala/reflect/internal/Definitions.scala6
-rw-r--r--src/reflect/scala/reflect/internal/Names.scala35
-rw-r--r--src/reflect/scala/reflect/internal/Symbols.scala2
-rw-r--r--src/reflect/scala/reflect/internal/util/Statistics.scala12
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/model/diagram/Diagram.scala4
-rw-r--r--test/files/codelib/.gitignore1
-rw-r--r--test/files/jvm/bytecode-test-example/Foo_1.flags1
-rw-r--r--test/files/jvm/nooptimise/Foo_1.flags2
-rw-r--r--test/files/jvm/scala-concurrent-tck.check3
-rw-r--r--test/files/jvm/scala-concurrent-tck.scala562
-rw-r--r--test/files/lib/.gitignore8
-rw-r--r--test/files/neg/case-collision.flags2
-rw-r--r--test/files/neg/case-collision2.check12
-rw-r--r--test/files/neg/case-collision2.flags1
-rw-r--r--test/files/neg/case-collision2.scala12
-rw-r--r--test/files/neg/valueclasses-impl-restrictions.check8
-rw-r--r--test/files/neg/valueclasses-impl-restrictions.scala4
-rw-r--r--test/files/pos/t7591/Demo.scala (renamed from src/compiler/scala/tools/cmd/Demo.scala)4
-rw-r--r--test/files/run/t6331.scala7
-rw-r--r--test/files/run/t6331b.scala9
-rw-r--r--test/files/run/t7008-scala-defined.flags1
-rw-r--r--test/files/run/t7271.scala1
-rw-r--r--test/files/run/t7439/Test_2.scala3
-rw-r--r--test/files/run/t7571.scala12
-rw-r--r--test/files/run/t7582-private-within.check12
-rw-r--r--test/files/run/t7582-private-within/JavaPackagePrivate.java8
-rw-r--r--test/files/run/t7582-private-within/Test.scala22
-rw-r--r--test/files/run/t7582.check2
-rw-r--r--test/files/run/t7582.flags1
-rw-r--r--test/files/run/t7582/InlineHolder.scala16
-rw-r--r--test/files/run/t7582/PackageProtectedJava.java6
-rw-r--r--test/files/run/t7582b.check2
-rw-r--r--test/files/run/t7582b.flags1
-rw-r--r--test/files/run/t7582b/InlineHolder.scala16
-rw-r--r--test/files/run/t7582b/PackageProtectedJava.java6
-rw-r--r--test/files/specialized/SI-7343.scala55
-rw-r--r--test/files/specialized/spec-ame.check2
-rw-r--r--test/files/specialized/spec-ame.scala3
-rw-r--r--test/files/speclib/.gitignore1
-rw-r--r--tools/.gitignore1
95 files changed, 7403 insertions, 896 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..378eac25d3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+build
diff --git a/lib/.gitignore b/lib/.gitignore
new file mode 100644
index 0000000000..0c507490be
--- /dev/null
+++ b/lib/.gitignore
@@ -0,0 +1,15 @@
+ant-contrib.jar
+ant-dotnet-1.0.jar
+ant.jar
+fjbg.jar
+forkjoin.jar
+jline.jar
+maven-ant-tasks-2.1.1.jar
+msil.jar
+scala-compiler.jar
+scala-compiler-src.jar
+scala-library.jar
+scala-library-src.jar
+scala-reflect.jar
+scala-reflect-src.jar
+vizant.jar
diff --git a/src/asm/scala/tools/asm/tree/MethodNode.java b/src/asm/scala/tools/asm/tree/MethodNode.java
index 5f9c778e0c..a161600edb 100644
--- a/src/asm/scala/tools/asm/tree/MethodNode.java
+++ b/src/asm/scala/tools/asm/tree/MethodNode.java
@@ -458,7 +458,7 @@ public class MethodNode extends MethodVisitor {
*/
protected LabelNode getLabelNode(final Label l) {
if (!(l.info instanceof LabelNode)) {
- l.info = new LabelNode();
+ l.info = new LabelNode(l);
}
return (LabelNode) l.info;
}
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/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..603f9af1b4 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._
@@ -619,6 +620,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 +1065,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/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/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
index fe9165203f..321baba562 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) {
@@ -198,6 +198,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 +226,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/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/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..a3ab948171 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
}
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/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..353e8e4810 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")
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/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala
index 99f2cd4056..5875a44025 100644
--- a/src/interactive/scala/tools/nsc/interactive/Global.scala
+++ b/src/interactive/scala/tools/nsc/interactive/Global.scala
@@ -9,7 +9,7 @@ import java.io.{ PrintWriter, StringWriter, FileReader, FileWriter }
import scala.collection.mutable
import mutable.{LinkedHashMap, SynchronizedMap, HashSet, SynchronizedSet}
import scala.util.control.ControlThrowable
-import scala.tools.nsc.io.{ AbstractFile, LogReplay, Logger, NullLogger, Replayer }
+import scala.tools.nsc.io.{ AbstractFile }
import scala.tools.nsc.util.MultiHashMap
import scala.reflect.internal.util.{ SourceFile, BatchSourceFile, Position, NoPosition }
import scala.tools.nsc.reporters._
diff --git a/src/compiler/scala/tools/nsc/io/Lexer.scala b/src/interactive/scala/tools/nsc/interactive/Lexer.scala
index 1c926aff6b..82e8de3f3d 100644
--- a/src/compiler/scala/tools/nsc/io/Lexer.scala
+++ b/src/interactive/scala/tools/nsc/interactive/Lexer.scala
@@ -1,4 +1,4 @@
-package scala.tools.nsc.io
+package scala.tools.nsc.interactive
import java.io.Reader
diff --git a/src/compiler/scala/tools/nsc/io/Pickler.scala b/src/interactive/scala/tools/nsc/interactive/Pickler.scala
index 0e7da37c52..83f3fab925 100644
--- a/src/compiler/scala/tools/nsc/io/Pickler.scala
+++ b/src/interactive/scala/tools/nsc/interactive/Pickler.scala
@@ -1,4 +1,4 @@
-package scala.tools.nsc.io
+package scala.tools.nsc.interactive
import Lexer._
import java.io.Writer
diff --git a/src/interactive/scala/tools/nsc/interactive/Picklers.scala b/src/interactive/scala/tools/nsc/interactive/Picklers.scala
index 900a06333d..e75b4a3cc6 100644
--- a/src/interactive/scala/tools/nsc/interactive/Picklers.scala
+++ b/src/interactive/scala/tools/nsc/interactive/Picklers.scala
@@ -7,10 +7,10 @@ package interactive
import util.InterruptReq
import scala.reflect.internal.util.{ SourceFile, BatchSourceFile }
-import io.{ AbstractFile, PlainFile, Pickler, CondPickler }
+import io.{ AbstractFile, PlainFile }
import util.EmptyAction
import scala.reflect.internal.util.{ RangePosition, OffsetPosition, TransparentPosition }
-import io.Pickler._
+import Pickler._
import scala.collection.mutable
import mutable.ListBuffer
diff --git a/src/compiler/scala/tools/nsc/io/PrettyWriter.scala b/src/interactive/scala/tools/nsc/interactive/PrettyWriter.scala
index 11d3703983..d7dadcc6a8 100644
--- a/src/compiler/scala/tools/nsc/io/PrettyWriter.scala
+++ b/src/interactive/scala/tools/nsc/interactive/PrettyWriter.scala
@@ -1,4 +1,4 @@
-package scala.tools.nsc.io
+package scala.tools.nsc.interactive
import java.io.Writer
diff --git a/src/compiler/scala/tools/nsc/io/Replayer.scala b/src/interactive/scala/tools/nsc/interactive/Replayer.scala
index e3dc8939a3..0e3e2493fe 100644
--- a/src/compiler/scala/tools/nsc/io/Replayer.scala
+++ b/src/interactive/scala/tools/nsc/interactive/Replayer.scala
@@ -1,4 +1,4 @@
-package scala.tools.nsc.io
+package scala.tools.nsc.interactive
import java.io.{Reader, Writer}
diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala
index 2e4c72cd71..b072cd653b 100644
--- a/src/library/scala/concurrent/Future.scala
+++ b/src/library/scala/concurrent/Future.scala
@@ -14,7 +14,7 @@ import java.util.concurrent.{ ConcurrentLinkedQueue, TimeUnit, Callable }
import java.util.concurrent.TimeUnit.{ NANOSECONDS => NANOS, MILLISECONDS ⇒ MILLIS }
import java.lang.{ Iterable => JIterable }
import java.util.{ LinkedList => JLinkedList }
-import java.util.concurrent.atomic.{ AtomicReferenceFieldUpdater, AtomicInteger, AtomicBoolean }
+import java.util.concurrent.atomic.{ AtomicReferenceFieldUpdater, AtomicInteger, AtomicLong, AtomicBoolean }
import scala.util.control.NonFatal
import scala.Option
@@ -101,7 +101,7 @@ trait Future[+T] extends Awaitable[T] {
// that also have an executor parameter, which
// keeps us from accidentally forgetting to use
// the executor parameter.
- private implicit def internalExecutor: ExecutionContext = Future.InternalCallbackExecutor
+ private def internalExecutor = Future.InternalCallbackExecutor
/* Callbacks */
@@ -116,9 +116,10 @@ trait Future[+T] extends Awaitable[T] {
* $callbackInContext
*/
def onSuccess[U](pf: PartialFunction[T, U])(implicit executor: ExecutionContext): Unit = onComplete {
- case Success(v) if pf isDefinedAt v => pf(v)
+ case Success(v) =>
+ pf.applyOrElse[T, Any](v, Predef.conforms[T]) // Exploiting the cached function to avoid MatchError
case _ =>
- }(executor)
+ }
/** When this future is completed with a failure (i.e. with a throwable),
* apply the provided callback to the throwable.
@@ -134,9 +135,10 @@ trait Future[+T] extends Awaitable[T] {
* $callbackInContext
*/
def onFailure[U](@deprecatedName('callback) pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Unit = onComplete {
- case Failure(t) if NonFatal(t) && pf.isDefinedAt(t) => pf(t)
+ case Failure(t) =>
+ pf.applyOrElse[Throwable, Any](t, Predef.conforms[Throwable]) // Exploiting the cached function to avoid MatchError
case _ =>
- }(executor)
+ }
/** When this future is completed, either through an exception, or a value,
* apply the provided function.
@@ -186,13 +188,12 @@ trait Future[+T] extends Awaitable[T] {
* and throws a corresponding exception if the original future fails.
*/
def failed: Future[Throwable] = {
+ implicit val ec = internalExecutor
val p = Promise[Throwable]()
-
onComplete {
case Failure(t) => p success t
case Success(v) => p failure (new NoSuchElementException("Future.failed not completed with a throwable."))
}
-
p.future
}
@@ -203,10 +204,7 @@ trait Future[+T] extends Awaitable[T] {
*
* Will not be called if the future fails.
*/
- def foreach[U](f: T => U)(implicit executor: ExecutionContext): Unit = onComplete {
- case Success(r) => f(r)
- case _ => // do nothing
- }(executor)
+ def foreach[U](f: T => U)(implicit executor: ExecutionContext): Unit = onComplete { _ foreach f }
/** Creates a new future by applying the 's' function to the successful result of
* this future, or the 'f' function to the failed result. If there is any non-fatal
@@ -221,19 +219,11 @@ trait Future[+T] extends Awaitable[T] {
*/
def transform[S](s: T => S, f: Throwable => Throwable)(implicit executor: ExecutionContext): Future[S] = {
val p = Promise[S]()
-
+ // transform on Try has the wrong shape for us here
onComplete {
- case result =>
- try {
- result match {
- case Failure(t) => p failure f(t)
- case Success(r) => p success s(r)
- }
- } catch {
- case NonFatal(t) => p failure t
- }
- }(executor)
-
+ case Success(r) => p complete Try(s(r))
+ case Failure(t) => p complete Try(throw f(t)) // will throw fatal errors!
+ }
p.future
}
@@ -245,19 +235,7 @@ trait Future[+T] extends Awaitable[T] {
*/
def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S] = { // transform(f, identity)
val p = Promise[S]()
-
- onComplete {
- case result =>
- try {
- result match {
- case Success(r) => p success f(r)
- case f: Failure[_] => p complete f.asInstanceOf[Failure[S]]
- }
- } catch {
- case NonFatal(t) => p failure t
- }
- }(executor)
-
+ onComplete { v => p complete (v map f) }
p.future
}
@@ -270,20 +248,10 @@ trait Future[+T] extends Awaitable[T] {
*/
def flatMap[S](f: T => Future[S])(implicit executor: ExecutionContext): Future[S] = {
val p = Promise[S]()
-
onComplete {
case f: Failure[_] => p complete f.asInstanceOf[Failure[S]]
- case Success(v) =>
- try {
- f(v).onComplete({
- case f: Failure[_] => p complete f.asInstanceOf[Failure[S]]
- case Success(v) => p success v
- })(internalExecutor)
- } catch {
- case NonFatal(t) => p failure t
- }
- }(executor)
-
+ case Success(v) => try f(v) onComplete p.complete catch { case NonFatal(t) => p failure t }
+ }
p.future
}
@@ -303,34 +271,14 @@ trait Future[+T] extends Awaitable[T] {
* Await.result(h, Duration.Zero) // throw a NoSuchElementException
* }}}
*/
- def filter(@deprecatedName('pred) p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = {
- val promise = Promise[T]()
-
- onComplete {
- case f: Failure[_] => promise complete f.asInstanceOf[Failure[T]]
- case Success(v) =>
- try {
- if (p(v)) promise success v
- else promise failure new NoSuchElementException("Future.filter predicate is not satisfied")
- } catch {
- case NonFatal(t) => promise failure t
- }
- }(executor)
-
- promise.future
- }
+ def filter(@deprecatedName('pred) p: T => Boolean)(implicit executor: ExecutionContext): Future[T] =
+ map {
+ r => if (p(r)) r else throw new NoSuchElementException("Future.filter predicate is not satisfied")
+ }
/** Used by for-comprehensions.
*/
final def withFilter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = filter(p)(executor)
- // final def withFilter(p: T => Boolean) = new FutureWithFilter[T](this, p)
-
- // final class FutureWithFilter[+S](self: Future[S], p: S => Boolean) {
- // def foreach(f: S => Unit): Unit = self filter p foreach f
- // def map[R](f: S => R) = self filter p map f
- // def flatMap[R](f: S => Future[R]) = self filter p flatMap f
- // def withFilter(q: S => Boolean): FutureWithFilter[S] = new FutureWithFilter[S](self, x => p(x) && q(x))
- // }
/** Creates a new future by mapping the value of the current future, if the given partial function is defined at that value.
*
@@ -352,22 +300,10 @@ trait Future[+T] extends Awaitable[T] {
* Await.result(h, Duration.Zero) // throw a NoSuchElementException
* }}}
*/
- def collect[S](pf: PartialFunction[T, S])(implicit executor: ExecutionContext): Future[S] = {
- val p = Promise[S]()
-
- onComplete {
- case f: Failure[_] => p complete f.asInstanceOf[Failure[S]]
- case Success(v) =>
- try {
- if (pf.isDefinedAt(v)) p success pf(v)
- else p failure new NoSuchElementException("Future.collect partial function is not defined at: " + v)
- } catch {
- case NonFatal(t) => p failure t
- }
- }(executor)
-
- p.future
- }
+ def collect[S](pf: PartialFunction[T, S])(implicit executor: ExecutionContext): Future[S] =
+ map {
+ r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t))
+ }
/** Creates a new future that will handle any matching throwable that this
* future might contain. If there is no match, or if this future contains
@@ -383,9 +319,7 @@ trait Future[+T] extends Awaitable[T] {
*/
def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U] = {
val p = Promise[U]()
-
- onComplete { case tr => p.complete(tr recover pf) }(executor)
-
+ onComplete { v => p complete (v recover pf) }
p.future
}
@@ -404,17 +338,10 @@ trait Future[+T] extends Awaitable[T] {
*/
def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U] = {
val p = Promise[U]()
-
onComplete {
- case Failure(t) if pf isDefinedAt t =>
- try {
- p completeWith pf(t)
- } catch {
- case NonFatal(t) => p failure t
- }
- case otherwise => p complete otherwise
- }(executor)
-
+ case Failure(t) => try pf.applyOrElse(t, (_: Throwable) => this) onComplete p.complete catch { case NonFatal(t) => p failure t }
+ case other => p complete other
+ }
p.future
}
@@ -427,19 +354,12 @@ trait Future[+T] extends Awaitable[T] {
* with the throwable stored in `that`.
*/
def zip[U](that: Future[U]): Future[(T, U)] = {
+ implicit val ec = internalExecutor
val p = Promise[(T, U)]()
-
- this onComplete {
+ onComplete {
case f: Failure[_] => p complete f.asInstanceOf[Failure[(T, U)]]
- case Success(r) =>
- that onSuccess {
- case r2 => p success ((r, r2))
- }
- that onFailure {
- case f => p failure f
- }
+ case Success(s) => that onComplete { c => p.complete(c map { s2 => (s, s2) }) }
}
-
p.future
}
@@ -458,6 +378,7 @@ trait Future[+T] extends Awaitable[T] {
* }}}
*/
def fallbackTo[U >: T](that: Future[U]): Future[U] = {
+ implicit val ec = internalExecutor
val p = Promise[U]()
onComplete {
case s @ Success(_) => p complete s
@@ -470,23 +391,13 @@ trait Future[+T] extends Awaitable[T] {
* that conforms to `S`'s erased type or a `ClassCastException` otherwise.
*/
def mapTo[S](implicit tag: ClassTag[S]): Future[S] = {
- def boxedType(c: Class[_]): Class[_] = {
+ implicit val ec = internalExecutor
+ val boxedClass = {
+ val c = tag.runtimeClass
if (c.isPrimitive) Future.toBoxed(c) else c
}
-
- val p = Promise[S]()
-
- onComplete {
- case f: Failure[_] => p complete f.asInstanceOf[Failure[S]]
- case Success(t) =>
- p complete (try {
- Success(boxedType(tag.runtimeClass).cast(t).asInstanceOf[S])
- } catch {
- case e: ClassCastException => Failure(e)
- })
- }
-
- p.future
+ require(boxedClass ne null)
+ map(s => boxedClass.cast(s).asInstanceOf[S])
}
/** Applies the side-effecting function to the result of this future, and returns
@@ -514,11 +425,9 @@ trait Future[+T] extends Awaitable[T] {
*/
def andThen[U](pf: PartialFunction[Try[T], U])(implicit executor: ExecutionContext): Future[T] = {
val p = Promise[T]()
-
onComplete {
- case r => try if (pf isDefinedAt r) pf(r) finally p complete r
- }(executor)
-
+ case r => try pf.applyOrElse[Try[T], Any](r, Predef.conforms[Try[T]]) finally p complete r
+ }
p.future
}
@@ -579,14 +488,12 @@ object Future {
} map (_.result())
}
- /** Returns a `Future` to the result of the first future in the list that is completed.
+ /** Returns a new `Future` to the result of the first future in the list that is completed.
*/
def firstCompletedOf[T](futures: TraversableOnce[Future[T]])(implicit executor: ExecutionContext): Future[T] = {
val p = Promise[T]()
-
val completeFirst: Try[T] => Unit = p tryComplete _
- futures.foreach(_ onComplete completeFirst)
-
+ futures foreach { _ onComplete completeFirst }
p.future
}
diff --git a/src/library/scala/util/parsing/combinator/Parsers.scala b/src/library/scala/util/parsing/combinator/Parsers.scala
index 4602c3cc53..16754646fd 100644
--- a/src/library/scala/util/parsing/combinator/Parsers.scala
+++ b/src/library/scala/util/parsing/combinator/Parsers.scala
@@ -531,10 +531,6 @@ trait Parsers {
}
}
- /*trait ElemFun
- case class EFCons(hd: Elem => ElemFun, tl: ElemFun) extends ElemFun
- case class EFNil(res: Boolean) extends ElemFun*/
-
/** A parser matching input elements that satisfy a given predicate.
*
* `elem(kind, p)` succeeds if the input starts with an element `e` for which `p(e)` is true.
diff --git a/src/library/scala/xml/dtd/Decl.scala b/src/library/scala/xml/dtd/Decl.scala
index e6804478bd..8bf859c460 100644
--- a/src/library/scala/xml/dtd/Decl.scala
+++ b/src/library/scala/xml/dtd/Decl.scala
@@ -12,9 +12,9 @@ package dtd
import Utility.sbToString
-abstract class Decl
+sealed abstract class Decl
-abstract class MarkupDecl extends Decl {
+sealed abstract class MarkupDecl extends Decl {
def buildString(sb: StringBuilder): StringBuilder
}
@@ -52,7 +52,7 @@ case class AttrDecl(name: String, tpe: String, default: DefaultDecl) {
}
/** an entity declaration */
-abstract class EntityDecl extends MarkupDecl
+sealed abstract class EntityDecl extends MarkupDecl
/** a parsed general entity declaration */
case class ParsedEntityDecl(name: String, entdef: EntityDef) extends EntityDecl {
@@ -85,7 +85,7 @@ case class NotationDecl( name:String, extID:ExternalID ) extends MarkupDecl {
}
}
-abstract class EntityDef {
+sealed abstract class EntityDef {
def buildString(sb: StringBuilder): StringBuilder
}
@@ -133,7 +133,7 @@ case class PEReference(ent:String) extends MarkupDecl {
// default declarations for attributes
-abstract class DefaultDecl {
+sealed abstract class DefaultDecl {
override def toString(): String
def buildString(sb: StringBuilder): StringBuilder
}
diff --git a/src/library/scala/xml/dtd/ExternalID.scala b/src/library/scala/xml/dtd/ExternalID.scala
index 5a1b5d1a19..880633d860 100644
--- a/src/library/scala/xml/dtd/ExternalID.scala
+++ b/src/library/scala/xml/dtd/ExternalID.scala
@@ -15,7 +15,7 @@ package dtd
*
* @author Burak Emir
*/
-abstract class ExternalID extends parsing.TokenTests {
+sealed abstract class ExternalID extends parsing.TokenTests {
def quoted(s: String) = {
val c = if (s contains '"') '\'' else '"'
c + s + c
diff --git a/src/partest/scala/tools/partest/PartestTask.scala b/src/partest/scala/tools/partest/PartestTask.scala
index b5b09a753a..8b88021dbf 100644
--- a/src/partest/scala/tools/partest/PartestTask.scala
+++ b/src/partest/scala/tools/partest/PartestTask.scala
@@ -15,6 +15,7 @@ import java.lang.reflect.Method
import org.apache.tools.ant.Task
import org.apache.tools.ant.types.{ Reference, FileSet}
import org.apache.tools.ant.types.Commandline.Argument
+import scala.tools.ant.ScalaTask
/** An Ant task to execute the Scala test suite (NSC).
*
@@ -34,7 +35,7 @@ import org.apache.tools.ant.types.Commandline.Argument
*
* @author Philippe Haller
*/
-class PartestTask extends Task with CompilationPathProperty {
+class PartestTask extends Task with CompilationPathProperty with ScalaTask {
type Path = org.apache.tools.ant.types.Path
private var kinds: List[String] = Nil
@@ -178,7 +179,7 @@ class PartestTask extends Task with CompilationPathProperty {
val allFailures = _results map (_._2) sum
val allFailedPaths = _results flatMap (_._3)
- def f = if (errorOnFailed && allFailures > 0) (sys error _) else log(_: String)
+ def f = if (errorOnFailed && allFailures > 0) buildError(_: String) else log(_: String)
def s = if (allFailures > 1) "s" else ""
val msg =
if (allFailures > 0)
diff --git a/src/partest/scala/tools/partest/TestKinds.scala b/src/partest/scala/tools/partest/TestKinds.scala
index ec682690ca..b4e8afd0d2 100644
--- a/src/partest/scala/tools/partest/TestKinds.scala
+++ b/src/partest/scala/tools/partest/TestKinds.scala
@@ -4,8 +4,7 @@ package partest
import nest.PathSettings.srcDir
object TestKinds {
- val standardKinds = "pos neg run jvm res buildmanager scalacheck scalap specialized instrumented presentation ant" split "\\s+" toList
- val standardArgs = standardKinds map ("--" + _)
+ val standardKinds = ("pos neg run jvm res scalacheck scalap specialized instrumented presentation ant" split "\\s+").toList
def denotesTestFile(p: Path) = p.isFile && p.hasExtension("scala", "res", "xml")
def denotesTestDir(p: Path) = kindOf(p) match {
diff --git a/src/partest/scala/tools/partest/nest/ConsoleRunner.scala b/src/partest/scala/tools/partest/nest/ConsoleRunner.scala
index 33bf836a7b..332131ca3a 100644
--- a/src/partest/scala/tools/partest/nest/ConsoleRunner.scala
+++ b/src/partest/scala/tools/partest/nest/ConsoleRunner.scala
@@ -9,13 +9,15 @@ package nest
import utils.Properties._
import scala.tools.nsc.Properties.{ versionMsg, setProp }
-import scala.tools.nsc.util.CommandLine
import scala.collection.{ mutable, immutable }
import PathSettings.srcDir
import TestKinds._
import scala.reflect.internal.util.Collections.distinctBy
+import scala.tools.cmd.{ CommandLine, CommandLineParser, Instance }
-class ConsoleRunner extends DirectRunner {
+class ConsoleRunner(argstr: String) extends {
+ val parsed = ConsoleRunnerSpec.creator(CommandLineParser tokenize argstr)
+} with DirectRunner with ConsoleRunnerSpec with Instance {
import NestUI._
import NestUI.color._
@@ -86,27 +88,15 @@ class ConsoleRunner extends DirectRunner {
}
}
- private val unaryArgs = List(
- "--pack", "--all",
- "--terse", "--verbose", "--show-diff", "--show-log", "--self-test",
- "--failed", "--update-check", "--version", "--ansi", "--debug", "--help"
- ) ::: standardArgs
-
- private val binaryArgs = List(
- "--grep", "--srcpath", "--buildpath", "--classpath", "--timeout"
- )
-
- def main(argstr: String) {
- val parsed = (new CommandLine(argstr)) withUnaryArgs unaryArgs withBinaryArgs binaryArgs
-
- if (parsed isSet "--debug") NestUI.setDebug()
- if (parsed isSet "--verbose") NestUI.setVerbose()
- if (parsed isSet "--terse") NestUI.setTerse()
- if (parsed isSet "--show-diff") NestUI.setDiffOnFail()
+ def run(): Unit = {
+ if (optDebug) NestUI.setDebug()
+ if (optVerbose) NestUI.setVerbose()
+ if (optTerse) NestUI.setTerse()
+ if (optShowDiff) NestUI.setDiffOnFail()
// Early return on no args, version, or invalid args
- if (parsed isSet "--version") return echo(versionMsg)
- if ((argstr == "") || (parsed isSet "--help")) return NestUI.usage()
+ if (optVersion) return echo(versionMsg)
+ if ((argstr == "") || optHelp) return NestUI.usage()
val (individualTests, invalid) = parsed.residualArgs map (p => Path(p)) partition denotesTestPath
if (invalid.nonEmpty) {
@@ -116,27 +106,26 @@ class ConsoleRunner extends DirectRunner {
echoWarning(s"Discarding ${invalid.size} invalid test paths")
}
- parsed get "--srcpath" foreach (x => setProp("partest.srcdir", x))
- parsed get "--timeout" foreach (x => setProp("partest.timeout", x))
+ optSourcePath foreach (x => setProp("partest.srcdir", x))
+ optTimeout foreach (x => setProp("partest.timeout", x))
fileManager =
- if (parsed isSet "--buildpath") new ConsoleFileManager(parsed("--buildpath"))
- else if (parsed isSet "--classpath") new ConsoleFileManager(parsed("--classpath"), true)
- else if (parsed isSet "--pack") new ConsoleFileManager("build/pack")
+ if (optBuildPath.isDefined) new ConsoleFileManager(optBuildPath.get)
+ else if (optClassPath.isDefined) new ConsoleFileManager(optClassPath.get, true)
+ else if (optPack) new ConsoleFileManager("build/pack")
else new ConsoleFileManager // auto detection, see ConsoleFileManager.findLatest
- fileManager.updateCheck = parsed isSet "--update-check"
- fileManager.failed = parsed isSet "--failed"
+ fileManager.updateCheck = optUpdateCheck
+ fileManager.failed = optFailed
val partestTests = (
- if (parsed isSet "--self-test") TestKinds.testsForPartest
+ if (optSelfTest) TestKinds.testsForPartest
else Nil
)
- val grepExpr = parsed get "--grep" getOrElse ""
+ val grepExpr = optGrep getOrElse ""
// If --grep is given we suck in every file it matches.
- var grepMessage = ""
val greppedTests = if (grepExpr == "") Nil else {
val paths = grepFor(grepExpr)
if (paths.isEmpty)
@@ -145,14 +134,14 @@ class ConsoleRunner extends DirectRunner {
paths.sortBy(_.toString)
}
- val isRerun = parsed isSet "--failed"
+ val isRerun = optFailed
val rerunTests = if (isRerun) TestKinds.failedTests else Nil
def miscTests = partestTests ++ individualTests ++ greppedTests ++ rerunTests
- val givenKinds = standardArgs filter parsed.isSet
+ val givenKinds = standardKinds filter parsed.isSet
val kinds = (
- if (parsed isSet "--all") standardKinds
- else if (givenKinds.nonEmpty) givenKinds map (_ stripPrefix "--")
+ if (optAll) standardKinds
+ else if (givenKinds.nonEmpty) givenKinds
else if (invalid.isEmpty && miscTests.isEmpty && !isRerun) standardKinds // If no kinds, --grep, or individual tests were given, assume --all
else Nil
)
@@ -223,4 +212,13 @@ class ConsoleRunner extends DirectRunner {
issueSummaryReport()
System exit ( if (isSuccess) 0 else 1 )
}
+
+ run()
}
+
+object ConsoleRunner {
+ def main(args: Array[String]): Unit = {
+ new ConsoleRunner(args mkString " ")
+ }
+}
+
diff --git a/src/partest/scala/tools/partest/nest/ConsoleRunnerSpec.scala b/src/partest/scala/tools/partest/nest/ConsoleRunnerSpec.scala
new file mode 100644
index 0000000000..f9143013e9
--- /dev/null
+++ b/src/partest/scala/tools/partest/nest/ConsoleRunnerSpec.scala
@@ -0,0 +1,54 @@
+package scala.tools.partest.nest
+
+import language.postfixOps
+
+import scala.tools.cmd.{ CommandLine, Interpolation, Meta, Reference, Spec }
+
+trait ConsoleRunnerSpec extends Spec with Meta.StdOpts with Interpolation {
+ def referenceSpec = ConsoleRunnerSpec
+ def programInfo = Spec.Info(
+ "console-runner",
+ "Usage: NestRunner [options] [test test ...]",
+ "scala.tools.partest.nest.ConsoleRunner")
+
+ heading("Test categories:")
+ val optAll = "all" / "run all tests" --?
+ val optPos = "pos" / "run compilation tests (success)" --?
+ val optNeg = "neg" / "run compilation tests (failure)" --?
+ val optRun = "run" / "run interpreter and backend tests" --?
+ val optJvm = "jvm" / "run JVM backend tests" --?
+ val optRes = "res" / "run resident compiler tests" --?
+ val optAnt = "ant" / "run Ant tests" --?
+ val optScalap = "scalap" / "run scalap tests" --?
+ val optSpecialized = "specialized" / "run specialization tests" --?
+ val optScalacheck = "scalacheck" / "run ScalaCheck tests" --?
+ val optInstrumented = "instrumented" / "run instrumented tests" --?
+ val optPresentation = "presentation" / "run presentation compiler tests" --?
+
+ heading("Test runner options:")
+ val optFailed = "failed" / "run only those tests that failed during the last run" --?
+ val optTimeout = "timeout" / "aborts the test suite after the given amount of time" --|
+ val optPack = "pack" / "pick compiler/reflect/library in build/pack, and run all tests" --?
+ val optGrep = "grep" / "run all tests whose source file contains the expression given to grep" --|
+ val optUpdateCheck = "update-check" / "instead of failing tests with output change, update checkfile (use with care!)" --?
+ val optBuildPath = "buildpath" / "set (relative) path to build jars (ex.: --buildpath build/pack)" --|
+ val optClassPath = "classpath" / "set (absolute) path to build classes" --|
+ val optSourcePath = "srcpath" / "set (relative) path to test source files (ex.: --srcpath pending)" --|
+
+ heading("Test output options:")
+ val optShowDiff = "show-diff" / "show diffs for failed tests" --?
+ val optVerbose = "verbose" / "show verbose progress information" --?
+ val optTerse = "terse" / "show terse progress information" --?
+ val optDebug = "debug" / "enable debugging output" --?
+
+ heading("Other options:")
+ val optVersion = "version" / "show Scala version and exit" --?
+ val optSelfTest = "self-test" / "run tests for partest itself" --?
+ val optHelp = "help" / "show this page and exit" --?
+
+}
+
+object ConsoleRunnerSpec extends ConsoleRunnerSpec with Reference {
+ type ThisCommandLine = CommandLine
+ def creator(args: List[String]): ThisCommandLine = new CommandLine(ConsoleRunnerSpec, args)
+}
diff --git a/src/partest/scala/tools/partest/nest/NestUI.scala b/src/partest/scala/tools/partest/nest/NestUI.scala
index 564270e531..5148115905 100644
--- a/src/partest/scala/tools/partest/nest/NestUI.scala
+++ b/src/partest/scala/tools/partest/nest/NestUI.scala
@@ -144,34 +144,8 @@ object NestUI {
}
def usage() {
- println("Usage: NestRunner [options] [test test ...]")
- println
- println(" Test categories:")
- println(" --all run all tests")
- println(" --pos run compilation tests (success)")
- println(" --neg run compilation tests (failure)")
- println(" --run run interpreter and backend tests")
- println(" --jvm run JVM backend tests")
- println(" --res run resident compiler tests")
- println(" --scalacheck run ScalaCheck tests")
- println(" --instrumented run instrumented tests")
- println(" --presentation run presentation compiler tests")
- println
- println(" Other options:")
- println(" --pack pick compiler/reflect/library in build/pack, and run all tests")
- println(" --grep <expr> run all tests whose source file contains <expr>")
- println(" --failed run only those tests that failed during the last run")
- println(" --update-check instead of failing tests with output change, update checkfile. (Use with care!)")
- println(" --verbose show progress information")
- println(" --buildpath set (relative) path to build jars")
- println(" ex.: --buildpath build/pack")
- println(" --classpath set (absolute) path to build classes")
- println(" --srcpath set (relative) path to test source files")
- println(" ex.: --srcpath pending")
- println(" --debug enable debugging output")
- println
- println(utils.Properties.versionString)
- println("maintained by Philipp Haller (EPFL)")
+ println(ConsoleRunnerSpec.programInfo.usage)
+ println(ConsoleRunnerSpec.helpMsg)
sys.exit(1)
}
diff --git a/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala b/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala
index 734affa153..3c77a03f1e 100644
--- a/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala
+++ b/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala
@@ -85,10 +85,9 @@ class ReflectiveRunner {
try {
val sepRunnerClass = sepLoader loadClass sepRunnerClassName
- val sepRunner = sepRunnerClass.newInstance()
- val sepMainMethod = sepRunnerClass.getMethod("main", Array(classOf[String]): _*)
- val cargs: Array[AnyRef] = Array(args)
- sepMainMethod.invoke(sepRunner, cargs: _*)
+ val sepMainMethod = sepRunnerClass.getMethod("main", classOf[Array[String]])
+ val cargs: Array[AnyRef] = Array(Array(args))
+ sepMainMethod.invoke(null, cargs: _*)
}
catch {
case cnfe: ClassNotFoundException =>
diff --git a/src/partest/scala/tools/partest/nest/Runner.scala b/src/partest/scala/tools/partest/nest/Runner.scala
index a53698eb77..d7d87bdcf5 100644
--- a/src/partest/scala/tools/partest/nest/Runner.scala
+++ b/src/partest/scala/tools/partest/nest/Runner.scala
@@ -772,15 +772,8 @@ trait DirectRunner {
import PartestDefaults.{ numThreads, waitTime }
- Thread.setDefaultUncaughtExceptionHandler(
- new Thread.UncaughtExceptionHandler {
- def uncaughtException(thread: Thread, t: Throwable) {
- val t1 = Exceptional unwrap t
- System.err.println(s"Uncaught exception on thread $thread: $t1")
- t1.printStackTrace()
- }
- }
- )
+ setUncaughtHandler
+
def runTestsForFiles(kindFiles: List[File], kind: String): List[TestState] = {
NestUI.resetTestNumber(kindFiles.size)
diff --git a/src/reflect/scala/reflect/internal/AnnotationInfos.scala b/src/reflect/scala/reflect/internal/AnnotationInfos.scala
index 73ca74f08e..215ab6abd6 100644
--- a/src/reflect/scala/reflect/internal/AnnotationInfos.scala
+++ b/src/reflect/scala/reflect/internal/AnnotationInfos.scala
@@ -73,7 +73,7 @@ trait AnnotationInfos extends api.Annotations { self: SymbolTable =>
* - arrays of constants
* - or nested classfile annotations
*/
- abstract class ClassfileAnnotArg extends Product
+ sealed abstract class ClassfileAnnotArg extends Product
implicit val JavaArgumentTag = ClassTag[ClassfileAnnotArg](classOf[ClassfileAnnotArg])
case object UnmappableAnnotArg extends ClassfileAnnotArg
diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala
index 3470b05495..bf8ef79a63 100644
--- a/src/reflect/scala/reflect/internal/Definitions.scala
+++ b/src/reflect/scala/reflect/internal/Definitions.scala
@@ -870,6 +870,12 @@ trait Definitions extends api.StandardDefinitions {
lazy val BoxedNumberClass = getClassByName(sn.BoxedNumber)
lazy val BoxedCharacterClass = getClassByName(sn.BoxedCharacter)
lazy val BoxedBooleanClass = getClassByName(sn.BoxedBoolean)
+ lazy val BoxedByteClass = requiredClass[java.lang.Byte]
+ lazy val BoxedShortClass = requiredClass[java.lang.Short]
+ lazy val BoxedIntClass = requiredClass[java.lang.Integer]
+ lazy val BoxedLongClass = requiredClass[java.lang.Long]
+ lazy val BoxedFloatClass = requiredClass[java.lang.Float]
+ lazy val BoxedDoubleClass = requiredClass[java.lang.Double]
lazy val Boxes_isNumberOrBool = getDecl(BoxesRunTimeClass, nme.isBoxedNumberOrBoolean)
lazy val Boxes_isNumber = getDecl(BoxesRunTimeClass, nme.isBoxedNumber)
diff --git a/src/reflect/scala/reflect/internal/Names.scala b/src/reflect/scala/reflect/internal/Names.scala
index ed5b92565d..0d78e24548 100644
--- a/src/reflect/scala/reflect/internal/Names.scala
+++ b/src/reflect/scala/reflect/internal/Names.scala
@@ -121,6 +121,41 @@ trait Names extends api.Names {
def newTypeName(bs: Array[Byte], offset: Int, len: Int): TypeName =
newTermName(bs, offset, len).toTypeName
+ /**
+ * Used only by the GenBCode backend, to represent bytecode-level types in a way that makes equals() and hashCode() efficient.
+ * For bytecode-level types of OBJECT sort, its internal name (not its descriptor) is stored.
+ * For those of ARRAY sort, its descriptor is stored ie has a leading '['
+ * For those of METHOD sort, its descriptor is stored ie has a leading '('
+ *
+ * can-multi-thread
+ */
+ final def lookupTypeName(cs: Array[Char]): TypeName = { lookupTypeNameIfExisting(cs, true) }
+
+ final def lookupTypeNameIfExisting(cs: Array[Char], failOnNotFound: Boolean): TypeName = {
+
+ val hterm = hashValue(cs, 0, cs.size) & HASH_MASK
+ var nterm = termHashtable(hterm)
+ while ((nterm ne null) && (nterm.length != cs.size || !equals(nterm.start, cs, 0, cs.size))) {
+ nterm = nterm.next
+ }
+ if (nterm eq null) {
+ if (failOnNotFound) { assert(false, "TermName not yet created: " + new String(cs)) }
+ return null
+ }
+
+ val htype = hashValue(chrs, nterm.start, nterm.length) & HASH_MASK
+ var ntype = typeHashtable(htype)
+ while ((ntype ne null) && ntype.start != nterm.start) {
+ ntype = ntype.next
+ }
+ if (ntype eq null) {
+ if (failOnNotFound) { assert(false, "TypeName not yet created: " + new String(cs)) }
+ return null
+ }
+
+ ntype
+ }
+
// Classes ----------------------------------------------------------------------
/** The name class.
diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala
index c3596fe62e..424296c212 100644
--- a/src/reflect/scala/reflect/internal/Symbols.scala
+++ b/src/reflect/scala/reflect/internal/Symbols.scala
@@ -3433,7 +3433,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
}
/** A class for type histories */
- private sealed case class TypeHistory(var validFrom: Period, info: Type, prev: TypeHistory) {
+ private case class TypeHistory(var validFrom: Period, info: Type, prev: TypeHistory) {
assert((prev eq null) || phaseId(validFrom) > phaseId(prev.validFrom), this)
assert(validFrom != NoPeriod, this)
diff --git a/src/reflect/scala/reflect/internal/util/Statistics.scala b/src/reflect/scala/reflect/internal/util/Statistics.scala
index b583137059..905f1bf26e 100644
--- a/src/reflect/scala/reflect/internal/util/Statistics.scala
+++ b/src/reflect/scala/reflect/internal/util/Statistics.scala
@@ -133,6 +133,12 @@ quant)
if (this.value < that.value) -1
else if (this.value > that.value) 1
else 0
+ override def equals(that: Any): Boolean =
+ that match {
+ case that: Counter => (this compare that) == 0
+ case _ => false
+ }
+ override def hashCode = value
override def toString = value.toString
}
@@ -184,6 +190,12 @@ quant)
if (this.specificNanos < that.specificNanos) -1
else if (this.specificNanos > that.specificNanos) 1
else 0
+ override def equals(that: Any): Boolean =
+ that match {
+ case that: StackableTimer => (this compare that) == 0
+ case _ => false
+ }
+ override def hashCode = specificNanos.##
override def toString = s"${super.toString} aggregate, ${show(specificNanos)} specific"
}
diff --git a/src/scaladoc/scala/tools/nsc/doc/model/diagram/Diagram.scala b/src/scaladoc/scala/tools/nsc/doc/model/diagram/Diagram.scala
index 150b293b81..1846f375cd 100644
--- a/src/scaladoc/scala/tools/nsc/doc/model/diagram/Diagram.scala
+++ b/src/scaladoc/scala/tools/nsc/doc/model/diagram/Diagram.scala
@@ -10,7 +10,7 @@ import model._
* @author Damien Obrist
* @author Vlad Ureche
*/
-abstract class Diagram {
+sealed abstract class Diagram {
def nodes: List[Node]
def edges: List[(Node, List[Node])]
def isContentDiagram = false // Implemented by ContentDiagram
@@ -44,7 +44,7 @@ trait DepthInfo {
def maxDepth: Int
}
-abstract class Node {
+sealed abstract class Node {
def name = tpe.name
def tpe: TypeEntity
def tpl: Option[TemplateEntity]
diff --git a/test/files/codelib/.gitignore b/test/files/codelib/.gitignore
new file mode 100644
index 0000000000..f77a26afb7
--- /dev/null
+++ b/test/files/codelib/.gitignore
@@ -0,0 +1 @@
+code.jar
diff --git a/test/files/jvm/bytecode-test-example/Foo_1.flags b/test/files/jvm/bytecode-test-example/Foo_1.flags
new file mode 100644
index 0000000000..49f2d2c4c8
--- /dev/null
+++ b/test/files/jvm/bytecode-test-example/Foo_1.flags
@@ -0,0 +1 @@
+-Ybackend:GenASM
diff --git a/test/files/jvm/nooptimise/Foo_1.flags b/test/files/jvm/nooptimise/Foo_1.flags
index 9686c20775..f493cf9f3f 100644
--- a/test/files/jvm/nooptimise/Foo_1.flags
+++ b/test/files/jvm/nooptimise/Foo_1.flags
@@ -1 +1 @@
--optimise -Ynooptimise \ No newline at end of file
+-Ybackend:GenASM -optimise -Ynooptimise \ No newline at end of file
diff --git a/test/files/jvm/scala-concurrent-tck.check b/test/files/jvm/scala-concurrent-tck.check
deleted file mode 100644
index 18fc456acb..0000000000
--- a/test/files/jvm/scala-concurrent-tck.check
+++ /dev/null
@@ -1,3 +0,0 @@
-scala-concurrent-tck.scala:429: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses
- f
- ^
diff --git a/test/files/jvm/scala-concurrent-tck.scala b/test/files/jvm/scala-concurrent-tck.scala
index 438ee310e6..6e2b8ca033 100644
--- a/test/files/jvm/scala-concurrent-tck.scala
+++ b/test/files/jvm/scala-concurrent-tck.scala
@@ -15,21 +15,17 @@ import scala.reflect.{ classTag, ClassTag }
import scala.tools.partest.TestUtil.intercept
trait TestBase {
-
- def once(body: (() => Unit) => Unit) {
- val sv = new SyncVar[Boolean]
- body(() => sv put true)
- sv.take(2000)
+ trait Done { def apply(proof: => Boolean): Unit }
+ def once(body: Done => Unit) {
+ import java.util.concurrent.{ LinkedBlockingQueue, TimeUnit }
+ val q = new LinkedBlockingQueue[Try[Boolean]]
+ body(new Done {
+ def apply(proof: => Boolean): Unit = q offer Try(proof)
+ })
+ assert(q.poll(2000, TimeUnit.MILLISECONDS).get)
+ // Check that we don't get more than one completion
+ assert(q.poll(50, TimeUnit.MILLISECONDS) eq null)
}
-
- // def assert(cond: => Boolean) {
- // try {
- // Predef.assert(cond)
- // } catch {
- // case e => e.printStackTrace()
- // }
- // }
-
}
@@ -39,99 +35,52 @@ trait FutureCallbacks extends TestBase {
def testOnSuccess(): Unit = once {
done =>
var x = 0
- val f = future {
- x = 1
- }
- f onSuccess {
- case _ =>
- done()
- assert(x == 1)
- }
+ val f = future { x = 1 }
+ f onSuccess { case _ => done(x == 1) }
}
def testOnSuccessWhenCompleted(): Unit = once {
done =>
var x = 0
- val f = future {
- x = 1
- }
+ val f = future { x = 1 }
f onSuccess {
- case _ =>
- assert(x == 1)
+ case _ if x == 1 =>
x = 2
- f onSuccess {
- case _ =>
- assert(x == 2)
- done()
- }
+ f onSuccess { case _ => done(x == 2) }
}
}
def testOnSuccessWhenFailed(): Unit = once {
done =>
- val f = future[Unit] {
- done()
- throw new Exception
- }
- f onSuccess {
- case _ => assert(false)
- }
+ val f = future[Unit] { throw new Exception }
+ f onSuccess { case _ => done(false) }
+ f onFailure { case _ => done(true) }
}
def testOnFailure(): Unit = once {
done =>
- var x = 0
- val f = future[Unit] {
- x = 1
- throw new Exception
- }
- f onSuccess {
- case _ =>
- done()
- assert(false)
- }
- f onFailure {
- case _ =>
- done()
- assert(x == 1)
- }
+ val f = future[Unit] { throw new Exception }
+ f onSuccess { case _ => done(false) }
+ f onFailure { case _ => done(true) }
}
def testOnFailureWhenSpecialThrowable(num: Int, cause: Throwable): Unit = once {
done =>
- val f = future[Unit] {
- throw cause
- }
- f onSuccess {
- case _ =>
- done()
- assert(false)
- }
+ val f = future[Unit] { throw cause }
+ f onSuccess { case _ => done(false) }
f onFailure {
- case e: ExecutionException if (e.getCause == cause) =>
- done()
- case _ =>
- done()
- assert(false)
+ case e: ExecutionException if e.getCause == cause => done(true)
+ case _ => done(false)
}
}
def testOnFailureWhenTimeoutException(): Unit = once {
done =>
- val f = future[Unit] {
- throw new TimeoutException()
- }
- f onSuccess {
- case _ =>
- done()
- assert(false)
- }
+ val f = future[Unit] { throw new TimeoutException() }
+ f onSuccess { case _ => done(false) }
f onFailure {
- case e: TimeoutException =>
- done()
- case other =>
- done()
- assert(false)
+ case e: TimeoutException => done(true)
+ case _ => done(false)
}
}
@@ -151,7 +100,6 @@ trait FutureCallbacks extends TestBase {
//testOnFailureWhenSpecialThrowable(7, new InterruptedException)
testThatNestedCallbacksDoNotYieldStackOverflow()
testOnFailureWhenTimeoutException()
-
}
@@ -162,207 +110,120 @@ trait FutureCombinators extends TestBase {
done =>
val f = future { 5 }
val g = f map { x => "result: " + x }
- g onSuccess {
- case s =>
- done()
- assert(s == "result: 5")
- }
- g onFailure {
- case _ =>
- done()
- assert(false)
- }
+ g onSuccess { case s => done(s == "result: 5") }
+ g onFailure { case _ => done(false) }
}
def testMapFailure(): Unit = once {
done =>
- val f = future {
- throw new Exception("exception message")
- }
+ val f = future[Unit] { throw new Exception("exception message") }
val g = f map { x => "result: " + x }
- g onSuccess {
- case _ =>
- done()
- assert(false)
- }
- g onFailure {
- case t =>
- done()
- assert(t.getMessage() == "exception message")
- }
+ g onSuccess { case _ => done(false) }
+ g onFailure { case t => done(t.getMessage() == "exception message") }
}
def testMapSuccessPF(): Unit = once {
done =>
val f = future { 5 }
val g = f map { case r => "result: " + r }
- g onSuccess {
- case s =>
- done()
- assert(s == "result: 5")
- }
- g onFailure {
- case _ =>
- done()
- assert(false)
- }
+ g onSuccess { case s => done(s == "result: 5") }
+ g onFailure { case _ => done(false) }
}
def testTransformSuccess(): Unit = once {
done =>
val f = future { 5 }
val g = f.transform(r => "result: " + r, identity)
- g onSuccess {
- case s =>
- done()
- assert(s == "result: 5")
- }
- g onFailure {
- case _ =>
- done()
- assert(false)
- }
+ g onSuccess { case s => done(s == "result: 5") }
+ g onFailure { case _ => done(false) }
}
def testTransformSuccessPF(): Unit = once {
done =>
val f = future { 5 }
val g = f.transform( { case r => "result: " + r }, identity)
- g onSuccess {
- case s =>
- done()
- assert(s == "result: 5")
- }
- g onFailure {
- case _ =>
- done()
- assert(false)
- }
+ g onSuccess { case s => done(s == "result: 5") }
+ g onFailure { case _ => done(false) }
+ }
+
+def testTransformFailure(): Unit = once {
+ done =>
+ val transformed = new Exception("transformed")
+ val f = future { throw new Exception("expected") }
+ val g = f.transform(identity, _ => transformed)
+ g onSuccess { case _ => done(false) }
+ g onFailure { case e => done(e eq transformed) }
+ }
+
+ def testTransformFailurePF(): Unit = once {
+ done =>
+ val e = new Exception("expected")
+ val transformed = new Exception("transformed")
+ val f = future[Unit] { throw e }
+ val g = f.transform(identity, { case `e` => transformed })
+ g onSuccess { case _ => done(false) }
+ g onFailure { case e => done(e eq transformed) }
}
def testFoldFailure(): Unit = once {
done =>
- val f = future {
- throw new Exception("exception message")
- }
+ val f = future[Unit] { throw new Exception("expected") }
val g = f.transform(r => "result: " + r, identity)
- g onSuccess {
- case _ =>
- done()
- assert(false)
- }
- g onFailure {
- case t =>
- done()
- assert(t.getMessage() == "exception message")
- }
+ g onSuccess { case _ => done(false) }
+ g onFailure { case t => done(t.getMessage() == "expected") }
}
def testFlatMapSuccess(): Unit = once {
done =>
val f = future { 5 }
val g = f flatMap { _ => future { 10 } }
- g onSuccess {
- case x =>
- done()
- assert(x == 10)
- }
- g onFailure {
- case _ =>
- done()
- assert(false)
- }
+ g onSuccess { case x => done(x == 10) }
+ g onFailure { case _ => done(false) }
}
def testFlatMapFailure(): Unit = once {
done =>
- val f = future {
- throw new Exception("exception message")
- }
+ val f = future[Unit] { throw new Exception("expected") }
val g = f flatMap { _ => future { 10 } }
- g onSuccess {
- case _ =>
- done()
- assert(false)
- }
- g onFailure {
- case t =>
- done()
- assert(t.getMessage() == "exception message")
- }
+ g onSuccess { case _ => done(false) }
+ g onFailure { case t => done(t.getMessage() == "expected") }
}
def testFilterSuccess(): Unit = once {
done =>
val f = future { 4 }
val g = f filter { _ % 2 == 0 }
- g onSuccess {
- case x: Int =>
- done()
- assert(x == 4)
- }
- g onFailure {
- case _ =>
- done()
- assert(false)
- }
+ g onSuccess { case x: Int => done(x == 4) }
+ g onFailure { case _ => done(false) }
}
def testFilterFailure(): Unit = once {
done =>
val f = future { 4 }
val g = f filter { _ % 2 == 1 }
- g onSuccess {
- case x: Int =>
- done()
- assert(false)
- }
+ g onSuccess { case x: Int => done(false) }
g onFailure {
- case e: NoSuchElementException =>
- done()
- assert(true)
- case _ =>
- done()
- assert(false)
+ case e: NoSuchElementException => done(true)
+ case _ => done(false)
}
}
def testCollectSuccess(): Unit = once {
done =>
val f = future { -5 }
- val g = f collect {
- case x if x < 0 => -x
- }
- g onSuccess {
- case x: Int =>
- done()
- assert(x == 5)
- }
- g onFailure {
- case _ =>
- done()
- assert(false)
- }
+ val g = f collect { case x if x < 0 => -x }
+ g onSuccess { case x: Int => done(x == 5) }
+ g onFailure { case _ => done(false) }
}
def testCollectFailure(): Unit = once {
done =>
val f = future { -5 }
- val g = f collect {
- case x if x > 0 => x * 2
- }
- g onSuccess {
- case _ =>
- done()
- assert(false)
- }
+ val g = f collect { case x if x > 0 => x * 2 }
+ g onSuccess { case _ => done(false) }
g onFailure {
- case e: NoSuchElementException =>
- done()
- assert(true)
- case _ =>
- done()
- assert(false)
+ case e: NoSuchElementException => done(true)
+ case _ => done(false)
}
}
@@ -376,16 +237,8 @@ trait FutureCombinators extends TestBase {
f foreach { x => p.success(x * 2) }
val g = p.future
- g.onSuccess {
- case res: Int =>
- done()
- assert(res == 10)
- }
- g.onFailure {
- case _ =>
- done()
- assert(false)
- }
+ g.onSuccess { case res: Int => done(res == 10) }
+ g.onFailure { case _ => done(false) }
}
def testForeachFailure(): Unit = once {
@@ -396,16 +249,8 @@ trait FutureCombinators extends TestBase {
f onFailure { case _ => p.failure(new Exception) }
val g = p.future
- g.onSuccess {
- case _ =>
- done()
- assert(false)
- }
- g.onFailure {
- case _ =>
- done()
- assert(true)
- }
+ g.onSuccess { case _ => done(false) }
+ g.onFailure { case _ => done(true) }
}
def testRecoverSuccess(): Unit = once {
@@ -415,18 +260,9 @@ trait FutureCombinators extends TestBase {
throw cause
} recover {
case re: RuntimeException =>
- "recovered"
- }
- f onSuccess {
- case x =>
- done()
- assert(x == "recovered")
- }
- f onFailure { case any =>
- done()
- assert(false)
- }
- f
+ "recovered" }
+ f onSuccess { case x => done(x == "recovered") }
+ f onFailure { case any => done(false) }
}
def testRecoverFailure(): Unit = once {
@@ -437,15 +273,8 @@ trait FutureCombinators extends TestBase {
} recover {
case te: TimeoutException => "timeout"
}
- f onSuccess {
- case x =>
- done()
- assert(false)
- }
- f onFailure { case any =>
- done()
- assert(any == cause)
- }
+ f onSuccess { case _ => done(false) }
+ f onFailure { case any => done(any == cause) }
}
def testRecoverWithSuccess(): Unit = once {
@@ -457,15 +286,8 @@ trait FutureCombinators extends TestBase {
case re: RuntimeException =>
future { "recovered" }
}
- f onSuccess {
- case x =>
- done()
- assert(x == "recovered")
- }
- f onFailure { case any =>
- done()
- assert(false)
- }
+ f onSuccess { case x => done(x == "recovered") }
+ f onFailure { case any => done(false) }
}
def testRecoverWithFailure(): Unit = once {
@@ -477,15 +299,8 @@ trait FutureCombinators extends TestBase {
case te: TimeoutException =>
future { "timeout" }
}
- f onSuccess {
- case x =>
- done()
- assert(false)
- }
- f onFailure { case any =>
- done()
- assert(any == cause)
- }
+ f onSuccess { case x => done(false) }
+ f onFailure { case any => done(any == cause) }
}
def testZipSuccess(): Unit = once {
@@ -493,52 +308,28 @@ trait FutureCombinators extends TestBase {
val f = future { 5 }
val g = future { 6 }
val h = f zip g
- h onSuccess {
- case (l: Int, r: Int) =>
- done()
- assert(l+r == 11)
- }
- h onFailure {
- case _ =>
- done()
- assert(false)
- }
+ h onSuccess { case (l: Int, r: Int) => done(l+r == 11) }
+ h onFailure { case _ => done(false) }
}
def testZipFailureLeft(): Unit = once {
done =>
- val cause = new Exception("exception message")
+ val cause = new Exception("expected")
val f = future { throw cause }
val g = future { 6 }
val h = f zip g
- h onSuccess {
- case _ =>
- done()
- assert(false)
- }
- h onFailure {
- case e: Exception =>
- done()
- assert(e.getMessage == "exception message")
- }
+ h onSuccess { case _ => done(false) }
+ h onFailure { case e: Exception => done(e.getMessage == "expected") }
}
def testZipFailureRight(): Unit = once {
done =>
- val cause = new Exception("exception message")
+ val cause = new Exception("expected")
val f = future { 5 }
val g = future { throw cause }
val h = f zip g
- h onSuccess {
- case _ =>
- done()
- assert(false)
- }
- h onFailure {
- case e: Exception =>
- done()
- assert(e.getMessage == "exception message")
- }
+ h onSuccess { case _ => done(false) }
+ h onFailure { case e: Exception => done(e.getMessage == "expected") }
}
def testFallbackTo(): Unit = once {
@@ -546,17 +337,8 @@ trait FutureCombinators extends TestBase {
val f = future { sys.error("failed") }
val g = future { 5 }
val h = f fallbackTo g
-
- h onSuccess {
- case x: Int =>
- done()
- assert(x == 5)
- }
- h onFailure {
- case _ =>
- done()
- assert(false)
- }
+ h onSuccess { case x: Int => done(x == 5) }
+ h onFailure { case _ => done(false) }
}
def testFallbackToFailure(): Unit = once {
@@ -566,16 +348,8 @@ trait FutureCombinators extends TestBase {
val g = future { throw cause }
val h = f fallbackTo g
- h onSuccess {
- case _ =>
- done()
- assert(false)
- }
- h onFailure {
- case e: Exception =>
- done()
- assert(e == cause)
- }
+ h onSuccess { case _ => done(false) }
+ h onFailure { case e => done(e eq cause) }
}
testMapSuccess()
@@ -597,6 +371,8 @@ trait FutureCombinators extends TestBase {
testZipFailureRight()
testFallbackTo()
testFallbackToFailure()
+ testTransformSuccess()
+ testTransformSuccessPF()
}
@@ -606,40 +382,26 @@ trait FutureProjections extends TestBase {
def testFailedFailureOnComplete(): Unit = once {
done =>
val cause = new RuntimeException
- val f = future {
- throw cause
- }
+ val f = future { throw cause }
f.failed onComplete {
- case Success(t) =>
- assert(t == cause)
- done()
- case Failure(t) =>
- assert(false)
+ case Success(t) => done(t == cause)
+ case Failure(t) => done(false)
}
}
def testFailedFailureOnSuccess(): Unit = once {
done =>
val cause = new RuntimeException
- val f = future {
- throw cause
- }
- f.failed onSuccess {
- case t =>
- assert(t == cause)
- done()
- }
+ val f = future { throw cause }
+ f.failed onSuccess { case t => done(t == cause) }
}
def testFailedSuccessOnComplete(): Unit = once {
done =>
val f = future { 0 }
f.failed onComplete {
- case Success(t) =>
- assert(false)
- case Failure(t) =>
- assert(t.isInstanceOf[NoSuchElementException])
- done()
+ case Failure(_: NoSuchElementException) => done(true)
+ case _ => done(false)
}
}
@@ -647,19 +409,17 @@ trait FutureProjections extends TestBase {
done =>
val f = future { 0 }
f.failed onFailure {
- case nsee: NoSuchElementException =>
- done()
+ case e: NoSuchElementException => done(true)
+ case _ => done(false)
}
+ f.failed onSuccess { case _ => done(false) }
}
def testFailedFailureAwait(): Unit = once {
done =>
val cause = new RuntimeException
- val f = future {
- throw cause
- }
- assert(Await.result(f.failed, Duration(500, "ms")) == cause)
- done()
+ val f = future { throw cause }
+ done(Await.result(f.failed, Duration(500, "ms")) == cause)
}
def testFailedSuccessAwait(): Unit = once {
@@ -667,9 +427,10 @@ trait FutureProjections extends TestBase {
val f = future { 0 }
try {
Await.result(f.failed, Duration(500, "ms"))
- assert(false)
+ done(false)
} catch {
- case nsee: NoSuchElementException => done()
+ case nsee: NoSuchElementException => done(true)
+ case _: Throwable => done(false)
}
}
@@ -682,8 +443,8 @@ trait FutureProjections extends TestBase {
Await.ready(f, Duration.Zero)
Await.ready(f, Duration(500, "ms"))
Await.ready(f, Duration.Inf)
- done()
- } onFailure { case x => throw x }
+ done(true)
+ } onFailure { case x => done(throw x) }
}
def testAwaitNegativeDuration(): Unit = once { done =>
@@ -692,8 +453,8 @@ trait FutureProjections extends TestBase {
intercept[TimeoutException] { Await.ready(f, Duration.Zero) }
intercept[TimeoutException] { Await.ready(f, Duration.MinusInf) }
intercept[TimeoutException] { Await.ready(f, Duration(-500, "ms")) }
- done()
- } onFailure { case x => throw x }
+ done(true)
+ } onFailure { case x => done(throw x) }
}
testFailedFailureOnComplete()
@@ -704,7 +465,6 @@ trait FutureProjections extends TestBase {
testFailedSuccessAwait()
testAwaitPositiveDuration()
testAwaitNegativeDuration()
-
}
@@ -714,33 +474,25 @@ trait Blocking extends TestBase {
def testAwaitSuccess(): Unit = once {
done =>
val f = future { 0 }
- Await.result(f, Duration(500, "ms"))
- done()
+ done(Await.result(f, Duration(500, "ms")) == 0)
}
def testAwaitFailure(): Unit = once {
done =>
val cause = new RuntimeException
- val f = future {
- throw cause
- }
+ val f = future { throw cause }
try {
Await.result(f, Duration(500, "ms"))
- assert(false)
+ done(false)
} catch {
- case t: Throwable =>
- assert(t == cause)
- done()
+ case t: Throwable => done(t == cause)
}
}
def testFQCNForAwaitAPI(): Unit = once {
done =>
-
- assert(classOf[CanAwait].getName == "scala.concurrent.CanAwait")
- assert(Await.getClass.getName == "scala.concurrent.Await")
-
- done()
+ done(classOf[CanAwait].getName == "scala.concurrent.CanAwait" &&
+ Await.getClass.getName == "scala.concurrent.Await")
}
testAwaitSuccess()
@@ -813,22 +565,26 @@ trait Promises extends TestBase {
val p = promise[Int]()
val f = p.future
- f onSuccess {
- case x =>
- done()
- assert(x == 5)
- }
- f onFailure {
- case any =>
- done()
- assert(false)
- }
+ f onSuccess { case x => done(x == 5) }
+ f onFailure { case any => done(false) }
p.success(5)
}
- testSuccess()
+ def testFailure(): Unit = once {
+ done =>
+ val e = new Exception("expected")
+ val p = promise[Int]()
+ val f = p.future
+
+ f onSuccess { case x => done(false) }
+ f onFailure { case any => done(any eq e) }
+
+ p.failure(e)
+ }
+ testSuccess()
+ testFailure()
}
@@ -888,11 +644,11 @@ trait CustomExecutionContext extends TestBase {
val count = countExecs { implicit ec =>
blocking {
once { done =>
- val f = future({ assertNoEC() })(defaultEC)
+ val f = future(assertNoEC())(defaultEC)
f onSuccess {
case _ =>
assertEC()
- done()
+ done(true)
}
assertNoEC()
}
@@ -911,7 +667,7 @@ trait CustomExecutionContext extends TestBase {
f onSuccess {
case _ =>
assertEC()
- done()
+ done(true)
}
}
}
@@ -935,15 +691,10 @@ trait CustomExecutionContext extends TestBase {
Promise.successful(x + 1).future.map(addOne).map(addOne)
} onComplete {
case Failure(t) =>
- try {
- throw new AssertionError("error in test: " + t.getMessage, t)
- } finally {
- done()
- }
+ done(throw new AssertionError("error in test: " + t.getMessage, t))
case Success(x) =>
assertEC()
- assert(x == 14)
- done()
+ done(x == 14)
}
assertNoEC()
}
@@ -999,21 +750,14 @@ trait ExecutionContextPrepare extends TestBase {
done =>
theLocal.set("secret")
val fut = future { 42 }
- fut onComplete {
- case _ =>
- assert(theLocal.get == "secret")
- done()
- }
+ fut onComplete { case _ => done(theLocal.get == "secret") }
}
def testMap(): Unit = once {
done =>
theLocal.set("secret2")
val fut = future { 42 }
- fut map { x =>
- assert(theLocal.get == "secret2")
- done()
- }
+ fut map { x => done(theLocal.get == "secret2") }
}
testOnComplete()
diff --git a/test/files/lib/.gitignore b/test/files/lib/.gitignore
new file mode 100644
index 0000000000..b4ac0b8789
--- /dev/null
+++ b/test/files/lib/.gitignore
@@ -0,0 +1,8 @@
+annotations.jar
+enums.jar
+genericNest.jar
+javac-artifacts.jar
+jsoup-1.3.1.jar
+methvsfield.jar
+nest.jar
+scalacheck.jar
diff --git a/test/files/neg/case-collision.flags b/test/files/neg/case-collision.flags
index 85d8eb2ba2..14c1069dee 100644
--- a/test/files/neg/case-collision.flags
+++ b/test/files/neg/case-collision.flags
@@ -1 +1 @@
--Xfatal-warnings
+-Ybackend:GenASM -Xfatal-warnings
diff --git a/test/files/neg/case-collision2.check b/test/files/neg/case-collision2.check
new file mode 100644
index 0000000000..b8481f46bb
--- /dev/null
+++ b/test/files/neg/case-collision2.check
@@ -0,0 +1,12 @@
+case-collision2.scala:5: warning: Class foo.BIPPY differs only in case from foo.Bippy. Such classes will overwrite one another on case-insensitive filesystems.
+class BIPPY
+ ^
+case-collision2.scala:8: warning: Class foo.DINGO$ differs only in case from foo.Dingo$. Such classes will overwrite one another on case-insensitive filesystems.
+object DINGO
+ ^
+case-collision2.scala:11: warning: Class foo.HyRaX$ differs only in case from foo.Hyrax$. Such classes will overwrite one another on case-insensitive filesystems.
+object HyRaX
+ ^
+error: No warnings can be incurred under -Xfatal-warnings.
+three warnings found
+one error found
diff --git a/test/files/neg/case-collision2.flags b/test/files/neg/case-collision2.flags
new file mode 100644
index 0000000000..5bfa9da5c5
--- /dev/null
+++ b/test/files/neg/case-collision2.flags
@@ -0,0 +1 @@
+-Ynooptimize -Ybackend:GenBCode -Xfatal-warnings
diff --git a/test/files/neg/case-collision2.scala b/test/files/neg/case-collision2.scala
new file mode 100644
index 0000000000..924e33005a
--- /dev/null
+++ b/test/files/neg/case-collision2.scala
@@ -0,0 +1,12 @@
+package foo
+
+class Bippy
+
+class BIPPY
+
+object Dingo
+object DINGO
+
+case class Hyrax()
+object HyRaX
+
diff --git a/test/files/neg/valueclasses-impl-restrictions.check b/test/files/neg/valueclasses-impl-restrictions.check
index 63924493aa..0af9173f74 100644
--- a/test/files/neg/valueclasses-impl-restrictions.check
+++ b/test/files/neg/valueclasses-impl-restrictions.check
@@ -6,12 +6,8 @@ valueclasses-impl-restrictions.scala:9: error: implementation restriction: neste
This restriction is planned to be removed in subsequent releases.
trait I2 {
^
-valueclasses-impl-restrictions.scala:15: error: implementation restriction: nested class is not allowed in value class
-This restriction is planned to be removed in subsequent releases.
- val i2 = new I2 { val q = x.s }
- ^
-valueclasses-impl-restrictions.scala:21: error: implementation restriction: nested class is not allowed in value class
+valueclasses-impl-restrictions.scala:23: error: implementation restriction: nested class is not allowed in value class
This restriction is planned to be removed in subsequent releases.
private[this] class I2(val q: String)
^
-four errors found
+three errors found
diff --git a/test/files/neg/valueclasses-impl-restrictions.scala b/test/files/neg/valueclasses-impl-restrictions.scala
index 137f3f854c..f0577a94aa 100644
--- a/test/files/neg/valueclasses-impl-restrictions.scala
+++ b/test/files/neg/valueclasses-impl-restrictions.scala
@@ -12,8 +12,10 @@ class X1(val s: String) extends AnyVal {
}
def y(x: X1) = {
- val i2 = new I2 { val q = x.s }
+ val i2 = new I2 { val q = x.s } // allowed as of SI-7571
i2.z
+
+ { case x => x } : PartialFunction[Int, Int] // allowed
}
}
diff --git a/src/compiler/scala/tools/cmd/Demo.scala b/test/files/pos/t7591/Demo.scala
index fc90140f8f..696d53585b 100644
--- a/src/compiler/scala/tools/cmd/Demo.scala
+++ b/test/files/pos/t7591/Demo.scala
@@ -3,9 +3,7 @@
* @author Paul Phillips
*/
-package scala
-package tools
-package cmd
+import scala.tools.cmd._
/** A sample command specification for illustrative purposes.
* First take advantage of the meta-options:
diff --git a/test/files/run/t6331.scala b/test/files/run/t6331.scala
index 5ac627a8ea..d9d46f10ea 100644
--- a/test/files/run/t6331.scala
+++ b/test/files/run/t6331.scala
@@ -1,9 +1,4 @@
-import scala.tools.partest._
-import java.io._
-import scala.tools.nsc._
-import scala.tools.cmd.CommandLineParser
-import scala.tools.nsc.{Global, Settings, CompilerCommand}
-import scala.tools.nsc.reporters.ConsoleReporter
+import scala.tools.partest.DirectTest
// Test of Constant#equals, which must must account for floating point intricacies.
object Test extends DirectTest {
diff --git a/test/files/run/t6331b.scala b/test/files/run/t6331b.scala
index c567455c5c..3e09965ee8 100644
--- a/test/files/run/t6331b.scala
+++ b/test/files/run/t6331b.scala
@@ -1,12 +1,5 @@
-import scala.tools.partest._
-import java.io._
-import scala.tools.nsc._
-import scala.tools.cmd.CommandLineParser
-import scala.tools.nsc.{Global, Settings, CompilerCommand}
-import scala.tools.nsc.reporters.ConsoleReporter
-
import scala.tools.partest.trace
-import scala.util.control.Exception._
+import scala.util.control.Exception.allCatch
object Test extends App {
diff --git a/test/files/run/t7008-scala-defined.flags b/test/files/run/t7008-scala-defined.flags
new file mode 100644
index 0000000000..49f2d2c4c8
--- /dev/null
+++ b/test/files/run/t7008-scala-defined.flags
@@ -0,0 +1 @@
+-Ybackend:GenASM
diff --git a/test/files/run/t7271.scala b/test/files/run/t7271.scala
index 55c388b7f5..69d5ea377e 100644
--- a/test/files/run/t7271.scala
+++ b/test/files/run/t7271.scala
@@ -1,5 +1,4 @@
import scala.tools.partest._
-import java.io._
import scala.tools.nsc._
import scala.tools.cmd.CommandLineParser
import scala.tools.nsc.{Global, Settings, CompilerCommand}
diff --git a/test/files/run/t7439/Test_2.scala b/test/files/run/t7439/Test_2.scala
index 3ebbcfe753..ce9b907145 100644
--- a/test/files/run/t7439/Test_2.scala
+++ b/test/files/run/t7439/Test_2.scala
@@ -23,7 +23,8 @@ object Test extends StoreReporterDirectTest {
val a1Class = new File(testOutput.path, "A_1.class")
assert(a1Class.exists)
assert(a1Class.delete())
- println(s"Recompiling after deleting $a1Class")
+ // testIdent normalizes to separate names using '/' regardless of platform, drops all but last two parts
+ println(s"Recompiling after deleting ${a1Class.testIdent}")
// bad symbolic reference error expected (but no stack trace!)
compileCode(C)
diff --git a/test/files/run/t7571.scala b/test/files/run/t7571.scala
new file mode 100644
index 0000000000..00b9695168
--- /dev/null
+++ b/test/files/run/t7571.scala
@@ -0,0 +1,12 @@
+class Foo(val a: Int) extends AnyVal {
+ def foo = { {case x => x + a}: PartialFunction[Int, Int]}
+
+ def bar = (new {}).toString
+}
+
+object Test extends App {
+ val x = new Foo(1).foo.apply(2)
+ assert(x == 3, x)
+ val s = new Foo(1).bar
+ assert(s.nonEmpty, s)
+}
diff --git a/test/files/run/t7582-private-within.check b/test/files/run/t7582-private-within.check
new file mode 100644
index 0000000000..b2743ffa06
--- /dev/null
+++ b/test/files/run/t7582-private-within.check
@@ -0,0 +1,12 @@
+private[package pack] class JavaPackagePrivate
+private[package pack] module JavaPackagePrivate
+private[package pack] module class JavaPackagePrivate
+private[package pack] field field
+private[package pack] primary constructor <init>
+private[package pack] method meth
+private[package pack] field staticField
+private[package pack] method staticMeth
+private[package pack] method <clinit>
+private[package pack] field staticField
+private[package pack] method staticMeth
+private[package pack] method <clinit>
diff --git a/test/files/run/t7582-private-within/JavaPackagePrivate.java b/test/files/run/t7582-private-within/JavaPackagePrivate.java
new file mode 100644
index 0000000000..672d19b57e
--- /dev/null
+++ b/test/files/run/t7582-private-within/JavaPackagePrivate.java
@@ -0,0 +1,8 @@
+package pack;
+
+class JavaPackagePrivate {
+ int field = 0;
+ static int staticField = 0;
+ void meth() { }
+ static void staticMeth() { }
+}
diff --git a/test/files/run/t7582-private-within/Test.scala b/test/files/run/t7582-private-within/Test.scala
new file mode 100644
index 0000000000..3d581f063b
--- /dev/null
+++ b/test/files/run/t7582-private-within/Test.scala
@@ -0,0 +1,22 @@
+import scala.tools.partest.DirectTest
+
+// Testing that the `privateWithin` field is correctly populated on all
+// the related symbols (e.g. module class) under separate compilation.
+object Test extends DirectTest {
+ def code = ???
+
+ def show(): Unit = {
+ val classpath = List(sys.props("partest.lib"), testOutput.path) mkString sys.props("path.separator")
+ val global = newCompiler("-usejavacp", "-cp", classpath, "-d", testOutput.path)
+ import global._
+ withRun(global) { _ =>
+ def check(sym: Symbol) = {
+ sym.initialize
+ println(f"${sym.accessString}%12s ${sym.accurateKindString} ${sym.name.decode}") // we want to see private[pack] for all of these.
+ }
+ val sym = rootMirror.getRequiredClass("pack.JavaPackagePrivate")
+ val syms = Seq(sym, sym.companionModule, sym.companionModule.moduleClass)
+ (syms ++ syms.flatMap(_.info.decls)).foreach(check)
+ }
+ }
+}
diff --git a/test/files/run/t7582.check b/test/files/run/t7582.check
new file mode 100644
index 0000000000..225fb1ace8
--- /dev/null
+++ b/test/files/run/t7582.check
@@ -0,0 +1,2 @@
+warning: there were 1 inliner warning(s); re-run with -Yinline-warnings for details
+2
diff --git a/test/files/run/t7582.flags b/test/files/run/t7582.flags
new file mode 100644
index 0000000000..1182725e86
--- /dev/null
+++ b/test/files/run/t7582.flags
@@ -0,0 +1 @@
+-optimize \ No newline at end of file
diff --git a/test/files/run/t7582/InlineHolder.scala b/test/files/run/t7582/InlineHolder.scala
new file mode 100644
index 0000000000..a18b9effaa
--- /dev/null
+++ b/test/files/run/t7582/InlineHolder.scala
@@ -0,0 +1,16 @@
+package p1 {
+ object InlineHolder {
+ @inline def inlinable = p1.PackageProtectedJava.protectedMethod() + 1
+ }
+}
+
+object O {
+ @noinline
+ def x = p1.InlineHolder.inlinable
+}
+
+object Test {
+ def main(args: Array[String]) {
+ println(O.x)
+ }
+}
diff --git a/test/files/run/t7582/PackageProtectedJava.java b/test/files/run/t7582/PackageProtectedJava.java
new file mode 100644
index 0000000000..b7ea2a7676
--- /dev/null
+++ b/test/files/run/t7582/PackageProtectedJava.java
@@ -0,0 +1,6 @@
+package p1;
+
+// public class, protected method
+public class PackageProtectedJava {
+ static final int protectedMethod() { return 1; }
+}
diff --git a/test/files/run/t7582b.check b/test/files/run/t7582b.check
new file mode 100644
index 0000000000..225fb1ace8
--- /dev/null
+++ b/test/files/run/t7582b.check
@@ -0,0 +1,2 @@
+warning: there were 1 inliner warning(s); re-run with -Yinline-warnings for details
+2
diff --git a/test/files/run/t7582b.flags b/test/files/run/t7582b.flags
new file mode 100644
index 0000000000..1182725e86
--- /dev/null
+++ b/test/files/run/t7582b.flags
@@ -0,0 +1 @@
+-optimize \ No newline at end of file
diff --git a/test/files/run/t7582b/InlineHolder.scala b/test/files/run/t7582b/InlineHolder.scala
new file mode 100644
index 0000000000..a18b9effaa
--- /dev/null
+++ b/test/files/run/t7582b/InlineHolder.scala
@@ -0,0 +1,16 @@
+package p1 {
+ object InlineHolder {
+ @inline def inlinable = p1.PackageProtectedJava.protectedMethod() + 1
+ }
+}
+
+object O {
+ @noinline
+ def x = p1.InlineHolder.inlinable
+}
+
+object Test {
+ def main(args: Array[String]) {
+ println(O.x)
+ }
+}
diff --git a/test/files/run/t7582b/PackageProtectedJava.java b/test/files/run/t7582b/PackageProtectedJava.java
new file mode 100644
index 0000000000..55a44b79f9
--- /dev/null
+++ b/test/files/run/t7582b/PackageProtectedJava.java
@@ -0,0 +1,6 @@
+package p1;
+
+// protected class, public method
+class PackageProtectedJava {
+ public static final int protectedMethod() { return 1; }
+}
diff --git a/test/files/specialized/SI-7343.scala b/test/files/specialized/SI-7343.scala
new file mode 100644
index 0000000000..5ee683064c
--- /dev/null
+++ b/test/files/specialized/SI-7343.scala
@@ -0,0 +1,55 @@
+class Parent[@specialized(Int) T]
+
+object Test extends App {
+
+ /**
+ * This method will check if specialization is correctly rewiring parents
+ * for classes defined inside methods. The pattern is important since this
+ * is how closures are currently represented: as locally-defined anonymous
+ * classes, which usually end up inside methods. For these closures we do
+ * want their parents rewired correctly:
+ *
+ * ```
+ * def checkSuperClass$mIc$sp[T](t: T, ...) = {
+ * class X extends Parent$mcI$sp // instead of just Parent
+ * ...
+ * }
+ */
+ def checkSuperClass[@specialized(Int) T](t: T, expectedXSuper: String) = {
+ // test target:
+ // - in checkSuperClass, X should extend Parent
+ // - in checkSuperClass$mIc$sp, X should extend Parent$mcI$sp
+ class X extends Parent[T]()
+
+ // get the superclass for X and make sure it's correct
+ val actualXSuper = (new X).getClass().getSuperclass().getSimpleName()
+ assert(actualXSuper == expectedXSuper, actualXSuper + " != " + expectedXSuper)
+ }
+
+ checkSuperClass("x", "Parent")
+ checkSuperClass(101, "Parent$mcI$sp")
+
+ /**
+ * This is the same check, but in value. It should work exactly the same
+ * as its method counterpart.
+ */
+ class Val[@specialized(Int) T](t: T, expectedXSuper: String) {
+ val check: T = {
+ class X extends Parent[T]()
+
+ // get the superclass for X and make sure it's correct
+ val actualXSuper = (new X).getClass().getSuperclass().getSimpleName()
+ assert(actualXSuper == expectedXSuper, actualXSuper + " != " + expectedXSuper)
+ t
+ }
+ }
+
+ new Val("x", "Parent")
+ new Val(101, "Parent$mcI$sp")
+
+ /**
+ * NOTE: The the same check, only modified to affect constructors, won't
+ * work since the class X definition will always be lifted to become a
+ * member of the class, making it impossible to force its duplication.
+ */
+}
diff --git a/test/files/specialized/spec-ame.check b/test/files/specialized/spec-ame.check
index 9c1713cc8a..cf18c01191 100644
--- a/test/files/specialized/spec-ame.check
+++ b/test/files/specialized/spec-ame.check
@@ -1,3 +1,3 @@
abc
10
-3 \ No newline at end of file
+2
diff --git a/test/files/specialized/spec-ame.scala b/test/files/specialized/spec-ame.scala
index 79ee4217ed..129fb9f447 100644
--- a/test/files/specialized/spec-ame.scala
+++ b/test/files/specialized/spec-ame.scala
@@ -13,6 +13,9 @@ object Test {
def main(args: Array[String]) {
println((new A("abc")).foo.value)
println((new A(10)).foo.value)
+ // before fixing SI-7343, this was printing 3. Now it's printing 2,
+ // since the anonymous class created by doing new B[T] { ... } when
+ // T = Int is now rewired to B$mcI$sp instead of just B[Int]
println(runtime.BoxesRunTime.integerBoxCount)
}
}
diff --git a/test/files/speclib/.gitignore b/test/files/speclib/.gitignore
new file mode 100644
index 0000000000..2b26f5dfc5
--- /dev/null
+++ b/test/files/speclib/.gitignore
@@ -0,0 +1 @@
+instrumented.jar
diff --git a/tools/.gitignore b/tools/.gitignore
new file mode 100644
index 0000000000..57701c8353
--- /dev/null
+++ b/tools/.gitignore
@@ -0,0 +1 @@
+push.jar