summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbuild.xml3
-rw-r--r--spec/13-syntax-summary.md2
-rw-r--r--src/compiler/scala/tools/ant/Scalac.scala2
-rwxr-xr-xsrc/compiler/scala/tools/ant/templates/tool-unix.tmpl12
-rw-r--r--src/compiler/scala/tools/ant/templates/tool-windows.tmpl2
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala147
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Parsers.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/JavaPlatform.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala7
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala13
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala184
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala510
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaSettings.scala26
-rw-r--r--src/compiler/scala/tools/nsc/transform/CleanUp.scala4
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Infer.scala6
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/RefChecks.scala6
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala96
-rw-r--r--src/compiler/scala/tools/nsc/util/ClassPath.scala19
-rwxr-xr-xsrc/library/scala/collection/IndexedSeqOptimized.scala4
-rw-r--r--src/library/scala/collection/IterableViewLike.scala4
-rw-r--r--src/library/scala/collection/Iterator.scala9
-rw-r--r--src/library/scala/collection/immutable/TreeMap.scala4
-rw-r--r--src/library/scala/collection/immutable/TreeSet.scala4
-rw-r--r--src/reflect/scala/reflect/internal/Definitions.scala2
-rw-r--r--src/reflect/scala/reflect/internal/tpe/TypeMaps.scala16
-rw-r--r--src/reflect/scala/reflect/io/AbstractFile.scala16
-rw-r--r--src/reflect/scala/reflect/runtime/JavaUniverseForce.scala1
-rw-r--r--src/repl/scala/tools/nsc/interpreter/ILoop.scala53
-rw-r--r--src/repl/scala/tools/nsc/interpreter/IMain.scala26
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/DocParser.scala3
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala2
-rw-r--r--test/files/neg/sammy_error_exist_no_crash.check6
-rw-r--r--test/files/neg/sammy_error_exist_no_crash.flags1
-rw-r--r--test/files/neg/sammy_error_exist_no_crash.scala6
-rw-r--r--test/files/neg/sammy_restrictions.scala28
-rw-r--r--test/files/neg/t8534.check4
-rw-r--r--test/files/neg/t8534.scala7
-rw-r--r--test/files/neg/t8534b.check4
-rw-r--r--test/files/neg/t8534b.scala4
-rw-r--r--test/files/neg/t963.check8
-rw-r--r--test/files/pos/sammy_exist.flags1
-rw-r--r--test/files/pos/sammy_exist.scala17
-rw-r--r--test/files/pos/sammy_overload.flags1
-rw-r--r--test/files/pos/sammy_overload.scala9
-rw-r--r--test/files/pos/sammy_override.flags1
-rw-r--r--test/files/pos/sammy_override.scala8
-rw-r--r--test/files/pos/t5413.scala9
-rw-r--r--test/files/pos/t7750.flags1
-rw-r--r--test/files/pos/t7750.scala8
-rw-r--r--test/files/pos/t8310.flags1
-rw-r--r--test/files/pos/t8310.scala22
-rw-r--r--test/files/pos/t8954.flags1
-rw-r--r--test/files/pos/t8954/t1.scala13
-rw-r--r--test/files/pos/t8954/t2.scala39
-rw-r--r--test/files/pos/t8965.flags1
-rw-r--r--test/files/pos/t8965.scala7
-rw-r--r--test/files/run/iterator-concat.check4
-rw-r--r--test/files/run/iterator-concat.scala15
-rw-r--r--test/files/run/iterator-iterate-lazy.scala5
-rw-r--r--test/files/run/iterators.check13
-rw-r--r--test/files/run/iterators.scala136
-rw-r--r--test/files/run/sammy_repeated.check1
-rw-r--r--test/files/run/sammy_repeated.flags1
-rw-r--r--test/files/run/sammy_repeated.scala8
-rw-r--r--test/files/run/t1994.scala20
-rw-r--r--test/files/run/t3516.check3
-rw-r--r--test/files/run/t3516.scala13
-rw-r--r--test/files/run/t5665.scala13
-rw-r--r--test/files/run/t6502.check8
-rw-r--r--test/files/run/t6502.scala101
-rw-r--r--test/files/run/t7407.flags2
-rw-r--r--test/files/run/t8925.check2
-rw-r--r--test/files/run/t8925.flags1
-rw-r--r--test/files/run/t8925.scala31
-rw-r--r--test/files/run/t8933.check1
-rw-r--r--test/files/run/t8933/A_1.scala6
-rw-r--r--test/files/run/t8933/Test_2.scala10
-rw-r--r--test/files/run/t8933b/A.scala4
-rw-r--r--test/files/run/t8933b/Test.scala9
-rw-r--r--test/files/run/t8933c.scala14
-rw-r--r--test/files/run/t8960.scala8
-rw-r--r--test/files/t8449/Client.scala3
-rw-r--r--test/files/t8449/Test.java10
-rw-r--r--test/junit/scala/collection/IndexedSeqOptimizedTest.scala13
-rw-r--r--test/junit/scala/collection/IterableViewLikeTest.scala20
-rw-r--r--test/junit/scala/collection/IteratorTest.scala133
-rw-r--r--test/junit/scala/collection/immutable/TreeMapTest.scala20
-rw-r--r--test/junit/scala/collection/immutable/TreeSetTest.scala20
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala21
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala50
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala80
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala8
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala99
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala83
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala221
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala60
-rw-r--r--test/junit/scala/tools/testing/AssertUtil.scala20
98 files changed, 2253 insertions, 433 deletions
diff --git a/build.xml b/build.xml
index cb72b05e3e..32be490a49 100755
--- a/build.xml
+++ b/build.xml
@@ -848,8 +848,7 @@ TODO:
-->
<path id="pack.reflect.files"> <fileset dir="${build-quick.dir}/classes/reflect"/> </path>
- <path id="pack.scalap.files"> <fileset dir="${build-quick.dir}/classes/scalap"/>
- <fileset file="${src.dir}/scalap/decoder.properties"/> </path>
+ <path id="pack.scalap.files"> <fileset dir="${build-quick.dir}/classes/scalap"/> </path>
<path id="pack.partest-extras.files"> <fileset dir="${build-quick.dir}/classes/partest-extras"/> </path>
<path id="pack.partest-javaagent.files"> <fileset dir="${build-quick.dir}/classes/partest-javaagent"/> </path>
diff --git a/spec/13-syntax-summary.md b/spec/13-syntax-summary.md
index 86efcf70a8..ae941f189e 100644
--- a/spec/13-syntax-summary.md
+++ b/spec/13-syntax-summary.md
@@ -210,7 +210,7 @@ grammar.
ClassParams ::= ClassParam {‘,’ ClassParam}
ClassParam ::= {Annotation} {Modifier} [(`val' | `var')]
id ‘:’ ParamType [‘=’ Expr]
- Bindings ::= ‘(’ Binding {‘,’ Binding ‘)’
+ Bindings ::= ‘(’ Binding {‘,’ Binding} ‘)’
Binding ::= (id | ‘_’) [‘:’ Type]
Modifier ::= LocalModifier
diff --git a/src/compiler/scala/tools/ant/Scalac.scala b/src/compiler/scala/tools/ant/Scalac.scala
index 1747405f03..8905c94eeb 100644
--- a/src/compiler/scala/tools/ant/Scalac.scala
+++ b/src/compiler/scala/tools/ant/Scalac.scala
@@ -97,7 +97,7 @@ class Scalac extends ScalaMatchingTask with ScalacShared {
/** Defines valid values for the `target` property. */
object Target extends PermissibleValue {
- val values = List("jvm-1.5", "jvm-1.6", "jvm-1.7")
+ val values = List("jvm-1.5", "jvm-1.6", "jvm-1.7", "jvm-1.8")
}
/** Defines valid values for the `deprecation` and `unchecked` properties. */
diff --git a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl
index f58223a39e..7acb3632d2 100755
--- a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl
+++ b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl
@@ -86,10 +86,14 @@ fi
TOOL_CLASSPATH="@classpath@"
if [[ -z "$TOOL_CLASSPATH" ]]; then
for ext in "$SCALA_HOME"/lib/* ; do
- if [[ -z "$TOOL_CLASSPATH" ]]; then
- TOOL_CLASSPATH="$ext"
- else
- TOOL_CLASSPATH="${TOOL_CLASSPATH}${SEP}${ext}"
+ file_extension="${ext##*.}"
+ # SI-8967 Only consider directories and files named '*.jar'
+ if [[ -d "$ext" || $file_extension == "jar" ]]; then
+ if [[ -z "$TOOL_CLASSPATH" ]]; then
+ TOOL_CLASSPATH="$ext"
+ else
+ TOOL_CLASSPATH="${TOOL_CLASSPATH}${SEP}${ext}"
+ fi
fi
done
fi
diff --git a/src/compiler/scala/tools/ant/templates/tool-windows.tmpl b/src/compiler/scala/tools/ant/templates/tool-windows.tmpl
index cf0e003f10..50e44fb669 100644
--- a/src/compiler/scala/tools/ant/templates/tool-windows.tmpl
+++ b/src/compiler/scala/tools/ant/templates/tool-windows.tmpl
@@ -128,7 +128,7 @@ if defined _JAVA_PARAMS set _JAVA_OPTS=%_JAVA_OPTS% %_JAVA_PARAMS%
set _TOOL_CLASSPATH=@classpath@
if "%_TOOL_CLASSPATH%"=="" (
- for %%f in ("!_SCALA_HOME!\lib\*") do call :add_cpath "%%f"
+ for %%f in ("!_SCALA_HOME!\lib\*.jar") do call :add_cpath "%%f"
for /d %%f in ("!_SCALA_HOME!\lib\*") do call :add_cpath "%%f"
)
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala
index 9cc9712b44..430424d0f8 100644
--- a/src/compiler/scala/tools/nsc/Global.scala
+++ b/src/compiler/scala/tools/nsc/Global.scala
@@ -8,12 +8,13 @@ package tools
package nsc
import java.io.{ File, FileOutputStream, PrintWriter, IOException, FileNotFoundException }
+import java.net.URL
import java.nio.charset.{ Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException }
import scala.compat.Platform.currentTime
import scala.collection.{ mutable, immutable }
import io.{ SourceReader, AbstractFile, Path }
import reporters.{ Reporter, ConsoleReporter }
-import util.{ ClassPath, StatisticsInfo, returning, stackTraceString }
+import util.{ ClassPath, MergedClassPath, StatisticsInfo, returning, stackTraceString }
import scala.reflect.ClassTag
import scala.reflect.internal.util.{ OffsetPosition, SourceFile, NoSourceFile, BatchSourceFile, ScriptSourceFile }
import scala.reflect.internal.pickling.{ PickleBuffer, PickleFormat }
@@ -841,6 +842,150 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
} reverse
}
+ // ------------ REPL utilities ---------------------------------
+
+ /** Extend classpath of `platform` and rescan updated packages. */
+ def extendCompilerClassPath(urls: URL*): Unit = {
+ val newClassPath = platform.classPath.mergeUrlsIntoClassPath(urls: _*)
+ platform.currentClassPath = Some(newClassPath)
+ // Reload all specified jars into this compiler instance
+ invalidateClassPathEntries(urls.map(_.getPath): _*)
+ }
+
+ // ------------ Invalidations ---------------------------------
+
+ /** Is given package class a system package class that cannot be invalidated?
+ */
+ private def isSystemPackageClass(pkg: Symbol) =
+ pkg == RootClass || (pkg.hasTransOwner(definitions.ScalaPackageClass) && !pkg.hasTransOwner(this.rootMirror.staticPackage("scala.tools").moduleClass.asClass))
+
+ /** Invalidates packages that contain classes defined in a classpath entry, and
+ * rescans that entry.
+ *
+ * First, the classpath entry referred to by one of the `paths` is rescanned,
+ * so that any new files or changes in subpackages are picked up.
+ * Second, any packages for which one of the following conditions is met is invalidated:
+ * - the classpath entry contained during the last compilation run now contains classfiles
+ * that represent a member in the package;
+ * - the classpath entry now contains classfiles that represent a member in the package;
+ * - the set of subpackages has changed.
+ *
+ * The invalidated packages are reset in their entirety; all member classes and member packages
+ * are re-accessed using the new classpath.
+ *
+ * System packages that the compiler needs to access as part of standard definitions
+ * are not invalidated. A system package is:
+ * Any package rooted in "scala", with the exception of packages rooted in "scala.tools".
+ *
+ * @param paths Fully-qualified names that refer to directories or jar files that are
+ * entries on the classpath.
+ */
+ def invalidateClassPathEntries(paths: String*): Unit = {
+ implicit object ClassPathOrdering extends Ordering[PlatformClassPath] {
+ def compare(a:PlatformClassPath, b:PlatformClassPath) = a.asClasspathString compare b.asClasspathString
+ }
+ val invalidated, failed = new mutable.ListBuffer[ClassSymbol]
+ classPath match {
+ case cp: MergedClassPath[_] =>
+ def assoc(path: String): List[(PlatformClassPath, PlatformClassPath)] = {
+ val dir = AbstractFile.getDirectory(path)
+ val canonical = dir.canonicalPath
+ def matchesCanonical(e: ClassPath[_]) = e.origin match {
+ case Some(opath) =>
+ AbstractFile.getDirectory(opath).canonicalPath == canonical
+ case None =>
+ false
+ }
+ cp.entries find matchesCanonical match {
+ case Some(oldEntry) =>
+ List(oldEntry -> cp.context.newClassPath(dir))
+ case None =>
+ error(s"Error adding entry to classpath. During invalidation, no entry named $path in classpath $classPath")
+ List()
+ }
+ }
+ val subst = immutable.TreeMap(paths flatMap assoc: _*)
+ if (subst.nonEmpty) {
+ platform updateClassPath subst
+ informProgress(s"classpath updated on entries [${subst.keys mkString ","}]")
+ def mkClassPath(elems: Iterable[PlatformClassPath]): PlatformClassPath =
+ if (elems.size == 1) elems.head
+ else new MergedClassPath(elems, classPath.context)
+ val oldEntries = mkClassPath(subst.keys)
+ val newEntries = mkClassPath(subst.values)
+ mergeNewEntries(newEntries, RootClass, Some(classPath), Some(oldEntries), invalidated, failed)
+ }
+ }
+ def show(msg: String, syms: scala.collection.Traversable[Symbol]) =
+ if (syms.nonEmpty)
+ informProgress(s"$msg: ${syms map (_.fullName) mkString ","}")
+ show("invalidated packages", invalidated)
+ show("could not invalidate system packages", failed)
+ }
+
+ /** Merges new classpath entries into the symbol table
+ *
+ * @param newEntries The new classpath entries
+ * @param root The root symbol to be resynced (a package class)
+ * @param allEntries Optionally, the corresponding package in the complete current classpath
+ * @param oldEntries Optionally, the corresponding package in the old classpath entries
+ * @param invalidated A listbuffer collecting the invalidated package classes
+ * @param failed A listbuffer collecting system package classes which could not be invalidated
+ *
+ * The merging strategy is determined by the absence or presence of classes and packages.
+ *
+ * If either oldEntries or newEntries contains classes, root is invalidated provided that a corresponding package
+ * exists in allEntries. Otherwise it is removed.
+ * Otherwise, the action is determined by the following matrix, with columns:
+ *
+ * old sym action
+ * + + recurse into all child packages of newEntries
+ * - + invalidate root
+ * - - create and enter root
+ *
+ * Here, old means classpath, and sym means symboltable. + is presence of an entry in its column, - is absence.
+ */
+ private def mergeNewEntries(newEntries: PlatformClassPath, root: ClassSymbol,
+ allEntries: OptClassPath, oldEntries: OptClassPath,
+ invalidated: mutable.ListBuffer[ClassSymbol], failed: mutable.ListBuffer[ClassSymbol]) {
+ ifDebug(informProgress(s"syncing $root, $oldEntries -> $newEntries"))
+
+ val getName: ClassPath[AbstractFile] => String = (_.name)
+ def hasClasses(cp: OptClassPath) = cp.isDefined && cp.get.classes.nonEmpty
+ def invalidateOrRemove(root: ClassSymbol) = {
+ allEntries match {
+ case Some(cp) => root setInfo new loaders.PackageLoader(cp)
+ case None => root.owner.info.decls unlink root.sourceModule
+ }
+ invalidated += root
+ }
+ def subPackage(cp: PlatformClassPath, name: String): OptClassPath =
+ cp.packages find (cp1 => getName(cp1) == name)
+
+ val classesFound = hasClasses(oldEntries) || newEntries.classes.nonEmpty
+ if (classesFound && !isSystemPackageClass(root)) {
+ invalidateOrRemove(root)
+ } else {
+ if (classesFound) {
+ if (root.isRoot) invalidateOrRemove(EmptyPackageClass)
+ else failed += root
+ }
+ if (!oldEntries.isDefined) invalidateOrRemove(root)
+ else
+ for (pstr <- newEntries.packages.map(getName)) {
+ val pname = newTermName(pstr)
+ val pkg = (root.info decl pname) orElse {
+ // package does not exist in symbol table, create symbol to track it
+ assert(!subPackage(oldEntries.get, pstr).isDefined)
+ loaders.enterPackage(root, pstr, new loaders.PackageLoader(allEntries.get))
+ }
+ mergeNewEntries(subPackage(newEntries, pstr).get, pkg.moduleClass.asClass,
+ subPackage(allEntries.get, pstr), subPackage(oldEntries.get, pstr),
+ invalidated, failed)
+ }
+ }
+ }
+
// ----------- Runs ---------------------------------------
private var curRun: Run = null
diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
index 15d6a4d1b4..4663810003 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
@@ -1559,7 +1559,7 @@ self =>
}
/** {{{
- * PrefixExpr ::= [`-' | `+' | `~' | `!' | `&'] SimpleExpr
+ * PrefixExpr ::= [`-' | `+' | `~' | `!'] SimpleExpr
* }}}
*/
def prefixExpr(): Tree = {
diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala
index 7236bf70d5..4877bd9b80 100644
--- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala
+++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala
@@ -16,7 +16,7 @@ trait JavaPlatform extends Platform {
import global._
import definitions._
- private var currentClassPath: Option[MergedClassPath[AbstractFile]] = None
+ private[nsc] var currentClassPath: Option[MergedClassPath[AbstractFile]] = None
def classPath: ClassPath[AbstractFile] = {
if (currentClassPath.isEmpty) currentClassPath = Some(new PathResolver(settings).result)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
index 2af2037fec..7269910af6 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
@@ -5,7 +5,7 @@
package scala.tools.nsc.backend.jvm
-import scala.tools.asm.tree.{ClassNode, MethodNode}
+import scala.tools.asm.tree.{AbstractInsnNode, ClassNode, MethodNode}
import java.io.PrintWriter
import scala.tools.asm.util.{TraceClassVisitor, TraceMethodVisitor, Textifier}
import scala.tools.asm.ClassReader
@@ -58,4 +58,9 @@ object AsmUtils {
new ClassReader(bytes).accept(node, 0)
node
}
+
+ def instructionString(instruction: AbstractInsnNode): String = instruction.getOpcode match {
+ case -1 => instruction.toString
+ case op => scala.tools.asm.util.Printer.OPCODES(op)
+ }
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala
index 4b9383c67c..03306f30aa 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala
@@ -14,7 +14,7 @@ object BackendStats {
val bcodeInitTimer = newSubTimer("bcode initialization", bcodeTimer)
val bcodeGenStat = newSubTimer("code generation", bcodeTimer)
- val bcodeDceTimer = newSubTimer("dead code elimination", bcodeTimer)
+ val methodOptTimer = newSubTimer("intra-method optimizations", bcodeTimer)
val bcodeWriteTimer = newSubTimer("classfile writing", bcodeTimer)
def timed[T](timer: Statistics.Timer)(body: => T): T = {
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
index ba94a9c44c..a45f586666 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
@@ -9,12 +9,12 @@ package tools.nsc
package backend
package jvm
-import scala.collection.{ mutable, immutable }
-import scala.annotation.switch
+import scala.collection.mutable
import scala.reflect.internal.util.Statistics
import scala.tools.asm
import scala.tools.asm.tree.ClassNode
+import scala.tools.nsc.backend.jvm.opt.LocalOpt
/*
* Prepare in-memory representations of classfiles using the ASM Tree API, and serialize them to disk.
@@ -215,13 +215,10 @@ abstract class GenBCode extends BCodeSyncAndTry {
* - converting the plain ClassNode to byte array and placing it on queue-3
*/
class Worker2 {
- def localOptimizations(classNode: ClassNode): Unit = {
- def dce(): Boolean = BackendStats.timed(BackendStats.bcodeDceTimer) {
- if (settings.YoptUnreachableCode) opt.LocalOpt.removeUnreachableCode(classNode)
- else false
- }
+ lazy val localOpt = new LocalOpt(settings)
- dce()
+ def localOptimizations(classNode: ClassNode): Unit = {
+ BackendStats.timed(BackendStats.methodOptTimer)(localOpt.methodOptimizations(classNode))
}
def run() {
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
new file mode 100644
index 0000000000..6b4047c0a7
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
@@ -0,0 +1,184 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2014 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import scala.annotation.{tailrec, switch}
+import scala.collection.mutable
+import scala.reflect.internal.util.Collections._
+import scala.tools.asm.Opcodes
+import scala.tools.asm.tree._
+import scala.collection.convert.decorateAsScala._
+
+object BytecodeUtils {
+
+ object Goto {
+ def unapply(instruction: AbstractInsnNode): Option[JumpInsnNode] = {
+ if (instruction.getOpcode == Opcodes.GOTO) Some(instruction.asInstanceOf[JumpInsnNode])
+ else None
+ }
+ }
+
+ object JumpNonJsr {
+ def unapply(instruction: AbstractInsnNode): Option[JumpInsnNode] = {
+ if (isJumpNonJsr(instruction)) Some(instruction.asInstanceOf[JumpInsnNode])
+ else None
+ }
+ }
+
+ object ConditionalJump {
+ def unapply(instruction: AbstractInsnNode): Option[JumpInsnNode] = {
+ if (isConditionalJump(instruction)) Some(instruction.asInstanceOf[JumpInsnNode])
+ else None
+ }
+ }
+
+ object VarInstruction {
+ def unapply(instruction: AbstractInsnNode): Option[VarInsnNode] = {
+ if (isVarInstruction(instruction)) Some(instruction.asInstanceOf[VarInsnNode])
+ else None
+ }
+
+ }
+
+ def isJumpNonJsr(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ // JSR is deprecated in classfile version 50, disallowed in 51. historically, it was used to implement finally.
+ op == Opcodes.GOTO || isConditionalJump(instruction)
+ }
+
+ def isConditionalJump(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ (op >= Opcodes.IFEQ && op <= Opcodes.IF_ACMPNE) || op == Opcodes.IFNULL || op == Opcodes.IFNONNULL
+ }
+
+ def isReturn(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ op >= Opcodes.IRETURN && op <= Opcodes.RETURN
+ }
+
+ def isVarInstruction(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ (op >= Opcodes.ILOAD && op <= Opcodes.ALOAD) || (op >= Opcodes.ISTORE && op <= Opcodes.ASTORE)
+ }
+
+ def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0
+
+ def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = {
+ var result = instruction
+ do { result = result.getNext }
+ while (result != null && !isExecutable(result) && !alsoKeep(result))
+ Option(result)
+ }
+
+ def sameTargetExecutableInstruction(a: JumpInsnNode, b: JumpInsnNode): Boolean = {
+ // Compare next executable instead of the the labels. Identifies a, b as the same target:
+ // LabelNode(a)
+ // LabelNode(b)
+ // Instr
+ nextExecutableInstruction(a.label) == nextExecutableInstruction(b.label)
+ }
+
+ def removeJumpAndAdjustStack(method: MethodNode, jump: JumpInsnNode) {
+ val instructions = method.instructions
+ val op = jump.getOpcode
+ if ((op >= Opcodes.IFEQ && op <= Opcodes.IFGE) || op == Opcodes.IFNULL || op == Opcodes.IFNONNULL) {
+ instructions.insert(jump, getPop(1))
+ } else if ((op >= Opcodes.IF_ICMPEQ && op <= Opcodes.IF_ICMPLE) || op == Opcodes.IF_ACMPEQ || op == Opcodes.IF_ACMPNE) {
+ instructions.insert(jump, getPop(1))
+ instructions.insert(jump, getPop(1))
+ } else {
+ // we can't remove JSR: its execution does not only jump, it also adds a return address to the stack
+ assert(jump.getOpcode == Opcodes.GOTO)
+ }
+ instructions.remove(jump)
+ }
+
+ def finalJumpTarget(source: JumpInsnNode): LabelNode = {
+ @tailrec def followGoto(label: LabelNode, seenLabels: Set[LabelNode]): LabelNode = nextExecutableInstruction(label) match {
+ case Some(Goto(dest)) =>
+ if (seenLabels(dest.label)) dest.label
+ else followGoto(dest.label, seenLabels + dest.label)
+
+ case _ => label
+ }
+ followGoto(source.label, Set(source.label))
+ }
+
+ def negateJumpOpcode(jumpOpcode: Int): Int = (jumpOpcode: @switch) match {
+ case Opcodes.IFEQ => Opcodes.IFNE
+ case Opcodes.IFNE => Opcodes.IFEQ
+
+ case Opcodes.IFLT => Opcodes.IFGE
+ case Opcodes.IFGE => Opcodes.IFLT
+
+ case Opcodes.IFGT => Opcodes.IFLE
+ case Opcodes.IFLE => Opcodes.IFGT
+
+ case Opcodes.IF_ICMPEQ => Opcodes.IF_ICMPNE
+ case Opcodes.IF_ICMPNE => Opcodes.IF_ICMPEQ
+
+ case Opcodes.IF_ICMPLT => Opcodes.IF_ICMPGE
+ case Opcodes.IF_ICMPGE => Opcodes.IF_ICMPLT
+
+ case Opcodes.IF_ICMPGT => Opcodes.IF_ICMPLE
+ case Opcodes.IF_ICMPLE => Opcodes.IF_ICMPGT
+
+ case Opcodes.IF_ACMPEQ => Opcodes.IF_ACMPNE
+ case Opcodes.IF_ACMPNE => Opcodes.IF_ACMPEQ
+
+ case Opcodes.IFNULL => Opcodes.IFNONNULL
+ case Opcodes.IFNONNULL => Opcodes.IFNULL
+ }
+
+ def getPop(size: Int): InsnNode = {
+ val op = if (size == 1) Opcodes.POP else Opcodes.POP2
+ new InsnNode(op)
+ }
+
+ def labelReferences(method: MethodNode): Map[LabelNode, Set[AnyRef]] = {
+ val res = mutable.Map.empty[LabelNode, Set[AnyRef]]
+ def add(l: LabelNode, ref: AnyRef) = if (res contains l) res(l) = res(l) + ref else res(l) = Set(ref)
+
+ method.instructions.iterator().asScala foreach {
+ case jump: JumpInsnNode => add(jump.label, jump)
+ case line: LineNumberNode => add(line.start, line)
+ case switch: LookupSwitchInsnNode => switch.labels.asScala.foreach(add(_, switch)); add(switch.dflt, switch)
+ case switch: TableSwitchInsnNode => switch.labels.asScala.foreach(add(_, switch)); add(switch.dflt, switch)
+ case _ =>
+ }
+ if (method.localVariables != null) {
+ method.localVariables.iterator().asScala.foreach(l => { add(l.start, l); add(l.end, l) })
+ }
+ if (method.tryCatchBlocks != null) {
+ method.tryCatchBlocks.iterator().asScala.foreach(l => { add(l.start, l); add(l.handler, l); add(l.end, l) })
+ }
+
+ res.toMap
+ }
+
+ def substituteLabel(reference: AnyRef, from: LabelNode, to: LabelNode): Unit = {
+ def substList(list: java.util.List[LabelNode]) = {
+ foreachWithIndex(list.asScala.toList) { case (l, i) =>
+ if (l == from) list.set(i, to)
+ }
+ }
+ reference match {
+ case jump: JumpInsnNode => jump.label = to
+ case line: LineNumberNode => line.start = to
+ case switch: LookupSwitchInsnNode => substList(switch.labels); if (switch.dflt == from) switch.dflt = to
+ case switch: TableSwitchInsnNode => substList(switch.labels); if (switch.dflt == from) switch.dflt = to
+ case local: LocalVariableNode =>
+ if (local.start == from) local.start = to
+ if (local.end == from) local.end = to
+ case handler: TryCatchBlockNode =>
+ if (handler.start == from) handler.start = to
+ if (handler.handler == from) handler.handler = to
+ if (handler.end == from) handler.end = to
+ }
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
index 3acd2d6154..273112b93c 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
@@ -7,125 +7,206 @@ package scala.tools.nsc
package backend.jvm
package opt
+import scala.annotation.switch
import scala.tools.asm.{Opcodes, MethodWriter, ClassWriter}
import scala.tools.asm.tree.analysis.{Analyzer, BasicValue, BasicInterpreter}
import scala.tools.asm.tree._
import scala.collection.convert.decorateAsScala._
-import scala.collection.{ mutable => m }
+import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
+import scala.tools.nsc.settings.ScalaSettings
/**
- * Intra-Method optimizations.
+ * Optimizations within a single method.
+ *
+ * unreachable code
+ * - removes instrucions of basic blocks to which no branch instruction points
+ * + enables eliminating some exception handlers and local variable descriptors
+ * > eliminating them is required for correctness, as explained in `removeUnreachableCode`
+ *
+ * empty exception handlers
+ * - removes exception handlers whose try block is empty
+ * + eliminating a handler where the try block is empty and reachable will turn the catch block
+ * unreachble. in this case "unreachable code" is invoked recursively until reaching a fixpiont.
+ * > for try blocks that are unreachable, "unreachable code" removes also the instructions of the
+ * catch block, and the recrusive invocation is not necessary.
+ *
+ * simplify jumps
+ * - various simplifications, see doc domments of individual optimizations
+ * + changing or eliminating jumps may render some code unreachable, therefore "simplify jumps" is
+ * executed in a loop with "unreachable code"
+ *
+ * empty local variable descriptors
+ * - removes entries from the local variable table where the variable is not actually used
+ * + enables eliminating labels that the entry points to (if they are not otherwise referenced)
+ *
+ * empty line numbers
+ * - eliminates line number nodes that describe no executable instructions
+ * + enables eliminating the label of the line number node (if it's not otherwise referenced)
+ *
+ * stale labels
+ * - eliminate labels that are not referenced, merge sequences of label definitions.
*/
-object LocalOpt {
+class LocalOpt(settings: ScalaSettings) {
/**
- * Remove unreachable instructions from all (non-abstract) methods.
+ * Remove unreachable instructions from all (non-abstract) methods and apply various other
+ * cleanups to the bytecode.
*
* @param clazz The class whose methods are optimized
* @return `true` if unreachable code was elminated in some method, `false` otherwise.
*/
- def removeUnreachableCode(clazz: ClassNode): Boolean = {
- clazz.methods.asScala.foldLeft(false) {
- case (changed, method) => removeUnreachableCode(method, clazz.name) || changed
+ def methodOptimizations(clazz: ClassNode): Boolean = {
+ settings.Yopt.value.nonEmpty && clazz.methods.asScala.foldLeft(false) {
+ case (changed, method) => methodOptimizations(method, clazz.name) || changed
}
}
/**
* Remove unreachable code from a method.
+ *
* We rely on dead code elimination provided by the ASM framework, as described in the ASM User
* Guide (http://asm.ow2.org/index.html), Section 8.2.1. It runs a data flow analysis, which only
* computes Frame information for reachable instructions. Instructions for which no Frame data is
* available after the analyis are unreachable.
*
- * TODO doc: it also removes empty handlers, unused local vars
+ * Also simplifies branching instructions, removes unused local variable descriptors, empty
+ * exception handlers, unnecessary label declarations and empty line number nodes.
*
- * Returns `true` if dead code in `method` has been eliminated.
+ * Returns `true` if the bytecode of `method` was changed.
*/
- private def removeUnreachableCode(method: MethodNode, ownerClassName: String): Boolean = {
+ private def methodOptimizations(method: MethodNode, ownerClassName: String): Boolean = {
if (method.instructions.size == 0) return false // fast path for abstract methods
- val codeRemoved = removeUnreachableCodeImpl(method, ownerClassName)
-
// unreachable-code also removes unused local variable nodes and empty exception handlers.
- // This is required for correctness: such nodes are not allowed to refer to instruction offsets
- // that don't exist (because they have been eliminated).
- val localsRemoved = removeUnusedLocalVariableNodes(method)
- val handlersRemoved = removeEmptyExceptionHandlers(method)
-
- // When eliminating a handler, the catch block becomes unreachable. The recursive invocation
- // removes these blocks.
- // Note that invoking removeUnreachableCode*Impl* a second time is not enough: removing the dead
- // catch block can render other handlers empty, which also have to be removed in turn.
- if (handlersRemoved) removeUnreachableCode(method, ownerClassName)
-
- // assert that we can leave local variable annotations as-is
+ // This is required for correctness, for example:
+ //
+ // def f = { return 0; try { 1 } catch { case _ => 2 } }
+ //
+ // The result after removeUnreachableCodeImpl:
+ //
+ // TRYCATCHBLOCK L0 L1 L2 java/lang/Exception
+ // L4
+ // ICONST_0
+ // IRETURN
+ // L0
+ // L1
+ // L2
+ //
+ // If we don't eliminate the handler, the ClassWriter emits:
+ //
+ // TRYCATCHBLOCK L0 L0 L0 java/lang/Exception
+ // L1
+ // ICONST_0
+ // IRETURN
+ // L0
+ //
+ // This triggers "ClassFormatError: Illegal exception table range in class file C". Similar
+ // for local variables in dead blocks. Maybe that's a bug in the ASM framework.
+
+ var recurse = true
+ var codeHandlersOrJumpsChanged = false
+ while (recurse) {
+ // unreachable-code, empty-handlers and simplify-jumps run until reaching a fixpoint (see doc on class LocalOpt)
+ val (codeRemoved, handlersRemoved, liveHandlerRemoved) = if (settings.YoptUnreachableCode) {
+ val (codeRemoved, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName)
+ val removedHandlers = removeEmptyExceptionHandlers(method)
+ (codeRemoved, removedHandlers.nonEmpty, removedHandlers.exists(h => liveLabels(h.start)))
+ } else {
+ (false, false, false)
+ }
+
+ val jumpsChanged = if (settings.YoptSimplifyJumps) simplifyJumps(method) else false
+
+ codeHandlersOrJumpsChanged ||= (codeRemoved || handlersRemoved || jumpsChanged)
+
+ // The doc comment of class LocalOpt explains why we recurse if jumpsChanged || liveHandlerRemoved
+ recurse = settings.YoptRecurseUnreachableJumps && (jumpsChanged || liveHandlerRemoved)
+ }
+
+ // (*) Removing stale local variable descriptors is required for correctness of unreachable-code
+ val localsRemoved =
+ if (settings.YoptCompactLocals) compactLocalVariables(method)
+ else if (settings.YoptUnreachableCode) removeUnusedLocalVariableNodes(method)() // (*)
+ else false
+
+ val lineNumbersRemoved = if (settings.YoptEmptyLineNumbers) removeEmptyLineNumbers(method) else false
+
+ val labelsRemoved = if (settings.YoptEmptyLabels) removeEmptyLabelNodes(method) else false
+
+ // assert that local variable annotations are empty (we don't emit them) - otherwise we'd have
+ // to eliminate those covering an empty range, similar to removeUnusedLocalVariableNodes.
def nullOrEmpty[T](l: java.util.List[T]) = l == null || l.isEmpty
assert(nullOrEmpty(method.visibleLocalVariableAnnotations), method.visibleLocalVariableAnnotations)
assert(nullOrEmpty(method.invisibleLocalVariableAnnotations), method.invisibleLocalVariableAnnotations)
- codeRemoved || localsRemoved || handlersRemoved
+ codeHandlersOrJumpsChanged || localsRemoved || lineNumbersRemoved || labelsRemoved
}
- private def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: String): Boolean = {
- val initialSize = method.instructions.size
- if (initialSize == 0) return false
-
+ /**
+ * Removes unreachable basic blocks.
+ *
+ * TODO: rewrite, don't use computeMaxLocalsMaxStack (runs a ClassWriter) / Analyzer. Too slow.
+ */
+ def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: String): (Boolean, Set[LabelNode]) = {
// The data flow analysis requires the maxLocals / maxStack fields of the method to be computed.
computeMaxLocalsMaxStack(method)
val a = new Analyzer[BasicValue](new BasicInterpreter)
a.analyze(ownerClassName, method)
val frames = a.getFrames
+ val initialSize = method.instructions.size
var i = 0
+ var liveLabels = Set.empty[LabelNode]
val itr = method.instructions.iterator()
while (itr.hasNext) {
- val ins = itr.next()
- // Don't remove label nodes: they might be referenced for example in a LocalVariableNode
- if (frames(i) == null && !ins.isInstanceOf[LabelNode]) {
- // Instruction iterators allow removing during iteration.
- // Removing is O(1): instructions are doubly linked list elements.
- itr.remove()
+ itr.next() match {
+ case l: LabelNode =>
+ if (frames(i) != null) liveLabels += l
+
+ case ins =>
+ // label nodes are not removed: they might be referenced for example in a LocalVariableNode
+ if (frames(i) == null || ins.getOpcode == Opcodes.NOP) {
+ // Instruction iterators allow removing during iteration.
+ // Removing is O(1): instructions are doubly linked list elements.
+ itr.remove()
+ }
}
i += 1
}
-
- method.instructions.size != initialSize
- }
-
- /**
- * Remove exception handlers that cover empty code blocks from all methods of `clazz`.
- * Returns `true` if any exception handler was eliminated.
- */
- def removeEmptyExceptionHandlers(clazz: ClassNode): Boolean = {
- clazz.methods.asScala.foldLeft(false) {
- case (changed, method) => removeEmptyExceptionHandlers(method) || changed
- }
+ (method.instructions.size != initialSize, liveLabels)
}
/**
* Remove exception handlers that cover empty code blocks. A block is considered empty if it
* consist only of labels, frames, line numbers, nops and gotos.
*
+ * There are no executable instructions that we can assume don't throw (eg ILOAD). The JVM spec
+ * basically says that a VirtualMachineError may be thrown at any time:
+ * http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.3
+ *
* Note that no instructions are eliminated.
*
- * @return `true` if some exception handler was eliminated.
+ * @return the set of removed handlers
*/
- def removeEmptyExceptionHandlers(method: MethodNode): Boolean = {
+ def removeEmptyExceptionHandlers(method: MethodNode): Set[TryCatchBlockNode] = {
/** True if there exists code between start and end. */
def containsExecutableCode(start: AbstractInsnNode, end: LabelNode): Boolean = {
- start != end && (start.getOpcode match {
+ start != end && ((start.getOpcode : @switch) match {
// FrameNode, LabelNode and LineNumberNode have opcode == -1.
- case -1 | Opcodes.NOP | Opcodes.GOTO => containsExecutableCode(start.getNext, end)
+ case -1 | Opcodes.GOTO => containsExecutableCode(start.getNext, end)
case _ => true
})
}
- val initialNumberHandlers = method.tryCatchBlocks.size
+ var removedHandlers = Set.empty[TryCatchBlockNode]
val handlersIter = method.tryCatchBlocks.iterator()
while(handlersIter.hasNext) {
val handler = handlersIter.next()
- if (!containsExecutableCode(handler.start, handler.end)) handlersIter.remove()
+ if (!containsExecutableCode(handler.start, handler.end)) {
+ removedHandlers += handler
+ handlersIter.remove()
+ }
}
- method.tryCatchBlocks.size != initialNumberHandlers
+ removedHandlers
}
/**
@@ -135,35 +216,107 @@ object LocalOpt {
* Note that each entry in the local variable table has a start, end and index. Two entries with
* the same index, but distinct start / end ranges are different variables, they may have not the
* same type or name.
- *
- * TODO: also re-allocate locals to occupy fewer slots after eliminating unused ones
*/
- def removeUnusedLocalVariableNodes(method: MethodNode): Boolean = {
+ def removeUnusedLocalVariableNodes(method: MethodNode)(fistLocalIndex: Int = parametersSize(method), renumber: Int => Int = identity): Boolean = {
def variableIsUsed(start: AbstractInsnNode, end: LabelNode, varIndex: Int): Boolean = {
start != end && (start match {
- case v: VarInsnNode => v.`var` == varIndex
+ case v: VarInsnNode if v.`var` == varIndex => true
case _ => variableIsUsed(start.getNext, end, varIndex)
})
}
val initialNumVars = method.localVariables.size
val localsIter = method.localVariables.iterator()
- // The parameters and `this` (for instance methods) have the lowest indices in the local variables
- // table. Note that double / long fields occupy two slots, so we sum up the sizes. Since getSize
- // returns 0 for void, we have to add `max 1`.
- val paramsSize = scala.tools.asm.Type.getArgumentTypes(method.desc).map(_.getSize max 1).sum
- val thisSize = if ((method.access & Opcodes.ACC_STATIC) == 0) 1 else 0
- val endParamIndex = paramsSize + thisSize
while (localsIter.hasNext) {
val local = localsIter.next()
- // parameters and `this` have the lowest indices, starting at 0
- val used = local.index < endParamIndex || variableIsUsed(local.start, local.end, local.index)
- if (!used)
- localsIter.remove()
+ val index = local.index
+ // parameters and `this` (the lowest indices, starting at 0) are never removed or renumbered
+ if (index >= fistLocalIndex) {
+ if (!variableIsUsed(local.start, local.end, index)) localsIter.remove()
+ else if (renumber(index) != index) local.index = renumber(index)
+ }
}
- method.localVariables.size == initialNumVars
+ method.localVariables.size != initialNumVars
}
+ /**
+ * The number of local varialbe slots used for parameters and for the `this` reference.
+ */
+ private def parametersSize(method: MethodNode): Int = {
+ // Double / long fields occupy two slots, so we sum up the sizes. Since getSize returns 0 for
+ // void, we have to add `max 1`.
+ val paramsSize = scala.tools.asm.Type.getArgumentTypes(method.desc).iterator.map(_.getSize max 1).sum
+ val thisSize = if ((method.access & Opcodes.ACC_STATIC) == 0) 1 else 0
+ paramsSize + thisSize
+ }
+
+ /**
+ * Compact the local variable slots used in the method's implementation. This prevents having
+ * unused slots for example after eliminating unreachable code.
+ *
+ * This transformation reduces the size of the frame for invoking the method. For example, if the
+ * method has an ISTORE instruction to the local variable 3, the maxLocals of the method is at
+ * least 4, even if some local variable slots below 3 are not used by any instruction.
+ *
+ * This could be improved by doing proper register allocation.
+ */
+ def compactLocalVariables(method: MethodNode): Boolean = {
+ // This array is built up to map local variable indices from old to new.
+ val renumber = collection.mutable.ArrayBuffer.empty[Int]
+
+ // Add the index of the local variable used by `varIns` to the `renumber` array.
+ def addVar(varIns: VarInsnNode): Unit = {
+ val index = varIns.`var`
+ val isWide = (varIns.getOpcode: @switch) match {
+ case Opcodes.LLOAD | Opcodes.DLOAD | Opcodes.LSTORE | Opcodes.DSTORE => true
+ case _ => false
+ }
+
+ // Ensure the length of `renumber`. Unused variable indices are mapped to -1.
+ val minLength = if (isWide) index + 2 else index + 1
+ for (i <- renumber.length until minLength) renumber += -1
+
+ renumber(index) = index
+ if (isWide) renumber(index + 1) = index
+ }
+
+ // first phase: collect all used local variables. if the variable at index x is used, set
+ // renumber(x) = x, otherwise renumber(x) = -1. if the variable is wide (long or double), set
+ // renumber(x+1) = x.
+
+ val firstLocalIndex = parametersSize(method)
+ for (i <- 0 until firstLocalIndex) renumber += i // parameters and `this` are always used.
+ method.instructions.iterator().asScala foreach {
+ case VarInstruction(varIns) => addVar(varIns)
+ case _ =>
+ }
+
+ // assign the next free slot to each used local variable.
+ // for example, rewrite (0, 1, -1, 3, -1, 5) to (0, 1, -1, 2, -1, 3).
+
+ var nextIndex = firstLocalIndex
+ for (i <- firstLocalIndex until renumber.length if renumber(i) != -1) {
+ renumber(i) = nextIndex
+ nextIndex += 1
+ }
+
+ // Update the local variable descriptors according to the renumber table, and eliminate stale entries
+ val removedLocalVariableDescriptors = removeUnusedLocalVariableNodes(method)(firstLocalIndex, renumber)
+
+ if (nextIndex == renumber.length) removedLocalVariableDescriptors
+ else {
+ // update variable instructions according to the renumber table
+ method.maxLocals = nextIndex
+ method.instructions.iterator().asScala.foreach {
+ case VarInstruction(varIns) =>
+ val oldIndex = varIns.`var`
+ if (oldIndex >= firstLocalIndex && renumber(oldIndex) != oldIndex)
+ varIns.`var` = renumber(varIns.`var`)
+ case _ =>
+ }
+ true
+ }
+ }
/**
* In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM
@@ -187,4 +340,223 @@ object LocalOpt {
method.maxLocals = mw.getMaxLocals
method.maxStack = mw.getMaxStack
}
+
+ /**
+ * Removes LineNumberNodes that don't describe any executable instructions.
+ *
+ * This method expects (and asserts) that the `start` label of each LineNumberNode is the
+ * lexically preceeding label declaration.
+ */
+ def removeEmptyLineNumbers(method: MethodNode): Boolean = {
+ def isEmpty(node: AbstractInsnNode): Boolean = node.getNext match {
+ case null => true
+ case l: LineNumberNode => true
+ case n if n.getOpcode >= 0 => false
+ case n => isEmpty(n)
+ }
+
+ val initialSize = method.instructions.size
+ val iterator = method.instructions.iterator()
+ var previousLabel: LabelNode = null
+ while (iterator.hasNext) {
+ iterator.next match {
+ case label: LabelNode => previousLabel = label
+ case line: LineNumberNode if isEmpty(line) =>
+ assert(line.start == previousLabel)
+ iterator.remove()
+ case _ =>
+ }
+ }
+ method.instructions.size != initialSize
+ }
+
+ /**
+ * Removes unreferenced label declarations, also squashes sequences of label definitions.
+ *
+ * [ops]; Label(a); Label(b); [ops];
+ * => subs([ops], b, a); Label(a); subs([ops], b, a);
+ */
+ def removeEmptyLabelNodes(method: MethodNode): Boolean = {
+ val references = labelReferences(method)
+
+ val initialSize = method.instructions.size
+ val iterator = method.instructions.iterator()
+ var prev: LabelNode = null
+ while (iterator.hasNext) {
+ iterator.next match {
+ case label: LabelNode =>
+ if (!references.contains(label)) iterator.remove()
+ else if (prev != null) {
+ references(label).foreach(substituteLabel(_, label, prev))
+ iterator.remove()
+ } else prev = label
+
+ case instruction =>
+ if (instruction.getOpcode >= 0) prev = null
+ }
+ }
+ method.instructions.size != initialSize
+ }
+
+ /**
+ * Apply various simplifications to branching instructions.
+ */
+ def simplifyJumps(method: MethodNode): Boolean = {
+ var changed = false
+
+ val allHandlers = method.tryCatchBlocks.asScala.toSet
+
+ // A set of all exception handlers that guard the current instruction, required for simplifyGotoReturn
+ var activeHandlers = Set.empty[TryCatchBlockNode]
+
+ // Instructions that need to be removed. simplifyBranchOverGoto returns an instruction to be
+ // removed. It cannot remove it itself because the instruction may be the successor of the current
+ // instruction of the iterator, which is not supported in ASM.
+ var instructionsToRemove = Set.empty[AbstractInsnNode]
+
+ val iterator = method.instructions.iterator()
+ while (iterator.hasNext) {
+ val instruction = iterator.next()
+
+ instruction match {
+ case l: LabelNode =>
+ activeHandlers ++= allHandlers.filter(_.start == l)
+ activeHandlers = activeHandlers.filter(_.end != l)
+ case _ =>
+ }
+
+ if (instructionsToRemove(instruction)) {
+ iterator.remove()
+ instructionsToRemove -= instruction
+ } else if (isJumpNonJsr(instruction)) { // fast path - all of the below only treat jumps
+ var jumpRemoved = simplifyThenElseSameTarget(method, instruction)
+
+ if (!jumpRemoved) {
+ changed = collapseJumpChains(instruction) || changed
+ jumpRemoved = removeJumpToSuccessor(method, instruction)
+
+ if (!jumpRemoved) {
+ val staleGoto = simplifyBranchOverGoto(method, instruction)
+ instructionsToRemove ++= staleGoto
+ changed ||= staleGoto.nonEmpty
+ changed = simplifyGotoReturn(method, instruction, inTryBlock = activeHandlers.nonEmpty) || changed
+ }
+ }
+ changed ||= jumpRemoved
+ }
+ }
+ assert(instructionsToRemove.isEmpty, "some optimization required removing a previously traversed instruction. add `instructionsToRemove.foreach(method.instructions.remove)`")
+ changed
+ }
+
+ /**
+ * Removes a conditional jump if it is followed by a GOTO to the same destination.
+ *
+ * CondJump l; [nops]; GOTO l; [...]
+ * POP*; [nops]; GOTO l; [...]
+ *
+ * Introduces 1 or 2 POP instructions, depending on the number of values consumed by the CondJump.
+ */
+ private def simplifyThenElseSameTarget(method: MethodNode, instruction: AbstractInsnNode): Boolean = instruction match {
+ case ConditionalJump(jump) =>
+ nextExecutableInstruction(instruction) match {
+ case Some(Goto(elseJump)) if sameTargetExecutableInstruction(jump, elseJump) =>
+ removeJumpAndAdjustStack(method, jump)
+ true
+
+ case _ => false
+ }
+ case _ => false
+ }
+
+ /**
+ * Replace jumps to a sequence of GOTO instructions by a jump to the final destination.
+ *
+ * Jump l; [any ops]; l: GOTO m; [any ops]; m: GOTO n; [any ops]; n: NotGOTO; [...]
+ * => Jump n; [rest unchaned]
+ *
+ * If there's a loop of GOTOs, the initial jump is replaced by one of the labels in the loop.
+ */
+ private def collapseJumpChains(instruction: AbstractInsnNode): Boolean = instruction match {
+ case JumpNonJsr(jump) =>
+ val target = finalJumpTarget(jump)
+ if (jump.label == target) false else {
+ jump.label = target
+ true
+ }
+
+ case _ => false
+ }
+
+ /**
+ * Eliminates unnecessary jump instructions
+ *
+ * Jump l; [nops]; l: [...]
+ * => POP*; [nops]; l: [...]
+ *
+ * Introduces 0, 1 or 2 POP instructions, depending on the number of values consumed by the Jump.
+ */
+ private def removeJumpToSuccessor(method: MethodNode, instruction: AbstractInsnNode) = instruction match {
+ case JumpNonJsr(jump) if nextExecutableInstruction(jump, alsoKeep = Set(jump.label)) == Some(jump.label) =>
+ removeJumpAndAdjustStack(method, jump)
+ true
+ case _ => false
+ }
+
+ /**
+ * If the "else" part of a conditional branch is a simple GOTO, negates the conditional branch
+ * and eliminates the GOTO.
+ *
+ * CondJump l; [nops, no labels]; GOTO m; [nops]; l: [...]
+ * => NegatedCondJump m; [nops, no labels]; [nops]; l: [...]
+ *
+ * Note that no label definitions are allowed in the first [nops] section. Otherwsie, there could
+ * be some other jump to the GOTO, and eliminating it would change behavior.
+ *
+ * For technical reasons, we cannot remove the GOTO here (*).Instead this method returns an Option
+ * containing the GOTO that needs to be eliminated.
+ *
+ * (*) The ASM instruction iterator (used in the caller [[simplifyJumps]]) has an undefined
+ * behavior if the successor of the current instruction is removed, which may be the case here
+ */
+ private def simplifyBranchOverGoto(method: MethodNode, instruction: AbstractInsnNode): Option[JumpInsnNode] = instruction match {
+ case ConditionalJump(jump) =>
+ // don't skip over labels, see doc comment
+ nextExecutableInstruction(jump, alsoKeep = _.isInstanceOf[LabelNode]) match {
+ case Some(Goto(goto)) =>
+ if (nextExecutableInstruction(goto, alsoKeep = Set(jump.label)) == Some(jump.label)) {
+ val newJump = new JumpInsnNode(negateJumpOpcode(jump.getOpcode), goto.label)
+ method.instructions.set(jump, newJump)
+ Some(goto)
+ } else None
+
+ case _ => None
+ }
+ case _ => None
+ }
+
+ /**
+ * Inlines xRETURN and ATHROW
+ *
+ * GOTO l; [any ops]; l: xRETURN/ATHROW
+ * => xRETURN/ATHROW; [any ops]; l: xRETURN/ATHROW
+ *
+ * inlining is only done if the GOTO instruction is not part of a try block, otherwise the
+ * rewrite might change the behavior. For xRETURN, the reason is that return insructions may throw
+ * an IllegalMonitorStateException, as described here:
+ * http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.return
+ */
+ private def simplifyGotoReturn(method: MethodNode, instruction: AbstractInsnNode, inTryBlock: Boolean): Boolean = !inTryBlock && (instruction match {
+ case Goto(jump) =>
+ nextExecutableInstruction(jump.label) match {
+ case Some(target) =>
+ if (isReturn(target) || target.getOpcode == Opcodes.ATHROW) {
+ method.instructions.set(jump, target.clone(null))
+ true
+ } else false
+
+ case _ => false
+ }
+ case _ => false
+ })
}
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
index c59d56d8f8..d6650595eb 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -211,21 +211,26 @@ trait ScalaSettings extends AbsScalaSettings
val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "inline")
object YoptChoices extends MultiChoiceEnumeration {
- val unreachableCode = Choice("unreachable-code", "Eliminate unreachable code")
+ val unreachableCode = Choice("unreachable-code", "Eliminate unreachable code, exception handlers protecting no instructions, debug information of eliminated variables.")
+ val simplifyJumps = Choice("simplify-jumps", "Simplify branching instructions, eliminate unnecessery ones.")
+ val recurseUnreachableJumps = Choice("recurse-unreachable-jumps", "Recursively apply unreachable-code and simplify-jumps (if enabled) until reaching a fixpoint.")
+ val emptyLineNumbers = Choice("empty-line-numbers", "Eliminate unnecessary line number information.")
+ val emptyLabels = Choice("empty-labels", "Eliminate and collapse redundant labels in the bytecode.")
+ val compactLocals = Choice("compact-locals", "Eliminate empty slots in the sequence of local variables.")
- val lNone = Choice("l:none", "Don't enable any optimizations")
+ val lNone = Choice("l:none", "Don't enable any optimizations.")
private val defaultChoices = List(unreachableCode)
- val lDefault = Choice("l:default", "Enable default optimizations: "+ defaultChoices.mkString(","), expandsTo = defaultChoices)
+ val lDefault = Choice("l:default", "Enable default optimizations: "+ defaultChoices.mkString(","), expandsTo = defaultChoices)
- private val methodChoices = List(lDefault)
- val lMethod = Choice("l:method", "Intra-method optimizations: "+ methodChoices.mkString(","), expandsTo = methodChoices)
+ private val methodChoices = List(unreachableCode, simplifyJumps, recurseUnreachableJumps, emptyLineNumbers, emptyLabels, compactLocals)
+ val lMethod = Choice("l:method", "Enable intra-method optimizations: "+ methodChoices.mkString(","), expandsTo = methodChoices)
private val projectChoices = List(lMethod)
- val lProject = Choice("l:project", "Cross-method optimizations within the current project: "+ projectChoices.mkString(","), expandsTo = projectChoices)
+ val lProject = Choice("l:project", "Enable cross-method optimizations within the current project: "+ projectChoices.mkString(","), expandsTo = projectChoices)
private val classpathChoices = List(lProject)
- val lClasspath = Choice("l:classpath", "Cross-method optmizations across the entire classpath: "+ classpathChoices.mkString(","), expandsTo = classpathChoices)
+ val lClasspath = Choice("l:classpath", "Enable cross-method optimizations across the entire classpath: "+ classpathChoices.mkString(","), expandsTo = classpathChoices)
}
val Yopt = MultiChoiceSetting(
@@ -234,7 +239,12 @@ trait ScalaSettings extends AbsScalaSettings
descr = "Enable optimizations",
domain = YoptChoices)
- def YoptUnreachableCode: Boolean = !Yopt.isSetByUser || Yopt.contains(YoptChoices.unreachableCode)
+ def YoptUnreachableCode = !Yopt.isSetByUser || Yopt.contains(YoptChoices.unreachableCode)
+ def YoptSimplifyJumps = Yopt.contains(YoptChoices.simplifyJumps)
+ def YoptRecurseUnreachableJumps = Yopt.contains(YoptChoices.recurseUnreachableJumps)
+ def YoptEmptyLineNumbers = Yopt.contains(YoptChoices.emptyLineNumbers)
+ def YoptEmptyLabels = Yopt.contains(YoptChoices.emptyLabels)
+ def YoptCompactLocals = Yopt.contains(YoptChoices.compactLocals)
private def removalIn212 = "This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug."
diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
index 1664fe0e0d..c29826551b 100644
--- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala
+++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
@@ -520,7 +520,9 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
* And, finally, be advised - Scala's Symbol literal (scala.Symbol) and the Symbol class of the compiler
* have little in common.
*/
- case Apply(fn, (arg @ Literal(Constant(symname: String))) :: Nil) if fn.symbol == Symbol_apply =>
+ case Apply(fn @ Select(qual, _), (arg @ Literal(Constant(symname: String))) :: Nil)
+ if treeInfo.isQualifierSafeToElide(qual) && fn.symbol == Symbol_apply && !currentClass.isTrait =>
+
def transformApply = {
// add the symbol name to a map if it's not there already
val rhs = gen.mkMethodCall(Symbol_apply, arg :: Nil)
diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
index ee2775ee26..8979b26719 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
@@ -295,11 +295,17 @@ trait Infer extends Checkable {
&& !isByNameParamType(tp)
&& isCompatible(tp, dropByName(pt))
)
+ def isCompatibleSam(tp: Type, pt: Type): Boolean = {
+ val samFun = typer.samToFunctionType(pt)
+ (samFun ne NoType) && isCompatible(tp, samFun)
+ }
+
val tp1 = normalize(tp)
( (tp1 weak_<:< pt)
|| isCoercible(tp1, pt)
|| isCompatibleByName(tp, pt)
+ || isCompatibleSam(tp, pt)
)
}
def isCompatibleArgs(tps: List[Type], pts: List[Type]) = (tps corresponds pts)(isCompatible)
diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
index af4e9e8927..d2931ff9e1 100644
--- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
@@ -543,7 +543,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
}
def checkOverrideDeprecated() {
- if (other.hasDeprecatedOverridingAnnotation) {
+ if (other.hasDeprecatedOverridingAnnotation && !member.ownerChain.exists(x => x.isDeprecated || x.hasBridgeAnnotation)) {
val suffix = other.deprecatedOverridingMessage map (": " + _) getOrElse ""
val msg = s"overriding ${other.fullLocationString} is deprecated$suffix"
currentRun.reporting.deprecationWarning(member.pos, other, msg)
@@ -1095,7 +1095,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
// better to have lubbed and lost
def warnIfLubless(): Unit = {
val common = global.lub(List(actual.tpe, receiver.tpe))
- if (ObjectTpe <:< common)
+ if (ObjectTpe <:< common && !(ObjectTpe <:< actual.tpe && ObjectTpe <:< receiver.tpe))
unrelatedTypes()
}
// warn if actual has a case parent that is not same as receiver's;
@@ -1404,7 +1404,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
if (symbol.isDeprecated) {
val concrOvers =
symbol.allOverriddenSymbols.filter(sym =>
- !sym.isDeprecated && !sym.isDeferred)
+ !sym.isDeprecated && !sym.isDeferred && !sym.hasDeprecatedOverridingAnnotation && !sym.enclClass.hasDeprecatedInheritanceAnnotation)
if(!concrOvers.isEmpty)
currentRun.reporting.deprecationWarning(
tree.pos,
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index 70acb03584..4d9a6a47ef 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -741,6 +741,26 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case _ =>
}
+ /**
+ * Convert a SAM type to the corresponding FunctionType,
+ * extrapolating BoundedWildcardTypes in the process
+ * (no type precision is lost by the extrapolation,
+ * but this facilitates dealing with the types arising from Java's use-site variance).
+ */
+ def samToFunctionType(tp: Type, sam: Symbol = NoSymbol): Type = {
+ val samSym = sam orElse samOf(tp)
+
+ def correspondingFunctionSymbol = {
+ val numVparams = samSym.info.params.length
+ if (numVparams > definitions.MaxFunctionArity) NoSymbol
+ else FunctionClass(numVparams)
+ }
+
+ if (samSym.exists && samSym.owner != correspondingFunctionSymbol) // don't treat Functions as SAMs
+ wildcardExtrapolation(normalize(tp memberInfo samSym))
+ else NoType
+ }
+
/** Perform the following adaptations of expression, pattern or type `tree` wrt to
* given mode `mode` and given prototype `pt`:
* (-1) For expressions with annotated types, let AnnotationCheckers decide what to do
@@ -824,7 +844,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case Block(_, tree1) => tree1.symbol
case _ => tree.symbol
}
- if (!meth.isConstructor && isFunctionType(pt)) { // (4.2)
+ if (!meth.isConstructor && (isFunctionType(pt) || samOf(pt).exists)) { // (4.2)
debuglog(s"eta-expanding $tree: ${tree.tpe} to $pt")
checkParamsConvertible(tree, tree.tpe)
val tree0 = etaExpand(context.unit, tree, this)
@@ -850,13 +870,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
def adaptType(): Tree = {
// @M When not typing a type constructor (!context.inTypeConstructorAllowed)
- // or raw type (tree.symbol.isJavaDefined && context.unit.isJava), types must be of kind *,
+ // or raw type, types must be of kind *,
// and thus parameterized types must be applied to their type arguments
// @M TODO: why do kind-* tree's have symbols, while higher-kinded ones don't?
def properTypeRequired = (
tree.hasSymbolField
&& !context.inTypeConstructorAllowed
- && !(tree.symbol.isJavaDefined && context.unit.isJava)
+ && !context.unit.isJava
)
// @M: don't check tree.tpe.symbol.typeParams. check tree.tpe.typeParams!!!
// (e.g., m[Int] --> tree.tpe.symbol.typeParams.length == 1, tree.tpe.typeParams.length == 0!)
@@ -1657,7 +1677,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val sameSourceFile = context.unit.source.file == psym.sourceFile
- if (!isPastTyper && psym.hasDeprecatedInheritanceAnnotation && !sameSourceFile) {
+ if (!isPastTyper && psym.hasDeprecatedInheritanceAnnotation &&
+ !sameSourceFile && !context.owner.ownerChain.exists(x => x.isDeprecated || x.hasBridgeAnnotation)) {
val suffix = psym.deprecatedInheritanceMessage map (": " + _) getOrElse ""
val msg = s"inheritance from ${psym.fullLocationString} is deprecated$suffix"
context.deprecationWarning(parent.pos, psym, msg)
@@ -2680,7 +2701,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* `{
* def apply$body(p1: T1, ..., pN: TN): T = body
* new S {
- * def apply(p1: T1, ..., pN: TN): T = apply$body(p1,..., pN)
+ * def apply(p1: T1', ..., pN: TN'): T' = apply$body(p1,..., pN)
* }
* }`
*
@@ -2690,6 +2711,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
*
* The `apply` method is identified by the argument `sam`; `S` corresponds to the argument `samClassTp`,
* and `resPt` is derived from `samClassTp` -- it may be fully defined, or not...
+ * If it is not fully defined, we derive `samClassTpFullyDefined` by inferring any unknown type parameters.
+ *
+ * The types T1' ... TN' and T' are derived from the method signature of the sam method,
+ * as seen from the fully defined `samClassTpFullyDefined`.
*
* The function's body is put in a method outside of the class definition to enforce scoping.
* S's members should not be in scope in `body`.
@@ -2701,6 +2726,22 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* However T must be fully defined before we type the instantiation, as it'll end up as a parent type,
* which must be fully defined. Would be nice to have some kind of mechanism to insert type vars in a block of code,
* and have the instantiation of the first occurrence propagate to the rest of the block.
+ *
+ * TODO: by-name params
+ * scala> trait LazySink { def accept(a: => Any): Unit }
+ * defined trait LazySink
+ *
+ * scala> val f: LazySink = (a) => (a, a)
+ * f: LazySink = $anonfun$1@1fb26910
+ *
+ * scala> f(println("!"))
+ * <console>:10: error: LazySink does not take parameters
+ * f(println("!"))
+ * ^
+ *
+ * scala> f.accept(println("!"))
+ * !
+ * !
*/
def synthesizeSAMFunction(sam: Symbol, fun: Function, resPt: Type, samClassTp: Type, mode: Mode): Tree = {
// assert(fun.vparams forall (vp => isFullyDefined(vp.tpt.tpe))) -- by construction, as we take them from sam's info
@@ -2781,14 +2822,21 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
samClassTp
}
- // `final override def ${sam.name}($p1: $T1, ..., $pN: $TN): $resPt = ${sam.name}\$body'($p1, ..., $pN)`
+ // what's the signature of the method that we should actually be overriding?
+ val samMethTp = samClassTpFullyDefined memberInfo sam
+ // Before the mutation, `tp <:< vpar.tpt.tpe` should hold.
+ // TODO: error message when this is not the case, as the expansion won't type check
+ // - Ti' <:< Ti and T <: T' must hold for the samDef body to type check
+ val funArgTps = foreach2(samMethTp.paramTypes, fun.vparams)((tp, vpar) => vpar.tpt setType tp)
+
+ // `final override def ${sam.name}($p1: $T1', ..., $pN: $TN'): ${samMethTp.finalResultType} = ${sam.name}\$body'($p1, ..., $pN)`
val samDef =
DefDef(Modifiers(FINAL | OVERRIDE | SYNTHETIC),
sam.name.toTermName,
Nil,
List(fun.vparams),
- TypeTree(samBodyDef.tpt.tpe) setPos sampos.focus,
- Apply(Ident(bodyName), fun.vparams map (p => Ident(p.name)))
+ TypeTree(samMethTp.finalResultType) setPos sampos.focus,
+ Apply(Ident(bodyName), fun.vparams map gen.paramToArg)
)
val serializableParentAddendum =
@@ -2818,6 +2866,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
)
}
+ // TODO: improve error reporting -- when we're in silent mode (from `silent(_.doTypedApply(tree, fun, args, mode, pt)) orElse onError`)
+ // the errors in the function don't get out...
+ if (block exists (_.isErroneous))
+ context.error(fun.pos, s"Could not derive subclass of $samClassTp\n (with SAM `def $sam$samMethTp`)\n based on: $fun.")
+
classDef.symbol addAnnotation SerialVersionUIDAnnotation
block
}
@@ -2838,7 +2891,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* as `(a => a): Int => Int` should not (yet) get the sam treatment.
*/
val sam =
- if (!settings.Xexperimental || pt.typeSymbol == FunctionSymbol) NoSymbol
+ if (pt.typeSymbol == FunctionSymbol) NoSymbol
else samOf(pt)
/* The SAM case comes first so that this works:
@@ -2848,15 +2901,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* Note that the arity of the sam must correspond to the arity of the function.
*/
val samViable = sam.exists && sameLength(sam.info.params, fun.vparams)
+ val ptNorm = if (samViable) samToFunctionType(pt, sam) else pt
val (argpts, respt) =
- if (samViable) {
- val samInfo = pt memberInfo sam
- (samInfo.paramTypes, samInfo.resultType)
- } else {
- pt baseType FunctionSymbol match {
- case TypeRef(_, FunctionSymbol, args :+ res) => (args, res)
- case _ => (fun.vparams map (_ => if (pt == ErrorType) ErrorType else NoType), WildcardType)
- }
+ ptNorm baseType FunctionSymbol match {
+ case TypeRef(_, FunctionSymbol, args :+ res) => (args, res)
+ case _ => (fun.vparams map (_ => if (pt == ErrorType) ErrorType else NoType), WildcardType)
}
if (!FunctionSymbol.exists)
@@ -5134,16 +5183,19 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
typed(tree.ref, MonoQualifierModes | mode.onlyTypePat, AnyRefTpe)
}
- if (!refTyped.isErrorTyped)
+ if (refTyped.isErrorTyped) {
+ setError(tree)
+ } else {
tree setType refTyped.tpe.resultType
-
- if (treeInfo.admitsTypeSelection(refTyped)) tree
- else UnstableTreeError(refTyped)
+ if (refTyped.isErrorTyped || treeInfo.admitsTypeSelection(refTyped)) tree
+ else UnstableTreeError(tree)
+ }
}
def typedSelectFromTypeTree(tree: SelectFromTypeTree) = {
val qual1 = typedType(tree.qualifier, mode)
- if (qual1.tpe.isVolatile) TypeSelectionFromVolatileTypeError(tree, qual1)
+ if (qual1.isErrorTyped) setError(treeCopy.SelectFromTypeTree(tree, qual1, tree.name))
+ else if (qual1.tpe.isVolatile) TypeSelectionFromVolatileTypeError(tree, qual1)
else typedSelect(tree, qual1, tree.name)
}
diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala
index e89f08ec6b..e78dee5eee 100644
--- a/src/compiler/scala/tools/nsc/util/ClassPath.scala
+++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala
@@ -197,6 +197,23 @@ abstract class ClassPath[T] {
def packages: IndexedSeq[ClassPath[T]]
def sourcepaths: IndexedSeq[AbstractFile]
+ /** The entries this classpath is composed of. In class `ClassPath` it's just the singleton list containing `this`.
+ * Subclasses such as `MergedClassPath` typically return lists with more elements.
+ */
+ def entries: IndexedSeq[ClassPath[T]] = IndexedSeq(this)
+
+ /** Merge classpath of `platform` and `urls` into merged classpath */
+ def mergeUrlsIntoClassPath(urls: URL*): MergedClassPath[T] = {
+ // Collect our new jars/directories and add them to the existing set of classpaths
+ val allEntries =
+ (entries ++
+ urls.map(url => context.newClassPath(io.AbstractFile.getURL(url)))
+ ).distinct
+
+ // Combine all of our classpaths (old and new) into one merged classpath
+ new MergedClassPath(allEntries, context)
+ }
+
/**
* Represents classes which can be loaded with a ClassfileLoader and/or SourcefileLoader.
*/
@@ -322,7 +339,7 @@ extends MergedClassPath[T](original.entries map (e => subst getOrElse (e, e)), o
* A classpath unifying multiple class- and sourcepath entries.
*/
class MergedClassPath[T](
- val entries: IndexedSeq[ClassPath[T]],
+ override val entries: IndexedSeq[ClassPath[T]],
val context: ClassPathContext[T])
extends ClassPath[T] {
def this(entries: TraversableOnce[ClassPath[T]], context: ClassPathContext[T]) =
diff --git a/src/library/scala/collection/IndexedSeqOptimized.scala b/src/library/scala/collection/IndexedSeqOptimized.scala
index 42cb37aa24..a7e06b4d1a 100755
--- a/src/library/scala/collection/IndexedSeqOptimized.scala
+++ b/src/library/scala/collection/IndexedSeqOptimized.scala
@@ -141,10 +141,10 @@ trait IndexedSeqOptimized[+A, +Repr] extends Any with IndexedSeqLike[A, Repr] {
def drop(n: Int): Repr = slice(n, length)
override /*IterableLike*/
- def takeRight(n: Int): Repr = slice(length - n, length)
+ def takeRight(n: Int): Repr = slice(length - math.max(n, 0), length)
override /*IterableLike*/
- def dropRight(n: Int): Repr = slice(0, length - n)
+ def dropRight(n: Int): Repr = slice(0, length - math.max(n, 0))
override /*TraversableLike*/
def splitAt(n: Int): (Repr, Repr) = (take(n), drop(n))
diff --git a/src/library/scala/collection/IterableViewLike.scala b/src/library/scala/collection/IterableViewLike.scala
index 668190f700..b84d90c51b 100644
--- a/src/library/scala/collection/IterableViewLike.scala
+++ b/src/library/scala/collection/IterableViewLike.scala
@@ -150,10 +150,10 @@ trait IterableViewLike[+A,
sliding(size, 1) // we could inherit this, but that implies knowledge of the way the super class is implemented.
override def dropRight(n: Int): This =
- take(thisSeq.length - n)
+ take(thisSeq.length - math.max(n, 0))
override def takeRight(n: Int): This =
- drop(thisSeq.length - n)
+ drop(thisSeq.length - math.max(n, 0))
override def stringPrefix = "IterableView"
}
diff --git a/src/library/scala/collection/Iterator.scala b/src/library/scala/collection/Iterator.scala
index 660cc5a42a..20712f918c 100644
--- a/src/library/scala/collection/Iterator.scala
+++ b/src/library/scala/collection/Iterator.scala
@@ -320,7 +320,14 @@ trait Iterator[+A] extends TraversableOnce[A] {
* it omits the first `n` values.
* @note Reuse: $consumesAndProducesIterator
*/
- def drop(n: Int): Iterator[A] = slice(n, Int.MaxValue)
+ def drop(n: Int): Iterator[A] = {
+ var j = 0
+ while (j < n && hasNext) {
+ next()
+ j += 1
+ }
+ this
+ }
/** Creates an iterator returning an interval of the values produced by this iterator.
*
diff --git a/src/library/scala/collection/immutable/TreeMap.scala b/src/library/scala/collection/immutable/TreeMap.scala
index 8cc99a53e6..662075cd93 100644
--- a/src/library/scala/collection/immutable/TreeMap.scala
+++ b/src/library/scala/collection/immutable/TreeMap.scala
@@ -101,8 +101,8 @@ class TreeMap[A, +B] private (tree: RB.Tree[A, B])(implicit val ordering: Orderi
else new TreeMap(RB.slice(tree, from, until))
}
- override def dropRight(n: Int) = take(size - n)
- override def takeRight(n: Int) = drop(size - n)
+ override def dropRight(n: Int) = take(size - math.max(n, 0))
+ override def takeRight(n: Int) = drop(size - math.max(n, 0))
override def splitAt(n: Int) = (take(n), drop(n))
private[this] def countWhile(p: ((A, B)) => Boolean): Int = {
diff --git a/src/library/scala/collection/immutable/TreeSet.scala b/src/library/scala/collection/immutable/TreeSet.scala
index 681dbbd1a8..7378211db0 100644
--- a/src/library/scala/collection/immutable/TreeSet.scala
+++ b/src/library/scala/collection/immutable/TreeSet.scala
@@ -87,8 +87,8 @@ class TreeSet[A] private (tree: RB.Tree[A, Unit])(implicit val ordering: Orderin
else newSet(RB.slice(tree, from, until))
}
- override def dropRight(n: Int) = take(size - n)
- override def takeRight(n: Int) = drop(size - n)
+ override def dropRight(n: Int) = take(size - math.max(n, 0))
+ override def takeRight(n: Int) = drop(size - math.max(n, 0))
override def splitAt(n: Int) = (take(n), drop(n))
private[this] def countWhile(p: A => Boolean): Int = {
diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala
index 6b979795ec..7e2d124486 100644
--- a/src/reflect/scala/reflect/internal/Definitions.scala
+++ b/src/reflect/scala/reflect/internal/Definitions.scala
@@ -790,7 +790,7 @@ trait Definitions extends api.StandardDefinitions {
* The class defining the method is a supertype of `tp` that
* has a public no-arg primary constructor.
*/
- def samOf(tp: Type): Symbol = {
+ def samOf(tp: Type): Symbol = if (!settings.Xexperimental) NoSymbol else {
// if tp has a constructor, it must be public and must not take any arguments
// (not even an implicit argument list -- to keep it simple for now)
val tpSym = tp.typeSymbol
diff --git a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala
index f06420de96..c705ca7069 100644
--- a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala
+++ b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala
@@ -422,6 +422,22 @@ private[internal] trait TypeMaps {
}
}
+ /**
+ * Get rid of BoundedWildcardType where variance allows us to do so.
+ * Invariant: `wildcardExtrapolation(tp) =:= tp`
+ *
+ * For example, the MethodType given by `def bla(x: (_ >: String)): (_ <: Int)`
+ * is both a subtype and a supertype of `def bla(x: String): Int`.
+ */
+ object wildcardExtrapolation extends TypeMap(trackVariance = true) {
+ def apply(tp: Type): Type =
+ tp match {
+ case BoundedWildcardType(TypeBounds(lo, AnyTpe)) if variance.isContravariant => lo
+ case BoundedWildcardType(TypeBounds(NothingTpe, hi)) if variance.isCovariant => hi
+ case tp => mapOver(tp)
+ }
+ }
+
/** Might the given symbol be important when calculating the prefix
* of a type? When tp.asSeenFrom(pre, clazz) is called on `tp`,
* the result will be `tp` unchanged if `pre` is trivial and `clazz`
diff --git a/src/reflect/scala/reflect/io/AbstractFile.scala b/src/reflect/scala/reflect/io/AbstractFile.scala
index ac1159b2ac..bcefcc471f 100644
--- a/src/reflect/scala/reflect/io/AbstractFile.scala
+++ b/src/reflect/scala/reflect/io/AbstractFile.scala
@@ -48,14 +48,16 @@ object AbstractFile {
else null
/**
- * If the specified URL exists and is a readable zip or jar archive,
- * returns an abstract directory backed by it. Otherwise, returns
- * `null`.
+ * If the specified URL exists and is a regular file or a directory, returns an
+ * abstract regular file or an abstract directory, respectively, backed by it.
+ * Otherwise, returns `null`.
*/
- def getURL(url: URL): AbstractFile = {
- if (url == null || !Path.isExtensionJarOrZip(url.getPath)) null
- else ZipArchive fromURL url
- }
+ def getURL(url: URL): AbstractFile =
+ if (url.getProtocol == "file") {
+ val f = new java.io.File(url.getPath)
+ if (f.isDirectory) getDirectory(f)
+ else getFile(f)
+ } else null
def getResources(url: URL): AbstractFile = ZipArchive fromManifestURL url
}
diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
index 18a3c5d63f..c87b810bdd 100644
--- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
+++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
@@ -170,6 +170,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
this.dropSingletonType
this.abstractTypesToBounds
this.dropIllegalStarTypes
+ this.wildcardExtrapolation
this.IsDependentCollector
this.ApproximateDependentMap
this.wildcardToTypeVarMap
diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala
index 1c751c8a9f..8bef424e2b 100644
--- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala
+++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala
@@ -19,6 +19,7 @@ import scala.reflect.internal.util.{ BatchSourceFile, ScalaClassLoader }
import ScalaClassLoader._
import scala.reflect.io.{ File, Directory }
import scala.tools.util._
+import io.AbstractFile
import scala.collection.generic.Clearable
import scala.concurrent.{ ExecutionContext, Await, Future, future }
import ExecutionContext.Implicits._
@@ -221,7 +222,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
nullary("power", "enable power user mode", powerCmd),
nullary("quit", "exit the interpreter", () => Result(keepRunning = false, None)),
cmd("replay", "[options]", "reset the repl and replay all previous commands", replayCommand),
- //cmd("require", "<path>", "add a jar or directory to the classpath", require), // TODO
+ cmd("require", "<path>", "add a jar to the classpath", require),
cmd("reset", "[options]", "reset the repl to its initial state, forgetting all session entries", resetCommand),
cmd("save", "<path>", "save replayable session to a file", saveCommand),
shCommand,
@@ -620,13 +621,57 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
val f = File(arg).normalize
if (f.exists) {
addedClasspath = ClassPath.join(addedClasspath, f.path)
- val totalClasspath = ClassPath.join(settings.classpath.value, addedClasspath)
- echo("Added '%s'. Your new classpath is:\n\"%s\"".format(f.path, totalClasspath))
- replay()
+ intp.addUrlsToClassPath(f.toURI.toURL)
+ echo("Added '%s' to classpath.".format(f.path, intp.global.classPath.asClasspathString))
+ repldbg("Added '%s'. Your new classpath is:\n\"%s\"".format(f.path, intp.global.classPath.asClasspathString))
}
else echo("The path '" + f + "' doesn't seem to exist.")
}
+ /** Adds jar file to the current classpath. Jar will only be added if it
+ * does not contain classes that already exist on the current classpath.
+ *
+ * Importantly, `require` adds jars to the classpath ''without'' resetting
+ * the state of the interpreter. This is in contrast to `replay` which can
+ * be used to add jars to the classpath and which creates a new instance of
+ * the interpreter and replays all interpreter expressions.
+ */
+ def require(arg: String): Unit = {
+ class InfoClassLoader extends java.lang.ClassLoader {
+ def classOf(arr: Array[Byte]): Class[_] =
+ super.defineClass(null, arr, 0, arr.length)
+ }
+
+ val f = File(arg).normalize
+
+ if (f.isDirectory) {
+ echo("Adding directories to the classpath is not supported. Add a jar instead.")
+ return
+ }
+
+ val jarFile = AbstractFile.getDirectory(new java.io.File(arg))
+
+ def flatten(f: AbstractFile): Iterator[AbstractFile] =
+ if (f.isClassContainer) f.iterator.flatMap(flatten)
+ else Iterator(f)
+
+ val entries = flatten(jarFile)
+ val cloader = new InfoClassLoader
+
+ def classNameOf(classFile: AbstractFile): String = cloader.classOf(classFile.toByteArray).getName
+ def alreadyDefined(clsName: String) = intp.classLoader.tryToLoadClass(clsName).isDefined
+ val exists = entries.filter(_.hasExtension("class")).map(classNameOf).exists(alreadyDefined)
+
+ if (!f.exists) echo(s"The path '$f' doesn't seem to exist.")
+ else if (exists) echo(s"The path '$f' cannot be loaded, because existing classpath entries conflict.") // TODO tell me which one
+ else {
+ addedClasspath = ClassPath.join(addedClasspath, f.path)
+ intp.addUrlsToClassPath(f.toURI.toURL)
+ echo("Added '%s' to classpath.".format(f.path, intp.global.classPath.asClasspathString))
+ repldbg("Added '%s'. Your new classpath is:\n\"%s\"".format(f.path, intp.global.classPath.asClasspathString))
+ }
+ }
+
def powerCmd(): Result = {
if (isReplPower) "Already in power mode."
else enablePowerMode(isDuringInit = false)
diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala
index 3f4922a602..b990e401ec 100644
--- a/src/repl/scala/tools/nsc/interpreter/IMain.scala
+++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala
@@ -18,9 +18,13 @@ import scala.reflect.internal.util.{ BatchSourceFile, SourceFile }
import scala.tools.util.PathResolver
import scala.tools.nsc.io.AbstractFile
import scala.tools.nsc.typechecker.{ TypeStrings, StructuredTypeStrings }
-import scala.tools.nsc.util.{ ScalaClassLoader, stringFromReader, stringFromWriter, StackTraceOps }
+import scala.tools.nsc.util.{ ScalaClassLoader, stringFromReader, stringFromWriter, StackTraceOps, ClassPath, MergedClassPath }
+import ScalaClassLoader.URLClassLoader
import scala.tools.nsc.util.Exceptional.unwrap
+import scala.tools.nsc.backend.JavaPlatform
import javax.script.{AbstractScriptEngine, Bindings, ScriptContext, ScriptEngine, ScriptEngineFactory, ScriptException, CompiledScript, Compilable}
+import java.net.URL
+import java.io.File
/** An interpreter for Scala code.
*
@@ -82,6 +86,8 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
private var _classLoader: util.AbstractFileClassLoader = null // active classloader
private val _compiler: ReplGlobal = newCompiler(settings, reporter) // our private compiler
+ private var _runtimeClassLoader: URLClassLoader = null // wrapper exposing addURL
+
def compilerClasspath: Seq[java.net.URL] = (
if (isInitializeComplete) global.classPath.asURLs
else new PathResolver(settings).result.asURLs // the compiler's classpath
@@ -237,6 +243,18 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
new Global(settings, reporter) with ReplGlobal { override def toString: String = "<global>" }
}
+ /**
+ * Adds all specified jars to the compile and runtime classpaths.
+ *
+ * @note Currently only supports jars, not directories.
+ * @param urls The list of items to add to the compile and runtime classpaths.
+ */
+ def addUrlsToClassPath(urls: URL*): Unit = {
+ new Run // force some initialization
+ urls.foreach(_runtimeClassLoader.addURL) // Add jars to runtime classloader
+ global.extendCompilerClassPath(urls: _*) // Add jars to compile-time classpath
+ }
+
/** Parent classloader. Overridable. */
protected def parentClassLoader: ClassLoader =
settings.explicitParentLoader.getOrElse( this.getClass.getClassLoader() )
@@ -329,9 +347,9 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
}
}
private def makeClassLoader(): util.AbstractFileClassLoader =
- new TranslatingClassLoader(parentClassLoader match {
- case null => ScalaClassLoader fromURLs compilerClasspath
- case p => new ScalaClassLoader.URLClassLoader(compilerClasspath, p)
+ new TranslatingClassLoader({
+ _runtimeClassLoader = new URLClassLoader(compilerClasspath, parentClassLoader)
+ _runtimeClassLoader
})
// Set the current Java "context" class loader to this interpreter's class loader
diff --git a/src/scaladoc/scala/tools/nsc/doc/DocParser.scala b/src/scaladoc/scala/tools/nsc/doc/DocParser.scala
index 6dc3e5a62b..f03b848af6 100644
--- a/src/scaladoc/scala/tools/nsc/doc/DocParser.scala
+++ b/src/scaladoc/scala/tools/nsc/doc/DocParser.scala
@@ -15,13 +15,14 @@ import DocParser.Parsed
* right after parsing so it can read `DocDefs` from source code which would
* otherwise cause the compiler to go haywire.
*/
-class DocParser(settings: nsc.Settings, reporter: Reporter) extends Global(settings, reporter) {
+class DocParser(settings: nsc.Settings, reporter: Reporter) extends Global(settings, reporter) with ScaladocGlobalTrait {
def this(settings: Settings) = this(settings, new ConsoleReporter(settings))
def this() = this(new Settings(Console println _))
// the usual global initialization
locally { new Run() }
+ override def forScaladoc = true
override protected def computeInternalPhases() {
phasesSet += syntaxAnalyzer
}
diff --git a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala
index 7a67055ffa..7289edc137 100644
--- a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala
+++ b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala
@@ -313,7 +313,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
/* Subclass cache */
private lazy val subClassesCache = (
- if (sym == AnyRefClass) null
+ if (sym == AnyRefClass || sym == AnyClass) null
else mutable.ListBuffer[DocTemplateEntity]()
)
def registerSubClass(sc: DocTemplateEntity): Unit = {
diff --git a/test/files/neg/sammy_error_exist_no_crash.check b/test/files/neg/sammy_error_exist_no_crash.check
new file mode 100644
index 0000000000..a0d2237ce0
--- /dev/null
+++ b/test/files/neg/sammy_error_exist_no_crash.check
@@ -0,0 +1,6 @@
+sammy_error_exist_no_crash.scala:5: error: Could not derive subclass of F[? >: String]
+ (with SAM `def method apply(s: String)Int`)
+ based on: ((x$1: String) => x$1.<parseInt: error>).
+ bar(_.parseInt)
+ ^
+one error found
diff --git a/test/files/neg/sammy_error_exist_no_crash.flags b/test/files/neg/sammy_error_exist_no_crash.flags
new file mode 100644
index 0000000000..e1b37447c9
--- /dev/null
+++ b/test/files/neg/sammy_error_exist_no_crash.flags
@@ -0,0 +1 @@
+-Xexperimental \ No newline at end of file
diff --git a/test/files/neg/sammy_error_exist_no_crash.scala b/test/files/neg/sammy_error_exist_no_crash.scala
new file mode 100644
index 0000000000..da7e47206f
--- /dev/null
+++ b/test/files/neg/sammy_error_exist_no_crash.scala
@@ -0,0 +1,6 @@
+abstract class F[T] { def apply(s: T): Int }
+
+object NeedsNiceError {
+ def bar(x: F[_ >: String]) = ???
+ bar(_.parseInt)
+} \ No newline at end of file
diff --git a/test/files/neg/sammy_restrictions.scala b/test/files/neg/sammy_restrictions.scala
index 5f1a04cd20..d003cfaf36 100644
--- a/test/files/neg/sammy_restrictions.scala
+++ b/test/files/neg/sammy_restrictions.scala
@@ -1,28 +1,28 @@
-class NoAbstract
+abstract class NoAbstract
-class TwoAbstract { def ap(a: Int): Int; def pa(a: Int): Int }
+abstract class TwoAbstract { def ap(a: Int): Int; def pa(a: Int): Int }
-class Base // check that the super class constructor isn't considered.
-class NoEmptyConstructor(a: Int) extends Base { def this(a: String) = this(0); def ap(a: Int): Int }
+abstract class Base // check that the super class constructor isn't considered.
+abstract class NoEmptyConstructor(a: Int) extends Base { def this(a: String) = this(0); def ap(a: Int): Int }
-class OneEmptyConstructor() { def this(a: Int) = this(); def ap(a: Int): Int }
+abstract class OneEmptyConstructor() { def this(a: Int) = this(); def ap(a: Int): Int }
-class OneEmptySecondaryConstructor(a: Int) { def this() = this(0); def ap(a: Int): Int }
+abstract class OneEmptySecondaryConstructor(a: Int) { def this() = this(0); def ap(a: Int): Int }
-class MultipleConstructorLists()() { def ap(a: Int): Int }
+abstract class MultipleConstructorLists()() { def ap(a: Int): Int }
-class MultipleMethodLists()() { def ap(a: Int)(): Int }
+abstract class MultipleMethodLists()() { def ap(a: Int)(): Int }
-class ImplicitConstructorParam()(implicit a: String) { def ap(a: Int): Int }
+abstract class ImplicitConstructorParam()(implicit a: String) { def ap(a: Int): Int }
-class ImplicitMethodParam() { def ap(a: Int)(implicit b: String): Int }
+abstract class ImplicitMethodParam() { def ap(a: Int)(implicit b: String): Int }
-class PolyClass[T] { def ap(a: T): T }
+abstract class PolyClass[T] { def ap(a: T): T }
-class PolyMethod { def ap[T](a: T): T }
+abstract class PolyMethod { def ap[T](a: T): T }
-class OneAbstract { def ap(a: Any): Any }
-class DerivedOneAbstract extends OneAbstract
+abstract class OneAbstract { def ap(a: Int): Any }
+abstract class DerivedOneAbstract extends OneAbstract
object Test {
implicit val s: String = ""
diff --git a/test/files/neg/t8534.check b/test/files/neg/t8534.check
new file mode 100644
index 0000000000..297e7c1beb
--- /dev/null
+++ b/test/files/neg/t8534.check
@@ -0,0 +1,4 @@
+t8534.scala:6: error: MyTrait is not an enclosing class
+ class BugTest {def isTheBugHere(in: MyTrait.this.type#SomeData) = false}
+ ^
+one error found
diff --git a/test/files/neg/t8534.scala b/test/files/neg/t8534.scala
new file mode 100644
index 0000000000..f118d22b82
--- /dev/null
+++ b/test/files/neg/t8534.scala
@@ -0,0 +1,7 @@
+object line1 {
+ trait MyTrait
+}
+object line2 {
+ import line2._
+ class BugTest {def isTheBugHere(in: MyTrait.this.type#SomeData) = false}
+}
diff --git a/test/files/neg/t8534b.check b/test/files/neg/t8534b.check
new file mode 100644
index 0000000000..39ffa41194
--- /dev/null
+++ b/test/files/neg/t8534b.check
@@ -0,0 +1,4 @@
+t8534b.scala:3: error: stable identifier required, but foo.type found.
+ type T = foo.type#Foo
+ ^
+one error found
diff --git a/test/files/neg/t8534b.scala b/test/files/neg/t8534b.scala
new file mode 100644
index 0000000000..73b6703a9c
--- /dev/null
+++ b/test/files/neg/t8534b.scala
@@ -0,0 +1,4 @@
+object Test {
+ def foo = ""
+ type T = foo.type#Foo
+}
diff --git a/test/files/neg/t963.check b/test/files/neg/t963.check
index 4dc202c7bd..483e53c77d 100644
--- a/test/files/neg/t963.check
+++ b/test/files/neg/t963.check
@@ -1,9 +1,9 @@
-t963.scala:14: error: stable identifier required, but Test.this.y3.x found.
+t963.scala:14: error: stable identifier required, but y3.x.type found.
val w3 : y3.x.type = y3.x
- ^
-t963.scala:17: error: stable identifier required, but Test.this.y4.x found.
+ ^
+t963.scala:17: error: stable identifier required, but y4.x.type found.
val w4 : y4.x.type = y4.x
- ^
+ ^
t963.scala:10: error: type mismatch;
found : AnyRef{def x: Integer}
required: AnyRef{val x: Integer}
diff --git a/test/files/pos/sammy_exist.flags b/test/files/pos/sammy_exist.flags
new file mode 100644
index 0000000000..48fd867160
--- /dev/null
+++ b/test/files/pos/sammy_exist.flags
@@ -0,0 +1 @@
+-Xexperimental
diff --git a/test/files/pos/sammy_exist.scala b/test/files/pos/sammy_exist.scala
new file mode 100644
index 0000000000..f05ae20463
--- /dev/null
+++ b/test/files/pos/sammy_exist.scala
@@ -0,0 +1,17 @@
+// scala> typeOf[java.util.stream.Stream[_]].nonPrivateMember(TermName("map")).info
+// [R](x$1: java.util.function.Function[_ >: T, _ <: R])java.util.stream.Stream[R]
+
+// java.util.function.Function
+trait Fun[A, B] { def apply(x: A): B }
+
+// java.util.stream.Stream
+class S[T](x: T) { def map[R](f: Fun[_ >: T, _ <: R]): R = f(x) }
+
+class Bla { def foo: Bla = this }
+
+// NOTE: inferred types show unmoored skolems, should pack them to display properly as bounded wildcards
+object T {
+ val aBlaSAM = (new S(new Bla)).map(_.foo)
+ val fun: Fun[Bla, Bla] = (x: Bla) => x
+ val aBlaSAMX = (new S(new Bla)).map(fun)
+}
diff --git a/test/files/pos/sammy_overload.flags b/test/files/pos/sammy_overload.flags
new file mode 100644
index 0000000000..48fd867160
--- /dev/null
+++ b/test/files/pos/sammy_overload.flags
@@ -0,0 +1 @@
+-Xexperimental
diff --git a/test/files/pos/sammy_overload.scala b/test/files/pos/sammy_overload.scala
new file mode 100644
index 0000000000..5472248f4d
--- /dev/null
+++ b/test/files/pos/sammy_overload.scala
@@ -0,0 +1,9 @@
+trait Consumer[T] {
+ def consume(x: T): Unit
+}
+
+object Test {
+ def foo(x: String): Unit = ???
+ def foo(): Unit = ???
+ val f: Consumer[_ >: String] = foo
+} \ No newline at end of file
diff --git a/test/files/pos/sammy_override.flags b/test/files/pos/sammy_override.flags
new file mode 100644
index 0000000000..48fd867160
--- /dev/null
+++ b/test/files/pos/sammy_override.flags
@@ -0,0 +1 @@
+-Xexperimental
diff --git a/test/files/pos/sammy_override.scala b/test/files/pos/sammy_override.scala
new file mode 100644
index 0000000000..a1d0651c39
--- /dev/null
+++ b/test/files/pos/sammy_override.scala
@@ -0,0 +1,8 @@
+trait IntConsumer {
+ def consume(x: Int): Unit
+}
+
+object Test {
+ def anyConsumer(x: Any): Unit = ???
+ val f: IntConsumer = anyConsumer
+} \ No newline at end of file
diff --git a/test/files/pos/t5413.scala b/test/files/pos/t5413.scala
new file mode 100644
index 0000000000..47af514a14
--- /dev/null
+++ b/test/files/pos/t5413.scala
@@ -0,0 +1,9 @@
+object Fail {
+ def nom (guard : => Boolean) (something : => Unit) { }
+ def main(args: Array[String]) {
+ nom {
+ val i = 0
+ (i != 3)
+ }()
+ }
+}
diff --git a/test/files/pos/t7750.flags b/test/files/pos/t7750.flags
new file mode 100644
index 0000000000..b216e74c97
--- /dev/null
+++ b/test/files/pos/t7750.flags
@@ -0,0 +1 @@
+-Xfatal-warnings -feature
diff --git a/test/files/pos/t7750.scala b/test/files/pos/t7750.scala
new file mode 100644
index 0000000000..befec76949
--- /dev/null
+++ b/test/files/pos/t7750.scala
@@ -0,0 +1,8 @@
+trait LazyCombiner[Elem, +To, Buff <: Growable[Elem] with Sizing]
+trait Growable[T]
+trait Sizing
+
+
+object Test {
+ null.isInstanceOf[LazyCombiner[_, _, _]] // issued an existential feature warning
+}
diff --git a/test/files/pos/t8310.flags b/test/files/pos/t8310.flags
new file mode 100644
index 0000000000..48fd867160
--- /dev/null
+++ b/test/files/pos/t8310.flags
@@ -0,0 +1 @@
+-Xexperimental
diff --git a/test/files/pos/t8310.scala b/test/files/pos/t8310.scala
new file mode 100644
index 0000000000..874caf4d3b
--- /dev/null
+++ b/test/files/pos/t8310.scala
@@ -0,0 +1,22 @@
+trait Comparinator[T] { def compare(a: T, b: T): Int }
+
+object TestOkay {
+ def sort(x: Comparinator[_ >: String]) = ()
+ sort((a: String, b: String) => a.compareToIgnoreCase(b))
+}
+
+object TestOkay2 {
+ def sort[T](x: Comparinator[_ >: T]) = ()
+ sort((a: String, b: String) => a.compareToIgnoreCase(b))
+}
+
+object TestOkay3 {
+ def sort[T](xs: Option[T], x: Comparinator[_ >: T]) = ()
+ sort(Some(""), (a: String, b: String) => a.compareToIgnoreCase(b))
+}
+
+object TestKoOverloaded {
+ def sort[T](xs: Option[T]) = ()
+ def sort[T](xs: Option[T], x: Comparinator[_ >: T]) = ()
+ sort(Some(""), (a: String, b: String) => a.compareToIgnoreCase(b))
+}
diff --git a/test/files/pos/t8954.flags b/test/files/pos/t8954.flags
new file mode 100644
index 0000000000..7de3c0f3ee
--- /dev/null
+++ b/test/files/pos/t8954.flags
@@ -0,0 +1 @@
+-Xfatal-warnings -deprecation
diff --git a/test/files/pos/t8954/t1.scala b/test/files/pos/t8954/t1.scala
new file mode 100644
index 0000000000..3986d9f3b5
--- /dev/null
+++ b/test/files/pos/t8954/t1.scala
@@ -0,0 +1,13 @@
+package scala.foo
+
+// 1. a class about to be made final
+@deprecatedInheritance class A {
+ def foo(): Unit = ???
+}
+
+// 1.1:
+// - no inheritance warning because same file
+// - no "override non-deprecated member" because @deprecatedInheritance
+class B2 extends A {
+ @deprecated("","") override def foo(): Unit = ???
+}
diff --git a/test/files/pos/t8954/t2.scala b/test/files/pos/t8954/t2.scala
new file mode 100644
index 0000000000..4def127832
--- /dev/null
+++ b/test/files/pos/t8954/t2.scala
@@ -0,0 +1,39 @@
+package scala.foo
+
+// 1.2 deprecated children should be fine...
+@deprecated("", "") class B extends A {
+
+ // 1.3 and shouldn't trigger the
+ // "overriding non-deprecated parent" warning
+ override def foo(): Unit = ???
+}
+
+@deprecated("","") class F {
+ // 1.4 a class inside a deprecated class should work too
+ class G extends A
+}
+
+// 2. a method about to be made final
+class C {
+ @deprecatedOverriding def foo(): Unit = ???
+}
+
+// 2.1 overriding with a deprecated def should be fine
+// and also shoudln't trigger the "deprecation is useless"
+// warning
+class D extends C {
+ @deprecated("","") override def foo(): Unit = ???
+}
+
+// 2.2 overriding from a deprecated class should be fine
+@deprecated("","") class E extends C {
+ override def foo(): Unit = ???
+}
+
+// 2.3 overriding from deeper inside a deprecated class
+// should work too
+@deprecated("","") class H {
+ class I extends C {
+ override def foo(): Unit = ???
+ }
+}
diff --git a/test/files/pos/t8965.flags b/test/files/pos/t8965.flags
new file mode 100644
index 0000000000..85d8eb2ba2
--- /dev/null
+++ b/test/files/pos/t8965.flags
@@ -0,0 +1 @@
+-Xfatal-warnings
diff --git a/test/files/pos/t8965.scala b/test/files/pos/t8965.scala
new file mode 100644
index 0000000000..4f39330f4e
--- /dev/null
+++ b/test/files/pos/t8965.scala
@@ -0,0 +1,7 @@
+class A {
+ def f(x: Any with AnyRef, y: Any with AnyRef) = x eq y
+ // a.scala:2: warning: Any and Any are unrelated: they will most likely never compare equal
+ // def f(x: Any with AnyRef, y: Any with AnyRef) = x eq y
+ // ^
+ // one warning found
+}
diff --git a/test/files/run/iterator-concat.check b/test/files/run/iterator-concat.check
deleted file mode 100644
index 23835b07ae..0000000000
--- a/test/files/run/iterator-concat.check
+++ /dev/null
@@ -1,4 +0,0 @@
-100
-1000
-10000
-100000
diff --git a/test/files/run/iterator-concat.scala b/test/files/run/iterator-concat.scala
deleted file mode 100644
index f11363410f..0000000000
--- a/test/files/run/iterator-concat.scala
+++ /dev/null
@@ -1,15 +0,0 @@
-object Test {
- // Create `size` Function0s, each of which evaluates to an Iterator
- // which produces 1. Then fold them over ++ to get a single iterator,
- // which should sum to "size".
- def mk(size: Int): Iterator[Int] = {
- val closures = (1 to size).toList.map(x => (() => Iterator(1)))
- closures.foldLeft(Iterator.empty: Iterator[Int])((res, f) => res ++ f())
- }
- def main(args: Array[String]): Unit = {
- println(mk(100).sum)
- println(mk(1000).sum)
- println(mk(10000).sum)
- println(mk(100000).sum)
- }
-}
diff --git a/test/files/run/iterator-iterate-lazy.scala b/test/files/run/iterator-iterate-lazy.scala
deleted file mode 100644
index 92b170062e..0000000000
--- a/test/files/run/iterator-iterate-lazy.scala
+++ /dev/null
@@ -1,5 +0,0 @@
-object Test {
- def main(args: Array[String]): Unit = {
- Iterator.iterate((1 to 5).toList)(_.tail).takeWhile(_.nonEmpty).map(_.head).toList
- }
-}
diff --git a/test/files/run/iterators.check b/test/files/run/iterators.check
deleted file mode 100644
index bb139c1610..0000000000
--- a/test/files/run/iterators.check
+++ /dev/null
@@ -1,13 +0,0 @@
-test check_from was successful
-test check_range was successful
-test check_range2 was successful
-test check_range3 was successful
-test check_take was successful
-test check_drop was successful
-test check_foreach was successful
-test check_forall was successful
-test check_fromArray was successful
-test check_toSeq was successful
-test check_indexOf was successful
-test check_findIndexOf was successful
-
diff --git a/test/files/run/iterators.scala b/test/files/run/iterators.scala
deleted file mode 100644
index 57e05d3472..0000000000
--- a/test/files/run/iterators.scala
+++ /dev/null
@@ -1,136 +0,0 @@
-//############################################################################
-// Iterators
-//############################################################################
-
-//############################################################################
-
-import scala.language.postfixOps
-
-object Test {
-
- def check_from: Int = {
- val it1 = Iterator.from(-1)
- val it2 = Iterator.from(0, -1)
- it1.next + it2.next
- }
-
- def check_range: Int = {
- val xs1 = Iterator.range(0, 10, 2) toList;
- val xs2 = Iterator.range(0, 10, -2) toList;
- val xs3 = Iterator.range(10, 0, -2) toList;
- val xs4 = Iterator.range(10, 0, 2) toList;
- val xs5 = Iterator.range(0, 10, 11) toList;
- xs1.length + xs2.length + xs3.length + xs4.length + xs5.length
- }
-
- def check_range2: Int = {
- val r1start = 0
- val r1end = 10
- val r1step = 1
- val r1 = Iterator.range(r1start, r1end, r1step) toList;
- val r2 = Iterator.range(r1start, r1end, r1step + 1) toList;
- val r3 = Iterator.range(r1end, r1start, -r1step) toList;
- val r4 = Iterator.range(0, 10, 11) toList;
- // 10 + 5 + 10 + 1
- r1.length + r2.length + r3.length + r4.length
- }
-
- def check_range3: Int = {
- def trues(xs: List[Boolean]) = xs.foldLeft(0)((a, b) => if (b) a+1 else a)
- val r1 = Iterator.range(0, 10)
- val xs1 = List(r1 contains 5, r1 contains 6)
- val r2a = Iterator.range(0, 10, 2)
- val r2b = Iterator.range(0, 10, 2)
- val xs2 = List(r2a contains 5, r2b contains 6)
- val r3 = Iterator.range(0, 10, 11)
- val xs3 = List(r3 contains 5, r3 contains 6)
- // 2 + 1 + 0
- trues(xs1) + trues(xs2) + trues(xs3)
- }
-
- def check_take: Int = {
- val it1 = Iterator.from(0)
- val xs1 = it1 take 10 toList;
- xs1.length
- }
-
- def check_drop: Int = {
- val it1 = Iterator.from(0)
- val it2 = it1 map { 2 * _ }
- val n1 = it1 drop 2 next
- val n2 = it2 drop 2 next;
- n1 + n2
- }
-
- def check_foreach: Int = {
- val it1 = Iterator.from(0) take 20
- var n = 0
- it1 foreach { n += _ }
- n
- }
-
- def check_forall: Int = {
- val it1 = Iterator.from(0)
- val it2 = Iterator.from(1)
- 0
- }
-
- def check_fromArray: Int = { // ticket #429
- val a = List(1, 2, 3, 4).toArray
- var xs0 = a.iterator.toList;
- var xs1 = a.slice(0, 1).iterator.toList;
- var xs2 = a.slice(0, 2).iterator.toList;
- var xs3 = a.slice(0, 3).iterator.toList;
- var xs4 = a.slice(0, 4).iterator.toList;
- xs0.length + xs1.length + xs2.length + xs3.length + xs4.length
- }
-
- def check_toSeq: String =
- List(1, 2, 3, 4, 5).iterator.toSeq.mkString("x")
-
- def check_indexOf: String = {
- val i = List(1, 2, 3, 4, 5).indexOf(4)
- val j = List(1, 2, 3, 4, 5).indexOf(16)
- "" + i + "x" + j
- }
-
- def check_findIndexOf: String = {
- val i = List(1, 2, 3, 4, 5).indexWhere { x: Int => x >= 4 }
- val j = List(1, 2, 3, 4, 5).indexWhere { x: Int => x >= 16 }
- "" + i + "x" + j
- }
-
- def check_success[A](name: String, closure: => A, expected: A) {
- print("test " + name)
- try {
- val actual: A = closure
- if (actual == expected)
- print(" was successful")
- else
- print(" failed: expected "+ expected +", found "+ actual)
- }
- catch {
- case exception: Throwable =>
- print(" raised exception " + exception)
- }
- println()
- }
-
- def main(args: Array[String]) {
- check_success("check_from", check_from, -1)
- check_success("check_range", check_range, 11)
- check_success("check_range2", check_range2, 26)
- check_success("check_range3", check_range3, 3)
- check_success("check_take", check_take, 10)
- check_success("check_drop", check_drop, 12)
- check_success("check_foreach", check_foreach, 190)
- check_success("check_forall", check_forall, 0)
- check_success("check_fromArray",check_fromArray, 14)
- check_success("check_toSeq", check_toSeq, "1x2x3x4x5")
- check_success("check_indexOf", check_indexOf, "3x-1")
- check_success("check_findIndexOf", check_findIndexOf, "3x-1")
- println()
- }
-}
-
-//############################################################################
diff --git a/test/files/run/sammy_repeated.check b/test/files/run/sammy_repeated.check
new file mode 100644
index 0000000000..1cff0f067c
--- /dev/null
+++ b/test/files/run/sammy_repeated.check
@@ -0,0 +1 @@
+WrappedArray(1)
diff --git a/test/files/run/sammy_repeated.flags b/test/files/run/sammy_repeated.flags
new file mode 100644
index 0000000000..e1b37447c9
--- /dev/null
+++ b/test/files/run/sammy_repeated.flags
@@ -0,0 +1 @@
+-Xexperimental \ No newline at end of file
diff --git a/test/files/run/sammy_repeated.scala b/test/files/run/sammy_repeated.scala
new file mode 100644
index 0000000000..c24dc41909
--- /dev/null
+++ b/test/files/run/sammy_repeated.scala
@@ -0,0 +1,8 @@
+trait RepeatedSink { def accept(a: Any*): Unit }
+
+object Test {
+ def main(args: Array[String]): Unit = {
+ val f: RepeatedSink = (a) => println(a)
+ f.accept(1)
+ }
+} \ No newline at end of file
diff --git a/test/files/run/t1994.scala b/test/files/run/t1994.scala
new file mode 100644
index 0000000000..0b463e3444
--- /dev/null
+++ b/test/files/run/t1994.scala
@@ -0,0 +1,20 @@
+class A {
+ protected def x = 0
+ protected[A] def y = 0
+}
+
+class B extends A {
+ override def x = 1
+ def superY = super[A].y
+ override def y = 1
+}
+
+
+object Test {
+ def main(args: Array[String]): Unit = {
+ val b = new B
+ assert(b.x == 1)
+ assert(b.y == 1)
+ assert(b.superY == 0)
+ }
+}
diff --git a/test/files/run/t3516.check b/test/files/run/t3516.check
deleted file mode 100644
index d0d10d82fa..0000000000
--- a/test/files/run/t3516.check
+++ /dev/null
@@ -1,3 +0,0 @@
-1
-1
-21
diff --git a/test/files/run/t3516.scala b/test/files/run/t3516.scala
deleted file mode 100644
index aa302ce85a..0000000000
--- a/test/files/run/t3516.scala
+++ /dev/null
@@ -1,13 +0,0 @@
-object Test {
- def mkIterator = (1 to 5).iterator map (x => { println(x) ; x })
- def mkInfinite = Iterator continually { println(1) ; 1 }
-
- def main(args: Array[String]): Unit = {
- // Stream is strict in its head so we should see 1 from each of them.
- val s1 = mkIterator.toStream
- val s2 = mkInfinite.toStream
- // back and forth without slipping into nontermination.
- println((Stream from 1).toIterator.drop(10).toStream.drop(10).toIterator.next)
- ()
- }
-}
diff --git a/test/files/run/t5665.scala b/test/files/run/t5665.scala
new file mode 100644
index 0000000000..3ac498b5c0
--- /dev/null
+++ b/test/files/run/t5665.scala
@@ -0,0 +1,13 @@
+object O {
+ trait T {
+ private[this] val c: Int = 42
+ def f =
+ { x: Int => c }
+ }
+}
+
+object Test {
+ def main(args: Array[String]): Unit = {
+ assert(new O.T{}.f(0) == 42)
+ }
+}
diff --git a/test/files/run/t6502.check b/test/files/run/t6502.check
new file mode 100644
index 0000000000..95d36ee221
--- /dev/null
+++ b/test/files/run/t6502.check
@@ -0,0 +1,8 @@
+test1 res1: true
+test1 res2: true
+test2 res1: true
+test2 res2: true
+test3 res1: true
+test3 res2: true
+test4 res1: true
+test4 res2: true
diff --git a/test/files/run/t6502.scala b/test/files/run/t6502.scala
new file mode 100644
index 0000000000..ced1b5812d
--- /dev/null
+++ b/test/files/run/t6502.scala
@@ -0,0 +1,101 @@
+import scala.tools.partest._
+import java.io.File
+import scala.tools.nsc.interpreter.ILoop
+
+object Test extends StoreReporterDirectTest {
+ def code = ???
+
+ def compileCode(code: String, jarFileName: String) = {
+ val classpath = List(sys.props("partest.lib"), testOutput.path) mkString sys.props("path.separator")
+ compileString(newCompiler("-cp", classpath, "-d", s"${testOutput.path}/$jarFileName"))(code)
+ }
+
+ def app1 = """
+ package test
+
+ object Test extends App {
+ def test(): Unit = {
+ println("testing...")
+ }
+ }"""
+
+ def app2 = """
+ package test
+
+ object Test extends App {
+ def test(): Unit = {
+ println("testing differently...")
+ }
+ }"""
+
+ def app3 = """
+ package test
+
+ object Test3 extends App {
+ def test(): Unit = {
+ println("new object in existing package")
+ }
+ }"""
+
+ def test1(): Unit = {
+ val jar = "test1.jar"
+ compileCode(app1, jar)
+
+ val output = ILoop.run(List(s":require ${testOutput.path}/$jar", "test.Test.test()"))
+ val lines = output.split("\n")
+ val res1 = lines(4).contains("Added") && lines(4).contains("test1.jar")
+ val res2 = lines(lines.length-3).contains("testing...")
+
+ println(s"test1 res1: $res1")
+ println(s"test1 res2: $res2")
+ }
+
+ def test2(): Unit = {
+ // should reject jars with conflicting entries
+ val jar1 = "test1.jar"
+ val jar2 = "test2.jar"
+ compileCode(app2, jar2)
+
+ val output = ILoop.run(List(s":require ${testOutput.path}/$jar1", s":require ${testOutput.path}/$jar2"))
+ val lines = output.split("\n")
+ val res1 = lines(4).contains("Added") && lines(4).contains("test1.jar")
+ val res2 = lines(lines.length-3).contains("test2.jar") && lines(lines.length-3).contains("existing classpath entries conflict")
+
+ println(s"test2 res1: $res1")
+ println(s"test2 res2: $res2")
+ }
+
+ def test3(): Unit = {
+ // should accept jars with overlapping packages, but no conflicts
+ val jar1 = "test1.jar"
+ val jar3 = "test3.jar"
+ compileCode(app3, jar3)
+
+ val output = ILoop.run(List(s":require ${testOutput.path}/$jar1", s":require ${testOutput.path}/$jar3", "test.Test3.test()"))
+ val lines = output.split("\n")
+ val res1 = lines(4).contains("Added") && lines(4).contains("test1.jar")
+ val res2 = lines(lines.length-3).contains("new object in existing package")
+
+ println(s"test3 res1: $res1")
+ println(s"test3 res2: $res2")
+ }
+
+ def test4(): Unit = {
+ // twice the same jar should be rejected
+ val jar1 = "test1.jar"
+ val output = ILoop.run(List(s":require ${testOutput.path}/$jar1", s":require ${testOutput.path}/$jar1"))
+ val lines = output.split("\n")
+ val res1 = lines(4).contains("Added") && lines(4).contains("test1.jar")
+ val res2 = lines(lines.length-3).contains("test1.jar") && lines(lines.length-3).contains("existing classpath entries conflict")
+
+ println(s"test4 res1: $res1")
+ println(s"test4 res2: $res2")
+ }
+
+ def show(): Unit = {
+ test1()
+ test2()
+ test3()
+ test4()
+ }
+}
diff --git a/test/files/run/t7407.flags b/test/files/run/t7407.flags
index c8547a27dc..be4ef0798a 100644
--- a/test/files/run/t7407.flags
+++ b/test/files/run/t7407.flags
@@ -1 +1 @@
--Ynooptimise -Ybackend:GenBCode
+-Ynooptimise -Yopt:l:none -Ybackend:GenBCode
diff --git a/test/files/run/t8925.check b/test/files/run/t8925.check
new file mode 100644
index 0000000000..112e7005df
--- /dev/null
+++ b/test/files/run/t8925.check
@@ -0,0 +1,2 @@
+bar
+abcd
diff --git a/test/files/run/t8925.flags b/test/files/run/t8925.flags
new file mode 100644
index 0000000000..be4ef0798a
--- /dev/null
+++ b/test/files/run/t8925.flags
@@ -0,0 +1 @@
+-Ynooptimise -Yopt:l:none -Ybackend:GenBCode
diff --git a/test/files/run/t8925.scala b/test/files/run/t8925.scala
new file mode 100644
index 0000000000..33f4505f03
--- /dev/null
+++ b/test/files/run/t8925.scala
@@ -0,0 +1,31 @@
+object Ex {
+ def unapply(t: Throwable): Option[Throwable] = Some(t)
+}
+
+class A {
+ var x = ""
+
+ def bar =
+ try {
+ "bar"
+ } finally {
+ try {
+ x += "a"
+ } finally {
+ x += "b"
+ try {
+ x += "c"
+ throw null
+ } catch {
+ case Ex(_) =>
+ x += "d"
+ }
+ }
+ }
+}
+
+object Test extends App {
+ val a = new A
+ println(a.bar)
+ println(a.x)
+}
diff --git a/test/files/run/t8933.check b/test/files/run/t8933.check
new file mode 100644
index 0000000000..d5ef468b98
--- /dev/null
+++ b/test/files/run/t8933.check
@@ -0,0 +1 @@
+'traitSymbol
diff --git a/test/files/run/t8933/A_1.scala b/test/files/run/t8933/A_1.scala
new file mode 100644
index 0000000000..996e3b4a2c
--- /dev/null
+++ b/test/files/run/t8933/A_1.scala
@@ -0,0 +1,6 @@
+class MotherClass
+
+trait MixinWithSymbol {
+ self: MotherClass =>
+ def symbolFromTrait: Symbol = 'traitSymbol
+}
diff --git a/test/files/run/t8933/Test_2.scala b/test/files/run/t8933/Test_2.scala
new file mode 100644
index 0000000000..c506a7c51f
--- /dev/null
+++ b/test/files/run/t8933/Test_2.scala
@@ -0,0 +1,10 @@
+class MotherClass extends MixinWithSymbol {
+ val classSymbol = 'classSymbol
+}
+
+object Test {
+ def main(args: Array[String]) {
+ val symbol = (new MotherClass).symbolFromTrait
+ println(symbol)
+ }
+}
diff --git a/test/files/run/t8933b/A.scala b/test/files/run/t8933b/A.scala
new file mode 100644
index 0000000000..d25d893c6f
--- /dev/null
+++ b/test/files/run/t8933b/A.scala
@@ -0,0 +1,4 @@
+trait MixinWithSymbol {
+ self: MotherClass =>
+ def symbolFromTrait: Any = 'traitSymbol
+}
diff --git a/test/files/run/t8933b/Test.scala b/test/files/run/t8933b/Test.scala
new file mode 100644
index 0000000000..46eedd660f
--- /dev/null
+++ b/test/files/run/t8933b/Test.scala
@@ -0,0 +1,9 @@
+class MotherClass extends MixinWithSymbol {
+ def foo = 'sym1
+}
+
+object Test {
+ def main(args: Array[String]) {
+ (new MotherClass).symbolFromTrait
+ }
+}
diff --git a/test/files/run/t8933c.scala b/test/files/run/t8933c.scala
new file mode 100644
index 0000000000..22011bc323
--- /dev/null
+++ b/test/files/run/t8933c.scala
@@ -0,0 +1,14 @@
+object Test {
+ def main(args: Array[String]): Unit = {
+ try {
+ {throw T; Symbol}.apply("a")
+ assert(false, "exception not thrown")
+ } catch {
+ case T => // ok
+ case t: Throwable =>
+ assert(false, "wrong not thrown: " + t)
+ }
+ }
+}
+
+object T extends Throwable
diff --git a/test/files/run/t8960.scala b/test/files/run/t8960.scala
index c6bcd0770c..a58ac53d33 100644
--- a/test/files/run/t8960.scala
+++ b/test/files/run/t8960.scala
@@ -1,6 +1,12 @@
object Test extends App {
def test(o: AnyRef, sp: Boolean = false) = {
- if (sp) assert(o.getClass.getSuperclass.getName contains "$sp")
+ val isSpecialized = o.getClass.getSuperclass.getName contains "$sp"
+ val isDelambdafyMethod = o.getClass.getName contains "$lambda$"
+ assert(
+ // delambdafy:method doesn't currently emit specialized anonymous function classes
+ if (sp) (isSpecialized || isDelambdafyMethod) else !isSpecialized,
+ o.getClass.getName)
+
val Some(f) = o.getClass.getDeclaredFields.find(_.getName == "serialVersionUID")
assert(f.getLong(null) == 0l)
}
diff --git a/test/files/t8449/Client.scala b/test/files/t8449/Client.scala
new file mode 100644
index 0000000000..5d273f06b2
--- /dev/null
+++ b/test/files/t8449/Client.scala
@@ -0,0 +1,3 @@
+object Client {
+ def foo: Any = new Test().foo
+}
diff --git a/test/files/t8449/Test.java b/test/files/t8449/Test.java
new file mode 100644
index 0000000000..ecb1711b24
--- /dev/null
+++ b/test/files/t8449/Test.java
@@ -0,0 +1,10 @@
+public class Test {
+ // Raw type over a Scala type constructor
+ public scala.Function1 foo() { return null; }
+ // scalac reported:
+ // % scalac-hash v2.11.2 -d /tmp sandbox/{Test.java,Client.scala}
+ // sandbox/Test.java:2: error: trait Function1 takes type parameters
+ // public scala.Function1 foo() { return null; }
+ // ^
+ // one error found
+}
diff --git a/test/junit/scala/collection/IndexedSeqOptimizedTest.scala b/test/junit/scala/collection/IndexedSeqOptimizedTest.scala
index e5382907af..419e1454cb 100644
--- a/test/junit/scala/collection/IndexedSeqOptimizedTest.scala
+++ b/test/junit/scala/collection/IndexedSeqOptimizedTest.scala
@@ -13,4 +13,17 @@ class IndexedSeqOptimizedTest {
assertEquals(0, (Array(2): collection.mutable.WrappedArray[Int]).lastIndexWhere(_ => true, 1))
assertEquals(2, "abc123".lastIndexWhere(_.isLetter, 6))
}
+
+ @Test
+ def hasCorrectDropAndTakeMethods() {
+ assertEquals("", "abc" take Int.MinValue)
+ assertEquals("", "abc" takeRight Int.MinValue)
+ assertEquals("abc", "abc" drop Int.MinValue)
+ assertEquals("abc", "abc" dropRight Int.MinValue)
+
+ assertArrayEquals(Array.empty[Int], Array(1, 2, 3) take Int.MinValue)
+ assertArrayEquals(Array.empty[Int], Array(1, 2, 3) takeRight Int.MinValue)
+ assertArrayEquals(Array(1, 2, 3), Array(1, 2, 3) drop Int.MinValue)
+ assertArrayEquals(Array(1, 2, 3), Array(1, 2, 3) dropRight Int.MinValue)
+ }
}
diff --git a/test/junit/scala/collection/IterableViewLikeTest.scala b/test/junit/scala/collection/IterableViewLikeTest.scala
new file mode 100644
index 0000000000..55da02744b
--- /dev/null
+++ b/test/junit/scala/collection/IterableViewLikeTest.scala
@@ -0,0 +1,20 @@
+package scala.collection
+
+import org.junit.Assert._
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(classOf[JUnit4])
+class IterableViewLikeTest {
+
+ @Test
+ def hasCorrectDropAndTakeMethods() {
+ val iter = Iterable(1, 2, 3)
+
+ assertEquals(Iterable.empty[Int], iter.view take Int.MinValue force)
+ assertEquals(Iterable.empty[Int], iter.view takeRight Int.MinValue force)
+ assertEquals(iter, iter.view drop Int.MinValue force)
+ assertEquals(iter, iter.view dropRight Int.MinValue force)
+ }
+}
diff --git a/test/junit/scala/collection/IteratorTest.scala b/test/junit/scala/collection/IteratorTest.scala
index b7a9805c9f..d5389afd0c 100644
--- a/test/junit/scala/collection/IteratorTest.scala
+++ b/test/junit/scala/collection/IteratorTest.scala
@@ -6,11 +6,14 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import scala.tools.testing.AssertUtil._
+
+import Seq.empty
+
@RunWith(classOf[JUnit4])
class IteratorTest {
- @Test
- def groupedIteratorShouldNotAskForUnneededElement(): Unit = {
+ @Test def groupedIteratorShouldNotAskForUnneededElement(): Unit = {
var counter = 0
val it = new Iterator[Int] { var i = 0 ; def hasNext = { counter = i; true } ; def next = { i += 1; i } }
val slidingIt = it sliding 2
@@ -25,4 +28,130 @@ class IteratorTest {
slidingIt.next
assertEquals("Counter should be one, that means we didn't look further than needed", 1, counter)
}
+
+ @Test def dropDoesNotGrowStack(): Unit = {
+ def it = new Iterator[Throwable] { def hasNext = true ; def next = new Throwable }
+
+ assertEquals(it.drop(1).next.getStackTrace.length, it.drop(1).drop(1).next.getStackTrace.length)
+ }
+
+ @Test def dropIsChainable(): Unit = {
+ assertSameElements(1 to 4, Iterator from 0 take 5 drop 1)
+ assertSameElements(3 to 4, Iterator from 0 take 5 drop 3)
+ assertSameElements(empty, Iterator from 0 take 5 drop 5)
+ assertSameElements(empty, Iterator from 0 take 5 drop 10)
+ assertSameElements(0 to 4, Iterator from 0 take 5 drop 0)
+ assertSameElements(0 to 4, Iterator from 0 take 5 drop -1)
+ assertSameElements(2 to 8 by 2, Iterator from 0 take 5 drop 1 map (2 * _))
+ assertSameElements(2 to 8 by 2, Iterator from 0 take 5 map (2 * _) drop 1)
+ assertSameElements(3 to 4, Iterator from 0 take 5 drop 1 drop 2)
+ assertSameElements(3 to 4, Iterator from 0 take 5 drop 2 drop 1)
+ }
+
+ @Test def sliceIsChainable(): Unit = {
+ assertSameElements(3 to 6, Iterator from 0 slice (3, 7))
+ assertSameElements(empty, Iterator from 0 slice (3, 3))
+ assertSameElements(0 to 2, Iterator from 0 slice (-1, 3))
+ assertSameElements(empty, Iterator from 0 slice (3, -1))
+ assertSameElements(6 to 12 by 2, Iterator from 0 slice (3, 7) map (2 * _))
+ assertSameElements(6 to 12 by 2, Iterator from 0 map (2 * _) slice (3, 7))
+ assertSameElements(4 to 6, Iterator from 0 slice (3, 7) drop 1)
+ assertSameElements(4 to 7, Iterator from 0 drop 1 slice (3, 7))
+ assertSameElements(4 to 5, Iterator from 0 slice (3, 7) slice (1, 3))
+ assertSameElements(4 to 6, Iterator from 0 slice (3, 7) slice (1, 10))
+ }
+
+ // test/files/run/iterator-concat.scala
+ @Test def concatIsStackFriendly(): Unit = {
+ // Create `size` Function0s, each of which evaluates to an Iterator
+ // which produces 1. Then fold them over ++ to get a single iterator,
+ // which should sum to "size".
+ def mk(size: Int): Iterator[Int] = {
+ //val closures = (1 to size).toList.map(x => (() => Iterator(1)))
+ //closures.foldLeft(Iterator.empty: Iterator[Int])((res, f) => res ++ f())
+ List.fill(size)(() => Iterator(1)).foldLeft(Iterator.empty: Iterator[Int])((res, f) => res ++ f())
+ }
+ assertEquals(100, mk(100).sum)
+ assertEquals(1000, mk(1000).sum)
+ assertEquals(10000, mk(10000).sum)
+ assertEquals(100000, mk(100000).sum)
+ }
+
+ @Test def from(): Unit = {
+ val it1 = Iterator.from(-1)
+ val it2 = Iterator.from(0, -1)
+ assertEquals(-1, it1.next())
+ assertEquals(0, it2.next())
+ }
+ @Test def range(): Unit = {
+ assertEquals(5, Iterator.range(0, 10, 2).size)
+ assertEquals(0, Iterator.range(0, 10, -2).size)
+ assertEquals(5, Iterator.range(10, 0, -2).size)
+ assertEquals(0, Iterator.range(10, 0, 2).size)
+ assertEquals(1, Iterator.range(0, 10, 11).size)
+ assertEquals(10, Iterator.range(0, 10, 1).size)
+ assertEquals(10, Iterator.range(10, 0, -1).size)
+ }
+ @Test def range3(): Unit = {
+ val r1 = Iterator.range(0, 10)
+ assertTrue(r1 contains 5)
+ assertTrue(r1 contains 6)
+ assertFalse(r1 contains 4)
+ val r2a = Iterator.range(0, 10, 2)
+ assertFalse(r2a contains 5)
+ val r2b = Iterator.range(0, 10, 2)
+ assertTrue(r2b contains 6)
+ val r3 = Iterator.range(0, 10, 11)
+ assertFalse(r3 contains 5)
+ assertTrue(r3.isEmpty)
+ }
+ @Test def take(): Unit = {
+ assertEquals(10, (Iterator from 0 take 10).size)
+ }
+ @Test def foreach(): Unit = {
+ val it1 = Iterator.from(0) take 20
+ var n = 0
+ it1 foreach { n += _ }
+ assertEquals(190, n)
+ }
+ // ticket #429
+ @Test def fromArray(): Unit = {
+ val a = List(1, 2, 3, 4).toArray
+ var xs0 = a.iterator.toList;
+ var xs1 = a.slice(0, 1).iterator
+ var xs2 = a.slice(0, 2).iterator
+ var xs3 = a.slice(0, 3).iterator
+ var xs4 = a.slice(0, 4).iterator
+ assertEquals(14, xs0.size + xs1.size + xs2.size + xs3.size + xs4.size)
+ }
+ @Test def toSeq(): Unit = {
+ assertEquals("1x2x3x4x5", List(1, 2, 3, 4, 5).iterator.mkString("x"))
+ }
+ @Test def indexOf(): Unit = {
+ assertEquals(3, List(1, 2, 3, 4, 5).iterator.indexOf(4))
+ assertEquals(-1, List(1, 2, 3, 4, 5).iterator.indexOf(16))
+ }
+ @Test def indexWhere(): Unit = {
+ assertEquals(3, List(1, 2, 3, 4, 5).iterator.indexWhere { x: Int => x >= 4 })
+ assertEquals(-1, List(1, 2, 3, 4, 5).iterator.indexWhere { x: Int => x >= 16 })
+ }
+ // iterator-iterate-lazy.scala
+ // was java.lang.UnsupportedOperationException: tail of empty list
+ @Test def iterateIsSufficientlyLazy(): Unit = {
+ //Iterator.iterate((1 to 5).toList)(_.tail).takeWhile(_.nonEmpty).toList // suffices
+ Iterator.iterate((1 to 5).toList)(_.tail).takeWhile(_.nonEmpty).map(_.head).toList
+ }
+ // SI-3516
+ @Test def toStreamIsSufficientlyLazy(): Unit = {
+ val results = collection.mutable.ListBuffer.empty[Int]
+ def mkIterator = (1 to 5).iterator map (x => { results += x ; x })
+ def mkInfinite = Iterator continually { results += 1 ; 1 }
+
+ // Stream is strict in its head so we should see 1 from each of them.
+ val s1 = mkIterator.toStream
+ val s2 = mkInfinite.toStream
+ // back and forth without slipping into nontermination.
+ results += (Stream from 1).toIterator.drop(10).toStream.drop(10).toIterator.next()
+ assertSameElements(List(1,1,21), results)
+ }
}
diff --git a/test/junit/scala/collection/immutable/TreeMapTest.scala b/test/junit/scala/collection/immutable/TreeMapTest.scala
new file mode 100644
index 0000000000..4c21b94b24
--- /dev/null
+++ b/test/junit/scala/collection/immutable/TreeMapTest.scala
@@ -0,0 +1,20 @@
+package scala.collection.immutable
+
+import org.junit.Assert._
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(classOf[JUnit4])
+class TreeMapTest {
+
+ @Test
+ def hasCorrectDropAndTakeMethods() {
+ val tree = TreeMap(1 -> "a", 2 -> "b", 3 -> "c")
+
+ assertEquals(TreeMap.empty[Int, String], tree take Int.MinValue)
+ assertEquals(TreeMap.empty[Int, String], tree takeRight Int.MinValue)
+ assertEquals(tree, tree drop Int.MinValue)
+ assertEquals(tree, tree dropRight Int.MinValue)
+ }
+}
diff --git a/test/junit/scala/collection/immutable/TreeSetTest.scala b/test/junit/scala/collection/immutable/TreeSetTest.scala
new file mode 100644
index 0000000000..8efe1bfeb8
--- /dev/null
+++ b/test/junit/scala/collection/immutable/TreeSetTest.scala
@@ -0,0 +1,20 @@
+package scala.collection.immutable
+
+import org.junit.Assert._
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(classOf[JUnit4])
+class TreeSetTest {
+
+ @Test
+ def hasCorrectDropAndTakeMethods() {
+ val set = TreeSet(1, 2, 3)
+
+ assertEquals(TreeSet.empty[Int], set take Int.MinValue)
+ assertEquals(TreeSet.empty[Int], set takeRight Int.MinValue)
+ assertEquals(set, set drop Int.MinValue)
+ assertEquals(set, set dropRight Int.MinValue)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
index b892eb36cf..c1c5a71b83 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
@@ -7,6 +7,8 @@ import scala.reflect.io.VirtualDirectory
import scala.tools.asm.Opcodes
import scala.tools.asm.tree.{AbstractInsnNode, LabelNode, ClassNode, MethodNode}
import scala.tools.cmd.CommandLineParser
+import scala.tools.nsc.backend.jvm.opt.LocalOpt
+import scala.tools.nsc.settings.{MutableSettings, ScalaSettings}
import scala.tools.nsc.{Settings, Global}
import scala.tools.partest.ASMConverters
import scala.collection.JavaConverters._
@@ -79,4 +81,23 @@ object CodeGenTools {
def getSingleMethod(classNode: ClassNode, name: String): Method =
convertMethod(classNode.methods.asScala.toList.find(_.name == name).get)
+
+ def assertHandlerLabelPostions(h: ExceptionHandler, instructions: List[Instruction], startIndex: Int, endIndex: Int, handlerIndex: Int): Unit = {
+ val insVec = instructions.toVector
+ assertTrue(h.start == insVec(startIndex) && h.end == insVec(endIndex) && h.handler == insVec(handlerIndex))
+ }
+
+ val localOpt = {
+ val settings = new MutableSettings(msg => throw new IllegalArgumentException(msg))
+ settings.processArguments(List("-Yopt:l:method"), processAll = true)
+ new LocalOpt(settings)
+ }
+
+ import scala.language.implicitConversions
+
+ implicit def aliveInstruction(ins: Instruction): (Instruction, Boolean) = (ins, true)
+
+ implicit class MortalInstruction(val ins: Instruction) extends AnyVal {
+ def dead: (Instruction, Boolean) = (ins, false)
+ }
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
index 2fb5bb8052..89900291ca 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
@@ -10,13 +10,12 @@ import scala.tools.partest.ASMConverters._
@RunWith(classOf[JUnit4])
class DirectCompileTest {
- val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode")
+ val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:method")
@Test
def testCompile(): Unit = {
val List(("C.class", bytes)) = compile(compiler)(
- """
- |class C {
+ """class C {
| def f = 1
|}
""".stripMargin)
@@ -26,19 +25,12 @@ class DirectCompileTest {
@Test
def testCompileClasses(): Unit = {
- val List(cClass, cModuleClass) = compileClasses(compiler)(
- """
- |class C
- |object C
- """.stripMargin)
+ val List(cClass, cModuleClass) = compileClasses(compiler)("class C; object C")
assertTrue(cClass.name == "C")
assertTrue(cModuleClass.name == "C$")
- val List(dMirror, dModuleClass) = compileClasses(compiler)(
- """
- |object D
- """.stripMargin)
+ val List(dMirror, dModuleClass) = compileClasses(compiler)("object D")
assertTrue(dMirror.name == "D")
assertTrue(dModuleClass.name == "D$")
@@ -47,35 +39,35 @@ class DirectCompileTest {
@Test
def testCompileMethods(): Unit = {
val List(f, g) = compileMethods(compiler)(
- """
- |def f = 10
+ """def f = 10
|def g = f
""".stripMargin)
assertTrue(f.name == "f")
assertTrue(g.name == "g")
- assertTrue(instructionsFromMethod(f).dropNonOp ===
+ assertSameCode(instructionsFromMethod(f).dropNonOp,
List(IntOp(BIPUSH, 10), Op(IRETURN)))
- assertTrue(instructionsFromMethod(g).dropNonOp ===
- List(VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "C", "f", "()I", false), Op(IRETURN)))
+ assertSameCode(instructionsFromMethod(g).dropNonOp,
+ List(VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "C", "f", "()I", itf = false), Op(IRETURN)))
}
@Test
def testDropNonOpAliveLabels(): Unit = {
+ // makes sure that dropNoOp doesn't drop labels that are being used
val List(f) = compileMethods(compiler)("""def f(x: Int) = if (x == 0) "a" else "b"""")
- assertTrue(instructionsFromMethod(f).dropNonOp === List(
- VarOp(ILOAD, 1),
- Op(ICONST_0),
- Jump(IF_ICMPEQ, Label(6)),
- Jump(GOTO, Label(10)),
- Label(6),
- Ldc(LDC, "a"),
- Jump(GOTO, Label(13)),
- Label(10),
- Ldc(LDC, "b"),
- Label(13),
- Op(ARETURN)
+ assertSameCode(instructionsFromMethod(f).dropLinesFrames, List(
+ Label(0),
+ VarOp(ILOAD, 1),
+ Op(ICONST_0),
+ Jump(IF_ICMPNE,
+ Label(7)),
+ Ldc(LDC, "a"),
+ Op(ARETURN),
+ Label(7),
+ Ldc(LDC, "b"),
+ Op(ARETURN),
+ Label(11)
))
}
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
new file mode 100644
index 0000000000..fc748196d0
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
@@ -0,0 +1,80 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+
+@RunWith(classOf[JUnit4])
+class CompactLocalVariablesTest {
+
+ // recurse-unreachable-jumps is required for eliminating catch blocks, in the first dce round they
+ // are still live.only after eliminating the empty handler the catch blocks become unreachable.
+ val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,recurse-unreachable-jumps,compact-locals")
+ val noCompactVarsCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,recurse-unreachable-jumps")
+
+ @Test
+ def compactUnused(): Unit = {
+ val code =
+ """def f: Double = {
+ | try { }
+ | catch {
+ | case _: Throwable =>
+ | // eliminated by dce
+ | val i = 1
+ | val d = 1d
+ | val f = 1f
+ | val l = 1l
+ | }
+ |
+ | val i = 1 // variable index 1 (it's an instance method, so at index 0 we have `this`)
+ | val d = 1d // 2,3
+ | val f = 1f // 4
+ | val l = 1l // 5,6
+ |
+ | try { }
+ | catch {
+ | case _: Throwable =>
+ | // eliminated by dce
+ | val i = 1
+ | val d = 1d
+ | val f = 1f
+ | val l = 1l
+ | }
+ |
+ | val ii = 1 // 7
+ | val dd = 1d // 8,9
+ | val ff = 1f // 10
+ | val ll = 1l // 11,12
+ |
+ | i + ii + d + dd + f + ff + l + ll
+ |}
+ |""".stripMargin
+
+ val List(noCompact) = compileMethods(noCompactVarsCompiler)(code)
+ val List(withCompact) = compileMethods(methodOptCompiler)(code)
+
+ // code is the same, except for local var indices
+ assertTrue(noCompact.instructions.size == withCompact.instructions.size)
+
+ val varOpSlots = convertMethod(withCompact).instructions collect {
+ case VarOp(_, v) => v
+ }
+ assertTrue(varOpSlots.toString, varOpSlots == List(1, 2, 4, 5, 7, 8, 10, 11, // stores
+ 1, 7, 2, 8, 4, 10, 5, 11)) // loads
+
+ // the local variables descriptor table is cleaned up to remove stale entries after dce,
+ // also when the slots are not compacted
+ assertTrue(noCompact.localVariables.size == withCompact.localVariables.size)
+
+ assertTrue(noCompact.maxLocals == 25)
+ assertTrue(withCompact.maxLocals == 13)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala
index 57fa1a7b66..7d83c54b5b 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala
@@ -26,7 +26,7 @@ class EmptyExceptionHandlersTest {
Op(RETURN)
)
assertTrue(convertMethod(asmMethod).handlers.length == 1)
- LocalOpt.removeEmptyExceptionHandlers(asmMethod)
+ localOpt.removeEmptyExceptionHandlers(asmMethod)
assertTrue(convertMethod(asmMethod).handlers.isEmpty)
}
@@ -35,12 +35,8 @@ class EmptyExceptionHandlersTest {
val handlers = List(ExceptionHandler(Label(1), Label(2), Label(2), Some(exceptionDescriptor)))
val asmMethod = genMethod(handlers = handlers)(
Label(1), // nops only
- Op(NOP),
- Op(NOP),
Jump(GOTO, Label(3)),
- Op(NOP),
Label(3),
- Op(NOP),
Jump(GOTO, Label(4)),
Label(2), // handler
@@ -51,7 +47,7 @@ class EmptyExceptionHandlersTest {
Op(RETURN)
)
assertTrue(convertMethod(asmMethod).handlers.length == 1)
- LocalOpt.removeEmptyExceptionHandlers(asmMethod)
+ localOpt.removeEmptyExceptionHandlers(asmMethod)
assertTrue(convertMethod(asmMethod).handlers.isEmpty)
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala
new file mode 100644
index 0000000000..8c0168826e
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala
@@ -0,0 +1,99 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+import scala.tools.testing.AssertUtil._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+
+@RunWith(classOf[JUnit4])
+class EmptyLabelsAndLineNumbersTest {
+ @Test
+ def removeEmptyLineNumbers(): Unit = {
+ val ops = List[(Instruction, Boolean)](
+ Label(1),
+ LineNumber(1, Label(1)),
+ Label(2),
+ Label(3),
+ Op(RETURN),
+
+ Label(4),
+ LineNumber(4, Label(4)).dead,
+ LineNumber(5, Label(4)),
+ Op(RETURN),
+
+ Label(5),
+ LineNumber(6, Label(5)).dead,
+ Label(6),
+ Label(7),
+ LineNumber(7, Label(7)),
+ Op(RETURN),
+
+ Label(9),
+ LineNumber(8, Label(9)).dead,
+ Label(10)
+ )
+
+ val method = genMethod()(ops.map(_._1): _*)
+ assertTrue(localOpt.removeEmptyLineNumbers(method))
+ assertSameCode(instructionsFromMethod(method), ops.filter(_._2).map(_._1))
+ }
+
+ @Test
+ def badlyLocatedLineNumbers(): Unit = {
+ def t(ops: Instruction*) =
+ assertThrows[AssertionError](localOpt.removeEmptyLineNumbers(genMethod()(ops: _*)))
+
+ // line numbers have to be right after their referenced label node
+ t(LineNumber(0, Label(1)), Label(1))
+ t(Label(0), Label(1), LineNumber(0, Label(0)))
+ }
+
+ @Test
+ def removeEmptyLabels(): Unit = {
+ val handler = List(ExceptionHandler(Label(4), Label(5), Label(6), Some("java/lang/Throwable")))
+ def ops(target1: Int, target2: Int, target3: Int, target4: Int, target5: Int, target6: Int) = List[(Instruction, Boolean)](
+ Label(1),
+ Label(2).dead,
+ Label(3).dead,
+ LineNumber(3, Label(target1)),
+ VarOp(ILOAD, 1),
+ Jump(IFGE, Label(target2)),
+
+ Label(4),
+ Label(5).dead,
+ Label(6).dead,
+ VarOp(ILOAD, 2),
+ Jump(IFGE, Label(target3)),
+
+ Label(7),
+ Label(8).dead,
+ Label(9).dead,
+ Op(RETURN),
+
+ LookupSwitch(LOOKUPSWITCH, Label(target4), List(1,2), List(Label(target4), Label(target5))),
+ TableSwitch(TABLESWITCH, 1, 2, Label(target4), List(Label(target4), Label(target5))),
+
+ Label(10),
+ LineNumber(10, Label(10)),
+ Label(11).dead,
+ LineNumber(12, Label(target6))
+ )
+
+ val method = genMethod(handlers = handler)(ops(2, 3, 8, 8, 9, 11).map(_._1): _*)
+ assertTrue(localOpt.removeEmptyLabelNodes(method))
+ val m = convertMethod(method)
+ assertSameCode(m.instructions, ops(1, 1, 7, 7, 7, 10).filter(_._2).map(_._1))
+ assertTrue(m.handlers match {
+ case List(ExceptionHandler(Label(4), Label(4), Label(4), _)) => true
+ case _ => false
+ })
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala
new file mode 100644
index 0000000000..5b0f0f238a
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala
@@ -0,0 +1,83 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import scala.tools.testing.AssertUtil._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+
+@RunWith(classOf[JUnit4])
+class MethodLevelOpts {
+ val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method")
+
+ def wrapInDefault(code: Instruction*) = List(Label(0), LineNumber(1, Label(0))) ::: code.toList ::: List(Label(1))
+
+ @Test
+ def eliminateEmptyTry(): Unit = {
+ val code = "def f = { try {} catch { case _: Throwable => 0; () }; 1 }"
+ assertSameCode(singleMethodInstructions(methodOptCompiler)(code), wrapInDefault(Op(ICONST_1), Op(IRETURN)))
+ }
+
+ @Test
+ def cannotEliminateLoadBoxedUnit(): Unit = {
+ // the compiler inserts a boxed into the try block. it's therefore non-empty (and live) and not eliminated.
+ val code = "def f = { try {} catch { case _: Throwable => 0 }; 1 }"
+ val m = singleMethod(methodOptCompiler)(code)
+ assertTrue(m.handlers.length == 1)
+ assertSameCode(m.instructions.take(3), List(Label(0), LineNumber(1, Label(0)), Field(GETSTATIC, "scala/runtime/BoxedUnit", "UNIT", "Lscala/runtime/BoxedUnit;")))
+ }
+
+ @Test
+ def inlineThrowInCatchNotTry(): Unit = {
+ // the try block does not contain the `ATHROW` instruction, but in the catch block, `ATHROW` is inlined
+ val code = "def f(e: Exception) = throw { try e catch { case _: Throwable => e } }"
+ val m = singleMethod(methodOptCompiler)(code)
+ assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5)
+ assertSameCode(m.instructions,
+ wrapInDefault(VarOp(ALOAD, 1), Label(3), Op(ATHROW), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), VarOp(ALOAD, 1), Op(ATHROW))
+ )
+ }
+
+ @Test
+ def inlineReturnInCachtNotTry(): Unit = {
+ val code = "def f: Int = return { try 1 catch { case _: Throwable => 2 } }"
+ // cannot inline the IRETURN into the try block (because RETURN may throw IllegalMonitorState)
+ val m = singleMethod(methodOptCompiler)(code)
+ assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5)
+ assertSameCode(m.instructions,
+ wrapInDefault(Op(ICONST_1), Label(3), Op(IRETURN), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), Op(ICONST_2), Op(IRETURN)))
+ }
+
+ @Test
+ def simplifyJumpsInTryCatchFinally(): Unit = {
+ val code =
+ """def f: Int =
+ | try {
+ | return 1
+ | } catch {
+ | case _: Throwable =>
+ | return 2
+ | } finally {
+ | return 2
+ | // dead
+ | val x = try 10 catch { case _: Throwable => 11 }
+ | println(x)
+ | }
+ """.stripMargin
+ val m = singleMethod(methodOptCompiler)(code)
+ assertTrue(m.handlers.length == 2)
+ assertSameCode(m.instructions.dropNonOp, // drop line numbers and lables that are only used by line numbers
+
+ // one single label left :-)
+ List(Op(ICONST_1), VarOp(ISTORE, 2), Jump(GOTO, Label(20)), Op(POP), Op(ICONST_2), VarOp(ISTORE, 2), Jump(GOTO, Label(20)), VarOp(ASTORE, 3), Op(ICONST_2), Op(IRETURN), Label(20), Op(ICONST_2), Op(IRETURN))
+ )
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala
new file mode 100644
index 0000000000..360fa1d23d
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala
@@ -0,0 +1,221 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+
+@RunWith(classOf[JUnit4])
+class SimplifyJumpsTest {
+ @Test
+ def simpleGotoReturn(): Unit = {
+ val ops = List(
+ Jump(GOTO, Label(2)), // replaced by RETURN
+ Op(ICONST_1), // need some code, otherwise removeJumpToSuccessor kicks in
+ Op(POP),
+ Label(1), // multiple labels OK
+ Label(2),
+ Label(3),
+ Op(RETURN)
+ )
+ val method = genMethod()(ops: _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), Op(RETURN) :: ops.tail)
+ }
+
+ @Test
+ def simpleGotoThrow(): Unit = {
+ val rest = List(
+ Op(ICONST_1), // need some code, otherwise removeJumpToSuccessor kicks in
+ Op(POP),
+ Label(1),
+ Label(2),
+ Label(3),
+ Op(ATHROW)
+ )
+ val method = genMethod()(
+ Op(ACONST_NULL) ::
+ Jump(GOTO, Label(2)) :: // replaced by ATHROW
+ rest: _*
+ )
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), Op(ACONST_NULL) :: Op(ATHROW) :: rest)
+ }
+
+ @Test
+ def gotoThrowInTry(): Unit = {
+ val handler = List(ExceptionHandler(Label(1), Label(2), Label(4), Some("java/lang/Throwable")))
+ val initialInstrs = List(
+ Label(1),
+ Op(ACONST_NULL),
+ Jump(GOTO, Label(3)), // not by ATHROW (would move the ATHROW into a try block)
+ Label(2),
+ Op(ICONST_1), // need some code, otherwise removeJumpToSuccessor kicks in
+ Op(POP),
+ Label(3),
+ Op(ATHROW),
+ Label(4),
+ Op(POP),
+ Op(RETURN)
+ )
+ val method = genMethod(handlers = handler)(initialInstrs: _*)
+ assertFalse(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), initialInstrs)
+
+ val optMethod = genMethod()(initialInstrs: _*) // no handler
+ assertTrue(localOpt.simplifyJumps(optMethod))
+ assertSameCode(instructionsFromMethod(optMethod).take(3), List(Label(1), Op(ACONST_NULL), Op(ATHROW)))
+ }
+
+ @Test
+ def simplifyBranchOverGoto(): Unit = {
+ val begin = List(
+ VarOp(ILOAD, 1),
+ Jump(IFGE, Label(2))
+ )
+ val rest = List(
+ Jump(GOTO, Label(3)),
+ Label(11), // other labels here are allowed
+ Label(2),
+ VarOp(ILOAD, 1),
+ Op(RETURN),
+ Label(3),
+ VarOp(ILOAD, 1),
+ Op(IRETURN)
+ )
+ val method = genMethod()(begin ::: rest: _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(
+ instructionsFromMethod(method),
+ List(VarOp(ILOAD, 1), Jump(IFLT, Label(3))) ::: rest.tail )
+
+ // no label allowed between begin and rest. if there's another label, then there could be a
+ // branch that label. eliminating the GOTO would change the behavior.
+ val nonOptMethod = genMethod()(begin ::: Label(22) :: rest: _*)
+ assertFalse(localOpt.simplifyJumps(nonOptMethod))
+ }
+
+ @Test
+ def ensureGotoRemoved(): Unit = {
+ def code(jumps: Instruction*) = List(
+ VarOp(ILOAD, 1)) ::: jumps.toList ::: List(
+ Label(2),
+
+ Op(RETURN),
+ Label(3),
+ Op(RETURN)
+ )
+
+ // ensures that the goto is safely removed. ASM supports removing while iterating, but not the
+ // next element of the current. Here, the current is the IFGE, the next is the GOTO.
+ val method = genMethod()(code(Jump(IFGE, Label(2)), Jump(GOTO, Label(3))): _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), code(Jump(IFLT, Label(3))))
+ }
+
+ @Test
+ def removeJumpToSuccessor(): Unit = {
+ val ops = List(
+ Jump(GOTO, Label(1)),
+ Label(11),
+ Label(1),
+ Label(2),
+ VarOp(ILOAD, 1),
+ Op(IRETURN)
+ )
+ val method = genMethod()(ops: _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), ops.tail)
+ }
+
+ @Test
+ def collapseJumpChains(): Unit = {
+ def ops(target1: Int, target2: Int, target3: Int) = List(
+ VarOp(ILOAD, 1),
+ Jump(IFGE, Label(target1)), // initially 1, then 3
+ VarOp(ILOAD, 1),
+ Op(IRETURN),
+
+ Label(2),
+ Jump(GOTO, Label(target3)),
+
+ Label(1),
+ Jump(GOTO, Label(target2)), // initially 2, then 3
+
+ VarOp(ILOAD, 1), // some code to prevent jumpToSuccessor optimization (once target2 is replaced by 3)
+ Op(RETURN),
+
+ Label(3),
+ VarOp(ILOAD, 1),
+ Op(IRETURN)
+ )
+ val method = genMethod()(ops(1, 2, 3): _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), ops(3, 3, 3))
+ }
+
+ @Test
+ def collapseJumpChainLoop(): Unit = {
+ def ops(target: Int) = List(
+ VarOp(ILOAD, 1),
+ Jump(IFGE, Label(target)),
+
+ Label(4),
+ Jump(GOTO, Label(3)),
+
+ VarOp(ILOAD, 1), // some code to prevent jumpToSuccessor (label 3)
+ Op(IRETURN),
+
+ Label(3),
+ Jump(GOTO, Label(4)),
+
+ Label(2),
+ Jump(GOTO, Label(3))
+ )
+
+ val method = genMethod()(ops(2): _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), ops(3))
+ }
+
+ @Test
+ def simplifyThenElseSameTarget(): Unit = {
+ def ops(jumpOp: Instruction) = List(
+ VarOp(ILOAD, 1),
+ jumpOp,
+ Label(2),
+ Jump(GOTO, Label(1)),
+
+ VarOp(ILOAD, 1), // some code to prevent jumpToSuccessor (label 1)
+ Op(IRETURN),
+
+ Label(1),
+ VarOp(ILOAD, 1),
+ Op(IRETURN)
+ )
+
+ val method = genMethod()(ops(Jump(IFGE, Label(1))): _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), ops(Op(POP)))
+ }
+
+ @Test
+ def thenElseSameTargetLoop(): Unit = {
+ def ops(br: List[Instruction]) = List(
+ VarOp(ILOAD, 1),
+ VarOp(ILOAD, 2)) ::: br ::: List(
+ Label(1),
+ Jump(GOTO, Label(1))
+ )
+ val method = genMethod()(ops(List(Jump(IF_ICMPGE, Label(1)))): _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), ops(List(Op(POP), Op(POP))))
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
index a3bd7ae6fe..4a45dd9138 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
@@ -16,12 +16,20 @@ import ASMConverters._
@RunWith(classOf[JUnit4])
class UnreachableCodeTest {
- import UnreachableCodeTest._
+
+ def assertEliminateDead(code: (Instruction, Boolean)*): Unit = {
+ val method = genMethod()(code.map(_._1): _*)
+ localOpt.removeUnreachableCodeImpl(method, "C")
+ val nonEliminated = instructionsFromMethod(method)
+ val expectedLive = code.filter(_._2).map(_._1).toList
+ assertSameCode(nonEliminated, expectedLive)
+ }
// jvm-1.6 enables emitting stack map frames, which impacts the code generation wrt dead basic blocks,
// see comment in BCodeBodyBuilder
- val dceCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code")
- val noOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:none")
+ val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method")
+ val dceCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code")
+ val noOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:none")
// jvm-1.5 disables computing stack map frames, and it emits dead code as-is.
val noOptNoFramesCompiler = newCompiler(extraArgs = "-target:jvm-1.5 -Ybackend:GenBCode -Yopt:l:none")
@@ -48,8 +56,8 @@ class UnreachableCodeTest {
@Test
def eliminateNop(): Unit = {
assertEliminateDead(
- // not dead, since visited by data flow analysis. need a different opt to eliminate it.
- Op(NOP),
+ // reachable, but removed anyway.
+ Op(NOP).dead,
Op(RETURN),
Op(NOP).dead
)
@@ -136,28 +144,31 @@ class UnreachableCodeTest {
@Test
def eliminateDeadCatchBlocks(): Unit = {
+ // the Label(1) is live: it's used in the local variable descriptor table (local variable "this" has a range from 0 to 1).
+ def wrapInDefault(code: Instruction*) = List(Label(0), LineNumber(1, Label(0))) ::: code.toList ::: List(Label(1))
+
val code = "def f: Int = { return 0; try { 1 } catch { case _: Exception => 2 } }"
- assertSameCode(singleMethodInstructions(dceCompiler)(code).dropNonOp,
- List(Op(ICONST_0), Op(IRETURN)))
+ val m = singleMethod(dceCompiler)(code)
+ assertTrue(m.handlers.isEmpty) // redundant (if code is gone, handler is gone), but done once here for extra safety
+ assertSameCode(m.instructions,
+ wrapInDefault(Op(ICONST_0), Op(IRETURN)))
val code2 = "def f: Unit = { try { } catch { case _: Exception => () }; () }"
- // DCE only removes dead basic blocks, but not NOPs, and also not useless jumps
- assertSameCode(singleMethodInstructions(dceCompiler)(code2).dropNonOp,
- List(Op(NOP), Jump(GOTO, Label(33)), Label(33), Op(RETURN)))
+ // requires fixpoint optimization of methodOptCompiler (dce alone is not enough): first the handler is eliminated, then it's dead catch block.
+ assertSameCode(singleMethodInstructions(methodOptCompiler)(code2), wrapInDefault(Op(RETURN)))
val code3 = "def f: Unit = { try { } catch { case _: Exception => try { } catch { case _: Exception => () } }; () }"
- assertSameCode(singleMethodInstructions(dceCompiler)(code3).dropNonOp,
- List(Op(NOP), Jump(GOTO, Label(33)), Label(33), Op(RETURN)))
+ assertSameCode(singleMethodInstructions(methodOptCompiler)(code3), wrapInDefault(Op(RETURN)))
+ // this example requires two iterations to get rid of the outer handler.
+ // the first iteration of DCE cannot remove the inner handler. then the inner (empty) handler is removed.
+ // then the second iteration of DCE removes the inner catch block, and then the outer handler is removed.
val code4 = "def f: Unit = { try { try { } catch { case _: Exception => () } } catch { case _: Exception => () }; () }"
- assertSameCode(singleMethodInstructions(dceCompiler)(code4).dropNonOp,
- List(Op(NOP), Jump(GOTO, Label(4)), Label(4), Jump(GOTO, Label(7)), Label(7), Op(RETURN)))
+ assertSameCode(singleMethodInstructions(methodOptCompiler)(code4), wrapInDefault(Op(RETURN)))
}
@Test // test the dce-testing tools
def metaTest(): Unit = {
- assertEliminateDead() // no instructions
-
assertThrows[AssertionError](
assertEliminateDead(Op(RETURN).dead),
_.contains("Expected: List()\nActual : List(Op(RETURN))")
@@ -198,20 +209,3 @@ class UnreachableCodeTest {
List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(1)), List("java/lang/Object", Label(3))), Label(1), Label(3)))
}
}
-
-object UnreachableCodeTest {
- import scala.language.implicitConversions
- implicit def aliveInstruction(ins: Instruction): (Instruction, Boolean) = (ins, true)
-
- implicit class MortalInstruction(val ins: Instruction) extends AnyVal {
- def dead: (Instruction, Boolean) = (ins, false)
- }
-
- def assertEliminateDead(code: (Instruction, Boolean)*): Unit = {
- val cls = wrapInClass(genMethod()(code.map(_._1): _*))
- LocalOpt.removeUnreachableCode(cls)
- val nonEliminated = instructionsFromMethod(cls.methods.get(0))
- val expectedLive = code.filter(_._2).map(_._1).toList
- assertSameCode(nonEliminated, expectedLive)
- }
-}
diff --git a/test/junit/scala/tools/testing/AssertUtil.scala b/test/junit/scala/tools/testing/AssertUtil.scala
index 9b4833d46b..83a637783f 100644
--- a/test/junit/scala/tools/testing/AssertUtil.scala
+++ b/test/junit/scala/tools/testing/AssertUtil.scala
@@ -1,6 +1,11 @@
package scala.tools
package testing
+import org.junit.Assert
+import Assert.fail
+import scala.runtime.ScalaRunTime.stringOf
+import scala.collection.{ GenIterable, IterableLike }
+
/** This module contains additional higher-level assert statements
* that are ultimately based on junit.Assert primitives.
*/
@@ -21,6 +26,19 @@ object AssertUtil {
throw e
else return
}
- throw new AssertionError("Expression did not throw!")
+ fail("Expression did not throw!")
}
+
+ /** JUnit-style assertion for `IterableLike.sameElements`.
+ */
+ def assertSameElements[A, B >: A](expected: IterableLike[A, _], actual: GenIterable[B], message: String = ""): Unit =
+ if (!(expected sameElements actual))
+ fail(
+ f"${ if (message.nonEmpty) s"$message " else "" }expected:<${ stringOf(expected) }> but was:<${ stringOf(actual) }>"
+ )
+
+ /** Convenient for testing iterators.
+ */
+ def assertSameElements[A, B >: A](expected: IterableLike[A, _], actual: Iterator[B]): Unit =
+ assertSameElements(expected, actual.toList, "")
}