summaryrefslogtreecommitdiff
path: root/src/compiler/scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/scala')
-rw-r--r--src/compiler/scala/reflect/quasiquotes/Holes.scala4
-rw-r--r--src/compiler/scala/reflect/quasiquotes/Reifiers.scala2
-rw-r--r--src/compiler/scala/reflect/reify/codegen/GenSymbols.scala2
-rw-r--r--src/compiler/scala/reflect/reify/codegen/GenTrees.scala4
-rw-r--r--src/compiler/scala/reflect/reify/codegen/GenUtils.scala8
-rw-r--r--src/compiler/scala/tools/ant/FastScalac.scala2
-rw-r--r--src/compiler/scala/tools/ant/sabbus/Compiler.scala2
-rw-r--r--src/compiler/scala/tools/ant/sabbus/ScalacFork.scala2
-rwxr-xr-xsrc/compiler/scala/tools/ant/templates/tool-unix.tmpl39
-rw-r--r--src/compiler/scala/tools/nsc/CompilationUnits.scala6
-rw-r--r--src/compiler/scala/tools/nsc/EvalLoop.scala3
-rw-r--r--src/compiler/scala/tools/nsc/GenericRunnerCommand.scala3
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala3
-rw-r--r--src/compiler/scala/tools/nsc/ObjectRunner.scala2
-rw-r--r--src/compiler/scala/tools/nsc/Reporting.scala12
-rwxr-xr-xsrc/compiler/scala/tools/nsc/ast/DocComments.scala19
-rw-r--r--src/compiler/scala/tools/nsc/ast/NodePrinters.scala1
-rw-r--r--src/compiler/scala/tools/nsc/ast/Trees.scala4
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Parsers.scala4
-rwxr-xr-xsrc/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala1
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/GenICode.scala16
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala21
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala246
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala58
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala145
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala42
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala26
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala392
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala289
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala265
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala5
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala86
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala29
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala123
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala132
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala189
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala148
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala648
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala88
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala24
-rw-r--r--src/compiler/scala/tools/nsc/javac/JavaParsers.scala3
-rw-r--r--src/compiler/scala/tools/nsc/plugins/Plugin.scala6
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaSettings.scala35
-rw-r--r--src/compiler/scala/tools/nsc/settings/Warnings.scala13
-rw-r--r--src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala8
-rw-r--r--src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/AddInterfaces.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/Constructors.scala1
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala6
-rw-r--r--src/compiler/scala/tools/nsc/transform/Erasure.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/Flatten.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/LambdaLift.scala26
-rw-r--r--src/compiler/scala/tools/nsc/transform/Mixin.scala18
-rw-r--r--src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala8
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala7
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala5
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala4
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala17
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Implicits.scala10
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Infer.scala5
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Macros.scala3
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala5
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Namers.scala9
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala18
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/RefChecks.scala6
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala7
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala6
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala79
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Unapplies.scala1
-rw-r--r--src/compiler/scala/tools/reflect/ToolBoxFactory.scala2
75 files changed, 2794 insertions, 627 deletions
diff --git a/src/compiler/scala/reflect/quasiquotes/Holes.scala b/src/compiler/scala/reflect/quasiquotes/Holes.scala
index 38b05f9d4b..6fa6b9b37a 100644
--- a/src/compiler/scala/reflect/quasiquotes/Holes.scala
+++ b/src/compiler/scala/reflect/quasiquotes/Holes.scala
@@ -132,7 +132,7 @@ trait Holes { self: Quasiquotes =>
private def mapF(tree: Tree, f: Tree => Tree): Tree =
if (f(Ident(TermName("x"))) equalsStructure Ident(TermName("x"))) tree
else {
- val x: TermName = c.freshName()
+ val x = TermName(c.freshName())
// q"$tree.map { $x => ${f(Ident(x))} }"
Apply(Select(tree, nme.map),
Function(ValDef(Modifiers(PARAM), x, TypeTree(), EmptyTree) :: Nil,
@@ -187,7 +187,7 @@ trait Holes { self: Quasiquotes =>
lazy val tree =
tptopt.map { tpt =>
val TypeDef(_, _, _, typedTpt) =
- try c.typeCheck(TypeDef(NoMods, TypeName("T"), Nil, tpt))
+ try c.typecheck(TypeDef(NoMods, TypeName("T"), Nil, tpt))
catch { case TypecheckException(pos, msg) => c.abort(pos.asInstanceOf[c.Position], msg) }
val tpe = typedTpt.tpe
val (iterableRank, _) = stripIterable(tpe)
diff --git a/src/compiler/scala/reflect/quasiquotes/Reifiers.scala b/src/compiler/scala/reflect/quasiquotes/Reifiers.scala
index cc98717c4e..7c0e7dfbb8 100644
--- a/src/compiler/scala/reflect/quasiquotes/Reifiers.scala
+++ b/src/compiler/scala/reflect/quasiquotes/Reifiers.scala
@@ -247,7 +247,7 @@ trait Reifiers { self: Quasiquotes =>
hole.tree
case Placeholder(hole: UnapplyHole) => hole.treeNoUnlift
case FreshName(prefix) if prefix != nme.QUASIQUOTE_NAME_PREFIX =>
- def fresh() = c.freshName[TermName](nme.QUASIQUOTE_NAME_PREFIX)
+ def fresh() = c.freshName(TermName(nme.QUASIQUOTE_NAME_PREFIX))
def introduceName() = { val n = fresh(); nameMap(name) += n; n}
def result(n: Name) = if (isReifyingExpressions) Ident(n) else Bind(n, Ident(nme.WILDCARD))
if (isReifyingPatterns) result(introduceName())
diff --git a/src/compiler/scala/reflect/reify/codegen/GenSymbols.scala b/src/compiler/scala/reflect/reify/codegen/GenSymbols.scala
index 52ddcb154b..e41fbf042a 100644
--- a/src/compiler/scala/reflect/reify/codegen/GenSymbols.scala
+++ b/src/compiler/scala/reflect/reify/codegen/GenSymbols.scala
@@ -39,7 +39,7 @@ trait GenSymbols {
else if (sym.isModuleClass)
if (sym.sourceModule.isLocatable) Select(Select(reify(sym.sourceModule), nme.asModule), nme.moduleClass)
else reifySymDef(sym)
- else if (sym.isPackage)
+ else if (sym.hasPackageFlag)
mirrorMirrorCall(nme.staticPackage, reify(sym.fullName))
else if (sym.isLocatable) {
/* This is a fancy conundrum that stems from the fact that Scala allows
diff --git a/src/compiler/scala/reflect/reify/codegen/GenTrees.scala b/src/compiler/scala/reflect/reify/codegen/GenTrees.scala
index 743fe131c4..f34d75140b 100644
--- a/src/compiler/scala/reflect/reify/codegen/GenTrees.scala
+++ b/src/compiler/scala/reflect/reify/codegen/GenTrees.scala
@@ -153,7 +153,7 @@ trait GenTrees {
else mirrorCall(nme.Ident, reify(name))
case Select(qual, name) =>
- if (qual.symbol != null && qual.symbol.isPackage) {
+ if (qual.symbol != null && qual.symbol.hasPackageFlag) {
mirrorBuildCall(nme.mkIdent, reify(sym))
} else {
val effectiveName = if (sym != null && sym != NoSymbol) sym.name else name
@@ -199,7 +199,7 @@ trait GenTrees {
}
}
else tree match {
- case Select(qual, name) if !qual.symbol.isPackage =>
+ case Select(qual, name) if !qual.symbol.hasPackageFlag =>
if (reifyDebug) println(s"reifying Select($qual, $name)")
mirrorCall(nme.Select, reify(qual), reify(name))
case SelectFromTypeTree(qual, name) =>
diff --git a/src/compiler/scala/reflect/reify/codegen/GenUtils.scala b/src/compiler/scala/reflect/reify/codegen/GenUtils.scala
index de9fec0df5..b5b0f93750 100644
--- a/src/compiler/scala/reflect/reify/codegen/GenUtils.scala
+++ b/src/compiler/scala/reflect/reify/codegen/GenUtils.scala
@@ -15,7 +15,7 @@ trait GenUtils {
def reifyProduct(prefix: String, elements: List[Any]): Tree = {
// reflection would be more robust, but, hey, this is a hot path
if (prefix.startsWith("Tuple")) scalaFactoryCall(prefix, (elements map reify).toList: _*)
- else mirrorCall(prefix, (elements map reify): _*)
+ else mirrorCall(TermName(prefix), (elements map reify): _*)
}
// helper functions
@@ -49,16 +49,16 @@ trait GenUtils {
call("" + nme.MIRROR_PREFIX + name, args: _*)
def mirrorFactoryCall(value: Product, args: Tree*): Tree =
- mirrorFactoryCall(value.productPrefix, args: _*)
+ mirrorFactoryCall(TermName(value.productPrefix), args: _*)
def mirrorFactoryCall(prefix: TermName, args: Tree*): Tree =
- mirrorCall("" + prefix, args: _*)
+ mirrorCall(TermName("" + prefix), args: _*)
def scalaFactoryCall(name: TermName, args: Tree*): Tree =
call(s"scala.$name.apply", args: _*)
def scalaFactoryCall(name: String, args: Tree*): Tree =
- scalaFactoryCall(name: TermName, args: _*)
+ scalaFactoryCall(TermName(name), args: _*)
def mkList(args: List[Tree]): Tree =
scalaFactoryCall("collection.immutable.List", args: _*)
diff --git a/src/compiler/scala/tools/ant/FastScalac.scala b/src/compiler/scala/tools/ant/FastScalac.scala
index c3eb9eef9c..6f0a30aa9d 100644
--- a/src/compiler/scala/tools/ant/FastScalac.scala
+++ b/src/compiler/scala/tools/ant/FastScalac.scala
@@ -15,7 +15,7 @@ import org.apache.tools.ant.types.Path
import scala.tools.nsc.Settings
import scala.tools.nsc.io.File
import scala.tools.nsc.settings.FscSettings
-import scala.tools.nsc.util.ScalaClassLoader
+import scala.reflect.internal.util.ScalaClassLoader
/** An Ant task to compile with the fast Scala compiler (`fsc`).
*
diff --git a/src/compiler/scala/tools/ant/sabbus/Compiler.scala b/src/compiler/scala/tools/ant/sabbus/Compiler.scala
index 65cd9f41c2..81cd1f3196 100644
--- a/src/compiler/scala/tools/ant/sabbus/Compiler.scala
+++ b/src/compiler/scala/tools/ant/sabbus/Compiler.scala
@@ -12,7 +12,7 @@ package scala.tools.ant.sabbus
import java.io.File
import java.net.URL
import java.lang.reflect.InvocationTargetException
-import scala.tools.nsc.util.ScalaClassLoader
+import scala.reflect.internal.util.ScalaClassLoader
class Compiler(classpath: Array[URL], val settings: Settings)
{
diff --git a/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala b/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala
index 595b45ae51..cde827ba54 100644
--- a/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala
+++ b/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala
@@ -16,7 +16,7 @@ import org.apache.tools.ant.taskdefs.Java
import org.apache.tools.ant.util.{ GlobPatternMapper, SourceFileScanner }
import org.apache.tools.ant.BuildException
import scala.tools.nsc.io
-import scala.tools.nsc.util.ScalaClassLoader
+import scala.reflect.internal.util.ScalaClassLoader
/** An Ant task to compile with the new Scala compiler (NSC).
*
diff --git a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl
index 7acb3632d2..9862ea7987 100755
--- a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl
+++ b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl
@@ -109,9 +109,6 @@ if [[ -n "$cygwin" ]]; then
JAVA_HOME="$(cygpath --$format "$JAVA_HOME")"
fi
TOOL_CLASSPATH="$(cygpath --path --$format "$TOOL_CLASSPATH")"
-elif [[ -n "$mingw" ]]; then
- SCALA_HOME="$(cmd //c echo "$SCALA_HOME")"
- TOOL_CLASSPATH="$(cmd //c echo "$TOOL_CLASSPATH")"
fi
if [[ -n "$cygwin$mingw" ]]; then
@@ -131,23 +128,6 @@ fi
declare -a java_args
declare -a scala_args
-# default to the boot classpath for speed, except on cygwin/mingw because
-# JLine on Windows requires a custom DLL to be loaded.
-unset usebootcp
-if [[ -z "$cygwin$mingw" ]]; then
- usebootcp="true"
-fi
-
-# If using the boot classpath, also pass an empty classpath
-# to java to suppress "." from materializing.
-classpathArgs () {
- if [[ -n $usebootcp ]]; then
- echo "-Xbootclasspath/a:$TOOL_CLASSPATH -classpath \"\""
- else
- echo "-classpath $TOOL_CLASSPATH"
- fi
-}
-
# SI-8358, SI-8368 -- the default should really be false,
# but I don't want to flip the default during 2.11's RC cycle
OVERRIDE_USEJAVACP="-Dscala.usejavacp=true"
@@ -200,6 +180,23 @@ if [[ -z "$JAVACMD" && -n "$JAVA_HOME" && -x "$JAVA_HOME/bin/java" ]]; then
JAVACMD="$JAVA_HOME/bin/java"
fi
+declare -a classpath_args
+
+# default to the boot classpath for speed, except on cygwin/mingw because
+# JLine on Windows requires a custom DLL to be loaded.
+unset usebootcp
+if [[ -z "$cygwin$mingw" ]]; then
+ usebootcp="true"
+fi
+
+# If using the boot classpath, also pass an empty classpath
+# to java to suppress "." from materializing.
+if [[ -n $usebootcp ]]; then
+ classpath_args=("-Xbootclasspath/a:$TOOL_CLASSPATH" -classpath "\"\"")
+else
+ classpath_args=(-classpath "$TOOL_CLASSPATH")
+fi
+
# note that variables which may intentionally be empty must not
# be quoted: otherwise an empty string will appear as a command line
# argument, and java will think that is the program to run.
@@ -207,7 +204,7 @@ execCommand \
"${JAVACMD:=java}" \
$JAVA_OPTS \
"${java_args[@@]}" \
- $(classpathArgs) \
+ "${classpath_args[@@]}" \
-Dscala.home="$SCALA_HOME" \
$OVERRIDE_USEJAVACP \
"$EMACS_OPT" \
diff --git a/src/compiler/scala/tools/nsc/CompilationUnits.scala b/src/compiler/scala/tools/nsc/CompilationUnits.scala
index 1a6843a249..6be1fda1b5 100644
--- a/src/compiler/scala/tools/nsc/CompilationUnits.scala
+++ b/src/compiler/scala/tools/nsc/CompilationUnits.scala
@@ -25,9 +25,9 @@ trait CompilationUnits { global: Global =>
class CompilationUnit(val source: SourceFile) extends CompilationUnitContextApi { self =>
/** the fresh name creator */
- implicit val fresh: FreshNameCreator = new FreshNameCreator
- def freshTermName(prefix: String = "x$") = global.freshTermName(prefix)
- def freshTypeName(prefix: String) = global.freshTypeName(prefix)
+ implicit val fresh: FreshNameCreator = new FreshNameCreator
+ def freshTermName(prefix: String = nme.FRESH_TERM_NAME_PREFIX) = global.freshTermName(prefix)
+ def freshTypeName(prefix: String) = global.freshTypeName(prefix)
/** the content of the compilation unit in tree form */
var body: Tree = EmptyTree
diff --git a/src/compiler/scala/tools/nsc/EvalLoop.scala b/src/compiler/scala/tools/nsc/EvalLoop.scala
index 15a296c836..73f4b9a119 100644
--- a/src/compiler/scala/tools/nsc/EvalLoop.scala
+++ b/src/compiler/scala/tools/nsc/EvalLoop.scala
@@ -6,6 +6,7 @@
package scala.tools.nsc
import scala.annotation.tailrec
+import scala.io.StdIn
import java.io.EOFException
trait EvalLoop {
@@ -14,7 +15,7 @@ trait EvalLoop {
def loop(action: (String) => Unit) {
@tailrec def inner() {
Console.print(prompt)
- val line = try Console.readLine() catch { case _: EOFException => null }
+ val line = try StdIn.readLine() catch { case _: EOFException => null }
if (line != null && line != "") {
action(line)
inner()
diff --git a/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala b/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala
index dbdeec809f..2584054686 100644
--- a/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala
+++ b/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala
@@ -6,6 +6,7 @@
package scala.tools.nsc
import GenericRunnerCommand._
+import scala.reflect.internal.util.ScalaClassLoader
/** A command for ScriptRunner */
class GenericRunnerCommand(
@@ -32,7 +33,7 @@ extends CompilerCommand(args, settings) {
private def guessHowToRun(target: String): GenericRunnerCommand.HowToRun = {
if (!ok) Error
else if (io.Jar.isJarOrZip(target)) AsJar
- else if (util.ScalaClassLoader.classExists(settings.classpathURLs, target)) AsObject
+ else if (ScalaClassLoader.classExists(settings.classpathURLs, target)) AsObject
else {
val f = io.File(target)
if (!f.hasExtension("class", "jar", "zip") && f.canRead) AsScript
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala
index 1c9dbad4dd..b233acf271 100644
--- a/src/compiler/scala/tools/nsc/Global.scala
+++ b/src/compiler/scala/tools/nsc/Global.scala
@@ -1551,7 +1551,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
if (reporter.hasErrors) {
for ((sym, file) <- symSource.iterator) {
- sym.reset(new loaders.SourcefileLoader(file))
+ if (file != null)
+ sym.reset(new loaders.SourcefileLoader(file))
if (sym.isTerm)
sym.moduleClass reset loaders.moduleClassLoader
}
diff --git a/src/compiler/scala/tools/nsc/ObjectRunner.scala b/src/compiler/scala/tools/nsc/ObjectRunner.scala
index 7c14f4943f..8e01418e8b 100644
--- a/src/compiler/scala/tools/nsc/ObjectRunner.scala
+++ b/src/compiler/scala/tools/nsc/ObjectRunner.scala
@@ -7,8 +7,8 @@
package scala.tools.nsc
import java.net.URL
-import util.ScalaClassLoader
import util.Exceptional.unwrap
+import scala.reflect.internal.util.ScalaClassLoader
trait CommonRunner {
/** Run a given object, specified by name, using a
diff --git a/src/compiler/scala/tools/nsc/Reporting.scala b/src/compiler/scala/tools/nsc/Reporting.scala
index 4d7e9e753f..72a4b69536 100644
--- a/src/compiler/scala/tools/nsc/Reporting.scala
+++ b/src/compiler/scala/tools/nsc/Reporting.scala
@@ -26,7 +26,7 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w
protected def PerRunReporting = new PerRunReporting
class PerRunReporting extends PerRunReportingBase {
/** Collects for certain classes of warnings during this run. */
- private class ConditionalWarning(what: String, option: Settings#BooleanSetting) {
+ private class ConditionalWarning(what: String, option: Settings#BooleanSetting)(reRunFlag: String = option.name) {
val warnings = mutable.LinkedHashMap[Position, String]()
def warn(pos: Position, msg: String) =
if (option) reporter.warning(pos, msg)
@@ -37,16 +37,16 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w
val warningVerb = if (numWarnings == 1) "was" else "were"
val warningCount = countElementsAsString(numWarnings, s"$what warning")
- reporter.warning(NoPosition, s"there $warningVerb $warningCount; re-run with ${option.name} for details")
+ reporter.warning(NoPosition, s"there $warningVerb $warningCount; re-run with $reRunFlag for details")
}
}
// This change broke sbt; I gave it the thrilling name of uncheckedWarnings0 so
// as to recover uncheckedWarnings for its ever-fragile compiler interface.
- private val _deprecationWarnings = new ConditionalWarning("deprecation", settings.deprecation)
- private val _uncheckedWarnings = new ConditionalWarning("unchecked", settings.unchecked)
- private val _featureWarnings = new ConditionalWarning("feature", settings.feature)
- private val _inlinerWarnings = new ConditionalWarning("inliner", settings.YinlinerWarnings)
+ private val _deprecationWarnings = new ConditionalWarning("deprecation", settings.deprecation)()
+ private val _uncheckedWarnings = new ConditionalWarning("unchecked", settings.unchecked)()
+ private val _featureWarnings = new ConditionalWarning("feature", settings.feature)()
+ private val _inlinerWarnings = new ConditionalWarning("inliner", settings.YinlinerWarnings)(if (settings.isBCodeAskedFor) settings.YoptWarnings.name else settings.YinlinerWarnings.name)
private val _allConditionalWarnings = List(_deprecationWarnings, _uncheckedWarnings, _featureWarnings, _inlinerWarnings)
// TODO: remove in favor of the overload that takes a Symbol, give that argument a default (NoSymbol)
diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala
index 02a199f7ac..055f658670 100755
--- a/src/compiler/scala/tools/nsc/ast/DocComments.scala
+++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala
@@ -129,25 +129,6 @@ trait DocComments { self: Global =>
getDocComment(sym) map getUseCases getOrElse List()
}
- private val wikiReplacements = List(
- ("""(\n\s*\*?)(\s*\n)""" .r, """$1 <p>$2"""),
- ("""<([^\w/])""" .r, """&lt;$1"""),
- ("""([^\w/])>""" .r, """$1&gt;"""),
- ("""\{\{\{(.*(?:\n.*)*)\}\}\}""".r, """<pre>$1</pre>"""),
- ("""`([^`]*)`""" .r, """<code>$1</code>"""),
- ("""__([^_]*)__""" .r, """<u>$1</u>"""),
- ("""''([^']*)''""" .r, """<i>$1</i>"""),
- ("""'''([^']*)'''""" .r, """<b>$1</b>"""),
- ("""\^([^^]*)\^""" .r, """<sup>$1</sup>"""),
- (""",,([^,]*),,""" .r, """<sub>$1</sub>"""))
-
- /** Returns just the wiki expansion (this would correspond to
- * a comment in the input format of the JavaDoc tool, modulo differences
- * in tags.)
- */
- def expandWiki(str: String): String =
- (str /: wikiReplacements) { (str1, regexRepl) => regexRepl._1 replaceAllIn(str1, regexRepl._2) }
-
private def getDocComment(sym: Symbol): Option[DocComment] =
mapFind(sym :: allInheritedOverriddenSymbols(sym))(docComments get _)
diff --git a/src/compiler/scala/tools/nsc/ast/NodePrinters.scala b/src/compiler/scala/tools/nsc/ast/NodePrinters.scala
index 9c8e13a1a9..6fe85cde7a 100644
--- a/src/compiler/scala/tools/nsc/ast/NodePrinters.scala
+++ b/src/compiler/scala/tools/nsc/ast/NodePrinters.scala
@@ -9,6 +9,7 @@ package ast
import scala.compat.Platform.EOL
import symtab.Flags._
import scala.language.postfixOps
+import scala.reflect.internal.util.ListOfNil
/** The object `nodePrinter` converts the internal tree
* representation to a string.
diff --git a/src/compiler/scala/tools/nsc/ast/Trees.scala b/src/compiler/scala/tools/nsc/ast/Trees.scala
index 3652f51153..934257092f 100644
--- a/src/compiler/scala/tools/nsc/ast/Trees.scala
+++ b/src/compiler/scala/tools/nsc/ast/Trees.scala
@@ -178,7 +178,7 @@ trait Trees extends scala.reflect.internal.Trees { self: Global =>
}
}
- // Finally, noone resetAllAttrs it anymore, so I'm removing it from the compiler.
+ // Finally, no one uses resetAllAttrs anymore, so I'm removing it from the compiler.
// Even though it's with great pleasure I'm doing that, I'll leave its body here to warn future generations about what happened in the past.
//
// So what actually happened in the past is that we used to have two flavors of resetAttrs: resetAllAttrs and resetLocalAttrs.
@@ -308,7 +308,7 @@ trait Trees extends scala.reflect.internal.Trees { self: Global =>
// Erasing locally-defined symbols is useful to prevent tree corruption, but erasing external bindings is not,
// therefore we want to retain those bindings, especially given that restoring them can be impossible
// if we move these trees into lexical contexts different from their original locations.
- if (dupl.hasSymbol) {
+ if (dupl.hasSymbolField) {
val sym = dupl.symbol
val vetoScope = !brutally && !(locals contains sym) && !(locals contains sym.deSkolemize)
val vetoThis = dupl.isInstanceOf[This] && sym.isPackageClass
diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
index 627a181793..1036dc853b 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
@@ -13,7 +13,7 @@ import scala.collection.{ mutable, immutable }
import mutable.{ ListBuffer, StringBuilder }
import scala.reflect.internal.{ Precedence, ModifierFlags => Flags }
import scala.reflect.internal.Chars.{ isScalaLetter }
-import scala.reflect.internal.util.{ SourceFile, Position, FreshNameCreator }
+import scala.reflect.internal.util.{ SourceFile, Position, FreshNameCreator, ListOfNil }
import Tokens._
/** Historical note: JavaParsers started life as a direct copy of Parsers
@@ -2800,7 +2800,7 @@ self =>
*/
def packageObjectDef(start: Offset): PackageDef = {
val defn = objectDef(in.offset, NoMods)
- val pidPos = o2p(defn.pos.startOrPoint)
+ val pidPos = o2p(defn.pos.start)
val pkgPos = r2p(start, pidPos.point)
gen.mkPackageObject(defn, pidPos, pkgPos)
}
diff --git a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala
index 8cd915bf22..d2a999cdec 100755
--- a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala
@@ -8,6 +8,7 @@ package ast.parser
import scala.collection.{ mutable, immutable }
import symtab.Flags.MUTABLE
+import scala.reflect.internal.util.ListOfNil
import scala.reflect.internal.util.StringOps.splitWhere
/** This class builds instance of `Tree` that represent XML.
diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
index 72aa44d8d9..b1b6c73f8d 100644
--- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
+++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
@@ -876,13 +876,15 @@ abstract class GenICode extends SubComponent {
genLoadModule(ctx, tree)
generatedType = toTypeKind(sym.info)
} else {
- try {
- val Some(l) = ctx.method.lookupLocal(sym)
- ctx.bb.emit(LOAD_LOCAL(l), tree.pos)
- generatedType = l.kind
- } catch {
- case ex: MatchError =>
- abort("symbol " + sym + " does not exist in " + ctx.method)
+ ctx.method.lookupLocal(sym) match {
+ case Some(l) =>
+ ctx.bb.emit(LOAD_LOCAL(l), tree.pos)
+ generatedType = l.kind
+ case None =>
+ val saved = settings.uniqid
+ settings.uniqid.value = true
+ try abort(s"symbol $sym does not exist in ${ctx.method}, which contains locals ${ctx.method.locals.mkString(",")}")
+ finally settings.uniqid.value = saved
}
}
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
index 75aa0fc984..0df1b2029d 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
@@ -7,9 +7,10 @@ package scala.tools.nsc.backend.jvm
import scala.tools.asm.tree.{InsnList, AbstractInsnNode, ClassNode, MethodNode}
import java.io.{StringWriter, PrintWriter}
-import scala.tools.asm.util.{TraceClassVisitor, TraceMethodVisitor, Textifier}
-import scala.tools.asm.ClassReader
+import scala.tools.asm.util.{CheckClassAdapter, TraceClassVisitor, TraceMethodVisitor, Textifier}
+import scala.tools.asm.{ClassWriter, Attribute, ClassReader}
import scala.collection.convert.decorateAsScala._
+import scala.tools.nsc.backend.jvm.opt.InlineInfoAttributePrototype
object AsmUtils {
@@ -49,7 +50,7 @@ object AsmUtils {
def readClass(bytes: Array[Byte]): ClassNode = {
val node = new ClassNode()
- new ClassReader(bytes).accept(node, 0)
+ new ClassReader(bytes).accept(node, Array[Attribute](InlineInfoAttributePrototype), 0)
node
}
@@ -105,4 +106,18 @@ object AsmUtils {
* Returns a human-readable representation of the given instruction sequence.
*/
def textify(insns: InsnList): String = textify(insns.iterator().asScala)
+
+ /**
+ * Run ASM's CheckClassAdapter over a class. Returns None if no problem is found, otherwise
+ * Some(msg) with the verifier's error message.
+ */
+ def checkClass(classNode: ClassNode): Option[String] = {
+ val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS)
+ classNode.accept(cw)
+ val sw = new StringWriter()
+ val pw = new PrintWriter(sw)
+ CheckClassAdapter.verify(new ClassReader(cw.toByteArray), false, pw)
+ val res = sw.toString
+ if (res.isEmpty) None else Some(res)
+ }
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
index a5f33aa786..162da4236a 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
@@ -3,9 +3,12 @@
* @author Martin Odersky
*/
-package scala.tools.nsc.backend.jvm
+package scala.tools.nsc
+package backend.jvm
import scala.tools.nsc.Global
+import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo, InlineInfo}
+import BackendReporting.ClassSymbolInfoFailureSI9111
/**
* This trait contains code shared between GenBCode and GenASM that depends on types defined in
@@ -22,6 +25,31 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
}
/**
+ * True for classes generated by the Scala compiler that are considered top-level in terms of
+ * the InnerClass / EnclosingMethod classfile attributes. See comment in BTypes.
+ */
+ def considerAsTopLevelImplementationArtifact(classSym: Symbol) = {
+ classSym.isImplClass || classSym.isSpecialized
+ }
+
+ /**
+ * Cache the value of delambdafy == "inline" for each run. We need to query this value many
+ * times, so caching makes sense.
+ */
+ object delambdafyInline {
+ private var runId = -1
+ private var value = false
+
+ def apply(): Boolean = {
+ if (runId != global.currentRunId) {
+ runId = global.currentRunId
+ value = settings.Ydelambdafy.value == "inline"
+ }
+ value
+ }
+ }
+
+ /**
* True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a
* member class. This method is used to decide if we should emit an EnclosingMethod attribute.
* It is also used to decide whether the "owner" field in the InnerClass attribute should be
@@ -29,10 +57,68 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
*/
def isAnonymousOrLocalClass(classSym: Symbol): Boolean = {
assert(classSym.isClass, s"not a class: $classSym")
- // Here used to be an `assert(!classSym.isDelambdafyFunction)`: delambdafy lambda classes are
- // always top-level. However, SI-8900 shows an example where the weak name-based implementation
- // of isDelambdafyFunction failed (for a function declared in a package named "lambda").
- classSym.isAnonymousClass || !classSym.originalOwner.isClass
+ val r = exitingPickler(classSym.isAnonymousClass) || !classSym.originalOwner.isClass
+ if (r && settings.Ybackend.value == "GenBCode") {
+ // this assertion only holds in GenBCode. lambda lift renames symbols and may accidentally
+ // introduce `$lambda` into a class name, making `isDelambdafyFunction` true. under GenBCode
+ // we prevent this, see `nonAnon` in LambdaLift.
+ // phase travel necessary: after flatten, the name includes the name of outer classes.
+ // if some outer name contains $lambda, a non-lambda class is considered lambda.
+ assert(exitingPickler(!classSym.isDelambdafyFunction), classSym.name)
+ }
+ r
+ }
+
+ /**
+ * The next enclosing definition in the source structure. Includes anonymous function classes
+ * under delambdafy:inline, even though they are only generated during UnCurry.
+ */
+ def nextEnclosing(sym: Symbol): Symbol = {
+ val origOwner = sym.originalOwner
+ // phase travel necessary: after flatten, the name includes the name of outer classes.
+ // if some outer name contains $anon, a non-anon class is considered anon.
+ if (delambdafyInline() && sym.rawowner.isAnonymousFunction) {
+ // SI-9105: special handling for anonymous functions under delambdafy:inline.
+ //
+ // class C { def t = () => { def f { class Z } } }
+ //
+ // class C { def t = byNameMethod { def f { class Z } } }
+ //
+ // In both examples, the method f lambda-lifted into the anonfun class.
+ //
+ // In both examples, the enclosing method of Z is f, the enclosing class is the anonfun.
+ // So nextEnclosing needs to return the following chain: Z - f - anonFunClassSym - ...
+ //
+ // In the first example, the initial owner of f is a TermSymbol named "$anonfun" (note: not the anonFunClassSym!)
+ // In the second, the initial owner of f is t (no anon fun term symbol for by-name args!).
+ //
+ // In both cases, the rawowner of class Z is the anonFunClassSym. So the check in the `if`
+ // above makes sure we don't jump over the anonymous function in the by-name argument case.
+ //
+ // However, we cannot directly return the rawowner: if `sym` is Z, we need to include method f
+ // in the result. This is done by comparing the rawowners (read: lambdalift-targets) of `sym`
+ // and `sym.originalOwner`: if they are the same, then the originalOwner is "in between", and
+ // we need to return it.
+ // If the rawowners are different, the symbol was not in between. In the first example, the
+ // originalOwner of `f` is the anonfun-term-symbol, whose rawowner is C. So the nextEnclosing
+ // of `f` is its rawowner, the anonFunClassSym.
+ //
+ // In delambdafy:method we don't have that problem. The f method is lambda-lifted into C,
+ // not into the anonymous function class. The originalOwner chain is Z - f - C.
+ if (sym.originalOwner.rawowner == sym.rawowner) sym.originalOwner
+ else sym.rawowner
+ } else {
+ origOwner
+ }
+ }
+
+ def nextEnclosingClass(sym: Symbol): Symbol = {
+ if (sym.isClass) sym
+ else nextEnclosingClass(nextEnclosing(sym))
+ }
+
+ def classOriginallyNestedInClass(nestedClass: Symbol, enclosingClass: Symbol) ={
+ nextEnclosingClass(nextEnclosing(nestedClass)) == enclosingClass
}
/**
@@ -60,12 +146,27 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
*/
private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = {
assert(classSym.isClass, classSym)
+
+ def doesNotExist(method: Symbol) = {
+ // (1) SI-9124, some trait methods don't exist in the generated interface. see comment in BTypes.
+ // (2) Value classes. Member methods of value classes exist in the generated box class. However,
+ // nested methods lifted into a value class are moved to the companion object and don't exist
+ // in the value class itself. We can identify such nested methods: the initial enclosing class
+ // is a value class, but the current owner is some other class (the module class).
+ method.owner.isTrait && method.isImplOnly || { // (1)
+ val enclCls = nextEnclosingClass(method)
+ exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls // (2)
+ }
+ }
+
def enclosingMethod(sym: Symbol): Option[Symbol] = {
if (sym.isClass || sym == NoSymbol) None
- else if (sym.isMethod) Some(sym)
- else enclosingMethod(sym.originalOwner)
+ else if (sym.isMethod) {
+ if (doesNotExist(sym)) None else Some(sym)
+ }
+ else enclosingMethod(nextEnclosing(sym))
}
- enclosingMethod(classSym.originalOwner)
+ enclosingMethod(nextEnclosing(classSym))
}
/**
@@ -74,11 +175,10 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
*/
private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = {
assert(classSym.isClass, classSym)
- def enclosingClass(sym: Symbol): Symbol = {
- if (sym.isClass) sym
- else enclosingClass(sym.originalOwner)
- }
- enclosingClass(classSym.originalOwner)
+ val r = nextEnclosingClass(nextEnclosing(classSym))
+ // this should be an assertion, but we are more cautious for now as it was introduced before the 2.11.6 minor release
+ if (considerAsTopLevelImplementationArtifact(r)) devWarning(s"enclosing class of $classSym should not be an implementation artifact class: $r")
+ r
}
final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String)
@@ -92,11 +192,22 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
* on the implementation of GenASM / GenBCode, so they need to be passed in.
*/
def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = {
- if (isAnonymousOrLocalClass(classSym)) {
- val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym)
- debuglog(s"enclosing method for $classSym is $methodOpt (in ${methodOpt.map(_.enclClass)})")
+ // trait impl classes are always top-level, see comment in BTypes
+ if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) {
+ val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym)
+ val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) match {
+ case some @ Some(m) =>
+ if (m.owner != enclosingClass) {
+ // This should never happen. In case it does, it prevents emitting an invalid
+ // EnclosingMethod attribute: if the attribute specifies an enclosing method,
+ // it needs to exist in the specified enclosing class.
+ devWarning(s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass")
+ None
+ } else some
+ case none => none
+ }
Some(EnclosingMethodEntry(
- classDesc(enclosingClassForEnclosingMethodAttribute(classSym)),
+ classDesc(enclosingClass),
methodOpt.map(_.javaSimpleName.toString).orNull,
methodOpt.map(methodDesc).orNull))
} else {
@@ -121,11 +232,13 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
* The member classes of a class symbol. Note that the result of this method depends on the
* current phase, for example, after lambdalift, all local classes become member of the enclosing
* class.
+ *
+ * Impl classes are always considered top-level, see comment in BTypes.
*/
- def memberClassesOf(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({
- case sym if sym.isClass =>
+ def memberClassesForInnerClassTable(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({
+ case sym if sym.isClass && !considerAsTopLevelImplementationArtifact(sym) =>
sym
- case sym if sym.isModule =>
+ case sym if sym.isModule && !considerAsTopLevelImplementationArtifact(sym) => // impl classes get the lateMODULE flag in mixin
val r = exitingPickler(sym.moduleClass)
assert(r != NoSymbol, sym.fullLocationString)
r
@@ -190,4 +303,97 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
}
interfaces.map(_.typeSymbol)
}
+
+ /**
+ * This is a hack to work around SI-9111. The completer of `methodSym` may report type errors. We
+ * cannot change the typer context of the completer at this point and make it silent: the context
+ * captured when creating the completer in the namer. However, we can temporarily replace
+ * global.reporter (it's a var) to store errors.
+ */
+ def completeSilentlyAndCheckErroneous(sym: Symbol): Boolean = {
+ if (sym.hasCompleteInfo) false
+ else {
+ val originalReporter = global.reporter
+ val storeReporter = new reporters.StoreReporter()
+ global.reporter = storeReporter
+ try {
+ sym.info
+ } finally {
+ global.reporter = originalReporter
+ }
+ sym.isErroneous
+ }
+ }
+
+ /**
+ * Build the [[InlineInfo]] for a class symbol.
+ */
+ def buildInlineInfoFromClassSymbol(classSym: Symbol, classSymToInternalName: Symbol => InternalName, methodSymToDescriptor: Symbol => String): InlineInfo = {
+ val selfType = {
+ // The mixin phase uses typeOfThis for the self parameter in implementation class methods.
+ val selfSym = classSym.typeOfThis.typeSymbol
+ if (selfSym != classSym) Some(classSymToInternalName(selfSym)) else None
+ }
+
+ val isEffectivelyFinal = classSym.isEffectivelyFinal
+
+ var warning = Option.empty[ClassSymbolInfoFailureSI9111]
+
+ // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some
+ // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]].
+ val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({
+ case methodSym =>
+ if (completeSilentlyAndCheckErroneous(methodSym)) {
+ // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler.
+ if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes")
+ warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName))
+ None
+ } else {
+ val name = methodSym.javaSimpleName.toString // same as in genDefDef
+ val signature = name + methodSymToDescriptor(methodSym)
+
+ // Some detours are required here because of changing flags (lateDEFERRED, lateMODULE):
+ // 1. Why the phase travel? Concrete trait methods obtain the lateDEFERRED flag in Mixin.
+ // This makes isEffectivelyFinalOrNotOverridden false, which would prevent non-final
+ // but non-overridden methods of sealed traits from being inlined.
+ // 2. Why the special case for `classSym.isImplClass`? Impl class symbols obtain the
+ // lateMODULE flag during Mixin. During the phase travel to exitingPickler, the late
+ // flag is ignored. The members are therefore not isEffectivelyFinal (their owner
+ // is not a module). Since we know that all impl class members are static, we can
+ // just take the shortcut.
+ val effectivelyFinal = classSym.isImplClass || exitingPickler(methodSym.isEffectivelyFinalOrNotOverridden)
+
+ // Identify trait interface methods that have a static implementation in the implementation
+ // class. Invocations of these methods can be re-wrired directly to the static implementation
+ // if they are final or the receiver is known.
+ //
+ // Using `erasure.needsImplMethod` is not enough: it keeps field accessors, module getters
+ // and super accessors. When AddInterfaces creates the impl class, these methods are
+ // initially added to it.
+ //
+ // The mixin phase later on filters out most of these members from the impl class (see
+ // Mixin.isImplementedStatically). However, accessors for concrete lazy vals remain in the
+ // impl class after mixin. So the filter in mixin is not exactly what we need here (we
+ // want to identify concrete trait methods, not any accessors). So we check some symbol
+ // properties manually.
+ val traitMethodWithStaticImplementation = {
+ import symtab.Flags._
+ classSym.isTrait && !classSym.isImplClass &&
+ erasure.needsImplMethod(methodSym) &&
+ !methodSym.isModule &&
+ !(methodSym hasFlag (ACCESSOR | SUPERACCESSOR))
+ }
+
+ val info = MethodInlineInfo(
+ effectivelyFinal = effectivelyFinal,
+ traitMethodWithStaticImplementation = traitMethodWithStaticImplementation,
+ annotatedInline = methodSym.hasAnnotation(ScalaInlineClass),
+ annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass)
+ )
+ Some((signature, info))
+ }
+ }).toMap
+
+ InlineInfo(selfType, isEffectivelyFinal, methodInlineInfos, warning)
+ }
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
index 89d7acaa11..5b53ac7bc6 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
@@ -12,6 +12,8 @@ package jvm
import scala.annotation.switch
import scala.tools.asm
+import GenBCode._
+import BackendReporting._
/*
*
@@ -92,7 +94,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val thrownKind = tpeTK(expr)
// `throw null` is valid although scala.Null (as defined in src/libray-aux) isn't a subtype of Throwable.
// Similarly for scala.Nothing (again, as defined in src/libray-aux).
- assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference))
+ assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference).get)
genLoad(expr, thrownKind)
lineNumber(expr)
emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level.
@@ -229,7 +231,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
if (isArithmeticOp(code)) genArithmeticOp(tree, code)
else if (code == scalaPrimitives.CONCAT) genStringConcat(tree)
- else if (code == scalaPrimitives.HASH) genScalaHash(receiver)
+ else if (code == scalaPrimitives.HASH) genScalaHash(receiver, tree.pos)
else if (isArrayOp(code)) genArrayOp(tree, code, expectedType)
else if (isLogicalOp(code) || isComparisonOp(code)) {
val success, failure, after = new asm.Label
@@ -583,7 +585,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
// if (fun.symbol.isConstructor) Static(true) else SuperCall(mix);
mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)
genLoadArguments(args, paramTKs(app))
- genCallMethod(fun.symbol, invokeStyle, pos = app.pos)
+ genCallMethod(fun.symbol, invokeStyle, app.pos)
generatedType = asmMethodType(fun.symbol).returnType
// 'new' constructor call: Note: since constructors are
@@ -613,7 +615,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
*/
for (i <- args.length until dims) elemKind = ArrayBType(elemKind)
}
- (argsSize : @switch) match {
+ argsSize match {
case 1 => bc newarray elemKind
case _ =>
val descr = ('[' * argsSize) + elemKind.descriptor // denotes the same as: arrayN(elemKind, argsSize).descriptor
@@ -625,7 +627,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
mnode.visitTypeInsn(asm.Opcodes.NEW, rt.internalName)
bc dup generatedType
genLoadArguments(args, paramTKs(app))
- genCallMethod(ctor, icodes.opcodes.Static(onInstance = true))
+ genCallMethod(ctor, icodes.opcodes.Static(onInstance = true), app.pos)
case _ =>
abort(s"Cannot instantiate $tpt of kind: $generatedType")
@@ -635,7 +637,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val nativeKind = tpeTK(expr)
genLoad(expr, nativeKind)
val MethodNameAndType(mname, methodType) = asmBoxTo(nativeKind)
- bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor)
+ bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos)
generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType)
case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) =>
@@ -643,7 +645,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe)
generatedType = boxType
val MethodNameAndType(mname, methodType) = asmUnboxTo(boxType)
- bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor)
+ bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos)
case app @ Apply(fun, args) =>
val sym = fun.symbol
@@ -694,10 +696,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
// descriptor (instead of a class internal name):
// invokevirtual #2; //Method "[I".clone:()Ljava/lang/Object
val target: String = targetTypeKind.asRefBType.classOrArrayType
- bc.invokevirtual(target, "clone", "()Ljava/lang/Object;")
+ bc.invokevirtual(target, "clone", "()Ljava/lang/Object;", app.pos)
}
else {
- genCallMethod(sym, invokeStyle, hostClass, app.pos)
+ genCallMethod(sym, invokeStyle, app.pos, hostClass)
}
} // end of genNormalMethodCall()
@@ -809,7 +811,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
}
def adapt(from: BType, to: BType) {
- if (!from.conformsTo(to)) {
+ if (!from.conformsTo(to).get) {
to match {
case UNIT => bc drop from
case _ => bc.emitT2T(from, to)
@@ -975,23 +977,23 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
// Optimization for expressions of the form "" + x. We can avoid the StringBuilder.
case List(Literal(Constant("")), arg) =>
genLoad(arg, ObjectReference)
- genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false))
+ genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false), arg.pos)
case concatenations =>
- bc.genStartConcat
+ bc.genStartConcat(tree.pos)
for (elem <- concatenations) {
val kind = tpeTK(elem)
genLoad(elem, kind)
- bc.genStringConcat(kind)
+ bc.genStringConcat(kind, elem.pos)
}
- bc.genEndConcat
+ bc.genEndConcat(tree.pos)
}
StringReference
}
- def genCallMethod(method: Symbol, style: InvokeStyle, hostClass0: Symbol = null, pos: Position = NoPosition) {
+ def genCallMethod(method: Symbol, style: InvokeStyle, pos: Position, hostClass0: Symbol = null) {
val siteSymbol = claszSymbol
val hostSymbol = if (hostClass0 == null) method.owner else hostClass0
@@ -1035,26 +1037,26 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
}
if (style.isStatic) {
- if (style.hasInstance) { bc.invokespecial (jowner, jname, mdescr) }
- else { bc.invokestatic (jowner, jname, mdescr) }
+ if (style.hasInstance) { bc.invokespecial (jowner, jname, mdescr, pos) }
+ else { bc.invokestatic (jowner, jname, mdescr, pos) }
}
else if (style.isDynamic) {
- if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr) }
- else { bc.invokevirtual (jowner, jname, mdescr) }
+ if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr, pos) }
+ else { bc.invokevirtual (jowner, jname, mdescr, pos) }
}
else {
assert(style.isSuper, s"An unknown InvokeStyle: $style")
- bc.invokespecial(jowner, jname, mdescr)
+ bc.invokespecial(jowner, jname, mdescr, pos)
initModule()
}
} // end of genCallMethod()
/* Generate the scala ## method. */
- def genScalaHash(tree: Tree): BType = {
+ def genScalaHash(tree: Tree, applyPos: Position): BType = {
genLoadModule(ScalaRunTimeModule) // TODO why load ScalaRunTimeModule if ## has InvokeStyle of Static(false) ?
genLoad(tree, ObjectReference)
- genCallMethod(hashMethodSym, icodes.opcodes.Static(onInstance = false))
+ genCallMethod(hashMethodSym, icodes.opcodes.Static(onInstance = false), applyPos)
INT
}
@@ -1186,8 +1188,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
// TODO !!!!!!!!!! isReferenceType, in the sense of TypeKind? (ie non-array, non-boxed, non-nothing, may be null)
if (scalaPrimitives.isUniversalEqualityOp(code) && tpeTK(lhs).isClass) {
// `lhs` has reference type
- if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure)
- else genEqEqPrimitive(lhs, rhs, failure, success)
+ if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure, tree.pos)
+ else genEqEqPrimitive(lhs, rhs, failure, success, tree.pos)
}
else if (scalaPrimitives.isComparisonOp(code))
genComparisonOp(lhs, rhs, code)
@@ -1207,7 +1209,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
* @param l left-hand-side of the '=='
* @param r right-hand-side of the '=='
*/
- def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label) {
+ def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label, pos: Position) {
/* True if the equality comparison is between values that require the use of the rich equality
* comparator (scala.runtime.Comparator.equals). This is the case when either side of the
@@ -1231,7 +1233,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
}
genLoad(l, ObjectReference)
genLoad(r, ObjectReference)
- genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false))
+ genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false), pos)
genCZJUMP(success, failure, icodes.NE, BOOL)
}
else {
@@ -1247,7 +1249,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
// SI-7852 Avoid null check if L is statically non-null.
genLoad(l, ObjectReference)
genLoad(r, ObjectReference)
- genCallMethod(Object_equals, icodes.opcodes.Dynamic)
+ genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos)
genCZJUMP(success, failure, icodes.NE, BOOL)
} else {
// l == r -> if (l eq null) r eq null else l.equals(r)
@@ -1268,7 +1270,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
markProgramPoint(lNonNull)
locals.load(eqEqTempLocal)
- genCallMethod(Object_equals, icodes.opcodes.Dynamic)
+ genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos)
genCZJUMP(success, failure, icodes.NE, BOOL)
}
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
index 8d1c37532e..18468f5ae3 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
@@ -10,6 +10,8 @@ package backend.jvm
import scala.tools.asm
import scala.collection.mutable
import scala.tools.nsc.io.AbstractFile
+import GenBCode._
+import BackendReporting._
/*
* Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes.
@@ -66,7 +68,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
override def getCommonSuperClass(inameA: String, inameB: String): String = {
val a = classBTypeFromInternalName(inameA)
val b = classBTypeFromInternalName(inameB)
- val lub = a.jvmWiseLUB(b)
+ val lub = a.jvmWiseLUB(b).get
val lubName = lub.internalName
assert(lubName != "scala/Any")
lubName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things.
@@ -204,12 +206,12 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
* can-multi-thread
*/
final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) {
- val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain).distinct
+ val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain.get).distinct
// sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler
for (nestedClass <- allNestedClasses.sortBy(_.internalName.toString)) {
// Extract the innerClassEntry - we know it exists, enclosingNestedClassesChain only returns nested classes.
- val Some(e) = nestedClass.innerClassAttributeEntry
+ val Some(e) = nestedClass.innerClassAttributeEntry.get
jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.flags)
}
}
@@ -331,125 +333,42 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
getClassBTypeAndRegisterInnerClass(classSym).internalName
}
- private def assertClassNotArray(sym: Symbol): Unit = {
- assert(sym.isClass, sym)
- assert(sym != definitions.ArrayClass || isCompilingArray, sym)
- }
-
- private def assertClassNotArrayNotPrimitive(sym: Symbol): Unit = {
- assertClassNotArray(sym)
- assert(!primitiveTypeMap.contains(sym) || isCompilingPrimitive, sym)
- }
-
/**
* The ClassBType for a class symbol. If the class is nested, the ClassBType is added to the
* innerClassBufferASM.
*
- * The class symbol scala.Nothing is mapped to the class scala.runtime.Nothing$. Similarly,
- * scala.Null is mapped to scala.runtime.Null$. This is because there exist no class files
- * for the Nothing / Null. If used for example as a parameter type, we use the runtime classes
- * in the classfile method signature.
- *
- * Note that the referenced class symbol may be an implementation class. For example when
- * compiling a mixed-in method that forwards to the static method in the implementation class,
- * the class descriptor of the receiver (the implementation class) is obtained by creating the
- * ClassBType.
+ * TODO: clean up the way we track referenced inner classes.
+ * doing it during code generation is not correct when the optimizer changes the code.
*/
final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = {
- assertClassNotArrayNotPrimitive(sym)
-
- if (sym == definitions.NothingClass) RT_NOTHING
- else if (sym == definitions.NullClass) RT_NULL
- else {
- val r = classBTypeFromSymbol(sym)
- if (r.isNestedClass) innerClassBufferASM += r
- r
- }
+ val r = classBTypeFromSymbol(sym)
+ if (r.isNestedClass.get) innerClassBufferASM += r
+ r
}
/**
- * This method returns the BType for a type reference, for example a parameter type.
- *
- * If the result is a ClassBType for a nested class, it is added to the innerClassBufferASM.
- *
- * If `t` references a class, toTypeKind ensures that the class is not an implementation class.
- * See also comment on getClassBTypeAndRegisterInnerClass, which is invoked for implementation
- * classes.
+ * The BType for a type reference. If the result is a ClassBType for a nested class, it is added
+ * to the innerClassBufferASM.
+ * TODO: clean up the way we track referenced inner classes.
*/
- final def toTypeKind(t: Type): BType = {
- import definitions.ArrayClass
-
- /**
- * Primitive types are represented as TypeRefs to the class symbol of, for example, scala.Int.
- * The `primitiveTypeMap` maps those class symbols to the corresponding PrimitiveBType.
- */
- def primitiveOrClassToBType(sym: Symbol): BType = {
- assertClassNotArray(sym)
- assert(!sym.isImplClass, sym)
- primitiveTypeMap.getOrElse(sym, getClassBTypeAndRegisterInnerClass(sym))
- }
-
- /**
- * When compiling Array.scala, the type parameter T is not erased and shows up in method
- * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference.
- */
- def nonClassTypeRefToBType(sym: Symbol): ClassBType = {
- assert(sym.isType && isCompilingArray, sym)
- ObjectReference
- }
-
- t.dealiasWiden match {
- case TypeRef(_, ArrayClass, List(arg)) => ArrayBType(toTypeKind(arg)) // Array type such as Array[Int] (kept by erasure)
- case TypeRef(_, sym, _) if !sym.isClass => nonClassTypeRefToBType(sym) // See comment on nonClassTypeRefToBType
- case TypeRef(_, sym, _) => primitiveOrClassToBType(sym) // Common reference to a type such as scala.Int or java.lang.String
- case ClassInfoType(_, _, sym) => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes toTypeKind(moduleClassSymbol.info)
-
- /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for
- * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning.
- * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala.
- */
- case a @ AnnotatedType(_, t) =>
- debuglog(s"typeKind of annotated type $a")
- toTypeKind(t)
-
- /* ExistentialType should (probably) be eliminated by erasure. We know they get here for
- * classOf constants:
- * class C[T]
- * class T { final val k = classOf[C[_]] }
- */
- case e @ ExistentialType(_, t) =>
- debuglog(s"typeKind of existential type $e")
- toTypeKind(t)
-
- /* The cases below should probably never occur. They are kept for now to avoid introducing
- * new compiler crashes, but we added a warning. The compiler / library bootstrap and the
- * test suite don't produce any warning.
- */
-
- case tp =>
- currentUnit.warning(tp.typeSymbol.pos,
- s"an unexpected type representation reached the compiler backend while compiling $currentUnit: $tp. " +
- "If possible, please file a bug on issues.scala-lang.org.")
-
- tp match {
- case ThisType(ArrayClass) => ObjectReference // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test
- case ThisType(sym) => getClassBTypeAndRegisterInnerClass(sym)
- case SingleType(_, sym) => primitiveOrClassToBType(sym)
- case ConstantType(_) => toTypeKind(t.underlying)
- case RefinedType(parents, _) => parents.map(toTypeKind(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b))
- }
- }
+ final def toTypeKind(t: Type): BType = typeToBType(t) match {
+ case c: ClassBType if c.isNestedClass.get =>
+ innerClassBufferASM += c
+ c
+ case r => r
}
- /*
- * must-single-thread
+ /**
+ * Class components that are nested classes are added to the innerClassBufferASM.
+ * TODO: clean up the way we track referenced inner classes.
*/
final def asmMethodType(msym: Symbol): MethodBType = {
- assert(msym.isMethod, s"not a method-symbol: $msym")
- val resT: BType =
- if (msym.isClassConstructor || msym.isConstructor) UNIT
- else toTypeKind(msym.tpe.resultType)
- MethodBType(msym.tpe.paramTypes map toTypeKind, resT)
+ val r = methodBTypeFromSymbol(msym)
+ (r.returnType :: r.argumentTypes) foreach {
+ case c: ClassBType if c.isNestedClass.get => innerClassBufferASM += c
+ case _ =>
+ }
+ r
}
/**
@@ -796,7 +715,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
val mirrorClass = new asm.tree.ClassNode
mirrorClass.visit(
classfileVersion,
- bType.info.flags,
+ bType.info.get.flags,
bType.internalName,
null /* no java-generic-signature */,
ObjectReference.internalName,
@@ -812,7 +731,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass)
- innerClassBufferASM ++= bType.info.nestedClasses
+ innerClassBufferASM ++= bType.info.get.nestedClasses
addInnerClassesASM(mirrorClass, innerClassBufferASM.toList)
mirrorClass.visitEnd()
@@ -861,8 +780,8 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
var fieldList = List[String]()
for (f <- fieldSymbols if f.hasGetter;
- g = f.getter(cls);
- s = f.setter(cls);
+ g = f.getterIn(cls);
+ s = f.setterIn(cls);
if g.isPublic && !(f.name startsWith "$")
) {
// inserting $outer breaks the bean
@@ -928,7 +847,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments
constructor.visitEnd()
- innerClassBufferASM ++= classBTypeFromSymbol(cls).info.nestedClasses
+ innerClassBufferASM ++= classBTypeFromSymbol(cls).info.get.nestedClasses
addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList)
beanInfoClass.visitEnd()
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
index c3db28151b..9993357eee 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
@@ -10,6 +10,8 @@ package backend.jvm
import scala.tools.asm
import scala.annotation.switch
import scala.collection.mutable
+import GenBCode._
+import scala.tools.asm.tree.MethodInsnNode
/*
* A high-level facade to the ASM API for bytecode generation.
@@ -42,9 +44,6 @@ abstract class BCodeIdiomatic extends SubComponent {
val StringBuilderClassName = "scala/collection/mutable/StringBuilder"
- val CLASS_CONSTRUCTOR_NAME = "<clinit>"
- val INSTANCE_CONSTRUCTOR_NAME = "<init>"
-
val EMPTY_STRING_ARRAY = Array.empty[String]
val EMPTY_INT_ARRAY = Array.empty[Int]
val EMPTY_LABEL_ARRAY = Array.empty[asm.Label]
@@ -107,7 +106,7 @@ abstract class BCodeIdiomatic extends SubComponent {
*/
abstract class JCodeMethodN {
- def jmethod: asm.MethodVisitor
+ def jmethod: asm.tree.MethodNode
import asm.Opcodes;
import icodes.opcodes.{ Static, Dynamic, SuperCall }
@@ -207,20 +206,21 @@ abstract class BCodeIdiomatic extends SubComponent {
/*
* can-multi-thread
*/
- final def genStartConcat {
+ final def genStartConcat(pos: Position): Unit = {
jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName)
jmethod.visitInsn(Opcodes.DUP)
invokespecial(
StringBuilderClassName,
INSTANCE_CONSTRUCTOR_NAME,
- "()V"
+ "()V",
+ pos
)
}
/*
* can-multi-thread
*/
- final def genStringConcat(el: BType) {
+ final def genStringConcat(el: BType, pos: Position): Unit = {
val jtype =
if (el.isArray || el.isClass) ObjectReference
@@ -228,14 +228,14 @@ abstract class BCodeIdiomatic extends SubComponent {
val bt = MethodBType(List(jtype), StringBuilderReference)
- invokevirtual(StringBuilderClassName, "append", bt.descriptor)
+ invokevirtual(StringBuilderClassName, "append", bt.descriptor, pos)
}
/*
* can-multi-thread
*/
- final def genEndConcat {
- invokevirtual(StringBuilderClassName, "toString", "()Ljava/lang/String;")
+ final def genEndConcat(pos: Position): Unit = {
+ invokevirtual(StringBuilderClassName, "toString", "()Ljava/lang/String;", pos)
}
/*
@@ -391,20 +391,26 @@ abstract class BCodeIdiomatic extends SubComponent {
final def rem(tk: BType) { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread
// can-multi-thread
- final def invokespecial(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, false)
+ final def invokespecial(owner: String, name: String, desc: String, pos: Position) {
+ addInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, false, pos)
}
// can-multi-thread
- final def invokestatic(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, false)
+ final def invokestatic(owner: String, name: String, desc: String, pos: Position) {
+ addInvoke(Opcodes.INVOKESTATIC, owner, name, desc, false, pos)
}
// can-multi-thread
- final def invokeinterface(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc, true)
+ final def invokeinterface(owner: String, name: String, desc: String, pos: Position) {
+ addInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, true, pos)
}
// can-multi-thread
- final def invokevirtual(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false)
+ final def invokevirtual(owner: String, name: String, desc: String, pos: Position) {
+ addInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, false, pos)
+ }
+
+ private def addInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position) = {
+ val node = new MethodInsnNode(opcode, owner, name, desc, itf)
+ jmethod.instructions.add(node)
+ if (settings.YoptInlinerEnabled) callsitePositions(node) = pos
}
// can-multi-thread
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
index 142c901c21..2a06c62e37 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
@@ -4,18 +4,17 @@
*/
-package scala
-package tools.nsc
+package scala.tools.nsc
package backend
package jvm
import scala.collection.{ mutable, immutable }
+import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository
import scala.tools.nsc.symtab._
-import scala.annotation.switch
import scala.tools.asm
-import scala.tools.asm.util.{TraceMethodVisitor, ASMifier}
-import java.io.PrintWriter
+import GenBCode._
+import BackendReporting._
/*
*
@@ -101,6 +100,8 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
isCZRemote = isRemote(claszSymbol)
thisName = internalName(claszSymbol)
+ val classBType = classBTypeFromSymbol(claszSymbol)
+
cnode = new asm.tree.ClassNode()
initJClass(cnode)
@@ -118,16 +119,21 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
addClassFields()
- innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.nestedClasses
+ innerClassBufferASM ++= classBType.info.get.nestedClasses
gen(cd.impl)
addInnerClassesASM(cnode, innerClassBufferASM.toList)
+ cnode.visitAttribute(classBType.inlineInfoAttribute.get)
+
if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern))
AsmUtils.traceClass(cnode)
- cnode.innerClasses
- assert(cd.symbol == claszSymbol, "Someone messed up BCodePhase.claszSymbol during genPlainClass().")
+ if (settings.YoptInlinerEnabled) {
+ // The inliner needs to find all classes in the code repo, also those being compiled
+ byteCodeRepository.add(cnode, ByteCodeRepository.CompilationUnit)
+ }
+ assert(cd.symbol == claszSymbol, "Someone messed up BCodePhase.claszSymbol during genPlainClass().")
} // end of method genPlainClass()
/*
@@ -137,9 +143,9 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
val ps = claszSymbol.info.parents
val superClass: String = if (ps.isEmpty) ObjectReference.internalName else internalName(ps.head.typeSymbol)
- val interfaceNames = classBTypeFromSymbol(claszSymbol).info.interfaces map {
+ val interfaceNames = classBTypeFromSymbol(claszSymbol).info.get.interfaces map {
case classBType =>
- if (classBType.isNestedClass) { innerClassBufferASM += classBType }
+ if (classBType.isNestedClass.get) { innerClassBufferASM += classBType }
classBType.internalName
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
index a9bce82acd..d8a17e975e 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
@@ -6,18 +6,23 @@
package scala.tools.nsc
package backend.jvm
+import scala.annotation.switch
+import scala.collection.concurrent.TrieMap
+import scala.reflect.internal.util.Position
import scala.tools.asm
import asm.Opcodes
-import scala.tools.asm.tree.{InnerClassNode, ClassNode}
-import opt.ByteCodeRepository
+import scala.tools.asm.tree.{MethodInsnNode, InnerClassNode, ClassNode}
+import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo}
+import scala.tools.nsc.backend.jvm.BackendReporting._
+import scala.tools.nsc.backend.jvm.opt._
import scala.collection.convert.decorateAsScala._
/**
- * The BTypes component defines The BType class hierarchy. BTypes encapsulate all type information
+ * The BTypes component defines The BType class hierarchy. A BType stores all type information
* that is required after building the ASM nodes. This includes optimizations, generation of
* InnerClass attributes and generation of stack map frames.
*
- * This representation is immutable and independent of the compiler data structures, hence it can
+ * The representation is immutable and independent of the compiler data structures, hence it can
* be queried by concurrent threads.
*/
abstract class BTypes {
@@ -34,9 +39,24 @@ abstract class BTypes {
*/
val byteCodeRepository: ByteCodeRepository
+ val inliner: Inliner[this.type]
+
+ val callGraph: CallGraph[this.type]
+
+ val backendReporting: BackendReporting
+
// Allows to define per-run caches here and in the CallGraph component, which don't have a global
def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T
+ // When building the call graph, we need to know if global inlining is allowed (the component doesn't have a global)
+ def inlineGlobalEnabled: Boolean
+
+ // When the inliner is not enabled, there's no point in adding InlineInfos to all ClassBTypes
+ def inlinerEnabled: Boolean
+
+ // Settings that define what kind of optimizer warnings are emitted.
+ def warnSettings: WarnSettings
+
/**
* A map from internal names to ClassBTypes. Every ClassBType is added to this map on its
* construction.
@@ -48,13 +68,57 @@ abstract class BTypes {
* Concurrent because stack map frames are computed when in the class writer, which might run
* on multiple classes concurrently.
*/
- val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType])
+ val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty)
/**
- * Parse the classfile for `internalName` and construct the [[ClassBType]].
+ * Store the position of every MethodInsnNode during code generation. This allows each callsite
+ * in the call graph to remember its source position, which is required for inliner warnings.
+ */
+ val callsitePositions: collection.concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty)
+
+ /**
+ * Contains the internal names of all classes that are defined in Java source files of the current
+ * compilation run (mixed compilation). Used for more detailed error reporting.
+ */
+ val javaDefinedClasses: collection.mutable.Set[InternalName] = recordPerRunCache(collection.mutable.Set.empty)
+
+ /**
+ * Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType
+ * is constructed by parsing the corresponding classfile.
+ *
+ * Some JVM operations use either a full descriptor or only an internal name. Example:
+ * ANEWARRAY java/lang/String // a new array of strings (internal name for the String class)
+ * ANEWARRAY [Ljava/lang/String; // a new array of array of string (full descriptor for the String class)
+ *
+ * This method supports both descriptors and internal names.
+ */
+ def bTypeForDescriptorOrInternalNameFromClassfile(desc: String): BType = (desc(0): @switch) match {
+ case 'V' => UNIT
+ case 'Z' => BOOL
+ case 'C' => CHAR
+ case 'B' => BYTE
+ case 'S' => SHORT
+ case 'I' => INT
+ case 'F' => FLOAT
+ case 'J' => LONG
+ case 'D' => DOUBLE
+ case '[' => ArrayBType(bTypeForDescriptorOrInternalNameFromClassfile(desc.substring(1)))
+ case 'L' if desc.last == ';' => classBTypeFromParsedClassfile(desc.substring(1, desc.length - 1))
+ case _ => classBTypeFromParsedClassfile(desc)
+ }
+
+ /**
+ * Parse the classfile for `internalName` and construct the [[ClassBType]]. If the classfile cannot
+ * be found in the `byteCodeRepository`, the `info` of the resulting ClassBType is undefined.
*/
def classBTypeFromParsedClassfile(internalName: InternalName): ClassBType = {
- classBTypeFromClassNode(byteCodeRepository.classNode(internalName))
+ classBTypeFromInternalName.getOrElse(internalName, {
+ val res = ClassBType(internalName)
+ byteCodeRepository.classNode(internalName) match {
+ case Left(msg) => res.info = Left(NoClassBTypeInfoMissingBytecode(msg)); res
+ case Right(c) => setClassInfoFromParsedClassfile(c, res)
+ }
+ })
}
/**
@@ -62,11 +126,11 @@ abstract class BTypes {
*/
def classBTypeFromClassNode(classNode: ClassNode): ClassBType = {
classBTypeFromInternalName.getOrElse(classNode.name, {
- setClassInfo(classNode, ClassBType(classNode.name))
+ setClassInfoFromParsedClassfile(classNode, ClassBType(classNode.name))
})
}
- private def setClassInfo(classNode: ClassNode, classBType: ClassBType): ClassBType = {
+ private def setClassInfoFromParsedClassfile(classNode: ClassNode, classBType: ClassBType): ClassBType = {
val superClass = classNode.superName match {
case null =>
assert(classNode.name == ObjectReference.internalName, s"class with missing super type: ${classNode.name}")
@@ -89,11 +153,13 @@ abstract class BTypes {
* For local and anonymous classes, innerClassNode.outerName is null. Such classes are required
* to have an EnclosingMethod attribute declaring the outer class. So we keep those local and
* anonymous classes whose outerClass is classNode.name.
- *
*/
def nestedInCurrentClass(innerClassNode: InnerClassNode): Boolean = {
(innerClassNode.outerName != null && innerClassNode.outerName == classNode.name) ||
- (innerClassNode.outerName == null && byteCodeRepository.classNode(innerClassNode.name).outerClass == classNode.name)
+ (innerClassNode.outerName == null && {
+ val classNodeForInnerClass = byteCodeRepository.classNode(innerClassNode.name).get // TODO: don't get here, but set the info to Left at the end
+ classNodeForInnerClass.outerClass == classNode.name
+ })
}
val nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({
@@ -116,11 +182,58 @@ abstract class BTypes {
val staticFlag = (innerEntry.access & Opcodes.ACC_STATIC) != 0
NestedInfo(enclosingClass, Option(innerEntry.outerName), Option(innerEntry.innerName), staticFlag)
}
- classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo)
+
+ val inlineInfo = inlineInfoFromClassfile(classNode)
+
+ classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo))
classBType
}
/**
+ * Build the InlineInfo for a class. For Scala classes, the information is stored in the
+ * ScalaInlineInfo attribute. If the attribute is missing, the InlineInfo is built using the
+ * metadata available in the classfile (ACC_FINAL flags, etc).
+ */
+ def inlineInfoFromClassfile(classNode: ClassNode): InlineInfo = {
+ def fromClassfileAttribute: Option[InlineInfo] = {
+ if (classNode.attrs == null) None
+ else classNode.attrs.asScala.collect({ case a: InlineInfoAttribute => a}).headOption.map(_.inlineInfo)
+ }
+
+ def fromClassfileWithoutAttribute = {
+ val warning = {
+ val isScala = classNode.attrs != null && classNode.attrs.asScala.exists(a => a.`type` == BTypes.ScalaAttributeName || a.`type` == BTypes.ScalaSigAttributeName)
+ if (isScala) Some(NoInlineInfoAttribute(classNode.name))
+ else None
+ }
+ // when building MethodInlineInfos for the members of a ClassSymbol, we exclude those methods
+ // in scalaPrimitives. This is necessary because some of them have non-erased types, which would
+ // require special handling. Excluding is OK because they are never inlined.
+ // Here we are parsing from a classfile and we don't need to do anything special. Many of these
+ // primitives don't even exist, for example Any.isInstanceOf.
+ val methodInfos = classNode.methods.asScala.map(methodNode => {
+ val info = MethodInlineInfo(
+ effectivelyFinal = BytecodeUtils.isFinalMethod(methodNode),
+ traitMethodWithStaticImplementation = false,
+ annotatedInline = false,
+ annotatedNoInline = false)
+ (methodNode.name + methodNode.desc, info)
+ }).toMap
+ InlineInfo(
+ traitImplClassSelfType = None,
+ isEffectivelyFinal = BytecodeUtils.isFinalClass(classNode),
+ methodInfos = methodInfos,
+ warning)
+ }
+
+ // The InlineInfo is built from the classfile (not from the symbol) for all classes that are NOT
+ // being compiled. For those classes, the info is only needed if the inliner is enabled, othewise
+ // we can save the memory.
+ if (!inlinerEnabled) BTypes.EmptyInlineInfo
+ else fromClassfileAttribute getOrElse fromClassfileWithoutAttribute
+ }
+
+ /**
* A BType is either a primitive type, a ClassBType, an ArrayBType of one of these, or a MethodType
* referring to BTypes.
*/
@@ -184,7 +297,7 @@ abstract class BTypes {
* promotions (e.g. BYTE to INT). Its operation can be visualized more easily in terms of the
* Java bytecode type hierarchy.
*/
- final def conformsTo(other: BType): Boolean = {
+ final def conformsTo(other: BType): Either[NoClassBTypeInfo, Boolean] = tryEither(Right({
assert(isRef || isPrimitive, s"conformsTo cannot handle $this")
assert(other.isRef || other.isPrimitive, s"conformsTo cannot handle $other")
@@ -192,7 +305,7 @@ abstract class BTypes {
case ArrayBType(component) =>
if (other == ObjectReference || other == jlCloneableReference || other == jioSerializableReference) true
else other match {
- case ArrayBType(otherComponoent) => component.conformsTo(otherComponoent)
+ case ArrayBType(otherComponoent) => component.conformsTo(otherComponoent).orThrow
case _ => false
}
@@ -201,7 +314,7 @@ abstract class BTypes {
if (other.isBoxed) this == other
else if (other == ObjectReference) true
else other match {
- case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) // e.g., java/lang/Double conforms to java/lang/Number
+ case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow // e.g., java/lang/Double conforms to java/lang/Number
case _ => false
}
} else if (isNullType) {
@@ -211,7 +324,7 @@ abstract class BTypes {
} else if (isNothingType) {
true
} else other match {
- case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType)
+ case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow
// case ArrayBType(_) => this.isNullType // documentation only, because `if (isNullType)` above covers this case
case _ =>
// isNothingType || // documentation only, because `if (isNothingType)` above covers this case
@@ -226,7 +339,7 @@ abstract class BTypes {
assert(isPrimitive && other.isPrimitive, s"Expected primitive types $this - $other")
this == other
}
- }
+ }))
/**
* Compute the upper bound of two types.
@@ -245,7 +358,7 @@ abstract class BTypes {
ObjectReference
case _: MethodBType =>
- throw new AssertionError(s"unexpected method type when computing maxType: $this")
+ assertionError(s"unexpected method type when computing maxType: $this")
}
/**
@@ -336,7 +449,7 @@ abstract class BTypes {
*/
final def maxValueType(other: BType): BType = {
- def uncomparable: Nothing = throw new AssertionError(s"Cannot compute maxValueType: $this, $other")
+ def uncomparable: Nothing = assertionError(s"Cannot compute maxValueType: $this, $other")
if (!other.isPrimitive && !other.isNothingType) uncomparable
@@ -527,7 +640,7 @@ abstract class BTypes {
* local and anonymous classes, no matter if there is an enclosing method or not. Accordingly, the
* "class" field (see below) must be always defined, while the "method" field may be null.
*
- * NOTE: When a EnclosingMethod attribute is requried (local and anonymous classes), the "outer"
+ * NOTE: When an EnclosingMethod attribute is requried (local and anonymous classes), the "outer"
* field in the InnerClass table must be null.
*
* Fields:
@@ -634,6 +747,28 @@ abstract class BTypes {
* }
* }
*
+ *
+ * Traits Members
+ * --------------
+ *
+ * Some trait methods don't exist in the generated interface, but only in the implementation class
+ * (private methods in traits for example). Since EnclosingMethod expresses a source-level property,
+ * but the source-level enclosing method doesn't exist in the classfile, we the enclosing method
+ * is null (the enclosing class is still emitted).
+ * See BCodeAsmCommon.considerAsTopLevelImplementationArtifact
+ *
+ *
+ * Implementation Classes, Specialized Classes, Delambdafy:method closure classes
+ * ------------------------------------------------------------------------------
+ *
+ * Trait implementation classes and specialized classes are always considered top-level. Again,
+ * the InnerClass / EnclosingMethod attributes describe a source-level properties. The impl
+ * classes are compilation artifacts.
+ *
+ * The same is true for delambdafy:method closure classes. These classes are generated at
+ * top-level in the delambdafy phase, no special support is required in the backend.
+ *
+ *
* Mirror Classes
* --------------
*
@@ -643,6 +778,21 @@ abstract class BTypes {
/**
* A ClassBType represents a class or interface type. The necessary information to build a
* ClassBType is extracted from compiler symbols and types, see BTypesFromSymbols.
+ *
+ * The `info` field contains either the class information on an error message why the info could
+ * not be computed. There are two reasons for an erroneous info:
+ * 1. The ClassBType was built from a class symbol that stems from a java source file, and the
+ * symbol's type could not be completed successfully (SI-9111)
+ * 2. The ClassBType should be built from a classfile, but the class could not be found on the
+ * compilation classpath.
+ *
+ * Note that all ClassBTypes required in a non-optimzied run are built during code generation from
+ * the class symbols referenced by the ASTs, so they have a valid info. Therefore the backend
+ * often invokes `info.get` (which asserts the info to exist) when reading data from the ClassBType.
+ *
+ * The inliner on the other hand uses ClassBTypes that are built from classfiles, which may have
+ * a missing info. In order not to crash the compiler unnecessarily, the inliner does not force
+ * infos using `get`, but it reports inliner warnings for missing infos that prevent inlining.
*/
final case class ClassBType(internalName: InternalName) extends RefBType {
/**
@@ -652,14 +802,14 @@ abstract class BTypes {
* B.info.nestedInfo.outerClass == A
* A.info.nestedClasses contains B
*/
- private var _info: ClassInfo = null
+ private var _info: Either[NoClassBTypeInfo, ClassInfo] = null
- def info: ClassInfo = {
+ def info: Either[NoClassBTypeInfo, ClassInfo] = {
assert(_info != null, s"ClassBType.info not yet assigned: $this")
_info
}
- def info_=(i: ClassInfo): Unit = {
+ def info_=(i: Either[NoClassBTypeInfo, ClassInfo]): Unit = {
assert(_info == null, s"Cannot set ClassBType.info multiple times: $this")
_info = i
checkInfoConsistency()
@@ -668,27 +818,29 @@ abstract class BTypes {
classBTypeFromInternalName(internalName) = this
private def checkInfoConsistency(): Unit = {
+ if (info.isLeft) return
+
// we assert some properties. however, some of the linked ClassBType (members, superClass,
// interfaces) may not yet have an `_info` (initialization of cyclic structures). so we do a
- // best-effort verification.
- def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || p(c)
+ // best-effort verification. also we don't report an error if the info is a Left.
+ def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || c.info.isLeft || p(c)
def isJLO(t: ClassBType) = t.internalName == ObjectReference.internalName
assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this")
assert(
- if (info.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) }
- else if (isInterface) isJLO(info.superClass.get)
- else !isJLO(this) && ifInit(info.superClass.get)(!_.isInterface),
- s"Invalid superClass in $this: ${info.superClass}"
+ if (info.get.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) }
+ else if (isInterface.get) isJLO(info.get.superClass.get)
+ else !isJLO(this) && ifInit(info.get.superClass.get)(!_.isInterface.get),
+ s"Invalid superClass in $this: ${info.get.superClass}"
)
assert(
- info.interfaces.forall(c => ifInit(c)(_.isInterface)),
- s"Invalid interfaces in $this: ${info.interfaces}"
+ info.get.interfaces.forall(c => ifInit(c)(_.isInterface.get)),
+ s"Invalid interfaces in $this: ${info.get.interfaces}"
)
- assert(info.nestedClasses.forall(c => ifInit(c)(_.isNestedClass)), info.nestedClasses)
+ assert(info.get.nestedClasses.forall(c => ifInit(c)(_.isNestedClass.get)), info.get.nestedClasses)
}
/**
@@ -696,20 +848,37 @@ abstract class BTypes {
*/
def simpleName: String = internalName.split("/").last
- def isInterface = (info.flags & asm.Opcodes.ACC_INTERFACE) != 0
+ def isInterface: Either[NoClassBTypeInfo, Boolean] = info.map(i => (i.flags & asm.Opcodes.ACC_INTERFACE) != 0)
+
+ def superClassesTransitive: Either[NoClassBTypeInfo, List[ClassBType]] = info.flatMap(i => i.superClass match {
+ case None => Right(Nil)
+ case Some(sc) => sc.superClassesTransitive.map(sc :: _)
+ })
- def superClassesTransitive: List[ClassBType] = info.superClass match {
- case None => Nil
- case Some(sc) => sc :: sc.superClassesTransitive
+ /**
+ * The prefix of the internal name until the last '/', or the empty string.
+ */
+ def packageInternalName: String = {
+ val name = internalName
+ name.lastIndexOf('/') match {
+ case -1 => ""
+ case i => name.substring(0, i)
+ }
}
- def isNestedClass = info.nestedInfo.isDefined
+ def isPublic: Either[NoClassBTypeInfo, Boolean] = info.map(i => (i.flags & asm.Opcodes.ACC_PUBLIC) != 0)
- def enclosingNestedClassesChain: List[ClassBType] =
- if (isNestedClass) this :: info.nestedInfo.get.enclosingClass.enclosingNestedClassesChain
- else Nil
+ def isNestedClass: Either[NoClassBTypeInfo, Boolean] = info.map(_.nestedInfo.isDefined)
+
+ def enclosingNestedClassesChain: Either[NoClassBTypeInfo, List[ClassBType]] = {
+ isNestedClass.flatMap(isNested => {
+ // if isNested is true, we know that info.get is defined, and nestedInfo.get is also defined.
+ if (isNested) info.get.nestedInfo.get.enclosingClass.enclosingNestedClassesChain.map(this :: _)
+ else Right(Nil)
+ })
+ }
- def innerClassAttributeEntry: Option[InnerClassEntry] = info.nestedInfo map {
+ def innerClassAttributeEntry: Either[NoClassBTypeInfo, Option[InnerClassEntry]] = info.map(i => i.nestedInfo map {
case NestedInfo(_, outerName, innerName, isStaticNestedClass) =>
InnerClassEntry(
internalName,
@@ -717,28 +886,39 @@ abstract class BTypes {
innerName.orNull,
GenBCode.mkFlags(
// the static flag in the InnerClass table has a special meaning, see InnerClass comment
- info.flags & ~Opcodes.ACC_STATIC,
+ i.flags & ~Opcodes.ACC_STATIC,
if (isStaticNestedClass) Opcodes.ACC_STATIC else 0
) & ClassBType.INNER_CLASSES_FLAGS
)
- }
+ })
- def isSubtypeOf(other: ClassBType): Boolean = {
- if (this == other) return true
+ def inlineInfoAttribute: Either[NoClassBTypeInfo, InlineInfoAttribute] = info.map(i => {
+ // InlineInfos are serialized for classes being compiled. For those the info was built by
+ // buildInlineInfoFromClassSymbol, which only adds a warning under SI-9111, which in turn
+ // only happens for class symbols of java source files.
+ // we could put this assertion into InlineInfoAttribute, but it is more safe to put it here
+ // where it affect only GenBCode, and not add any assertion to GenASM in 2.11.6.
+ assert(i.inlineInfo.warning.isEmpty, i.inlineInfo.warning)
+ InlineInfoAttribute(i.inlineInfo)
+ })
- if (isInterface) {
- if (other == ObjectReference) return true // interfaces conform to Object
- if (!other.isInterface) return false // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false.
+ def isSubtypeOf(other: ClassBType): Either[NoClassBTypeInfo, Boolean] = try {
+ if (this == other) return Right(true)
+ if (isInterface.orThrow) {
+ if (other == ObjectReference) return Right(true) // interfaces conform to Object
+ if (!other.isInterface.orThrow) return Right(false) // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false.
// else: this and other are both interfaces. continue to (*)
} else {
- val sc = info.superClass
- if (sc.isDefined && sc.get.isSubtypeOf(other)) return true // the superclass of this class conforms to other
- if (!other.isInterface) return false // this and other are both classes, and the superclass of this does not conform
+ val sc = info.orThrow.superClass
+ if (sc.isDefined && sc.get.isSubtypeOf(other).orThrow) return Right(true) // the superclass of this class conforms to other
+ if (!other.isInterface.orThrow) return Right(false) // this and other are both classes, and the superclass of this does not conform
// else: this is a class, the other is an interface. continue to (*)
}
// (*) check if some interface of this class conforms to other.
- info.interfaces.exists(_.isSubtypeOf(other))
+ Right(info.orThrow.interfaces.exists(_.isSubtypeOf(other).orThrow))
+ } catch {
+ case Invalid(noInfo: NoClassBTypeInfo) => Left(noInfo)
}
/**
@@ -748,34 +928,36 @@ abstract class BTypes {
* http://comments.gmane.org/gmane.comp.java.vm.languages/2293
* https://issues.scala-lang.org/browse/SI-3872
*/
- def jvmWiseLUB(other: ClassBType): ClassBType = {
+ def jvmWiseLUB(other: ClassBType): Either[NoClassBTypeInfo, ClassBType] = {
def isNotNullOrNothing(c: ClassBType) = !c.isNullType && !c.isNothingType
assert(isNotNullOrNothing(this) && isNotNullOrNothing(other), s"jvmWiseLub for null or nothing: $this - $other")
- val res: ClassBType = (this.isInterface, other.isInterface) match {
- case (true, true) =>
- // exercised by test/files/run/t4761.scala
- if (other.isSubtypeOf(this)) this
- else if (this.isSubtypeOf(other)) other
- else ObjectReference
-
- case (true, false) =>
- if (other.isSubtypeOf(this)) this else ObjectReference
-
- case (false, true) =>
- if (this.isSubtypeOf(other)) other else ObjectReference
+ tryEither {
+ val res: ClassBType = (this.isInterface.orThrow, other.isInterface.orThrow) match {
+ case (true, true) =>
+ // exercised by test/files/run/t4761.scala
+ if (other.isSubtypeOf(this).orThrow) this
+ else if (this.isSubtypeOf(other).orThrow) other
+ else ObjectReference
+
+ case (true, false) =>
+ if (other.isSubtypeOf(this).orThrow) this else ObjectReference
+
+ case (false, true) =>
+ if (this.isSubtypeOf(other).orThrow) other else ObjectReference
+
+ case _ =>
+ // TODO @lry I don't really understand the reasoning here.
+ // Both this and other are classes. The code takes (transitively) all superclasses and
+ // finds the first common one.
+ // MOST LIKELY the answer can be found here, see the comments and links by Miguel:
+ // - https://issues.scala-lang.org/browse/SI-3872
+ firstCommonSuffix(this :: this.superClassesTransitive.orThrow, other :: other.superClassesTransitive.orThrow)
+ }
- case _ =>
- // TODO @lry I don't really understand the reasoning here.
- // Both this and other are classes. The code takes (transitively) all superclasses and
- // finds the first common one.
- // MOST LIKELY the answer can be found here, see the comments and links by Miguel:
- // - https://issues.scala-lang.org/browse/SI-3872
- firstCommonSuffix(this :: this.superClassesTransitive, other :: other.superClassesTransitive)
+ assert(isNotNullOrNothing(res), s"jvmWiseLub computed: $res")
+ Right(res)
}
-
- assert(isNotNullOrNothing(res), s"jvmWiseLub computed: $res")
- res
}
private def firstCommonSuffix(as: List[ClassBType], bs: List[ClassBType]): ClassBType = {
@@ -837,9 +1019,11 @@ abstract class BTypes {
* @param nestedClasses Classes nested in this class. Those need to be added to the
* InnerClass table, see the InnerClass spec summary above.
* @param nestedInfo If this describes a nested class, information for the InnerClass table.
+ * @param inlineInfo Information about this class for the inliner.
*/
final case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int,
- nestedClasses: List[ClassBType], nestedInfo: Option[NestedInfo])
+ nestedClasses: List[ClassBType], nestedInfo: Option[NestedInfo],
+ inlineInfo: InlineInfo)
/**
* Information required to add a class to an InnerClass table.
@@ -914,4 +1098,60 @@ object BTypes {
* But that would create overhead in a Collection[InternalName].
*/
type InternalName = String
+
+ /**
+ * Metadata about a ClassBType, used by the inliner.
+ *
+ * More information may be added in the future to enable more elaborate inlinine heuristics.
+ *
+ * @param traitImplClassSelfType `Some(tp)` if this InlineInfo describes a trait, and the `self`
+ * parameter type of the methods in the implementation class is not
+ * the trait itself. Example:
+ * trait T { self: U => def f = 1 }
+ * Generates something like:
+ * class T$class { static def f(self: U) = 1 }
+ *
+ * In order to inline a trat method call, the INVOKEINTERFACE is
+ * rewritten to an INVOKESTATIC of the impl class, so we need the
+ * self type (U) to get the right signature.
+ *
+ * `None` if the self type is the interface type, or if this
+ * InlineInfo does not describe a trait.
+ *
+ * @param isEffectivelyFinal True if the class cannot have subclasses: final classes, module
+ * classes, trait impl classes.
+ *
+ * @param methodInfos The [[MethodInlineInfo]]s for the methods declared in this class.
+ * The map is indexed by the string s"$name$descriptor" (to
+ * disambiguate overloads).
+ *
+ * @param warning Contains an warning message if an error occured when building this
+ * InlineInfo, for example if some classfile could not be found on
+ * the classpath. This warning can be reported later by the inliner.
+ */
+ final case class InlineInfo(traitImplClassSelfType: Option[InternalName],
+ isEffectivelyFinal: Boolean,
+ methodInfos: Map[String, MethodInlineInfo],
+ warning: Option[ClassInlineInfoWarning])
+
+ val EmptyInlineInfo = InlineInfo(None, false, Map.empty, None)
+
+ /**
+ * Metadata about a method, used by the inliner.
+ *
+ * @param effectivelyFinal True if the method cannot be overridden (in Scala)
+ * @param traitMethodWithStaticImplementation True if the method is an interface method method of
+ * a trait method and has a static counterpart in the
+ * implementation class.
+ * @param annotatedInline True if the method is annotated `@inline`
+ * @param annotatedNoInline True if the method is annotated `@noinline`
+ */
+ final case class MethodInlineInfo(effectivelyFinal: Boolean,
+ traitMethodWithStaticImplementation: Boolean,
+ annotatedInline: Boolean,
+ annotatedNoInline: Boolean)
+
+ // no static way (without symbol table instance) to get to nme.ScalaATTR / ScalaSignatureATTR
+ val ScalaAttributeName = "Scala"
+ val ScalaSigAttributeName = "ScalaSig"
} \ No newline at end of file
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
index 94f9b585d9..eeb6ed24a2 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
@@ -7,10 +7,9 @@ package scala.tools.nsc
package backend.jvm
import scala.tools.asm
-import opt.ByteCodeRepository
-import scala.tools.asm.tree.ClassNode
-import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository.Source
-import BTypes.InternalName
+import scala.tools.nsc.backend.jvm.opt.{CallGraph, Inliner, ByteCodeRepository}
+import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo, InternalName}
+import BackendReporting._
/**
* This class mainly contains the method classBTypeFromSymbol, which extracts the necessary
@@ -36,7 +35,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
val coreBTypes = new CoreBTypesProxy[this.type](this)
import coreBTypes._
- val byteCodeRepository = new ByteCodeRepository(global.classPath, recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, (ClassNode, Source)]))
+ val byteCodeRepository = new ByteCodeRepository(global.classPath, javaDefinedClasses, recordPerRunCache(collection.concurrent.TrieMap.empty))
+
+ val inliner: Inliner[this.type] = new Inliner(this)
+
+ val callGraph: CallGraph[this.type] = new CallGraph(this)
+
+ val backendReporting: BackendReporting = new BackendReportingImpl(global)
final def initializeCoreBTypes(): Unit = {
coreBTypes.setBTypes(new CoreBTypes[this.type](this))
@@ -44,6 +49,20 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T = perRunCaches.recordCache(cache)
+ def inlineGlobalEnabled: Boolean = settings.YoptInlineGlobal
+
+ def inlinerEnabled: Boolean = settings.YoptInlinerEnabled
+
+ def warnSettings: WarnSettings = {
+ val c = settings.YoptWarningsChoices
+ // cannot extract settings.YoptWarnings into a local val due to some dependent typing issue.
+ WarnSettings(
+ !settings.YoptWarnings.isSetByUser || settings.YoptWarnings.contains(c.atInlineFailedSummary.name) || settings.YoptWarnings.contains(c.atInlineFailed.name),
+ settings.YoptWarnings.contains(c.noInlineMixed.name),
+ settings.YoptWarnings.contains(c.noInlineMissingBytecode.name),
+ settings.YoptWarnings.contains(c.noInlineMissingScalaInlineInfoAttr.name))
+ }
+
// helpers that need access to global.
// TODO @lry create a separate component, they don't belong to BTypesFromSymbols
@@ -76,22 +95,131 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
// end helpers
/**
- * The ClassBType for a class symbol `sym`.
+ * The ClassBType for a class symbol `classSym`.
+ *
+ * The class symbol scala.Nothing is mapped to the class scala.runtime.Nothing$. Similarly,
+ * scala.Null is mapped to scala.runtime.Null$. This is because there exist no class files
+ * for the Nothing / Null. If used for example as a parameter type, we use the runtime classes
+ * in the classfile method signature.
+ *
+ * Note that the referenced class symbol may be an implementation class. For example when
+ * compiling a mixed-in method that forwards to the static method in the implementation class,
+ * the class descriptor of the receiver (the implementation class) is obtained by creating the
+ * ClassBType.
*/
final def classBTypeFromSymbol(classSym: Symbol): ClassBType = {
assert(classSym != NoSymbol, "Cannot create ClassBType from NoSymbol")
assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym")
- assert(
- (!primitiveTypeMap.contains(classSym) || isCompilingPrimitive) &&
- (classSym != NothingClass && classSym != NullClass),
- s"Cannot create ClassBType for special class symbol ${classSym.fullName}")
+ assertClassNotArrayNotPrimitive(classSym)
+ assert(!primitiveTypeMap.contains(classSym) || isCompilingPrimitive, s"Cannot create ClassBType for primitive class symbol $classSym")
+ if (classSym == NothingClass) RT_NOTHING
+ else if (classSym == NullClass) RT_NULL
+ else {
+ val internalName = classSym.javaBinaryName.toString
+ classBTypeFromInternalName.getOrElse(internalName, {
+ // The new ClassBType is added to the map in its constructor, before we set its info. This
+ // allows initializing cyclic dependencies, see the comment on variable ClassBType._info.
+ val res = ClassBType(internalName)
+ if (completeSilentlyAndCheckErroneous(classSym)) {
+ res.info = Left(NoClassBTypeInfoClassSymbolInfoFailedSI9111(classSym.fullName))
+ res
+ } else {
+ setClassInfo(classSym, res)
+ }
+ })
+ }
+ }
- val internalName = classSym.javaBinaryName.toString
- classBTypeFromInternalName.getOrElse(internalName, {
- // The new ClassBType is added to the map in its constructor, before we set its info. This
- // allows initializing cyclic dependencies, see the comment on variable ClassBType._info.
- setClassInfo(classSym, ClassBType(internalName))
- })
+ /**
+ * Builds a [[MethodBType]] for a method symbol.
+ */
+ final def methodBTypeFromSymbol(methodSymbol: Symbol): MethodBType = {
+ assert(methodSymbol.isMethod, s"not a method-symbol: $methodSymbol")
+ val resultType: BType =
+ if (methodSymbol.isClassConstructor || methodSymbol.isConstructor) UNIT
+ else typeToBType(methodSymbol.tpe.resultType)
+ MethodBType(methodSymbol.tpe.paramTypes map typeToBType, resultType)
+ }
+
+ /**
+ * This method returns the BType for a type reference, for example a parameter type.
+ *
+ * If `t` references a class, typeToBType ensures that the class is not an implementation class.
+ * See also comment on classBTypeFromSymbol, which is invoked for implementation classes.
+ */
+ final def typeToBType(t: Type): BType = {
+ import definitions.ArrayClass
+
+ /**
+ * Primitive types are represented as TypeRefs to the class symbol of, for example, scala.Int.
+ * The `primitiveTypeMap` maps those class symbols to the corresponding PrimitiveBType.
+ */
+ def primitiveOrClassToBType(sym: Symbol): BType = {
+ assertClassNotArray(sym)
+ assert(!sym.isImplClass, sym)
+ primitiveTypeMap.getOrElse(sym, classBTypeFromSymbol(sym))
+ }
+
+ /**
+ * When compiling Array.scala, the type parameter T is not erased and shows up in method
+ * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference.
+ */
+ def nonClassTypeRefToBType(sym: Symbol): ClassBType = {
+ assert(sym.isType && isCompilingArray, sym)
+ ObjectReference
+ }
+
+ t.dealiasWiden match {
+ case TypeRef(_, ArrayClass, List(arg)) => ArrayBType(typeToBType(arg)) // Array type such as Array[Int] (kept by erasure)
+ case TypeRef(_, sym, _) if !sym.isClass => nonClassTypeRefToBType(sym) // See comment on nonClassTypeRefToBType
+ case TypeRef(_, sym, _) => primitiveOrClassToBType(sym) // Common reference to a type such as scala.Int or java.lang.String
+ case ClassInfoType(_, _, sym) => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes typeToBType(moduleClassSymbol.info)
+
+ /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for
+ * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning.
+ * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala.
+ */
+ case a @ AnnotatedType(_, t) =>
+ debuglog(s"typeKind of annotated type $a")
+ typeToBType(t)
+
+ /* ExistentialType should (probably) be eliminated by erasure. We know they get here for
+ * classOf constants:
+ * class C[T]
+ * class T { final val k = classOf[C[_]] }
+ */
+ case e @ ExistentialType(_, t) =>
+ debuglog(s"typeKind of existential type $e")
+ typeToBType(t)
+
+ /* The cases below should probably never occur. They are kept for now to avoid introducing
+ * new compiler crashes, but we added a warning. The compiler / library bootstrap and the
+ * test suite don't produce any warning.
+ */
+
+ case tp =>
+ currentUnit.warning(tp.typeSymbol.pos,
+ s"an unexpected type representation reached the compiler backend while compiling $currentUnit: $tp. " +
+ "If possible, please file a bug on issues.scala-lang.org.")
+
+ tp match {
+ case ThisType(ArrayClass) => ObjectReference // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test
+ case ThisType(sym) => classBTypeFromSymbol(sym)
+ case SingleType(_, sym) => primitiveOrClassToBType(sym)
+ case ConstantType(_) => typeToBType(t.underlying)
+ case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b).get)
+ }
+ }
+ }
+
+ def assertClassNotArray(sym: Symbol): Unit = {
+ assert(sym.isClass, sym)
+ assert(sym != definitions.ArrayClass || isCompilingArray, sym)
+ }
+
+ def assertClassNotArrayNotPrimitive(sym: Symbol): Unit = {
+ assertClassNotArray(sym)
+ assert(!primitiveTypeMap.contains(sym) || isCompilingPrimitive, sym)
}
private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = {
@@ -125,39 +253,71 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
* nested classes, but NOT nested in C, that are used within C.
*/
val nestedClassSymbols = {
+ val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases
+
// The lambdalift phase lifts all nested classes to the enclosing class, so if we collect
// member classes right after lambdalift, we obtain all nested classes, including local and
// anonymous ones.
val nestedClasses = {
- val nested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(classSym))
+ val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(classSym))
+ val nested = {
+ // Classes nested in value classes are nested in the companion at this point. For InnerClass /
+ // EnclosingMethod, we use the value class as the outer class. So we remove nested classes
+ // from the companion that were originally nested in the value class.
+ if (exitingPickler(linkedClass.isDerivedValueClass)) allNested.filterNot(classOriginallyNestedInClass(_, linkedClass))
+ else allNested
+ }
+
if (isTopLevelModuleClass(classSym)) {
// For Java compatibility, member classes of top-level objects are treated as members of
// the top-level companion class, see comment below.
- val members = exitingPickler(memberClassesOf(classSym))
+ val members = exitingPickler(memberClassesForInnerClassTable(classSym))
nested diff members
} else {
nested
}
}
- // If this is a top-level class, the member classes of the companion object are added as
- // members of the class. For example:
- // class C { }
- // object C {
- // class D
- // def f = { class E }
- // }
- // The class D is added as a member of class C. The reason is: for Java compatibility, the
- // InnerClass attribute for D has "C" (NOT the module class "C$") as the outer class of D
- // (done by buildNestedInfo). See comment in BTypes.
- // For consistency, the InnerClass entry for D needs to be present in C - to Java it looks
- // like D is a member of C, not C$.
- val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases
- val companionModuleMembers = {
- // phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes,
- // not local classes of the companion module (E in the exmaple) that were lifted by lambdalift.
- if (isTopLevelModuleClass(linkedClass)) exitingPickler(memberClassesOf(linkedClass))
- else Nil
+ val companionModuleMembers = if (considerAsTopLevelImplementationArtifact(classSym)) Nil else {
+ // If this is a top-level non-impl (*) class, the member classes of the companion object are
+ // added as members of the class. For example:
+ // class C { }
+ // object C {
+ // class D
+ // def f = { class E }
+ // }
+ // The class D is added as a member of class C. The reason is: for Java compatibility, the
+ // InnerClass attribute for D has "C" (NOT the module class "C$") as the outer class of D
+ // (done by buildNestedInfo). See comment in BTypes.
+ // For consistency, the InnerClass entry for D needs to be present in C - to Java it looks
+ // like D is a member of C, not C$.
+ //
+ // (*) We exclude impl classes: if the classfile for the impl class exists on the classpath,
+ // a linkedClass symbol is found for which isTopLevelModule is true, so we end up searching
+ // members of that weird impl-class-module-class-symbol. that search probably cannot return
+ // any classes, but it's better to exclude it.
+ val javaCompatMembers = {
+ if (linkedClass != NoSymbol && isTopLevelModuleClass(linkedClass))
+ // phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only sees member
+ // classes, not local classes of the companion module (E in the exmaple) that were lifted by lambdalift.
+ exitingPickler(memberClassesForInnerClassTable(linkedClass))
+ else
+ Nil
+ }
+
+ // Classes nested in value classes are nested in the companion at this point. For InnerClass /
+ // EnclosingMethod we use the value class as enclosing class. Here we search nested classes
+ // in the companion that were originally nested in the value class, and we add them as nested
+ // in the value class.
+ val valueClassCompanionMembers = {
+ if (linkedClass != NoSymbol && exitingPickler(classSym.isDerivedValueClass)) {
+ val moduleMemberClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(linkedClass))
+ moduleMemberClasses.filter(classOriginallyNestedInClass(_, classSym))
+ } else
+ Nil
+ }
+
+ javaCompatMembers ++ valueClassCompanionMembers
}
nestedClasses ++ companionModuleMembers
@@ -183,7 +343,9 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
val nestedInfo = buildNestedInfo(classSym)
- classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo)
+ val inlineInfo = buildInlineInfo(classSym, classBType.internalName)
+
+ classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo))
classBType
}
@@ -191,7 +353,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
assert(innerClassSym.isClass, s"Cannot build NestedInfo for non-class symbol $innerClassSym")
val isTopLevel = innerClassSym.rawowner.isPackageClass
- if (isTopLevel) None
+ // impl classes are considered top-level, see comment in BTypes
+ if (isTopLevel || considerAsTopLevelImplementationArtifact(innerClassSym)) None
else {
// See comment in BTypes, when is a class marked static in the InnerClass table.
val isStaticNestedClass = isOriginallyStaticOwner(innerClassSym.originalOwner)
@@ -227,7 +390,9 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
}
val innerName: Option[String] = {
- if (innerClassSym.isAnonymousClass || innerClassSym.isAnonymousFunction) None
+ // phase travel necessary: after flatten, the name includes the name of outer classes.
+ // if some outer name contains $anon, a non-anon class is considered anon.
+ if (exitingPickler(innerClassSym.isAnonymousClass || innerClassSym.isAnonymousFunction)) None
else Some(innerClassSym.rawname + innerClassSym.moduleSuffix) // moduleSuffix for module classes
}
@@ -236,6 +401,40 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
}
/**
+ * Build the InlineInfo for a ClassBType from the class symbol.
+ *
+ * Note that the InlineInfo is only built from the symbolic information for classes that are being
+ * compiled. For all other classes we delegate to inlineInfoFromClassfile. The reason is that
+ * mixed-in methods are only added to class symbols being compiled, but not to other classes
+ * extending traits. Creating the InlineInfo from the symbol would prevent these mixins from being
+ * inlined.
+ *
+ * So for classes being compiled, the InlineInfo is created here and stored in the ScalaInlineInfo
+ * classfile attribute.
+ */
+ private def buildInlineInfo(classSym: Symbol, internalName: InternalName): InlineInfo = {
+ def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym, classBTypeFromSymbol(_).internalName, methodBTypeFromSymbol(_).descriptor)
+
+ // phase travel required, see implementation of `compiles`. for nested classes, it checks if the
+ // enclosingTopLevelClass is being compiled. after flatten, all classes are considered top-level,
+ // so `compiles` would return `false`.
+ if (exitingPickler(currentRun.compiles(classSym))) buildFromSymbol // InlineInfo required for classes being compiled, we have to create the classfile attribute
+ else if (!inlinerEnabled) BTypes.EmptyInlineInfo // For other classes, we need the InlineInfo only inf the inliner is enabled.
+ else {
+ // For classes not being compiled, the InlineInfo is read from the classfile attribute. This
+ // fixes an issue with mixed-in methods: the mixin phase enters mixin methods only to class
+ // symbols being compiled. For non-compiled classes, we could not build MethodInlineInfos
+ // for those mixin members, which prevents inlining.
+ byteCodeRepository.classNode(internalName) match {
+ case Right(classNode) =>
+ inlineInfoFromClassfile(classNode)
+ case Left(missingClass) =>
+ InlineInfo(None, false, Map.empty, Some(ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass)))
+ }
+ }
+ }
+
+ /**
* For top-level objects without a companion class, the compilere generates a mirror class with
* static forwarders (Java compat). There's no symbol for the mirror class, but we still need a
* ClassBType (its info.nestedClasses will hold the InnerClass entries, see comment in BTypes).
@@ -246,14 +445,14 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
classBTypeFromInternalName.getOrElse(internalName, {
val c = ClassBType(internalName)
// class info consistent with BCodeHelpers.genMirrorClass
- val nested = exitingPickler(memberClassesOf(moduleClassSym)) map classBTypeFromSymbol
- c.info = ClassInfo(
+ val nested = exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol
+ c.info = Right(ClassInfo(
superClass = Some(ObjectReference),
interfaces = Nil,
flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL,
nestedClasses = nested,
- nestedInfo = None
- )
+ nestedInfo = None,
+ InlineInfo(None, true, Map.empty, None))) // no InlineInfo needed, scala never invokes methods on the mirror class
c
})
}
@@ -287,8 +486,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
}
// legacy, to be removed when the @remote annotation gets removed
- final def isRemote(s: Symbol) = (s hasAnnotation definitions.RemoteAttr)
- final def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0)
+ final def isRemote(s: Symbol) = s hasAnnotation definitions.RemoteAttr
+ final def hasPublicBitSet(flags: Int) = (flags & asm.Opcodes.ACC_PUBLIC) != 0
/**
* Return the Java modifiers for the given symbol.
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
new file mode 100644
index 0000000000..a06fb4bab8
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
@@ -0,0 +1,265 @@
+package scala.tools.nsc
+package backend.jvm
+
+import scala.tools.asm.tree.{AbstractInsnNode, MethodNode}
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.reflect.internal.util.Position
+
+/**
+ * Interface for emitting inline warnings. The interface is required because the implementation
+ * depends on Global, which is not available in BTypes (only in BTypesFromSymbols).
+ */
+sealed abstract class BackendReporting {
+ def inlinerWarning(pos: Position, message: String): Unit
+}
+
+final class BackendReportingImpl(val global: Global) extends BackendReporting {
+ import global._
+
+ def inlinerWarning(pos: Position, message: String): Unit = {
+ currentRun.reporting.inlinerWarning(pos, message)
+ }
+}
+
+/**
+ * Utilities for error reporting.
+ *
+ * Defines some tools to make error reporting with Either easier. Would be subsumed by a right-biased
+ * Either in the standard library (or scalaz \/) (Validation is different, it accumulates multiple
+ * errors).
+ */
+object BackendReporting {
+ def methodSignature(classInternalName: InternalName, name: String, desc: String) = {
+ classInternalName + "::" + name + desc
+ }
+
+ def methodSignature(classInternalName: InternalName, method: MethodNode): String = {
+ methodSignature(classInternalName, method.name, method.desc)
+ }
+
+ def assertionError(message: String): Nothing = throw new AssertionError(message)
+
+ implicit class RightBiasedEither[A, B](val v: Either[A, B]) extends AnyVal {
+ def map[U](f: B => U) = v.right.map(f)
+ def flatMap[BB](f: B => Either[A, BB]) = v.right.flatMap(f)
+ def filter(f: B => Boolean)(implicit empty: A): Either[A, B] = v match {
+ case Left(_) => v
+ case Right(e) => if (f(e)) v else Left(empty) // scalaz.\/ requires an implicit Monoid m to get m.empty
+ }
+ def foreach[U](f: B => U) = v.right.foreach(f)
+
+ def getOrElse[BB >: B](alt: => BB): BB = v.right.getOrElse(alt)
+
+ /**
+ * Get the value, fail with an assertion if this is an error.
+ */
+ def get: B = {
+ assert(v.isRight, v.left.get)
+ v.right.get
+ }
+
+ /**
+ * Get the right value of an `Either` by throwing a potential error message. Can simplify the
+ * implementation of methods that act on multiple `Either` instances. Instead of flat-mapping,
+ * the first error can be collected as
+ *
+ * tryEither {
+ * eitherOne.orThrow .... eitherTwo.orThrow ... eitherThree.orThrow
+ * }
+ */
+ def orThrow: B = v match {
+ case Left(m) => throw Invalid(m)
+ case Right(t) => t
+ }
+ }
+
+ case class Invalid[A](e: A) extends Exception
+
+ /**
+ * See documentation of orThrow above.
+ */
+ def tryEither[A, B](op: => Either[A, B]): Either[A, B] = try { op } catch { case Invalid(e) => Left(e.asInstanceOf[A]) }
+
+ final case class WarnSettings(atInlineFailed: Boolean, noInlineMixed: Boolean, noInlineMissingBytecode: Boolean, noInlineMissingScalaInlineInfoAttr: Boolean)
+
+ sealed trait OptimizerWarning {
+ def emitWarning(settings: WarnSettings): Boolean
+ }
+
+ // Method filter in RightBiasedEither requires an implicit empty value. Taking the value here
+ // in scope allows for-comprehensions that desugar into filter calls (for example when using a
+ // tuple de-constructor).
+ implicit object emptyOptimizerWarning extends OptimizerWarning {
+ def emitWarning(settings: WarnSettings): Boolean = false
+ }
+
+ sealed trait MissingBytecodeWarning extends OptimizerWarning {
+ override def toString = this match {
+ case ClassNotFound(internalName, definedInJavaSource) =>
+ s"The classfile for $internalName could not be found on the compilation classpath." + {
+ if (definedInJavaSource) "\nThe class is defined in a Java source file that is being compiled (mixed compilation), therefore no bytecode is available."
+ else ""
+ }
+
+ case MethodNotFound(name, descriptor, ownerInternalName, missingClasses) =>
+ val (javaDef, others) = missingClasses.partition(_.definedInJavaSource)
+ s"The method $name$descriptor could not be found in the class $ownerInternalName or any of its parents." +
+ (if (others.isEmpty) "" else others.map(_.internalName).mkString("\nNote that the following parent classes could not be found on the classpath: ", ", ", "")) +
+ (if (javaDef.isEmpty) "" else javaDef.map(_.internalName).mkString("\nNote that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: ", ",", ""))
+
+ case FieldNotFound(name, descriptor, ownerInternalName, missingClass) =>
+ s"The field node $name$descriptor could not be found because the classfile $ownerInternalName cannot be found on the classpath." +
+ missingClass.map(c => s" Reason:\n$c").getOrElse("")
+ }
+
+ def emitWarning(settings: WarnSettings): Boolean = this match {
+ case ClassNotFound(_, javaDefined) =>
+ if (javaDefined) settings.noInlineMixed
+ else settings.noInlineMissingBytecode
+
+ case m @ MethodNotFound(_, _, _, missing) =>
+ if (m.isArrayMethod) false
+ else settings.noInlineMissingBytecode || missing.exists(_.emitWarning(settings))
+
+ case FieldNotFound(_, _, _, missing) =>
+ settings.noInlineMissingBytecode || missing.exists(_.emitWarning(settings))
+ }
+ }
+
+ case class ClassNotFound(internalName: InternalName, definedInJavaSource: Boolean) extends MissingBytecodeWarning
+ case class MethodNotFound(name: String, descriptor: String, ownerInternalNameOrArrayDescriptor: InternalName, missingClasses: List[ClassNotFound]) extends MissingBytecodeWarning {
+ def isArrayMethod = ownerInternalNameOrArrayDescriptor.charAt(0) == '['
+ }
+ case class FieldNotFound(name: String, descriptor: String, ownerInternalName: InternalName, missingClass: Option[ClassNotFound]) extends MissingBytecodeWarning
+
+ sealed trait NoClassBTypeInfo extends OptimizerWarning {
+ override def toString = this match {
+ case NoClassBTypeInfoMissingBytecode(cause) =>
+ cause.toString
+
+ case NoClassBTypeInfoClassSymbolInfoFailedSI9111(classFullName) =>
+ s"Failed to get the type of class symbol $classFullName due to SI-9111."
+ }
+
+ def emitWarning(settings: WarnSettings): Boolean = this match {
+ case NoClassBTypeInfoMissingBytecode(cause) => cause.emitWarning(settings)
+ case NoClassBTypeInfoClassSymbolInfoFailedSI9111(_) => settings.noInlineMissingBytecode
+ }
+ }
+
+ case class NoClassBTypeInfoMissingBytecode(cause: MissingBytecodeWarning) extends NoClassBTypeInfo
+ case class NoClassBTypeInfoClassSymbolInfoFailedSI9111(classFullName: String) extends NoClassBTypeInfo
+
+ /**
+ * Used in the CallGraph for nodes where an issue occurred determining the callee information.
+ */
+ sealed trait CalleeInfoWarning extends OptimizerWarning {
+ def declarationClass: InternalName
+ def name: String
+ def descriptor: String
+
+ def warningMessageSignature = BackendReporting.methodSignature(declarationClass, name, descriptor)
+
+ override def toString = this match {
+ case MethodInlineInfoIncomplete(_, _, _, cause) =>
+ s"The inline information for $warningMessageSignature may be incomplete:\n" + cause
+
+ case MethodInlineInfoMissing(_, _, _, cause) =>
+ s"No inline information for method $warningMessageSignature could be found." +
+ cause.map(" Possible reason:\n" + _).getOrElse("")
+
+ case MethodInlineInfoError(_, _, _, cause) =>
+ s"Error while computing the inline information for method $warningMessageSignature:\n" + cause
+
+ case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) =>
+ cause.toString
+ }
+
+ def emitWarning(settings: WarnSettings): Boolean = this match {
+ case MethodInlineInfoIncomplete(_, _, _, cause) => cause.emitWarning(settings)
+
+ case MethodInlineInfoMissing(_, _, _, Some(cause)) => cause.emitWarning(settings)
+ case MethodInlineInfoMissing(_, _, _, None) => settings.noInlineMissingBytecode
+
+ case MethodInlineInfoError(_, _, _, cause) => cause.emitWarning(settings)
+
+ case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) => cause.emitWarning(settings)
+ }
+ }
+
+ case class MethodInlineInfoIncomplete(declarationClass: InternalName, name: String, descriptor: String, cause: ClassInlineInfoWarning) extends CalleeInfoWarning
+ case class MethodInlineInfoMissing(declarationClass: InternalName, name: String, descriptor: String, cause: Option[ClassInlineInfoWarning]) extends CalleeInfoWarning
+ case class MethodInlineInfoError(declarationClass: InternalName, name: String, descriptor: String, cause: NoClassBTypeInfo) extends CalleeInfoWarning
+ case class RewriteTraitCallToStaticImplMethodFailed(declarationClass: InternalName, name: String, descriptor: String, cause: OptimizerWarning) extends CalleeInfoWarning
+
+ sealed trait CannotInlineWarning extends OptimizerWarning {
+ def calleeDeclarationClass: InternalName
+ def name: String
+ def descriptor: String
+
+ def calleeMethodSig = BackendReporting.methodSignature(calleeDeclarationClass, name, descriptor)
+
+ override def toString = this match {
+ case IllegalAccessInstruction(_, _, _, callsiteClass, instruction) =>
+ s"The callee $calleeMethodSig contains the instruction ${AsmUtils.textify(instruction)}" +
+ s"\nthat would cause an IllegalAccessError when inlined into class $callsiteClass."
+
+ case IllegalAccessCheckFailed(_, _, _, callsiteClass, instruction, cause) =>
+ s"Failed to check if $calleeMethodSig can be safely inlined to $callsiteClass without causing an IllegalAccessError. Checking instruction ${AsmUtils.textify(instruction)} failed:\n" + cause
+
+ case MethodWithHandlerCalledOnNonEmptyStack(_, _, _, callsiteClass, callsiteName, callsiteDesc) =>
+ s"""The operand stack at the callsite in ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)} contains more values than the
+ |arguments expected by the callee $calleeMethodSig. These values would be discarded
+ |when entering an exception handler declared in the inlined method.""".stripMargin
+
+ case SynchronizedMethod(_, _, _) =>
+ s"Method $calleeMethodSig cannot be inlined because it is synchronized."
+ }
+
+ def emitWarning(settings: WarnSettings): Boolean = this match {
+ case _: IllegalAccessInstruction | _: MethodWithHandlerCalledOnNonEmptyStack | _: SynchronizedMethod =>
+ settings.atInlineFailed
+
+ case IllegalAccessCheckFailed(_, _, _, _, _, cause) =>
+ cause.emitWarning(settings)
+ }
+ }
+ case class IllegalAccessInstruction(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ callsiteClass: InternalName, instruction: AbstractInsnNode) extends CannotInlineWarning
+ case class IllegalAccessCheckFailed(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ callsiteClass: InternalName, instruction: AbstractInsnNode, cause: OptimizerWarning) extends CannotInlineWarning
+ case class MethodWithHandlerCalledOnNonEmptyStack(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning
+ case class SynchronizedMethod(calleeDeclarationClass: InternalName, name: String, descriptor: String) extends CannotInlineWarning
+
+ /**
+ * Used in the InlineInfo of a ClassBType, when some issue occurred obtaining the inline information.
+ */
+ sealed trait ClassInlineInfoWarning extends OptimizerWarning {
+ override def toString = this match {
+ case NoInlineInfoAttribute(internalName) =>
+ s"The Scala classfile $internalName does not have a ScalaInlineInfo attribute."
+
+ case ClassSymbolInfoFailureSI9111(classFullName) =>
+ s"Failed to get the type of a method of class symbol $classFullName due to SI-9111."
+
+ case ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass) =>
+ s"Failed to build the inline information: $missingClass."
+
+ case UnknownScalaInlineInfoVersion(internalName, version) =>
+ s"Cannot read ScalaInlineInfo version $version in classfile $internalName. Use a more recent compiler."
+ }
+
+ def emitWarning(settings: WarnSettings): Boolean = this match {
+ case NoInlineInfoAttribute(_) => settings.noInlineMissingScalaInlineInfoAttr
+ case ClassNotFoundWhenBuildingInlineInfoFromSymbol(cause) => cause.emitWarning(settings)
+ case ClassSymbolInfoFailureSI9111(_) => settings.noInlineMissingBytecode
+ case UnknownScalaInlineInfoVersion(_, _) => settings.noInlineMissingScalaInlineInfoAttr
+ }
+ }
+
+ case class NoInlineInfoAttribute(internalName: InternalName) extends ClassInlineInfoWarning
+ case class ClassSymbolInfoFailureSI9111(classFullName: String) extends ClassInlineInfoWarning
+ case class ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass: ClassNotFound) extends ClassInlineInfoWarning
+ case class UnknownScalaInlineInfoVersion(internalName: InternalName, version: Int) extends ClassInlineInfoWarning
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala
index 246235f395..492fe3ae79 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala
@@ -99,10 +99,9 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
*
* Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal
* names of NothingClass and NullClass can't be emitted as-is.
- * TODO @lry Once there's a 2.11.3 starr, use the commented argument list. The current starr crashes on the type literal `scala.runtime.Nothing$`
*/
- lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(rootMirror.getRequiredClass("scala.runtime.Nothing$")) // (requiredClass[scala.runtime.Nothing$])
- lazy val RT_NULL : ClassBType = classBTypeFromSymbol(rootMirror.getRequiredClass("scala.runtime.Null$")) // (requiredClass[scala.runtime.Null$])
+ lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Nothing$])
+ lazy val RT_NULL : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Null$])
lazy val ObjectReference : ClassBType = classBTypeFromSymbol(ObjectClass)
lazy val objArrayReference : ArrayBType = ArrayBType(ObjectReference)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
index abe3bc512c..f866c0d038 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
@@ -9,6 +9,7 @@ package backend.jvm
import scala.collection.{ mutable, immutable }
import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer }
+import scala.tools.nsc.backend.jvm.opt.InlineInfoAttribute
import scala.tools.nsc.symtab._
import scala.tools.asm
import asm.Label
@@ -532,7 +533,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
}
bytecodeWriter.writeClass(label, jclassName, arr, outF)
} catch {
- case e: java.lang.RuntimeException if e != null && (e.getMessage contains "too large!") =>
+ case e: java.lang.RuntimeException if e.getMessage != null && (e.getMessage contains "too large!") =>
reporter.error(sym.pos,
s"Could not write class $jclassName because it exceeds JVM code size limits. ${e.getMessage}")
}
@@ -595,7 +596,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
val x = innerClassSymbolFor(s)
if(x ne NoSymbol) {
assert(x.isClass, "not an inner-class symbol")
- val isInner = !x.rawowner.isPackageClass
+ // impl classes are considered top-level, see comment in BTypes
+ val isInner = !considerAsTopLevelImplementationArtifact(s) && !x.rawowner.isPackageClass
if (isInner) {
innerClassBuffer += x
collectInnerClass(x.rawowner)
@@ -687,35 +689,60 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
null
else {
val outerName = javaName(innerSym.rawowner)
- if (isTopLevelModule(innerSym.rawowner)) "" + nme.stripModuleSuffix(newTermName(outerName))
+ if (isTopLevelModule(innerSym.rawowner)) "" + TermName(outerName).dropModule
else outerName
}
}
- def innerName(innerSym: Symbol): String =
- if (innerSym.isAnonymousClass || innerSym.isAnonymousFunction)
- null
- else
- innerSym.rawname + innerSym.moduleSuffix
+ def innerName(innerSym: Symbol): String = {
+ // phase travel necessary: after flatten, the name includes the name of outer classes.
+ // if some outer name contains $anon, a non-anon class is considered anon.
+ if (exitingPickler(innerSym.isAnonymousClass || innerSym.isAnonymousFunction)) null
+ else innerSym.rawname + innerSym.moduleSuffix
+ }
+
+ val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases
innerClassBuffer ++= {
- val members = exitingPickler(memberClassesOf(csym))
+ val members = exitingPickler(memberClassesForInnerClassTable(csym))
// lambdalift makes all classes (also local, anonymous) members of their enclosing class
- val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(csym))
+ val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(csym))
+ val nested = {
+ // Classes nested in value classes are nested in the companion at this point. For InnerClass /
+ // EnclosingMethod, we use the value class as the outer class. So we remove nested classes
+ // from the companion that were originally nested in the value class.
+ if (exitingPickler(linkedClass.isDerivedValueClass)) allNested.filterNot(classOriginallyNestedInClass(_, linkedClass))
+ else allNested
+ }
- // for the mirror class, we take the members of the companion module class (Java compat,
- // see doc in BTypes.scala). for module classes, we filter out those members.
- if (isMirror) members
- else if (isTopLevelModule(csym)) allNested diff members
- else allNested
- }
+ // for the mirror class, we take the members of the companion module class (Java compat, see doc in BTypes.scala).
+ // for module classes, we filter out those members.
+ if (isMirror) members
+ else if (isTopLevelModule(csym)) nested diff members
+ else nested
+ }
+
+ if (!considerAsTopLevelImplementationArtifact(csym)) {
+ // If this is a top-level non-impl class, add members of the companion object. These are the
+ // classes for which we change the InnerClass entry to allow using them from Java.
+ // We exclude impl classes: if the classfile for the impl class exists on the classpath, a
+ // linkedClass symbol is found for which isTopLevelModule is true, so we end up searching
+ // members of that weird impl-class-module-class-symbol. that search probably cannot return
+ // any classes, but it's better to exclude it.
+ if (linkedClass != NoSymbol && isTopLevelModule(linkedClass)) {
+ // phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only
+ // sees member classes, not local classes that were lifted by lambdalift.
+ innerClassBuffer ++= exitingPickler(memberClassesForInnerClassTable(linkedClass))
+ }
- // If this is a top-level class, add members of the companion object.
- val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases
- if (isTopLevelModule(linkedClass)) {
- // phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes,
- // not local classes that were lifted by lambdalift.
- innerClassBuffer ++= exitingPickler(memberClassesOf(linkedClass))
+ // Classes nested in value classes are nested in the companion at this point. For InnerClass /
+ // EnclosingMethod we use the value class as enclosing class. Here we search nested classes
+ // in the companion that were originally nested in the value class, and we add them as nested
+ // in the value class.
+ if (linkedClass != NoSymbol && exitingPickler(csym.isDerivedValueClass)) {
+ val moduleMemberClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(linkedClass))
+ innerClassBuffer ++= moduleMemberClasses.filter(classOriginallyNestedInClass(_, csym))
+ }
}
val allInners: List[Symbol] = innerClassBuffer.toList filterNot deadCode.elidedClosures
@@ -1266,6 +1293,9 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
jclass.visitAttribute(if(ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
emitAnnotations(jclass, c.symbol.annotations ++ ssa)
+ if (!settings.YskipInlineInfoAttribute.value)
+ jclass.visitAttribute(InlineInfoAttribute(buildInlineInfoFromClassSymbol(c.symbol, javaName, javaType(_).getDescriptor)))
+
// typestate: entering mode with valid call sequences:
// ( visitInnerClass | visitField | visitMethod )* visitEnd
@@ -2024,7 +2054,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
seen ::= LocVarEntry(lv, start, end)
case _ =>
// TODO SI-6049 track down the cause for these.
- debugwarn(s"$iPos: Visited SCOPE_EXIT before visiting corresponding SCOPE_ENTER. SI-6191")
+ devWarning(s"$iPos: Visited SCOPE_EXIT before visiting corresponding SCOPE_ENTER. SI-6191")
}
}
@@ -2394,7 +2424,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
// SI-6102: Determine whether eliding this JUMP results in an empty range being covered by some EH.
// If so, emit a NOP in place of the elided JUMP, to avoid "java.lang.ClassFormatError: Illegal exception table range"
else if (newNormal.isJumpOnly(b) && m.exh.exists(eh => eh.covers(b))) {
- debugwarn("Had a jump only block that wasn't collapsed")
+ devWarning("Had a jump only block that wasn't collapsed")
emit(asm.Opcodes.NOP)
}
@@ -2853,8 +2883,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
var fieldList = List[String]()
for (f <- clasz.fields if f.symbol.hasGetter;
- g = f.symbol.getter(clasz.symbol);
- s = f.symbol.setter(clasz.symbol)
+ g = f.symbol.getterIn(clasz.symbol);
+ s = f.symbol.setterIn(clasz.symbol)
if g.isPublic && !(f.symbol.name startsWith "$")
) {
// inserting $outer breaks the bean
@@ -3106,13 +3136,13 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
val (remappings, cycles) = detour partition {case (source, target) => source != target}
for ((source, target) <- remappings) {
debuglog(s"Will elide jump only block $source because it can be jumped around to get to $target.")
- if (m.startBlock == source) debugwarn("startBlock should have been re-wired by now")
+ if (m.startBlock == source) devWarning("startBlock should have been re-wired by now")
}
val sources = remappings.keySet
val targets = remappings.values.toSet
val intersection = sources intersect targets
- if (intersection.nonEmpty) debugwarn(s"contradiction: we seem to have some source and target overlap in blocks ${intersection.mkString}. Map was ${detour.mkString}")
+ if (intersection.nonEmpty) devWarning(s"contradiction: we seem to have some source and target overlap in blocks ${intersection.mkString}. Map was ${detour.mkString}")
for ((source, _) <- cycles) {
debuglog(s"Block $source is in a do-nothing infinite loop. Did the user write 'while(true){}'?")
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
index d5e95c47cf..be1595dc29 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
@@ -217,11 +217,26 @@ abstract class GenBCode extends BCodeSyncAndTry {
class Worker2 {
lazy val localOpt = new LocalOpt(settings)
+ def runGlobalOptimizations(): Unit = {
+ import scala.collection.convert.decorateAsScala._
+ q2.asScala foreach {
+ case Item2(_, _, plain, _, _) =>
+ // skip mirror / bean: wd don't inline into tem, and they are not used in the plain class
+ if (plain != null) {
+ localOpt.minimalRemoveUnreachableCode(plain)
+ callGraph.addClass(plain)
+ }
+ }
+ bTypes.inliner.runInliner()
+ }
+
def localOptimizations(classNode: ClassNode): Unit = {
BackendStats.timed(BackendStats.methodOptTimer)(localOpt.methodOptimizations(classNode))
}
def run() {
+ if (settings.YoptInlinerEnabled) runGlobalOptimizations()
+
while (true) {
val item = q2.poll
if (item.isPoison) {
@@ -269,7 +284,12 @@ abstract class GenBCode extends BCodeSyncAndTry {
var arrivalPos = 0
- /*
+ /**
+ * The `run` method is overridden because the backend has a different data flow than the default
+ * phase: the backend does not transform compilation units one by one, but on all units in the
+ * same run. This allows cross-unit optimizations and running some stages of the backend
+ * concurrently on multiple units.
+ *
* A run of the BCodePhase phase comprises:
*
* (a) set-up steps (most notably supporting maps in `BCodeTypes`,
@@ -287,6 +307,10 @@ abstract class GenBCode extends BCodeSyncAndTry {
arrivalPos = 0 // just in case
scalaPrimitives.init()
bTypes.initializeCoreBTypes()
+ bTypes.javaDefinedClasses.clear()
+ bTypes.javaDefinedClasses ++= currentRun.symSource collect {
+ case (sym, _) if sym.isJavaDefined => sym.javaBinaryName.toString
+ }
Statistics.stopTimer(BackendStats.bcodeInitTimer, initStart)
// initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated.
@@ -410,4 +434,7 @@ object GenBCode {
final val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC
final val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL
+
+ val CLASS_CONSTRUCTOR_NAME = "<clinit>"
+ val INSTANCE_CONSTRUCTOR_NAME = "<init>"
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
index 7b424d2107..607b7145d6 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
@@ -10,11 +10,14 @@ package opt
import scala.tools.asm
import asm.tree._
import scala.collection.convert.decorateAsScala._
+import scala.tools.asm.Attribute
+import scala.tools.nsc.backend.jvm.BackendReporting._
import scala.tools.nsc.io.AbstractFile
import scala.tools.nsc.util.ClassFileLookup
-import OptimizerReporting._
+import BytecodeUtils._
import ByteCodeRepository._
import BTypes.InternalName
+import java.util.concurrent.atomic.AtomicLong
/**
* The ByteCodeRepository provides utilities to read the bytecode of classfiles from the compilation
@@ -24,58 +27,125 @@ import BTypes.InternalName
* @param classes Cache for parsed ClassNodes. Also stores the source of the bytecode:
* [[Classfile]] if read from `classPath`, [[CompilationUnit]] if the bytecode
* corresponds to a class being compiled.
+ * The `Long` field encodes the age of the node in the map, which allows removing
+ * old entries when the map grows too large.
+ * For Java classes in mixed compilation, the map contains an error message: no
+ * ClassNode is generated by the backend and also no classfile that could be parsed.
*/
-class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val classes: collection.concurrent.Map[InternalName, (ClassNode, Source)]) {
+class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJavaSourceDefined: InternalName => Boolean, val classes: collection.concurrent.Map[InternalName, Either[ClassNotFound, (ClassNode, Source, Long)]]) {
+
+ private val maxCacheSize = 1500
+ private val targetSize = 500
+
+ private val idCounter = new AtomicLong(0)
+
+ /**
+ * Prevent the code repository from growing too large. Profiling reveals that the average size
+ * of a ClassNode is about 30 kb. I observed having 17k+ classes in the cache, i.e., 500 mb.
+ *
+ * We can only remove classes with `Source == Classfile`, those can be parsed again if requested.
+ */
+ private def limitCacheSize(): Unit = {
+ if (classes.count(c => c._2.isRight && c._2.right.get._2 == Classfile) > maxCacheSize) {
+ val removeId = idCounter.get - targetSize
+ val toRemove = classes.iterator.collect({
+ case (name, Right((_, Classfile, id))) if id < removeId => name
+ }).toList
+ toRemove foreach classes.remove
+ }
+ }
+
+ def add(classNode: ClassNode, source: Source) = {
+ classes(classNode.name) = Right((classNode, source, idCounter.incrementAndGet()))
+ }
+
/**
* The class node and source for an internal name. If the class node is not yet available, it is
* parsed from the classfile on the compile classpath.
*/
- def classNodeAndSource(internalName: InternalName): (ClassNode, Source) = {
- classes.getOrElseUpdate(internalName, (parseClass(internalName), Classfile))
+ def classNodeAndSource(internalName: InternalName): Either[ClassNotFound, (ClassNode, Source)] = {
+ val r = classes.getOrElseUpdate(internalName, {
+ limitCacheSize()
+ parseClass(internalName).map((_, Classfile, idCounter.incrementAndGet()))
+ })
+ r.map(v => (v._1, v._2))
}
/**
* The class node for an internal name. If the class node is not yet available, it is parsed from
* the classfile on the compile classpath.
*/
- def classNode(internalName: InternalName) = classNodeAndSource(internalName)._1
+ def classNode(internalName: InternalName): Either[ClassNotFound, ClassNode] = classNodeAndSource(internalName).map(_._1)
/**
* The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`.
* The declaration of the field may be in one of the superclasses.
*
- * @return The [[FieldNode]] of the requested field and the [[InternalName]] of its declaring class.
+ * @return The [[FieldNode]] of the requested field and the [[InternalName]] of its declaring
+ * class, or an error message if the field could not be found
*/
- def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Option[(FieldNode, InternalName)] = {
- val c = classNode(classInternalName)
- c.fields.asScala.find(f => f.name == name && f.desc == descriptor).map((_, classInternalName)) orElse {
- Option(c.superName).flatMap(n => fieldNode(n, name, descriptor))
+ def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Either[FieldNotFound, (FieldNode, InternalName)] = {
+ def fieldNodeImpl(parent: InternalName): Either[FieldNotFound, (FieldNode, InternalName)] = {
+ def msg = s"The field node $name$descriptor could not be found in class $classInternalName or any of its superclasses."
+ classNode(parent) match {
+ case Left(e) => Left(FieldNotFound(name, descriptor, classInternalName, Some(e)))
+ case Right(c) =>
+ c.fields.asScala.find(f => f.name == name && f.desc == descriptor) match {
+ case Some(f) => Right((f, parent))
+ case None =>
+ if (c.superName == null) Left(FieldNotFound(name, descriptor, classInternalName, None))
+ else fieldNode(c.superName, name, descriptor)
+ }
+ }
}
+ fieldNodeImpl(classInternalName)
}
/**
* The method node for a method matching `name` and `descriptor`, accessed in class `classInternalName`.
* The declaration of the method may be in one of the parents.
*
- * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring class.
+ * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring
+ * class, or an error message if the method could not be found.
*/
- def methodNode(classInternalName: InternalName, name: String, descriptor: String): Option[(MethodNode, InternalName)] = {
- val c = classNode(classInternalName)
- c.methods.asScala.find(m => m.name == name && m.desc == descriptor).map((_, classInternalName)) orElse {
- val parents = Option(c.superName) ++ c.interfaces.asScala
- // `view` to stop at the first result
- parents.view.flatMap(methodNode(_, name, descriptor)).headOption
+ def methodNode(ownerInternalNameOrArrayDescriptor: String, name: String, descriptor: String): Either[MethodNotFound, (MethodNode, InternalName)] = {
+ // on failure, returns a list of class names that could not be found on the classpath
+ def methodNodeImpl(ownerInternalName: InternalName): Either[List[ClassNotFound], (MethodNode, InternalName)] = {
+ classNode(ownerInternalName) match {
+ case Left(e) => Left(List(e))
+ case Right(c) =>
+ c.methods.asScala.find(m => m.name == name && m.desc == descriptor) match {
+ case Some(m) => Right((m, ownerInternalName))
+ case None => findInParents(Option(c.superName) ++: c.interfaces.asScala.toList, Nil)
+ }
+ }
}
+
+ // find the MethodNode in one of the parent classes
+ def findInParents(parents: List[InternalName], failedClasses: List[ClassNotFound]): Either[List[ClassNotFound], (MethodNode, InternalName)] = parents match {
+ case x :: xs => methodNodeImpl(x).left.flatMap(failed => findInParents(xs, failed ::: failedClasses))
+ case Nil => Left(failedClasses)
+ }
+
+ // In a MethodInsnNode, the `owner` field may be an array descriptor, for exmple when invoking `clone`. We don't have a method node to return in this case.
+ if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[')
+ Left(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, Nil))
+ else
+ methodNodeImpl(ownerInternalNameOrArrayDescriptor).left.map(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, _))
}
- private def parseClass(internalName: InternalName): ClassNode = {
+ private def parseClass(internalName: InternalName): Either[ClassNotFound, ClassNode] = {
val fullName = internalName.replace('/', '.')
classPath.findClassFile(fullName) map { classFile =>
val classNode = new asm.tree.ClassNode()
val classReader = new asm.ClassReader(classFile.toByteArray)
+
+ // Passing the InlineInfoAttributePrototype makes the ClassReader invoke the specific `read`
+ // method of the InlineInfoAttribute class, instead of putting the byte array into a generic
+ // Attribute.
// We don't need frames when inlining, but we want to keep the local variable table, so we
// don't use SKIP_DEBUG.
- classReader.accept(classNode, asm.ClassReader.SKIP_FRAMES)
+ classReader.accept(classNode, Array[Attribute](InlineInfoAttributePrototype), asm.ClassReader.SKIP_FRAMES)
// SKIP_FRAMES leaves line number nodes. Remove them because they are not correct after
// inlining.
// TODO: we need to remove them also for classes that are not parsed from classfiles, why not simplify and do it once when inlining?
@@ -85,18 +155,9 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class
// https://jcp.org/aboutJava/communityprocess/final/jsr045/index.html
removeLineNumberNodes(classNode)
classNode
- } getOrElse {
- inlineFailure(s"Class file for class $fullName not found.")
- }
- }
-
- private def removeLineNumberNodes(classNode: ClassNode): Unit = {
- for (method <- classNode.methods.asScala) {
- val iter = method.instructions.iterator()
- while (iter.hasNext) iter.next() match {
- case _: LineNumberNode => iter.remove()
- case _ =>
- }
+ } match {
+ case Some(node) => Right(node)
+ case None => Left(ClassNotFound(internalName, isJavaSourceDefined(internalName)))
}
}
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
index 6b4047c0a7..14e8cccc60 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
@@ -10,9 +10,14 @@ 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.analysis._
+import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes}
import scala.tools.asm.tree._
import scala.collection.convert.decorateAsScala._
+import GenBCode._
+import scala.collection.convert.decorateAsScala._
+import scala.collection.convert.decorateAsJava._
+import scala.tools.nsc.backend.jvm.BTypes._
object BytecodeUtils {
@@ -68,6 +73,20 @@ object BytecodeUtils {
def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0
+ def isConstructor(methodNode: MethodNode): Boolean = {
+ methodNode.name == INSTANCE_CONSTRUCTOR_NAME || methodNode.name == CLASS_CONSTRUCTOR_NAME
+ }
+
+ def isStaticMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STATIC) != 0
+
+ def isAbstractMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_ABSTRACT) != 0
+
+ def isSynchronizedMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_SYNCHRONIZED) != 0
+
+ def isFinalClass(classNode: ClassNode): Boolean = (classNode.access & Opcodes.ACC_FINAL) != 0
+
+ def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) != 0
+
def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = {
var result = instruction
do { result = result.getNext }
@@ -181,4 +200,115 @@ object BytecodeUtils {
if (handler.end == from) handler.end = to
}
}
+
+ /**
+ * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM
+ * framework only computes these values during bytecode generation.
+ *
+ * Since there's currently no better way, we run a bytecode generator on the method and extract
+ * the computed values. This required changes to the ASM codebase:
+ * - the [[MethodWriter]] class was made public
+ * - accessors for maxLocals / maxStack were added to the MethodWriter class
+ *
+ * We could probably make this faster (and allocate less memory) by hacking the ASM framework
+ * more: create a subclass of MethodWriter with a /dev/null byteVector. Another option would be
+ * to create a separate visitor for computing those values, duplicating the functionality from the
+ * MethodWriter.
+ */
+ def computeMaxLocalsMaxStack(method: MethodNode) {
+ val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS)
+ val excs = method.exceptions.asScala.toArray
+ val mw = cw.visitMethod(method.access, method.name, method.desc, method.signature, excs).asInstanceOf[MethodWriter]
+ method.accept(mw)
+ method.maxLocals = mw.getMaxLocals
+ method.maxStack = mw.getMaxStack
+ }
+
+ def removeLineNumberNodes(classNode: ClassNode): Unit = {
+ for (m <- classNode.methods.asScala) removeLineNumberNodes(m.instructions)
+ }
+
+ def removeLineNumberNodes(instructions: InsnList): Unit = {
+ val iter = instructions.iterator()
+ while (iter.hasNext) iter.next() match {
+ case _: LineNumberNode => iter.remove()
+ case _ =>
+ }
+ }
+
+ def cloneLabels(methodNode: MethodNode): Map[LabelNode, LabelNode] = {
+ methodNode.instructions.iterator().asScala.collect({
+ case labelNode: LabelNode => (labelNode, newLabelNode)
+ }).toMap
+ }
+
+ /**
+ * Create a new [[LabelNode]] with a correctly associated [[Label]].
+ */
+ def newLabelNode: LabelNode = {
+ val label = new Label
+ val labelNode = new LabelNode(label)
+ label.info = labelNode
+ labelNode
+ }
+
+ /**
+ * Clone the instructions in `methodNode` into a new [[InsnList]], mapping labels according to
+ * the `labelMap`. Returns the new instruction list and a map from old to new instructions.
+ */
+ def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode]): (InsnList, Map[AbstractInsnNode, AbstractInsnNode]) = {
+ val javaLabelMap = labelMap.asJava
+ val result = new InsnList
+ var map = Map.empty[AbstractInsnNode, AbstractInsnNode]
+ for (ins <- methodNode.instructions.iterator.asScala) {
+ val cloned = ins.clone(javaLabelMap)
+ result add cloned
+ map += ((ins, cloned))
+ }
+ (result, map)
+ }
+
+ /**
+ * Clone the local variable descriptors of `methodNode` and map their `start` and `end` labels
+ * according to the `labelMap`.
+ */
+ def cloneLocalVariableNodes(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], prefix: String): List[LocalVariableNode] = {
+ methodNode.localVariables.iterator().asScala.map(localVariable => new LocalVariableNode(
+ prefix + localVariable.name,
+ localVariable.desc,
+ localVariable.signature,
+ labelMap(localVariable.start),
+ labelMap(localVariable.end),
+ localVariable.index
+ )).toList
+ }
+
+ /**
+ * Clone the local try/catch blocks of `methodNode` and map their `start` and `end` and `handler`
+ * labels according to the `labelMap`.
+ */
+ def cloneTryCatchBlockNodes(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode]): List[TryCatchBlockNode] = {
+ methodNode.tryCatchBlocks.iterator().asScala.map(tryCatch => new TryCatchBlockNode(
+ labelMap(tryCatch.start),
+ labelMap(tryCatch.end),
+ labelMap(tryCatch.handler),
+ tryCatch.`type`
+ )).toList
+ }
+
+ /**
+ * A wrapper to make ASM's Analyzer a bit easier to use.
+ */
+ class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, interpreter: Interpreter[V] = new BasicInterpreter) {
+ val analyzer = new Analyzer(interpreter)
+ analyzer.analyze(classInternalName, methodNode)
+ def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.getFrames()(methodNode.instructions.indexOf(instruction))
+ }
+
+ implicit class `frame extensions`[V <: Value](val frame: Frame[V]) extends AnyVal {
+ def peekDown(n: Int): V = {
+ val topIndex = frame.getStackSize - 1
+ frame.getStack(topIndex - n)
+ }
+ }
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
new file mode 100644
index 0000000000..47d32c94cb
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
@@ -0,0 +1,189 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2014 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import scala.reflect.internal.util.{NoPosition, Position}
+import scala.tools.asm.tree._
+import scala.collection.convert.decorateAsScala._
+import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InternalName}
+import scala.tools.nsc.backend.jvm.BackendReporting._
+import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer
+import ByteCodeRepository.{Source, CompilationUnit}
+
+class CallGraph[BT <: BTypes](val btypes: BT) {
+ import btypes._
+
+ val callsites: collection.concurrent.Map[MethodInsnNode, Callsite] = recordPerRunCache(collection.concurrent.TrieMap.empty[MethodInsnNode, Callsite])
+
+ def addClass(classNode: ClassNode): Unit = {
+ for (m <- classNode.methods.asScala; callsite <- analyzeCallsites(m, classBTypeFromClassNode(classNode)))
+ callsites(callsite.callsiteInstruction) = callsite
+ }
+
+ def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): List[Callsite] = {
+
+ case class CallsiteInfo(safeToInline: Boolean, safeToRewrite: Boolean,
+ annotatedInline: Boolean, annotatedNoInline: Boolean,
+ warning: Option[CalleeInfoWarning])
+
+ /**
+ * Analyze a callsite and gather meta-data that can be used for inlining decisions.
+ */
+ def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, receiverTypeInternalName: InternalName, calleeSource: Source): CallsiteInfo = {
+ val methodSignature = calleeMethodNode.name + calleeMethodNode.desc
+
+ try {
+ // The inlineInfo.methodInfos of a ClassBType holds an InlineInfo for each method *declared*
+ // within a class (not for inherited methods). Since we already have the classBType of the
+ // callee, we only check there for the methodInlineInfo, we should find it there.
+ calleeDeclarationClassBType.info.orThrow.inlineInfo.methodInfos.get(methodSignature) match {
+ case Some(methodInlineInfo) =>
+ val canInlineFromSource = inlineGlobalEnabled || calleeSource == CompilationUnit
+
+ val isAbstract = BytecodeUtils.isAbstractMethod(calleeMethodNode)
+
+ // (1) A non-final method can be safe to inline if the receiver type is a final subclass. Example:
+ // class A { @inline def f = 1 }; object B extends A; B.f // can be inlined
+ //
+ // TODO: type analysis can render more calls statically resolved. Example˜∫
+ // new A.f // can be inlined, the receiver type is known to be exactly A.
+ val isStaticallyResolved: Boolean = {
+ methodInlineInfo.effectivelyFinal ||
+ classBTypeFromParsedClassfile(receiverTypeInternalName).info.orThrow.inlineInfo.isEffectivelyFinal // (1)
+ }
+
+ val isRewritableTraitCall = isStaticallyResolved && methodInlineInfo.traitMethodWithStaticImplementation
+
+ val warning = calleeDeclarationClassBType.info.orThrow.inlineInfo.warning.map(
+ MethodInlineInfoIncomplete(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, _))
+
+ // (1) For invocations of final trait methods, the callee isStaticallyResolved but also
+ // abstract. Such a callee is not safe to inline - it needs to be re-written to the
+ // static impl method first (safeToRewrite).
+ // (2) Final trait methods can be rewritten from the interface to the static implementation
+ // method to enable inlining.
+ CallsiteInfo(
+ safeToInline = canInlineFromSource && isStaticallyResolved && !isAbstract, // (1)
+ safeToRewrite = canInlineFromSource && isRewritableTraitCall, // (2)
+ annotatedInline = methodInlineInfo.annotatedInline,
+ annotatedNoInline = methodInlineInfo.annotatedNoInline,
+ warning = warning)
+
+ case None =>
+ val warning = MethodInlineInfoMissing(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, calleeDeclarationClassBType.info.orThrow.inlineInfo.warning)
+ CallsiteInfo(false, false, false, false, Some(warning))
+ }
+ } catch {
+ case Invalid(noInfo: NoClassBTypeInfo) =>
+ val warning = MethodInlineInfoError(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, noInfo)
+ CallsiteInfo(false, false, false, false, Some(warning))
+ }
+ }
+
+ // TODO: run dataflow analyses to make the call graph more precise
+ // - producers to get forwarded parameters (ForwardedParam)
+ // - typeAnalysis for more precise argument types, more precise callee
+ // - nullAnalysis to skip emitting the receiver-null-check when inlining
+
+ // TODO: for now we run a basic analyzer to get the stack height at the call site.
+ // once we run a more elaborate analyzer (types, nullness), we can get the stack height out of there.
+ val analyzer = new AsmAnalyzer(methodNode, definingClass.internalName)
+
+ methodNode.instructions.iterator.asScala.collect({
+ case call: MethodInsnNode =>
+ val callee: Either[OptimizerWarning, Callee] = for {
+ (method, declarationClass) <- byteCodeRepository.methodNode(call.owner, call.name, call.desc): Either[OptimizerWarning, (MethodNode, InternalName)]
+ (declarationClassNode, source) <- byteCodeRepository.classNodeAndSource(declarationClass): Either[OptimizerWarning, (ClassNode, Source)]
+ declarationClassBType = classBTypeFromClassNode(declarationClassNode)
+ } yield {
+ val CallsiteInfo(safeToInline, safeToRewrite, annotatedInline, annotatedNoInline, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source)
+ Callee(
+ callee = method,
+ calleeDeclarationClass = declarationClassBType,
+ safeToInline = safeToInline,
+ safeToRewrite = safeToRewrite,
+ annotatedInline = annotatedInline,
+ annotatedNoInline = annotatedNoInline,
+ calleeInfoWarning = warning)
+ }
+
+ val argInfos = if (callee.isLeft) Nil else {
+ // TODO: for now it's Nil, because we don't run any data flow analysis
+ // there's no point in using the parameter types, that doesn't add any information.
+ // NOTE: need to run the same analyses after inlining, to re-compute the argInfos for the
+ // new duplicated callsites, see Inliner.inline
+ Nil
+ }
+
+ Callsite(
+ callsiteInstruction = call,
+ callsiteMethod = methodNode,
+ callsiteClass = definingClass,
+ callee = callee,
+ argInfos = argInfos,
+ callsiteStackHeight = analyzer.frameAt(call).getStackSize,
+ callsitePosition = callsitePositions.getOrElse(call, NoPosition)
+ )
+ }).toList
+ }
+
+ /**
+ * A callsite in the call graph.
+ *
+ * @param callsiteInstruction The invocation instruction
+ * @param callsiteMethod The method containing the callsite
+ * @param callsiteClass The class containing the callsite
+ * @param callee The callee, as it appears in the invocation instruction. For virtual
+ * calls, an override of the callee might be invoked. Also, the callee
+ * can be abstract. Contains a warning message if the callee MethodNode
+ * cannot be found in the bytecode repository.
+ * @param argInfos Information about the invocation receiver and arguments
+ * @param callsiteStackHeight The stack height at the callsite, required by the inliner
+ * @param callsitePosition The source position of the callsite, used for inliner warnings.
+ */
+ final case class Callsite(callsiteInstruction: MethodInsnNode, callsiteMethod: MethodNode, callsiteClass: ClassBType,
+ callee: Either[OptimizerWarning, Callee], argInfos: List[ArgInfo],
+ callsiteStackHeight: Int, callsitePosition: Position) {
+ override def toString =
+ "Invocation of" +
+ s" ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}" +
+ s"@${callsiteMethod.instructions.indexOf(callsiteInstruction)}" +
+ s" in ${callsiteClass.internalName}.${callsiteMethod.name}"
+ }
+
+ /**
+ * Information about invocation arguments, obtained through data flow analysis of the callsite method.
+ */
+ sealed trait ArgInfo
+ final case class ArgTypeInfo(argType: BType, isPrecise: Boolean, knownNotNull: Boolean) extends ArgInfo
+ final case class ForwardedParam(index: Int) extends ArgInfo
+ // can be extended, e.g., with constant types
+
+ /**
+ * A callee in the call graph.
+ *
+ * @param callee The callee, as it appears in the invocation instruction. For
+ * virtual calls, an override of the callee might be invoked. Also,
+ * the callee can be abstract.
+ * @param calleeDeclarationClass The class in which the callee is declared
+ * @param safeToInline True if the callee can be safely inlined: it cannot be overridden,
+ * and the inliner settings (project / global) allow inlining it.
+ * @param safeToRewrite True if the callee the interface method of a concrete trait method
+ * that can be safely re-written to the static implementation method.
+ * @param annotatedInline True if the callee is annotated @inline
+ * @param annotatedNoInline True if the callee is annotated @noinline
+ * @param calleeInfoWarning An inliner warning if some information was not available while
+ * gathering the information about this callee.
+ */
+ final case class Callee(callee: MethodNode, calleeDeclarationClass: ClassBType,
+ safeToInline: Boolean, safeToRewrite: Boolean,
+ annotatedInline: Boolean, annotatedNoInline: Boolean,
+ calleeInfoWarning: Option[CalleeInfoWarning]) {
+ assert(!(safeToInline && safeToRewrite), s"A callee of ${callee.name} can be either safeToInline or safeToRewrite, but not both.")
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
new file mode 100644
index 0000000000..e7dd5abc57
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
@@ -0,0 +1,148 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2014 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import scala.tools.asm._
+import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo}
+import scala.tools.nsc.backend.jvm.BackendReporting.UnknownScalaInlineInfoVersion
+
+/**
+ * This attribute stores the InlineInfo for a ClassBType as an independent classfile attribute.
+ * The compiler does so for every class being compiled.
+ *
+ * The reason is that a precise InlineInfo can only be obtained if the symbol for a class is available.
+ * For example, we need to know if a method is final in Scala's terms, or if it has the @inline annotation.
+ * Looking up a class symbol for a given class filename is brittle (name-mangling).
+ *
+ * The attribute is also helpful for inlining mixin methods. The mixin phase only adds mixin method
+ * symbols to classes that are being compiled. For all other class symbols, there are no mixin members.
+ * However, the inliner requires an InlineInfo for inlining mixin members. That problem is solved by
+ * reading the InlineInfo from this attribute.
+ *
+ * In principle we could encode the InlineInfo into a Java annotation (instead of a classfile attribute).
+ * However, an attribute allows us to save many bits. In particular, note that the strings in an
+ * InlineInfo are serialized as references to constants in the constant pool, and those strings
+ * (traitImplClassSelfType, method names, method signatures) would exist in there anyway. So the
+ * ScalaInlineAttribute remains relatively compact.
+ */
+case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineInfoAttribute.attributeName) {
+ /**
+ * Not sure what this method is good for, it is not invoked anywhere in the ASM framework. However,
+ * the example in the ASM manual also overrides it to `false` for custom attributes, so it might be
+ * a good idea.
+ */
+ override def isUnknown: Boolean = false
+
+ /**
+ * Serialize the `inlineInfo` into a byte array. Strings are added to the constant pool and serialized
+ * as references.
+ */
+ override def write(cw: ClassWriter, code: Array[Byte], len: Int, maxStack: Int, maxLocals: Int): ByteVector = {
+ val result = new ByteVector()
+
+ result.putByte(InlineInfoAttribute.VERSION)
+
+ var hasSelfIsFinal = 0
+ if (inlineInfo.isEffectivelyFinal) hasSelfIsFinal |= 1
+ if (inlineInfo.traitImplClassSelfType.isDefined) hasSelfIsFinal |= 2
+ result.putByte(hasSelfIsFinal)
+
+ for (selfInternalName <- inlineInfo.traitImplClassSelfType) {
+ result.putShort(cw.newUTF8(selfInternalName))
+ }
+
+ // The method count fits in a short (the methods_count in a classfile is also a short)
+ result.putShort(inlineInfo.methodInfos.size)
+
+ // Sort the methodInfos for stability of classfiles
+ for ((nameAndType, info) <- inlineInfo.methodInfos.toList.sortBy(_._1)) {
+ val (name, desc) = nameAndType.span(_ != '(')
+ // Name and desc are added separately because a NameAndType entry also stores them separately.
+ // This makes sure that we use the existing constant pool entries for the method.
+ result.putShort(cw.newUTF8(name))
+ result.putShort(cw.newUTF8(desc))
+
+ var inlineInfo = 0
+ if (info.effectivelyFinal) inlineInfo |= 1
+ if (info.traitMethodWithStaticImplementation) inlineInfo |= 2
+ if (info.annotatedInline) inlineInfo |= 4
+ if (info.annotatedNoInline) inlineInfo |= 8
+ result.putByte(inlineInfo)
+ }
+
+ result
+ }
+
+ /**
+ * De-serialize the attribute into an InlineInfo. The attribute starts at cr.b(off), but we don't
+ * need to access that array directly, we can use the `read` methods provided by the ClassReader.
+ *
+ * `buf` is a pre-allocated character array that is guaranteed to be long enough to hold any
+ * string of the constant pool. So we can use it to invoke `cr.readUTF8`.
+ */
+ override def read(cr: ClassReader, off: Int, len: Int, buf: Array[Char], codeOff: Int, labels: Array[Label]): InlineInfoAttribute = {
+ var next = off
+
+ def nextByte() = { val r = cr.readByte(next) ; next += 1; r }
+ def nextUTF8() = { val r = cr.readUTF8(next, buf); next += 2; r }
+ def nextShort() = { val r = cr.readShort(next) ; next += 2; r }
+
+ val version = nextByte()
+ if (version == 1) {
+ val hasSelfIsFinal = nextByte()
+ val isFinal = (hasSelfIsFinal & 1) != 0
+ val hasSelf = (hasSelfIsFinal & 2) != 0
+
+ val self = if (hasSelf) {
+ val selfName = nextUTF8()
+ Some(selfName)
+ } else {
+ None
+ }
+
+ val numEntries = nextShort()
+ val infos = (0 until numEntries).map(_ => {
+ val name = nextUTF8()
+ val desc = nextUTF8()
+
+ val inlineInfo = nextByte()
+ val isFinal = (inlineInfo & 1) != 0
+ val traitMethodWithStaticImplementation = (inlineInfo & 2) != 0
+ val isInline = (inlineInfo & 4) != 0
+ val isNoInline = (inlineInfo & 8) != 0
+ (name + desc, MethodInlineInfo(isFinal, traitMethodWithStaticImplementation, isInline, isNoInline))
+ }).toMap
+
+ InlineInfoAttribute(InlineInfo(self, isFinal, infos, None))
+ } else {
+ val msg = UnknownScalaInlineInfoVersion(cr.getClassName, version)
+ InlineInfoAttribute(BTypes.EmptyInlineInfo.copy(warning = Some(msg)))
+ }
+ }
+}
+
+object InlineInfoAttribute {
+ /**
+ * [u1] version
+ * [u1] isEffectivelyFinal (<< 0), hasTraitImplClassSelfType (<< 1)
+ * [u2]? traitImplClassSelfType (reference)
+ * [u2] numMethodEntries
+ * [u2] name (reference)
+ * [u2] descriptor (reference)
+ * [u1] isFinal (<< 0), traitMethodWithStaticImplementation (<< 1), hasInlineAnnotation (<< 2), hasNoInlineAnnotation (<< 3)
+ */
+ final val VERSION: Byte = 1
+
+ final val attributeName = "ScalaInlineInfo"
+}
+
+/**
+ * In order to instruct the ASM framework to de-serialize the ScalaInlineInfo attribute, we need
+ * to pass a prototype instance when running the class reader.
+ */
+object InlineInfoAttributePrototype extends InlineInfoAttribute(InlineInfo(null, false, null, null))
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
new file mode 100644
index 0000000000..e14e57d3ab
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
@@ -0,0 +1,648 @@
+/* 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
+import scala.tools.asm
+import asm.Opcodes._
+import asm.tree._
+import scala.collection.convert.decorateAsScala._
+import scala.collection.convert.decorateAsJava._
+import AsmUtils._
+import BytecodeUtils._
+import collection.mutable
+import scala.tools.asm.tree.analysis.{SourceInterpreter, Analyzer}
+import BackendReporting._
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
+
+class Inliner[BT <: BTypes](val btypes: BT) {
+ import btypes._
+ import callGraph._
+
+ def runInliner(): Unit = {
+ rewriteFinalTraitMethodInvocations()
+
+ for (request <- collectAndOrderInlineRequests) {
+ val Right(callee) = request.callee // collectAndOrderInlineRequests returns callsites with a known callee
+
+ val r = inline(request.callsiteInstruction, request.callsiteStackHeight, request.callsiteMethod, request.callsiteClass,
+ callee.callee, callee.calleeDeclarationClass,
+ receiverKnownNotNull = false, keepLineNumbers = false)
+
+ for (warning <- r) {
+ if ((callee.annotatedInline && btypes.warnSettings.atInlineFailed) || warning.emitWarning(warnSettings)) {
+ val annotWarn = if (callee.annotatedInline) " is annotated @inline but" else ""
+ val msg = s"${BackendReporting.methodSignature(callee.calleeDeclarationClass.internalName, callee.callee)}$annotWarn could not be inlined:\n$warning"
+ backendReporting.inlinerWarning(request.callsitePosition, msg)
+ }
+ }
+ }
+ }
+
+ /**
+ * Ordering for inline requests. Required to make the inliner deterministic:
+ * - Always remove the same request when breaking inlining cycles
+ * - Perform inlinings in a consistent order
+ */
+ object callsiteOrdering extends Ordering[Callsite] {
+ override def compare(x: Callsite, y: Callsite): Int = {
+ val cls = x.callsiteClass.internalName compareTo y.callsiteClass.internalName
+ if (cls != 0) return cls
+
+ val name = x.callsiteMethod.name compareTo y.callsiteMethod.name
+ if (name != 0) return name
+
+ val desc = x.callsiteMethod.desc compareTo y.callsiteMethod.desc
+ if (desc != 0) return desc
+
+ def pos(c: Callsite) = c.callsiteMethod.instructions.indexOf(c.callsiteInstruction)
+ pos(x) - pos(y)
+ }
+ }
+
+ /**
+ * Select callsites from the call graph that should be inlined. The resulting list of inlining
+ * requests is allowed to have cycles, and the callsites can appear in any order.
+ */
+ def selectCallsitesForInlining: List[Callsite] = {
+ callsites.valuesIterator.filter({
+ case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, annotatedInline, _, warning)), _, _, pos) =>
+ val res = doInlineCallsite(callsite)
+
+ if (!res) {
+ if (annotatedInline && btypes.warnSettings.atInlineFailed) {
+ // if the callsite is annotated @inline, we report an inline warning even if the underlying
+ // reason is, for example, mixed compilation (which has a separate -Yopt-warning flag).
+ def initMsg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)} is annotated @inline but cannot be inlined"
+ def warnMsg = warning.map(" Possible reason:\n" + _).getOrElse("")
+ if (doRewriteTraitCallsite(callsite))
+ backendReporting.inlinerWarning(pos, s"$initMsg: the trait method call could not be rewritten to the static implementation method." + warnMsg)
+ else if (!safeToInline)
+ backendReporting.inlinerWarning(pos, s"$initMsg: the method is not final and may be overridden." + warnMsg)
+ else
+ backendReporting.inlinerWarning(pos, s"$initMsg." + warnMsg)
+ } else if (warning.isDefined && warning.get.emitWarning(warnSettings)) {
+ // when annotatedInline is false, and there is some warning, the callsite metadata is possibly incomplete.
+ backendReporting.inlinerWarning(pos, s"there was a problem determining if method ${callee.name} can be inlined: \n"+ warning.get)
+ }
+ }
+
+ res
+
+ case Callsite(ins, _, _, Left(warning), _, _, pos) =>
+ if (warning.emitWarning(warnSettings))
+ backendReporting.inlinerWarning(pos, s"failed to determine if ${ins.name} should be inlined:\n$warning")
+ false
+ }).toList
+ }
+
+ /**
+ * The current inlining heuristics are simple: inline calls to methods annotated @inline.
+ */
+ def doInlineCallsite(callsite: Callsite): Boolean = callsite match {
+ case Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, annotatedInline, _, warning)), _, _, pos) =>
+ annotatedInline && safeToInline
+
+ case _ => false
+ }
+
+ def rewriteFinalTraitMethodInvocations(): Unit = {
+ // Rewriting final trait method callsites to the implementation class enables inlining.
+ // We cannot just iterate over the values of the `callsites` map because the rewrite changes the
+ // map. Therefore we first copy the values to a list.
+ callsites.values.toList.foreach(rewriteFinalTraitMethodInvocation)
+ }
+
+ /**
+ * True for statically resolved trait callsites that should be rewritten to the static implementation method.
+ */
+ def doRewriteTraitCallsite(callsite: Callsite) = callsite.callee match {
+ case Right(Callee(callee, calleeDeclarationClass, safeToInline, true, annotatedInline, annotatedNoInline, infoWarning)) => true
+ case _ => false
+ }
+
+ /**
+ * Rewrite the INVOKEINTERFACE callsite of a final trait method invocation to INVOKESTATIC of the
+ * corresponding method in the implementation class. This enables inlining final trait methods.
+ *
+ * In a final trait method callsite, the callee is safeToInline and the callee method is abstract
+ * (the receiver type is the interface, so the method is abstract).
+ */
+ def rewriteFinalTraitMethodInvocation(callsite: Callsite): Unit = {
+ if (doRewriteTraitCallsite(callsite)) {
+ val Right(Callee(callee, calleeDeclarationClass, _, _, annotatedInline, annotatedNoInline, infoWarning)) = callsite.callee
+
+ val traitMethodArgumentTypes = asm.Type.getArgumentTypes(callee.desc)
+
+ val implClassInternalName = calleeDeclarationClass.internalName + "$class"
+
+ val selfParamTypeV: Either[OptimizerWarning, ClassBType] = calleeDeclarationClass.info.map(_.inlineInfo.traitImplClassSelfType match {
+ case Some(internalName) => classBTypeFromParsedClassfile(internalName)
+ case None => calleeDeclarationClass
+ })
+
+ def implClassMethodV(implMethodDescriptor: String): Either[OptimizerWarning, MethodNode] = {
+ byteCodeRepository.methodNode(implClassInternalName, callee.name, implMethodDescriptor).map(_._1)
+ }
+
+ // The rewrite reading the implementation class and the implementation method from the bytecode
+ // repository. If either of the two fails, the rewrite is not performed.
+ val res = for {
+ selfParamType <- selfParamTypeV
+ implMethodDescriptor = asm.Type.getMethodDescriptor(asm.Type.getReturnType(callee.desc), selfParamType.toASMType +: traitMethodArgumentTypes: _*)
+ implClassMethod <- implClassMethodV(implMethodDescriptor)
+ implClassBType = classBTypeFromParsedClassfile(implClassInternalName)
+ selfTypeOk <- calleeDeclarationClass.isSubtypeOf(selfParamType)
+ } yield {
+
+ // The self parameter type may be incompatible with the trait type.
+ // trait T { self: S => def foo = 1 }
+ // The $self parameter type of T$class.foo is S, which may be unrelated to T. If we re-write
+ // a call to T.foo to T$class.foo, we need to cast the receiver to S, otherwise we get a
+ // VerifyError. We run a `SourceInterpreter` to find all producer instructions of the
+ // receiver value and add a cast to the self type after each.
+ if (!selfTypeOk) {
+ val analyzer = new AsmAnalyzer(callsite.callsiteMethod, callsite.callsiteClass.internalName, new SourceInterpreter)
+ val receiverValue = analyzer.frameAt(callsite.callsiteInstruction).peekDown(traitMethodArgumentTypes.length)
+ for (i <- receiverValue.insns.asScala) {
+ val cast = new TypeInsnNode(CHECKCAST, selfParamType.internalName)
+ callsite.callsiteMethod.instructions.insert(i, cast)
+ }
+ }
+
+ val newCallsiteInstruction = new MethodInsnNode(INVOKESTATIC, implClassInternalName, callee.name, implMethodDescriptor, false)
+ callsite.callsiteMethod.instructions.insert(callsite.callsiteInstruction, newCallsiteInstruction)
+ callsite.callsiteMethod.instructions.remove(callsite.callsiteInstruction)
+
+ callGraph.callsites.remove(callsite.callsiteInstruction)
+ val staticCallsite = Callsite(
+ callsiteInstruction = newCallsiteInstruction,
+ callsiteMethod = callsite.callsiteMethod,
+ callsiteClass = callsite.callsiteClass,
+ callee = Right(Callee(
+ callee = implClassMethod,
+ calleeDeclarationClass = implClassBType,
+ safeToInline = true,
+ safeToRewrite = false,
+ annotatedInline = annotatedInline,
+ annotatedNoInline = annotatedNoInline,
+ calleeInfoWarning = infoWarning)),
+ argInfos = Nil,
+ callsiteStackHeight = callsite.callsiteStackHeight,
+ callsitePosition = callsite.callsitePosition
+ )
+ callGraph.callsites(newCallsiteInstruction) = staticCallsite
+ }
+
+ for (warning <- res.left) {
+ val Right(callee) = callsite.callee
+ val newCallee = callee.copy(calleeInfoWarning = Some(RewriteTraitCallToStaticImplMethodFailed(calleeDeclarationClass.internalName, callee.callee.name, callee.callee.desc, warning)))
+ callGraph.callsites(callsite.callsiteInstruction) = callsite.copy(callee = Right(newCallee))
+ }
+ }
+ }
+
+ /**
+ * Returns the callsites that can be inlined. Ensures that the returned inline request graph does
+ * not contain cycles.
+ *
+ * The resulting list is sorted such that the leaves of the inline request graph are on the left.
+ * Once these leaves are inlined, the successive elements will be leaves, etc.
+ */
+ private def collectAndOrderInlineRequests: List[Callsite] = {
+ val requests = selectCallsitesForInlining
+
+ // This map is an index to look up the inlining requests for a method. The value sets are mutable
+ // to allow removing elided requests (to break inlining cycles). The map itself is mutable to
+ // allow efficient building: requests.groupBy would build values as List[Callsite] that need to
+ // be transformed to mutable sets.
+ val inlineRequestsForMethod: mutable.Map[MethodNode, mutable.Set[Callsite]] = mutable.HashMap.empty.withDefaultValue(mutable.HashSet.empty)
+ for (r <- requests) inlineRequestsForMethod.getOrElseUpdate(r.callsiteMethod, mutable.HashSet.empty) += r
+
+ /**
+ * Break cycles in the inline request graph by removing callsites.
+ *
+ * The list `requests` is traversed left-to-right, removing those callsites that are part of a
+ * cycle. Elided callsites are also removed from the `inlineRequestsForMethod` map.
+ */
+ def breakInlineCycles(requests: List[Callsite]): List[Callsite] = {
+ // is there a path of inline requests from start to goal?
+ def isReachable(start: MethodNode, goal: MethodNode): Boolean = {
+ @tailrec def reachableImpl(check: List[MethodNode], visited: Set[MethodNode]): Boolean = check match {
+ case x :: xs =>
+ if (x == goal) true
+ else if (visited(x)) reachableImpl(xs, visited)
+ else {
+ val callees = inlineRequestsForMethod(x).map(_.callee.get.callee)
+ reachableImpl(xs ::: callees.toList, visited + x)
+ }
+
+ case Nil =>
+ false
+ }
+ reachableImpl(List(start), Set.empty)
+ }
+
+ val result = new mutable.ListBuffer[Callsite]()
+ // sort the inline requests to ensure that removing requests is deterministic
+ for (r <- requests.sorted(callsiteOrdering)) {
+ // is there a chain of inlining requests that would inline the callsite method into the callee?
+ if (isReachable(r.callee.get.callee, r.callsiteMethod))
+ inlineRequestsForMethod(r.callsiteMethod) -= r
+ else
+ result += r
+ }
+ result.toList
+ }
+
+ // sort the remaining inline requests such that the leaves appear first, then those requests
+ // that become leaves, etc.
+ def leavesFirst(requests: List[Callsite], visited: Set[Callsite] = Set.empty): List[Callsite] = {
+ if (requests.isEmpty) Nil
+ else {
+ val (leaves, others) = requests.partition(r => {
+ val inlineRequestsForCallee = inlineRequestsForMethod(r.callee.get.callee)
+ inlineRequestsForCallee.forall(visited)
+ })
+ assert(leaves.nonEmpty, requests)
+ leaves ::: leavesFirst(others, visited ++ leaves)
+ }
+ }
+
+ leavesFirst(breakInlineCycles(requests))
+ }
+
+
+ /**
+ * Copy and adapt the instructions of a method to a callsite.
+ *
+ * Preconditions:
+ * - The maxLocals and maxStack values of the callsite method are correctly computed
+ * - The callsite method contains no unreachable basic blocks, i.e., running an [[Analyzer]]
+ * does not produce any `null` frames
+ *
+ * @param callsiteInstruction The invocation instruction
+ * @param callsiteStackHeight The stack height at the callsite
+ * @param callsiteMethod The method in which the invocation occurs
+ * @param callsiteClass The class in which the callsite method is defined
+ * @param callee The invoked method
+ * @param calleeDeclarationClass The class in which the invoked method is defined
+ * @param receiverKnownNotNull `true` if the receiver is known to be non-null
+ * @param keepLineNumbers `true` if LineNumberNodes should be copied to the call site
+ * @return `Some(message)` if inlining cannot be performed, `None` otherwise
+ */
+ def inline(callsiteInstruction: MethodInsnNode, callsiteStackHeight: Int, callsiteMethod: MethodNode, callsiteClass: ClassBType,
+ callee: MethodNode, calleeDeclarationClass: ClassBType,
+ receiverKnownNotNull: Boolean, keepLineNumbers: Boolean): Option[CannotInlineWarning] = {
+ canInline(callsiteInstruction, callsiteStackHeight, callsiteMethod, callsiteClass, callee, calleeDeclarationClass) orElse {
+ // New labels for the cloned instructions
+ val labelsMap = cloneLabels(callee)
+ val (clonedInstructions, instructionMap) = cloneInstructions(callee, labelsMap)
+ if (!keepLineNumbers) {
+ removeLineNumberNodes(clonedInstructions)
+ }
+
+ // local vars in the callee are shifted by the number of locals at the callsite
+ val localVarShift = callsiteMethod.maxLocals
+ clonedInstructions.iterator.asScala foreach {
+ case varInstruction: VarInsnNode => varInstruction.`var` += localVarShift
+ case _ => ()
+ }
+
+ // add a STORE instruction for each expected argument, including for THIS instance if any
+ val argStores = new InsnList
+ var nextLocalIndex = callsiteMethod.maxLocals
+ if (!isStaticMethod(callee)) {
+ if (!receiverKnownNotNull) {
+ argStores.add(new InsnNode(DUP))
+ val nonNullLabel = newLabelNode
+ argStores.add(new JumpInsnNode(IFNONNULL, nonNullLabel))
+ argStores.add(new InsnNode(ACONST_NULL))
+ argStores.add(new InsnNode(ATHROW))
+ argStores.add(nonNullLabel)
+ }
+ argStores.add(new VarInsnNode(ASTORE, nextLocalIndex))
+ nextLocalIndex += 1
+ }
+
+ // We just use an asm.Type here, no need to create the MethodBType.
+ val calleAsmType = asm.Type.getMethodType(callee.desc)
+
+ for(argTp <- calleAsmType.getArgumentTypes) {
+ val opc = argTp.getOpcode(ISTORE) // returns the correct xSTORE instruction for argTp
+ argStores.insert(new VarInsnNode(opc, nextLocalIndex)) // "insert" is "prepend" - the last argument is on the top of the stack
+ nextLocalIndex += argTp.getSize
+ }
+
+ clonedInstructions.insert(argStores)
+
+ // label for the exit of the inlined functions. xRETURNs are rplaced by GOTOs to this label.
+ val postCallLabel = newLabelNode
+ clonedInstructions.add(postCallLabel)
+
+ // replace xRETURNs:
+ // - store the return value (if any)
+ // - clear the stack of the inlined method (insert DROPs)
+ // - load the return value
+ // - GOTO postCallLabel
+
+ val returnType = calleAsmType.getReturnType
+ val hasReturnValue = returnType.getSort != asm.Type.VOID
+ val returnValueIndex = callsiteMethod.maxLocals + callee.maxLocals
+ nextLocalIndex += returnType.getSize
+
+ def returnValueStore(returnInstruction: AbstractInsnNode) = {
+ val opc = returnInstruction.getOpcode match {
+ case IRETURN => ISTORE
+ case LRETURN => LSTORE
+ case FRETURN => FSTORE
+ case DRETURN => DSTORE
+ case ARETURN => ASTORE
+ }
+ new VarInsnNode(opc, returnValueIndex)
+ }
+
+ // We run an interpreter to know the stack height at each xRETURN instruction and the sizes
+ // of the values on the stack.
+ val analyzer = new AsmAnalyzer(callee, calleeDeclarationClass.internalName)
+
+ for (originalReturn <- callee.instructions.iterator().asScala if isReturn(originalReturn)) {
+ val frame = analyzer.frameAt(originalReturn)
+ var stackHeight = frame.getStackSize
+
+ val inlinedReturn = instructionMap(originalReturn)
+ val returnReplacement = new InsnList
+
+ def drop(slot: Int) = returnReplacement add getPop(frame.peekDown(slot).getSize)
+
+ // for non-void methods, store the stack top into the return local variable
+ if (hasReturnValue) {
+ returnReplacement add returnValueStore(originalReturn)
+ stackHeight -= 1
+ }
+
+ // drop the rest of the stack
+ for (i <- 0 until stackHeight) drop(i)
+
+ returnReplacement add new JumpInsnNode(GOTO, postCallLabel)
+ clonedInstructions.insert(inlinedReturn, returnReplacement)
+ clonedInstructions.remove(inlinedReturn)
+ }
+
+ // Load instruction for the return value
+ if (hasReturnValue) {
+ val retVarLoad = {
+ val opc = returnType.getOpcode(ILOAD)
+ new VarInsnNode(opc, returnValueIndex)
+ }
+ clonedInstructions.insert(postCallLabel, retVarLoad)
+ }
+
+ callsiteMethod.instructions.insert(callsiteInstruction, clonedInstructions)
+ callsiteMethod.instructions.remove(callsiteInstruction)
+
+ callsiteMethod.localVariables.addAll(cloneLocalVariableNodes(callee, labelsMap, callee.name + "_").asJava)
+ callsiteMethod.tryCatchBlocks.addAll(cloneTryCatchBlockNodes(callee, labelsMap).asJava)
+
+ // Add all invocation instructions that were inlined to the call graph
+ callee.instructions.iterator().asScala foreach {
+ case originalCallsiteIns: MethodInsnNode =>
+ callGraph.callsites.get(originalCallsiteIns) match {
+ case Some(originalCallsite) =>
+ val newCallsiteIns = instructionMap(originalCallsiteIns).asInstanceOf[MethodInsnNode]
+ callGraph.callsites(newCallsiteIns) = Callsite(
+ callsiteInstruction = newCallsiteIns,
+ callsiteMethod = callsiteMethod,
+ callsiteClass = callsiteClass,
+ callee = originalCallsite.callee,
+ argInfos = Nil, // TODO: re-compute argInfos for new destination (once we actually compute them)
+ callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight,
+ callsitePosition = originalCallsite.callsitePosition
+ )
+
+ case None =>
+ }
+
+ case _ =>
+ }
+ // Remove the elided invocation from the call graph
+ callGraph.callsites.remove(callsiteInstruction)
+
+ callsiteMethod.maxLocals += returnType.getSize + callee.maxLocals
+ callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, callee.maxStack + callsiteStackHeight)
+
+ None
+ }
+ }
+
+ /**
+ * Check whether an inling can be performed. Parmeters are described in method [[inline]].
+ * @return `Some(message)` if inlining cannot be performed, `None` otherwise
+ */
+ def canInline(callsiteInstruction: MethodInsnNode, callsiteStackHeight: Int, callsiteMethod: MethodNode, callsiteClass: ClassBType,
+ callee: MethodNode, calleeDeclarationClass: ClassBType): Option[CannotInlineWarning] = {
+
+ def calleeDesc = s"${callee.name} of type ${callee.desc} in ${calleeDeclarationClass.internalName}"
+ def methodMismatch = s"Wrong method node for inlining ${textify(callsiteInstruction)}: $calleeDesc"
+ assert(callsiteInstruction.name == callee.name, methodMismatch)
+ assert(callsiteInstruction.desc == callee.desc, methodMismatch)
+ assert(!isConstructor(callee), s"Constructors cannot be inlined: $calleeDesc")
+ assert(!BytecodeUtils.isAbstractMethod(callee), s"Callee is abstract: $calleeDesc")
+ assert(callsiteMethod.instructions.contains(callsiteInstruction), s"Callsite ${textify(callsiteInstruction)} is not an instruction of $calleeDesc")
+
+ // When an exception is thrown, the stack is cleared before jumping to the handler. When
+ // inlining a method that catches an exception, all values that were on the stack before the
+ // call (in addition to the arguments) would be cleared (SI-6157). So we don't inline methods
+ // with handlers in case there are values on the stack.
+ // Alternatively, we could save all stack values below the method arguments into locals, but
+ // that would be inefficient: we'd need to pop all parameters, save the values, and push the
+ // parameters back for the (inlined) invocation. Similarly for the result after the call.
+ def stackHasNonParameters: Boolean = {
+ val expectedArgs = asm.Type.getArgumentTypes(callsiteInstruction.desc).length + (callsiteInstruction.getOpcode match {
+ case INVOKEVIRTUAL | INVOKESPECIAL | INVOKEINTERFACE => 1
+ case INVOKESTATIC => 0
+ case INVOKEDYNAMIC =>
+ assertionError(s"Unexpected opcode, cannot inline ${textify(callsiteInstruction)}")
+ })
+ callsiteStackHeight > expectedArgs
+ }
+
+ if (isSynchronizedMethod(callee)) {
+ // Could be done by locking on the receiver, wrapping the inlined code in a try and unlocking
+ // in finally. But it's probably not worth the effort, scala never emits synchronized methods.
+ Some(SynchronizedMethod(calleeDeclarationClass.internalName, callee.name, callee.desc))
+ } else if (!callee.tryCatchBlocks.isEmpty && stackHasNonParameters) {
+ Some(MethodWithHandlerCalledOnNonEmptyStack(
+ calleeDeclarationClass.internalName, callee.name, callee.desc,
+ callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc))
+ } else findIllegalAccess(callee.instructions, calleeDeclarationClass, callsiteClass) map {
+ case (illegalAccessIns, None) =>
+ IllegalAccessInstruction(
+ calleeDeclarationClass.internalName, callee.name, callee.desc,
+ callsiteClass.internalName, illegalAccessIns)
+
+ case (illegalAccessIns, Some(warning)) =>
+ IllegalAccessCheckFailed(
+ calleeDeclarationClass.internalName, callee.name, callee.desc,
+ callsiteClass.internalName, illegalAccessIns, warning)
+ }
+ }
+
+ /**
+ * Returns the first instruction in the `instructions` list that would cause a
+ * [[java.lang.IllegalAccessError]] when inlined into the `destinationClass`.
+ *
+ * If validity of some instruction could not be checked because an error occurred, the instruction
+ * is returned together with a warning message that describes the problem.
+ */
+ def findIllegalAccess(instructions: InsnList, calleeDeclarationClass: ClassBType, destinationClass: ClassBType): Option[(AbstractInsnNode, Option[OptimizerWarning])] = {
+
+ /**
+ * Check if a type is accessible to some class, as defined in JVMS 5.4.4.
+ * (A1) C is public
+ * (A2) C and D are members of the same run-time package
+ */
+ def classIsAccessible(accessed: BType, from: ClassBType = destinationClass): Either[OptimizerWarning, Boolean] = (accessed: @unchecked) match {
+ // TODO: A2 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok?
+ case c: ClassBType => c.isPublic.map(_ || c.packageInternalName == from.packageInternalName)
+ case a: ArrayBType => classIsAccessible(a.elementType, from)
+ case _: PrimitiveBType => Right(true)
+ }
+
+ /**
+ * Check if a member reference is accessible from the [[destinationClass]], as defined in the
+ * JVMS 5.4.4. Note that the class name in a field / method reference is not necessarily the
+ * class in which the member is declared:
+ *
+ * class A { def f = 0 }; class B extends A { f }
+ *
+ * The INVOKEVIRTUAL instruction uses a method reference "B.f ()I". Therefore this method has
+ * two parameters:
+ *
+ * @param memberDeclClass The class in which the member is declared (A)
+ * @param memberRefClass The class used in the member reference (B)
+ *
+ * JVMS 5.4.4 summary: A field or method R is accessible to a class D (destinationClass) iff
+ * (B1) R is public
+ * (B2) R is protected, declared in C (memberDeclClass) and D is a subclass of C.
+ * If R is not static, R must contain a symbolic reference to a class T (memberRefClass),
+ * such that T is either a subclass of D, a superclass of D, or D itself.
+ * (B3) R is either protected or has default access and declared by a class in the same
+ * run-time package as D.
+ * (B4) R is private and is declared in D.
+ */
+ def memberIsAccessible(memberFlags: Int, memberDeclClass: ClassBType, memberRefClass: ClassBType): Either[OptimizerWarning, Boolean] = {
+ // TODO: B3 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok?
+ def samePackageAsDestination = memberDeclClass.packageInternalName == destinationClass.packageInternalName
+
+ val key = (ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE) & memberFlags
+ key match {
+ case ACC_PUBLIC => // B1
+ Right(true)
+
+ case ACC_PROTECTED => // B2
+ tryEither {
+ val condB2 = destinationClass.isSubtypeOf(memberDeclClass).orThrow && {
+ val isStatic = (ACC_STATIC & memberFlags) != 0
+ isStatic || memberRefClass.isSubtypeOf(destinationClass).orThrow || destinationClass.isSubtypeOf(memberRefClass).orThrow
+ }
+ Right(condB2 || samePackageAsDestination) // B3 (protected)
+ }
+
+ case 0 => // B3 (default access)
+ Right(samePackageAsDestination)
+
+ case ACC_PRIVATE => // B4
+ Right(memberDeclClass == destinationClass)
+ }
+ }
+
+ /**
+ * Check if `instruction` can be transplanted to `destinationClass`.
+ *
+ * If the instruction references a class, method or field that cannot be found in the
+ * byteCodeRepository, it is considered as not legal. This is known to happen in mixed
+ * compilation: for Java classes there is no classfile that could be parsed, nor does the
+ * compiler generate any bytecode.
+ *
+ * Returns a warning message describing the problem if checking the legality for the instruction
+ * failed.
+ */
+ def isLegal(instruction: AbstractInsnNode): Either[OptimizerWarning, Boolean] = instruction match {
+ case ti: TypeInsnNode =>
+ // NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. For these instructions, the reference
+ // "must be a symbolic reference to a class, array, or interface type" (JVMS 6), so
+ // it can be an internal name, or a full array descriptor.
+ classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ti.desc))
+
+ case ma: MultiANewArrayInsnNode =>
+ // "a symbolic reference to a class, array, or interface type"
+ classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ma.desc))
+
+ case fi: FieldInsnNode =>
+ val fieldRefClass = classBTypeFromParsedClassfile(fi.owner)
+ for {
+ (fieldNode, fieldDeclClassNode) <- byteCodeRepository.fieldNode(fieldRefClass.internalName, fi.name, fi.desc): Either[OptimizerWarning, (FieldNode, InternalName)]
+ fieldDeclClass = classBTypeFromParsedClassfile(fieldDeclClassNode)
+ res <- memberIsAccessible(fieldNode.access, fieldDeclClass, fieldRefClass)
+ } yield {
+ res
+ }
+
+ case mi: MethodInsnNode =>
+ if (mi.owner.charAt(0) == '[') Right(true) // array methods are accessible
+ else {
+ def canInlineCall(opcode: Int, methodFlags: Int, methodDeclClass: ClassBType, methodRefClass: ClassBType): Either[OptimizerWarning, Boolean] = {
+ opcode match {
+ case INVOKESPECIAL if mi.name != GenBCode.INSTANCE_CONSTRUCTOR_NAME =>
+ // invokespecial is used for private method calls, super calls and instance constructor calls.
+ // private method and super calls can only be inlined into the same class.
+ Right(destinationClass == calleeDeclarationClass)
+
+ case _ => // INVOKEVIRTUAL, INVOKESTATIC, INVOKEINTERFACE and INVOKESPECIAL of constructors
+ memberIsAccessible(methodFlags, methodDeclClass, methodRefClass)
+ }
+ }
+
+ val methodRefClass = classBTypeFromParsedClassfile(mi.owner)
+ for {
+ (methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, mi.name, mi.desc): Either[OptimizerWarning, (MethodNode, InternalName)]
+ methodDeclClass = classBTypeFromParsedClassfile(methodDeclClassNode)
+ res <- canInlineCall(mi.getOpcode, methodNode.access, methodDeclClass, methodRefClass)
+ } yield {
+ res
+ }
+ }
+
+ case ivd: InvokeDynamicInsnNode =>
+ // TODO @lry check necessary conditions to inline an indy, instead of giving up
+ Right(false)
+
+ case ci: LdcInsnNode => ci.cst match {
+ case t: asm.Type => classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName))
+ case _ => Right(true)
+ }
+
+ case _ => Right(true)
+ }
+
+ val it = instructions.iterator.asScala
+ @tailrec def find: Option[(AbstractInsnNode, Option[OptimizerWarning])] = {
+ if (!it.hasNext) None // all instructions are legal
+ else {
+ val i = it.next()
+ isLegal(i) match {
+ case Left(warning) => Some((i, Some(warning))) // checking isLegal for i failed
+ case Right(false) => Some((i, None)) // an illegal instruction was found
+ case _ => find
+ }
+ }
+ }
+ find
+ }
+}
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 87ad715e4d..f6cfc5598b 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
@@ -8,10 +8,11 @@ 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.Opcodes
+import scala.tools.asm.tree.analysis.{Analyzer, BasicInterpreter}
import scala.tools.asm.tree._
import scala.collection.convert.decorateAsScala._
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
import scala.tools.nsc.settings.ScalaSettings
@@ -48,6 +49,45 @@ import scala.tools.nsc.settings.ScalaSettings
*/
class LocalOpt(settings: ScalaSettings) {
/**
+ * Remove unreachable code from all methods of `classNode`. See of its overload.
+ *
+ * @param classNode The class to optimize
+ * @return `true` if unreachable code was removed from any method
+ */
+ def minimalRemoveUnreachableCode(classNode: ClassNode): Boolean = {
+ classNode.methods.asScala.foldLeft(false) {
+ case (changed, method) => minimalRemoveUnreachableCode(method, classNode.name) || changed
+ }
+ }
+
+ /**
+ * Remove unreachable code from a method.
+ *
+ * This implementation only removes instructions that are unreachable for an ASM analyzer /
+ * interpreter. This ensures that future analyses will not produce `null` frames. The inliner
+ * and call graph builder depend on this property.
+ */
+ def minimalRemoveUnreachableCode(method: MethodNode, ownerClassName: InternalName): Boolean = {
+ if (method.instructions.size == 0) return false // fast path for abstract methods
+
+ // For correctness, after removing unreachable code, we have to eliminate empty exception
+ // handlers, see scaladoc of def methodOptimizations. Removing an live handler may render more
+ // code unreachable and therefore requires running another round.
+ def removalRound(): Boolean = {
+ val (codeRemoved, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName)
+ if (codeRemoved) {
+ val liveHandlerRemoved = removeEmptyExceptionHandlers(method).exists(h => liveLabels(h.start))
+ if (liveHandlerRemoved) removalRound()
+ }
+ codeRemoved
+ }
+
+ val codeRemoved = removalRound()
+ if (codeRemoved) removeUnusedLocalVariableNodes(method)()
+ codeRemoved
+ }
+
+ /**
* Remove unreachable instructions from all (non-abstract) methods and apply various other
* cleanups to the bytecode.
*
@@ -73,7 +113,7 @@ class LocalOpt(settings: ScalaSettings) {
*
* Returns `true` if the bytecode of `method` was changed.
*/
- private def methodOptimizations(method: MethodNode, ownerClassName: String): Boolean = {
+ def methodOptimizations(method: MethodNode, ownerClassName: InternalName): Boolean = {
if (method.instructions.size == 0) return false // fast path for abstract methods
// unreachable-code also removes unused local variable nodes and empty exception handlers.
@@ -102,9 +142,7 @@ class LocalOpt(settings: ScalaSettings) {
// 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) {
+ def removalRound(): Boolean = {
// 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)
@@ -116,15 +154,18 @@ class LocalOpt(settings: ScalaSettings) {
val jumpsChanged = if (settings.YoptSimplifyJumps) simplifyJumps(method) else false
- codeHandlersOrJumpsChanged ||= (codeRemoved || handlersRemoved || jumpsChanged)
+ // Eliminating live handlers and simplifying jump instructions may render more code
+ // unreachable, so we need to run another round.
+ if (liveHandlerRemoved || jumpsChanged) removalRound()
- // The doc comment of class LocalOpt explains why we recurse if jumpsChanged || liveHandlerRemoved
- recurse = settings.YoptRecurseUnreachableJumps && (jumpsChanged || liveHandlerRemoved)
+ codeRemoved || handlersRemoved || jumpsChanged
}
+ val codeHandlersOrJumpsChanged = removalRound()
+
// (*) Removing stale local variable descriptors is required for correctness of unreachable-code
val localsRemoved =
- if (settings.YoptCompactLocals) compactLocalVariables(method)
+ if (settings.YoptCompactLocals) compactLocalVariables(method) // also removes unused
else if (settings.YoptUnreachableCode) removeUnusedLocalVariableNodes(method)() // (*)
else false
@@ -146,10 +187,10 @@ class LocalOpt(settings: ScalaSettings) {
*
* TODO: rewrite, don't use computeMaxLocalsMaxStack (runs a ClassWriter) / Analyzer. Too slow.
*/
- def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: String): (Boolean, Set[LabelNode]) = {
+ def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: InternalName): (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)
+ val a = new Analyzer(new BasicInterpreter)
a.analyze(ownerClassName, method)
val frames = a.getFrames
@@ -319,29 +360,6 @@ class LocalOpt(settings: ScalaSettings) {
}
/**
- * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM
- * framework only computes these values during bytecode generation.
- *
- * Since there's currently no better way, we run a bytecode generator on the method and extract
- * the computed values. This required changes to the ASM codebase:
- * - the [[MethodWriter]] class was made public
- * - accessors for maxLocals / maxStack were added to the MethodWriter class
- *
- * We could probably make this faster (and allocate less memory) by hacking the ASM framework
- * more: create a subclass of MethodWriter with a /dev/null byteVector. Another option would be
- * to create a separate visitor for computing those values, duplicating the functionality from the
- * MethodWriter.
- */
- private def computeMaxLocalsMaxStack(method: MethodNode) {
- val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS)
- val excs = method.exceptions.asScala.toArray
- val mw = cw.visitMethod(method.access, method.name, method.desc, method.signature, excs).asInstanceOf[MethodWriter]
- method.accept(mw)
- 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
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala
deleted file mode 100644
index 7002e43d98..0000000000
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala
+++ /dev/null
@@ -1,24 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2014 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend.jvm
-
-import scala.tools.asm
-import asm.tree._
-
-/**
- * Reporting utilities used in the optimizer.
- */
-object OptimizerReporting {
- def methodSignature(className: String, methodName: String, methodDescriptor: String): String = {
- className + "::" + methodName + methodDescriptor
- }
-
- def methodSignature(className: String, method: MethodNode): String = methodSignature(className, method.name, method.desc)
-
- def inlineFailure(reason: String): Nothing = MissingRequirementError.signal(reason)
- def assertionError(message: String): Nothing = throw new AssertionError(message)
-}
diff --git a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala
index 9433ddcf31..d34c14be0f 100644
--- a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala
+++ b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala
@@ -13,6 +13,7 @@ import symtab.Flags
import JavaTokens._
import scala.language.implicitConversions
import scala.reflect.internal.util.Position
+import scala.reflect.internal.util.ListOfNil
trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
val global : Global
@@ -125,7 +126,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
def makeSyntheticParam(count: Int, tpt: Tree): ValDef =
makeParam(nme.syntheticParamName(count), tpt)
def makeParam(name: String, tpt: Tree): ValDef =
- makeParam(name: TermName, tpt)
+ makeParam(TermName(name), tpt)
def makeParam(name: TermName, tpt: Tree): ValDef =
ValDef(Modifiers(Flags.JAVA | Flags.PARAM), name, tpt, EmptyTree)
diff --git a/src/compiler/scala/tools/nsc/plugins/Plugin.scala b/src/compiler/scala/tools/nsc/plugins/Plugin.scala
index 7837f9a11a..5a7a0df595 100644
--- a/src/compiler/scala/tools/nsc/plugins/Plugin.scala
+++ b/src/compiler/scala/tools/nsc/plugins/Plugin.scala
@@ -7,7 +7,7 @@ package scala.tools.nsc
package plugins
import scala.tools.nsc.io.{ Jar }
-import scala.tools.nsc.util.ScalaClassLoader
+import scala.reflect.internal.util.ScalaClassLoader
import scala.reflect.io.{ Directory, File, Path }
import java.io.InputStream
import java.util.zip.ZipException
@@ -60,13 +60,13 @@ abstract class Plugin {
* @return true to continue, or false to opt out
*/
def init(options: List[String], error: String => Unit): Boolean = {
- processOptions(options, error)
+ if (!options.isEmpty) error(s"Error: $name takes no options")
true
}
@deprecated("use Plugin#init instead", since="2.11")
def processOptions(options: List[String], error: String => Unit): Unit = {
- if (!options.isEmpty) error(s"Error: $name takes no options")
+ init(options, error)
}
/** A description of this plugin's options, suitable as a response
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
index a5b722612d..d273995e6e 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -213,26 +213,29 @@ trait ScalaSettings extends AbsScalaSettings
// the current standard is "inline" but we are moving towards "method"
val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "inline")
+ val YskipInlineInfoAttribute = BooleanSetting("-Yskip-inline-info-attribute", "Do not add the ScalaInlineInfo attribute to classfiles generated by -Ybackend:GenASM")
+
object YoptChoices extends MultiChoiceEnumeration {
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 unnecessary 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 inlineProject = Choice("inline-project", "Inline only methods defined in the files being compiled")
+ val inlineGlobal = Choice("inline-global", "Inline methods from any source, including classfiles on the compile classpath")
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)
- private val methodChoices = List(unreachableCode, simplifyJumps, recurseUnreachableJumps, emptyLineNumbers, emptyLabels, compactLocals)
+ private val methodChoices = List(unreachableCode, simplifyJumps, emptyLineNumbers, emptyLabels, compactLocals)
val lMethod = Choice("l:method", "Enable intra-method optimizations: "+ methodChoices.mkString(","), expandsTo = methodChoices)
- private val projectChoices = List(lMethod)
+ private val projectChoices = List(lMethod, inlineProject)
val lProject = Choice("l:project", "Enable cross-method optimizations within the current project: "+ projectChoices.mkString(","), expandsTo = projectChoices)
- private val classpathChoices = List(lProject)
+ private val classpathChoices = List(lProject, inlineGlobal)
val lClasspath = Choice("l:classpath", "Enable cross-method optimizations across the entire classpath: "+ classpathChoices.mkString(","), expandsTo = classpathChoices)
}
@@ -245,11 +248,33 @@ trait ScalaSettings extends AbsScalaSettings
def YoptNone = Yopt.isSetByUser && Yopt.value.isEmpty
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)
+ def YoptInlineProject = Yopt.contains(YoptChoices.inlineProject)
+ def YoptInlineGlobal = Yopt.contains(YoptChoices.inlineGlobal)
+ def YoptInlinerEnabled = YoptInlineProject || YoptInlineGlobal
+
+ object YoptWarningsChoices extends MultiChoiceEnumeration {
+ val none = Choice("none" , "No optimizer warnings.")
+ val atInlineFailedSummary = Choice("at-inline-failed-summary" , "One-line summary if there were @inline method calls that could not be inlined.")
+ val atInlineFailed = Choice("at-inline-failed" , "A detailed warning for each @inline method call that could not be inlined.")
+ val noInlineMixed = Choice("no-inline-mixed" , "In mixed compilation, warn at callsites methods defined in java sources (the inlining decision cannot be made without bytecode).")
+ val noInlineMissingBytecode = Choice("no-inline-missing-bytecode" , "Warn if an inlining decision cannot be made because a the bytecode of a class or member cannot be found on the compilation classpath.")
+ val noInlineMissingScalaInlineInfoAttr = Choice("no-inline-missing-attribute", "Warn if an inlining decision cannot be made because a Scala classfile does not have a ScalaInlineInfo attribute.")
+ }
+
+ val YoptWarnings = MultiChoiceSetting(
+ name = "-Yopt-warnings",
+ helpArg = "warnings",
+ descr = "Enable optimizer warnings",
+ domain = YoptWarningsChoices,
+ default = Some(List(YoptWarningsChoices.atInlineFailed.name))) withPostSetHook (self => {
+ if (self.value subsetOf Set(YoptWarningsChoices.none, YoptWarningsChoices.atInlineFailedSummary)) YinlinerWarnings.value = false
+ else YinlinerWarnings.value = true
+ })
+
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."
object YstatisticsPhases extends MultiChoiceEnumeration { val parser, typer, patmat, erasure, cleanup, jvm = Value }
diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala
index d174dc86c7..41ce0837cb 100644
--- a/src/compiler/scala/tools/nsc/settings/Warnings.scala
+++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala
@@ -28,10 +28,11 @@ trait Warnings {
val warnUnusedImport = BooleanSetting("-Ywarn-unused-import", "Warn when imports are unused.")
// Experimental lint warnings that are turned off, but which could be turned on programmatically.
- // These warnings are said to blind those who dare enable them.
- // They are not activated by -Xlint and can't be enabled on the command line.
- val warnValueOverrides = { // Currently turned off as experimental. Created using constructor (new BS), so not available on the command line.
- val flag = new BooleanSetting("value-overrides", "Generated value class method overrides an implementation")
+ // They are not activated by -Xlint and can't be enabled on the command line because they are not
+ // created using the standard factory methods.
+
+ val warnValueOverrides = {
+ val flag = new BooleanSetting("value-overrides", "Generated value class method overrides an implementation.")
flag.value = false
flag
}
@@ -53,10 +54,11 @@ trait Warnings {
val TypeParameterShadow = LintWarning("type-parameter-shadow", "A local type parameter shadows a type already in scope.")
val PolyImplicitOverload = LintWarning("poly-implicit-overload", "Parameterized overloaded implicit methods are not visible as view bounds.")
val OptionImplicit = LintWarning("option-implicit", "Option.apply used implicit view.")
- val DelayedInitSelect = LintWarning("delayedinit-select", "Selecting member of DelayedInit")
+ val DelayedInitSelect = LintWarning("delayedinit-select", "Selecting member of DelayedInit.")
val ByNameRightAssociative = LintWarning("by-name-right-associative", "By-name parameter of right associative operator.")
val PackageObjectClasses = LintWarning("package-object-classes", "Class or object defined in package object.")
val UnsoundMatch = LintWarning("unsound-match", "Pattern match may not be typesafe.")
+ val StarsAlign = LintWarning("stars-align", "Pattern sequence wildcard must align with sequence component.")
def allLintWarnings = values.toSeq.asInstanceOf[Seq[LintWarning]]
}
@@ -77,6 +79,7 @@ trait Warnings {
def warnByNameRightAssociative = lint contains ByNameRightAssociative
def warnPackageObjectClasses = lint contains PackageObjectClasses
def warnUnsoundMatch = lint contains UnsoundMatch
+ def warnStarsAlign = lint contains StarsAlign
// Lint warnings that are currently -Y, but deprecated in that usage
@deprecated("Use warnAdaptedArgs", since="2.11.2")
diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
index 4d08be3c24..1ffa064b78 100644
--- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
+++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
@@ -807,10 +807,10 @@ abstract class ClassfileParser {
val c = pool.getConstant(u2)
val c1 = convertTo(c, symtype)
if (c1 ne null) sym.setInfo(ConstantType(c1))
- else debugwarn(s"failure to convert $c to $symtype")
+ else devWarning(s"failure to convert $c to $symtype")
case tpnme.ScalaSignatureATTR =>
if (!isScalaAnnot) {
- debugwarn(s"symbol ${sym.fullName} has pickled signature in attribute")
+ devWarning(s"symbol ${sym.fullName} has pickled signature in attribute")
unpickler.unpickle(in.buf, in.bp, clazz, staticModule, in.file.name)
}
in.skip(attrLen)
@@ -1103,7 +1103,7 @@ abstract class ClassfileParser {
def enclosing = if (jflags.isStatic) enclModule else enclClass
// The name of the outer class, without its trailing $ if it has one.
- private def strippedOuter = nme stripModuleSuffix outerName
+ private def strippedOuter = outerName.dropModule
private def isInner = innerClasses contains strippedOuter
private def enclClass = if (isInner) innerClasses innerSymbol strippedOuter else classNameToSymbol(strippedOuter)
private def enclModule = enclClass.companionModule
@@ -1123,7 +1123,7 @@ abstract class ClassfileParser {
def add(entry: InnerClassEntry): Unit = {
inners get entry.externalName foreach (existing =>
- debugwarn(s"Overwriting inner class entry! Was $existing, now $entry")
+ devWarning(s"Overwriting inner class entry! Was $existing, now $entry")
)
inners(entry.externalName) = entry
}
diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala
index bd1fa4e707..ea46116976 100644
--- a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala
+++ b/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala
@@ -74,7 +74,7 @@ abstract class ICodeReader extends ClassfileParser {
first != CONSTANT_METHODREF &&
first != CONSTANT_INTFMETHODREF) errorBadTag(start)
val ownerTpe = getClassOrArrayType(in.getChar(start + 1).toInt)
- debuglog("getMemberSymbol(static: " + static + "): owner type: " + ownerTpe + " " + ownerTpe.typeSymbol.originalName)
+ debuglog("getMemberSymbol(static: " + static + "): owner type: " + ownerTpe + " " + ownerTpe.typeSymbol.unexpandedName)
val (name0, tpe0) = getNameAndType(in.getChar(start + 3).toInt, ownerTpe)
debuglog("getMemberSymbol: name and tpe: " + name0 + ": " + tpe0)
diff --git a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala
index f786ffb8f3..3591372bbe 100644
--- a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala
+++ b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala
@@ -55,7 +55,7 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure =>
)
/** Does symbol need an implementation method? */
- private def needsImplMethod(sym: Symbol) = (
+ def needsImplMethod(sym: Symbol) = (
sym.isMethod
&& isInterfaceMember(sym)
&& (!sym.hasFlag(DEFERRED | SUPERACCESSOR) || (sym hasFlag lateDEFERRED))
diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala
index 362cbde04f..d0fca12e6a 100644
--- a/src/compiler/scala/tools/nsc/transform/Constructors.scala
+++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala
@@ -8,6 +8,7 @@ package transform
import scala.collection.{ mutable, immutable }
import scala.collection.mutable.ListBuffer
+import scala.reflect.internal.util.ListOfNil
import symtab.Flags._
/** This phase converts classes with parameters into Java-like classes with
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
index d2c511a2d1..94e88589f5 100644
--- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
+++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
@@ -113,7 +113,9 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
// after working on the entire compilation until we'll have a set of
// new class definitions to add to the top level
override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = {
- super.transformStats(stats, exprOwner) ++ lambdaClassDefs(exprOwner)
+ // Need to remove from the lambdaClassDefs map: there may be multiple PackageDef for the same
+ // package when defining a package object. We only add the lambda class to one. See SI-9097.
+ super.transformStats(stats, exprOwner) ++ lambdaClassDefs.remove(exprOwner).getOrElse(Nil)
}
private def optionSymbol(sym: Symbol): Option[Symbol] = if (sym.exists) Some(sym) else None
@@ -253,6 +255,8 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
val name = unit.freshTypeName(s"$oldClassPart$suffix".replace("$anon", "$nestedInAnon"))
val lambdaClass = pkg newClassSymbol(name, originalFunction.pos, FINAL | SYNTHETIC) addAnnotation SerialVersionUIDAnnotation
+ // make sure currentRun.compiles(lambdaClass) is true (AddInterfaces does the same for trait impl classes)
+ currentRun.symSource(lambdaClass) = funOwner.sourceFile
lambdaClass setInfo ClassInfoType(parents, newScope, lambdaClass)
assert(!lambdaClass.isAnonymousClass && !lambdaClass.isAnonymousFunction, "anonymous class name: "+ lambdaClass.name)
assert(lambdaClass.isDelambdafyFunction, "not lambda class name: " + lambdaClass.name)
diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala
index f686df60fd..2f0afe79b6 100644
--- a/src/compiler/scala/tools/nsc/transform/Erasure.scala
+++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala
@@ -1036,7 +1036,7 @@ abstract class Erasure extends AddInterfaces
// See SI-5568.
tree setSymbol Object_getClass
} else {
- debugwarn(s"The symbol '${fn.symbol}' was interecepted but didn't match any cases, that means the intercepted methods set doesn't match the code")
+ devWarning(s"The symbol '${fn.symbol}' was interecepted but didn't match any cases, that means the intercepted methods set doesn't match the code")
tree
}
} else qual match {
diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala
index f3cab8184c..f12f6c4e18 100644
--- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala
+++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala
@@ -207,7 +207,7 @@ abstract class ExplicitOuter extends InfoTransform
// class needs to have a common naming scheme, independently of whether
// the field was accessed from an inner class or not. See #2946
if (sym.owner.isTrait && sym.isLocalToThis &&
- (sym.getter(sym.owner.toInterface) == NoSymbol))
+ (sym.getterIn(sym.owner.toInterface) == NoSymbol))
sym.makeNotPrivate(sym.owner)
tp
}
diff --git a/src/compiler/scala/tools/nsc/transform/Flatten.scala b/src/compiler/scala/tools/nsc/transform/Flatten.scala
index 6149e40fa7..fbb0307773 100644
--- a/src/compiler/scala/tools/nsc/transform/Flatten.scala
+++ b/src/compiler/scala/tools/nsc/transform/Flatten.scala
@@ -166,7 +166,7 @@ abstract class Flatten extends InfoTransform {
override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = {
val stats1 = super.transformStats(stats, exprOwner)
if (currentOwner.isPackageClass) {
- val lifted = liftedDefs(currentOwner).toList
+ val lifted = liftedDefs.remove(currentOwner).toList.flatten
stats1 ::: lifted
}
else stats1
diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala
index fa0c1f797b..5e2fe21eec 100644
--- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala
+++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala
@@ -250,21 +250,30 @@ abstract class LambdaLift extends InfoTransform {
debuglog("renaming in %s: %s => %s".format(sym.owner.fullLocationString, originalName, sym.name))
}
+ // make sure that the name doesn't make the symbol accidentally `isAnonymousClass` (et.al) by
+ // introducing `$anon` in its name. to be cautious, we don't make this change in the default
+ // backend under 2.11.x, so only in GenBCode.
+ def nonAnon(s: String) = if (settings.Ybackend.value == "GenBCode") nme.ensureNonAnon(s) else s
+
def newName(sym: Symbol): Name = {
val originalName = sym.name
def freshen(prefix: String): Name =
if (originalName.isTypeName) unit.freshTypeName(prefix)
else unit.freshTermName(prefix)
+ val join = nme.NAME_JOIN_STRING
if (sym.isAnonymousFunction && sym.owner.isMethod) {
- freshen(sym.name + nme.NAME_JOIN_STRING + sym.owner.name + nme.NAME_JOIN_STRING)
+ freshen(sym.name + join + nonAnon(sym.owner.name.toString) + join)
} else {
+ val name = freshen(sym.name + join)
// SI-5652 If the lifted symbol is accessed from an inner class, it will be made public. (where?)
- // Generating a unique name, mangled with the enclosing class name, avoids a VerifyError
- // in the case that a sub-class happens to lifts out a method with the *same* name.
- val name = freshen("" + sym.name + nme.NAME_JOIN_STRING)
- if (originalName.isTermName && !sym.enclClass.isImplClass && calledFromInner(sym)) nme.expandedName(name.toTermName, sym.enclClass)
- else name
+ // Generating a unique name, mangled with the enclosing full class name (including
+ // package - subclass might have the same name), avoids a VerifyError in the case
+ // that a sub-class happens to lifts out a method with the *same* name.
+ if (originalName.isTermName && !sym.enclClass.isImplClass && calledFromInner(sym))
+ newTermNameCached(nonAnon(sym.enclClass.fullName('$')) + nme.EXPAND_SEPARATOR_STRING + name)
+ else
+ name
}
}
@@ -539,12 +548,11 @@ abstract class LambdaLift extends InfoTransform {
override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = {
def addLifted(stat: Tree): Tree = stat match {
case ClassDef(_, _, _, _) =>
- val lifted = liftedDefs get stat.symbol match {
+ val lifted = liftedDefs remove stat.symbol match {
case Some(xs) => xs reverseMap addLifted
case _ => log("unexpectedly no lifted defs for " + stat.symbol) ; Nil
}
- try deriveClassDef(stat)(impl => deriveTemplate(impl)(_ ::: lifted))
- finally liftedDefs -= stat.symbol
+ deriveClassDef(stat)(impl => deriveTemplate(impl)(_ ::: lifted))
case DefDef(_, _, _, _, _, Block(Nil, expr)) if !stat.symbol.isConstructor =>
deriveDefDef(stat)(_ => expr)
diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala
index 7927875583..408f4466e1 100644
--- a/src/compiler/scala/tools/nsc/transform/Mixin.scala
+++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala
@@ -232,13 +232,13 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
for (member <- impl.info.decls) {
if (!member.isMethod && !member.isModule && !member.isModuleVar) {
assert(member.isTerm && !member.isDeferred, member)
- if (member.getter(impl).isPrivate) {
+ if (member.getterIn(impl).isPrivate) {
member.makeNotPrivate(clazz) // this will also make getter&setter not private
}
- val getter = member.getter(clazz)
+ val getter = member.getterIn(clazz)
if (getter == NoSymbol) addMember(clazz, newGetter(member))
if (!member.tpe.isInstanceOf[ConstantType] && !member.isLazy) {
- val setter = member.setter(clazz)
+ val setter = member.setterIn(clazz)
if (setter == NoSymbol) addMember(clazz, newSetter(member))
}
}
@@ -267,7 +267,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
/* Mix in members of implementation class mixinClass into class clazz */
def mixinImplClassMembers(mixinClass: Symbol, mixinInterface: Symbol) {
- if (!mixinClass.isImplClass) debugwarn ("Impl class flag is not set " +
+ if (!mixinClass.isImplClass) devWarning ("Impl class flag is not set " +
((mixinClass.debugLocationString, mixinInterface.debugLocationString)))
for (member <- mixinClass.info.decls ; if isForwarded(member)) {
@@ -872,7 +872,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
}
def mkCheckedAccessor(clazz: Symbol, retVal: Tree, offset: Int, pos: Position, fieldSym: Symbol): Tree = {
- val sym = fieldSym.getter(fieldSym.owner)
+ val sym = fieldSym.getterIn(fieldSym.owner)
val bitmapSym = bitmapFor(clazz, offset, sym)
val kind = bitmapKind(sym)
val mask = maskForOffset(offset, sym, kind)
@@ -921,7 +921,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
deriveDefDef(stat)(addInitBits(clazz, _))
}
else if (settings.checkInit && !clazz.isTrait && sym.isSetter) {
- val getter = sym.getter(clazz)
+ val getter = sym.getterIn(clazz)
if (needsInitFlag(getter) && fieldOffset.isDefinedAt(getter))
deriveDefDef(stat)(rhs => Block(List(rhs, localTyper.typed(mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter)))), UNIT))
else stat
@@ -1057,7 +1057,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
def isOverriddenSetter(sym: Symbol) =
nme.isTraitSetterName(sym.name) && {
val other = sym.nextOverriddenSymbol
- isOverriddenAccessor(other.getter(other.owner), clazz.info.baseClasses)
+ isOverriddenAccessor(other.getterIn(other.owner), clazz.info.baseClasses)
}
// for all symbols `sym` in the class definition, which are mixed in:
@@ -1232,7 +1232,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
// refer to fields in some implementation class via an abstract
// getter in the interface.
val iface = toInterface(sym.owner.tpe).typeSymbol
- val ifaceGetter = sym getter iface
+ val ifaceGetter = sym getterIn iface
if (ifaceGetter == NoSymbol) abort("No getter for " + sym + " in " + iface)
else typedPos(tree.pos)((qual DOT ifaceGetter)())
@@ -1240,7 +1240,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
case Assign(Apply(lhs @ Select(qual, _), List()), rhs) =>
// assign to fields in some implementation class via an abstract
// setter in the interface.
- def setter = lhs.symbol.setter(toInterface(lhs.symbol.owner.tpe).typeSymbol) setPos lhs.pos
+ def setter = lhs.symbol.setterIn(toInterface(lhs.symbol.owner.tpe).typeSymbol) setPos lhs.pos
typedPos(tree.pos)((qual DOT setter)(rhs))
diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
index 1691b01e3e..086512677e 100644
--- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
+++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
@@ -699,7 +699,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
} else if (m.isValue && !m.isMethod && !m.hasFlag(LAZY)) { // concrete value definition
def mkAccessor(field: Symbol, name: Name) = {
- val newFlags = (SPECIALIZED | m.getter(clazz).flags) & ~(LOCAL | CASEACCESSOR | PARAMACCESSOR)
+ val newFlags = (SPECIALIZED | m.getterIn(clazz).flags) & ~(LOCAL | CASEACCESSOR | PARAMACCESSOR)
// we rely on the super class to initialize param accessors
val sym = sClass.newMethod(name.toTermName, field.pos, newFlags)
info(sym) = SpecializedAccessor(field)
@@ -720,7 +720,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
if (nme.isLocalName(m.name)) {
val specGetter = mkAccessor(specVal, specVal.getterName) setInfo MethodType(Nil, specVal.info)
- val origGetter = overrideIn(sClass, m.getter(clazz))
+ val origGetter = overrideIn(sClass, m.getterIn(clazz))
info(origGetter) = Forward(specGetter)
enterMember(specGetter)
enterMember(origGetter)
@@ -733,12 +733,12 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
debuglog("override case field accessor %s -> %s".format(m.name.decode, cfaGetter.name.decode))
}
- if (specVal.isVariable && m.setter(clazz) != NoSymbol) {
+ if (specVal.isVariable && m.setterIn(clazz) != NoSymbol) {
val specSetter = mkAccessor(specVal, specGetter.setterName)
.resetFlag(STABLE)
specSetter.setInfo(MethodType(specSetter.newSyntheticValueParams(List(specVal.info)),
UnitTpe))
- val origSetter = overrideIn(sClass, m.setter(clazz))
+ val origSetter = overrideIn(sClass, m.setterIn(clazz))
info(origSetter) = Forward(specSetter)
enterMember(specSetter)
enterMember(origSetter)
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index 3544dc9966..3330fbcae2 100644
--- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala
+++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -10,6 +10,7 @@ package transform
import symtab.Flags._
import scala.collection.{ mutable, immutable }
import scala.language.postfixOps
+import scala.reflect.internal.util.ListOfNil
/*<export> */
/** - uncurry all symbol and tree types (@see UnCurryPhase) -- this includes normalizing all proper types.
@@ -206,7 +207,7 @@ abstract class UnCurry extends InfoTransform
// (() => Int) { def apply(): Int @typeConstraint }
case RefinedType(List(funTp), decls) =>
debuglog(s"eliminate refinement from function type ${fun.tpe}")
- fun.tpe = funTp
+ fun.setType(funTp)
case _ =>
()
}
@@ -225,6 +226,10 @@ abstract class UnCurry extends InfoTransform
if (inlineFunctionExpansion || !canUseDelamdafyMethod) {
val parents = addSerializable(abstractFunctionForFunctionType(fun.tpe))
val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation
+ // The original owner is used in the backend for the EnclosingMethod attribute. If fun is
+ // nested in a value-class method, its owner was already changed to the extension method.
+ // Saving the original owner allows getting the source structure from the class symbol.
+ defineOriginalOwner(anonClass, fun.symbol.originalOwner)
anonClass setInfo ClassInfoType(parents, newScope, anonClass)
val applyMethodDef = mkMethod(anonClass, nme.apply)
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
index b703b5bc6d..e1fe220556 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
@@ -577,8 +577,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
lengthMax3(casesNoSubstOnly) > 2
}
val requireSwitch = hasSwitchAnnotation && exceedsTwoCasesOrAlts
- if (hasSwitchAnnotation && !requireSwitch)
- reporter.warning(scrut.pos, "matches with two cases or fewer are emitted using if-then-else instead of switch")
(suppression, requireSwitch)
case _ =>
(Suppression.NoSuppression, false)
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala
index d35aad964d..b2f2516b5b 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala
@@ -239,6 +239,11 @@ trait Interface extends ast.TreeDSL {
case Ident(_) => subst(from, to)
case _ => super.transform(tree)
}
+ tree1 match {
+ case _: DefTree =>
+ tree1.symbol.modifyInfo(_.substituteTypes(from, toTypes))
+ case _ =>
+ }
tree1.modifyType(_.substituteTypes(from, toTypes))
}
}
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala b/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala
index 8924394b72..2753baa51d 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala
@@ -110,8 +110,10 @@ trait ScalacPatternExpanders {
err("Star pattern must correspond with varargs or unapplySeq")
else if (elementArity < 0)
arityError("not enough")
- else if (elementArity > 0 && !extractor.hasSeq)
+ else if (elementArity > 0 && !isSeq)
arityError("too many")
+ else if (settings.warnStarsAlign && isSeq && productArity > 0 && (elementArity > 0 || !isStar))
+ warn("A repeated case parameter or extracted sequence should be matched only by a sequence wildcard (_*).")
aligned
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala b/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala
index 5a70d4c524..2c27bdb03a 100644
--- a/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala
@@ -447,6 +447,6 @@ trait AnalyzerPlugins { self: Analyzer =>
// performance opt
if (macroPlugins.isEmpty) stats
else macroPlugins.foldLeft(stats)((current, plugin) =>
- if (!plugin.isActive()) current else plugin.pluginsEnterStats(typer, stats))
+ if (!plugin.isActive()) current else plugin.pluginsEnterStats(typer, current))
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
index 5c36bd9d28..c80aaea160 100644
--- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
@@ -45,6 +45,14 @@ trait ContextErrors {
case class NormalTypeError(underlyingTree: Tree, errMsg: String)
extends TreeTypeError
+ /**
+ * Marks a TypeError that was constructed from a CyclicReference (under silent).
+ * This is used for named arguments, where we need to know if an assignment expression
+ * failed with a cyclic reference or some other type error.
+ */
+ class NormalTypeErrorFromCyclicReference(underlyingTree: Tree, errMsg: String)
+ extends NormalTypeError(underlyingTree, errMsg)
+
case class AccessTypeError(underlyingTree: Tree, errMsg: String)
extends TreeTypeError
@@ -1087,8 +1095,9 @@ trait ContextErrors {
// hence we (together with reportTypeError in TypeDiagnostics) make sure that this CyclicReference
// evades all the handlers on its way and successfully reaches `isCyclicOrErroneous` in Implicits
throw ex
- case CyclicReference(sym, info: TypeCompleter) =>
- issueNormalTypeError(tree, typer.cyclicReferenceMessage(sym, info.tree) getOrElse ex.getMessage())
+ case c @ CyclicReference(sym, info: TypeCompleter) =>
+ val error = new NormalTypeErrorFromCyclicReference(tree, typer.cyclicReferenceMessage(sym, info.tree) getOrElse ex.getMessage)
+ issueTypeError(error)
case _ =>
contextNamerErrorGen.issue(TypeErrorWithUnderlyingTree(tree, ex))
}
@@ -1275,8 +1284,8 @@ trait ContextErrors {
}
def WarnAfterNonSilentRecursiveInference(param: Symbol, arg: Tree)(implicit context: Context) = {
- val note = "type-checking the invocation of "+ param.owner +" checks if the named argument expression '"+ param.name + " = ...' is a valid assignment\n"+
- "in the current scope. The resulting type inference error (see above) can be fixed by providing an explicit type in the local definition for "+ param.name +"."
+ val note = "failed to determine if '"+ param.name + " = ...' is a named argument or an assignment expression.\n"+
+ "an explicit type is required for the definition mentioned in the error message above."
context.warning(arg.pos, note)
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
index 4435ed0b60..ec76ece00f 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
@@ -276,7 +276,7 @@ trait Implicits {
/** An extractor for types of the form ? { name: (? >: argtpe <: Any*)restp }
*/
object HasMethodMatching {
- val dummyMethod = NoSymbol.newTermSymbol("typer$dummy") setInfo NullaryMethodType(AnyTpe)
+ val dummyMethod = NoSymbol.newTermSymbol(TermName("typer$dummy")) setInfo NullaryMethodType(AnyTpe)
def templateArgType(argtpe: Type) = new BoundedWildcardType(TypeBounds.lower(argtpe))
@@ -609,7 +609,7 @@ trait Implicits {
val itree2 = if (!isView) fallback else pt match {
case Function1(arg1, arg2) =>
typed1(
- atPos(itree0.pos)(Apply(itree1, List(Ident("<argument>") setType approximate(arg1)))),
+ atPos(itree0.pos)(Apply(itree1, List(Ident(nme.argument) setType approximate(arg1)))),
EXPRmode,
approximate(arg2)
) match {
@@ -893,7 +893,7 @@ trait Implicits {
try improves(firstPending, alt)
catch {
case e: CyclicReference =>
- debugwarn(s"Discarding $firstPending during implicit search due to cyclic reference.")
+ devWarning(s"Discarding $firstPending during implicit search due to cyclic reference.")
true
}
)
@@ -918,7 +918,7 @@ trait Implicits {
/** Returns all eligible ImplicitInfos and their SearchResults in a map.
*/
- def findAll() = mapFrom(eligible)(typedImplicit(_, ptChecked = false, isLocalToCallsite))
+ def findAll() = linkedMapFrom(eligible)(typedImplicit(_, ptChecked = false, isLocalToCallsite))
/** Returns the SearchResult of the best match.
*/
@@ -963,7 +963,7 @@ trait Implicits {
* symbols of the same name in succeeding lists.
* @return map from infos to search results
*/
- def applicableInfos(iss: Infoss, isLocalToCallsite: Boolean): Map[ImplicitInfo, SearchResult] = {
+ def applicableInfos(iss: Infoss, isLocalToCallsite: Boolean): mutable.LinkedHashMap[ImplicitInfo, SearchResult] = {
val start = if (Statistics.canEnable) Statistics.startCounter(subtypeAppInfos) else null
val computation = new ImplicitComputation(iss, isLocalToCallsite) { }
val applicable = computation.findAll()
diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
index b9ccf0977c..e18b9c10f0 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
@@ -553,9 +553,8 @@ trait Infer extends Checkable {
// ...or lower bound of a type param, since they're asking for it.
def canWarnAboutAny = {
val loBounds = tparams map (_.info.bounds.lo)
- val hasAny = pt :: restpe :: formals ::: argtpes ::: loBounds exists (t =>
- (t contains AnyClass) || (t contains AnyValClass)
- )
+ def containsAny(t: Type) = (t contains AnyClass) || (t contains AnyValClass)
+ val hasAny = pt :: restpe :: formals ::: argtpes ::: loBounds exists (_.dealiasWidenChain exists containsAny)
!hasAny
}
def argumentPosition(idx: Int): Position = context.tree match {
diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala
index da7b8b09aa..10aefae20b 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala
@@ -3,13 +3,14 @@ package typechecker
import java.lang.Math.min
import symtab.Flags._
-import scala.tools.nsc.util._
+import scala.reflect.internal.util.ScalaClassLoader
import scala.reflect.runtime.ReflectionUtils
import scala.collection.mutable.ListBuffer
import scala.reflect.ClassTag
import scala.reflect.internal.util.Statistics
import scala.reflect.macros.util._
import scala.util.control.ControlThrowable
+import scala.reflect.internal.util.ListOfNil
import scala.reflect.macros.runtime.{AbortMacroException, MacroRuntimes}
import scala.reflect.runtime.{universe => ru}
import scala.reflect.macros.compiler.DefaultMacroCompiler
diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala
index 0aa62d771e..f90e32ce8a 100644
--- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala
@@ -8,6 +8,7 @@ package typechecker
import symtab.Flags._
import scala.reflect.internal.util.StringOps.{ ojoin }
import scala.reflect.ClassTag
+import scala.reflect.internal.util.ListOfNil
import scala.reflect.runtime.{ universe => ru }
import scala.language.higherKinds
@@ -384,7 +385,7 @@ trait MethodSynthesis {
}
}
case class Getter(tree: ValDef) extends BaseGetter(tree) {
- override def derivedSym = if (mods.isDeferred) basisSym else basisSym.getter(enclClass)
+ override def derivedSym = if (mods.isDeferred) basisSym else basisSym.getterIn(enclClass)
private def derivedRhs = if (mods.isDeferred) EmptyTree else fieldSelection
private def derivedTpt = {
// For existentials, don't specify a type for the getter, even one derived
@@ -451,7 +452,7 @@ trait MethodSynthesis {
def flagsMask = SetterFlags
def flagsExtra = ACCESSOR
- override def derivedSym = basisSym.setter(enclClass)
+ override def derivedSym = basisSym.setterIn(enclClass)
}
case class Field(tree: ValDef) extends DerivedFromValDef {
def name = tree.localName
diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala
index 711cfba24d..24238b8e41 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala
@@ -10,6 +10,7 @@ import scala.collection.mutable
import scala.annotation.tailrec
import symtab.Flags._
import scala.language.postfixOps
+import scala.reflect.internal.util.ListOfNil
/** This trait declares methods to create symbols and to enter them into scopes.
*
@@ -464,7 +465,7 @@ trait Namers extends MethodSynthesis {
def enterModuleSymbol(tree : ModuleDef): Symbol = {
var m: Symbol = context.scope lookupModule tree.name
val moduleFlags = tree.mods.flags | MODULE
- if (m.isModule && !m.isPackage && inCurrentScope(m) && (currentRun.canRedefine(m) || m.isSynthetic)) {
+ if (m.isModule && !m.hasPackageFlag && inCurrentScope(m) && (currentRun.canRedefine(m) || m.isSynthetic)) {
// This code accounts for the way the package objects found in the classpath are opened up
// early by the completer of the package itself. If the `packageobjects` phase then finds
// the same package object in sources, we have to clean the slate and remove package object
@@ -487,7 +488,7 @@ trait Namers extends MethodSynthesis {
m.moduleClass setFlag moduleClassFlags(moduleFlags)
setPrivateWithin(tree, m.moduleClass)
}
- if (m.isTopLevel && !m.isPackage) {
+ if (m.isTopLevel && !m.hasPackageFlag) {
m.moduleClass.associatedFile = contextFile
currentRun.symSource(m) = m.moduleClass.sourceFile
registerTopLevelSym(m)
@@ -844,7 +845,7 @@ trait Namers extends MethodSynthesis {
private def widenIfNecessary(sym: Symbol, tpe: Type, pt: Type): Type = {
val getter =
if (sym.isValue && sym.owner.isClass && sym.isPrivate)
- sym.getter(sym.owner)
+ sym.getterIn(sym.owner)
else sym
def isHidden(tp: Type): Boolean = tp match {
case SingleType(pre, sym) =>
@@ -1305,7 +1306,7 @@ trait Namers extends MethodSynthesis {
// by martin: the null case can happen in IDE; this is really an ugly hack on top of an ugly hack but it seems to work
case Some(cda) =>
if (cda.companionModuleClassNamer == null) {
- debugwarn(s"SI-6576 The companion module namer for $meth was unexpectedly null")
+ devWarning(s"SI-6576 The companion module namer for $meth was unexpectedly null")
return
}
val p = (cda.classWithDefault, cda.companionModuleClassNamer)
diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala
index 187dae3fe1..1bc5daac65 100644
--- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala
@@ -522,8 +522,22 @@ trait NamesDefaults { self: Analyzer =>
WarnAfterNonSilentRecursiveInference(param, arg)(context)
res
} match {
- case SilentResultValue(t) => !t.isErroneous // #4041
- case _ => false
+ case SilentResultValue(t) =>
+ !t.isErroneous // #4041
+ case SilentTypeError(e: NormalTypeErrorFromCyclicReference) =>
+ // If we end up here, the CyclicReference was reported in a silent context. This can
+ // happen for local definitions, when the completer for a definition is created during
+ // type checking in silent mode. ContextErrors.TypeSigError catches that cyclic reference
+ // and transforms it into a NormalTypeErrorFromCyclicReference.
+ // The cycle needs to be reported, because the program cannot be typed: we don't know
+ // if we have an assignment or a named arg.
+ context.issue(e)
+ // 'err = true' is required because we're in a silent context
+ WarnAfterNonSilentRecursiveInference(param, arg)(context)
+ false
+ case _ =>
+ // We got a type error, so it cannot be an assignment (it doesn't type check as one).
+ false
}
catch {
// `silent` only catches and returns TypeErrors which are not
diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala
index fa4a764f1b..8a66c7d274 100644
--- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala
@@ -268,7 +268,7 @@ trait PatternTypers {
def freshArgType(tp: Type): Type = tp match {
case MethodType(param :: _, _) => param.tpe
- case PolyType(tparams, restpe) => createFromClonedSymbols(tparams, freshArgType(restpe))(polyType)
+ case PolyType(tparams, restpe) => createFromClonedSymbols(tparams, freshArgType(restpe))(genPolyType)
case OverloadedType(_, _) => OverloadedUnapplyError(fun) ; ErrorType
case _ => UnapplyWithSingleArgError(fun) ; ErrorType
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
index d2931ff9e1..5abfbe850f 100644
--- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
@@ -718,7 +718,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
// Check the remainder for invalid absoverride.
for (member <- rest ; if (member.isAbstractOverride && member.isIncompleteIn(clazz))) {
- val other = member.superSymbol(clazz)
+ val other = member.superSymbolIn(clazz)
val explanation =
if (other != NoSymbol) " and overrides incomplete superclass member " + infoString(other)
else ", but no concrete implementation could be found in a base class"
@@ -1684,9 +1684,9 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
case _ =>
}
if (skipBounds) {
- tree.tpe = tree.tpe.map {
+ tree.setType(tree.tpe.map {
_.filterAnnotations(_.symbol != UncheckedBoundsClass)
- }
+ })
}
tree
diff --git a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala
index 8f13507fa9..d65d2092ad 100644
--- a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala
@@ -326,7 +326,7 @@ trait SyntheticMethods extends ast.TreeDSL {
// Without a means to suppress this warning, I've thought better of it.
if (settings.warnValueOverrides) {
(clazz.info nonPrivateMember m.name) filter (m => (m.owner != AnyClass) && (m.owner != clazz) && !m.isDeferred) andAlso { m =>
- currentUnit.warning(clazz.pos, s"Implementation of ${m.name} inherited from ${m.owner} overridden in $clazz to enforce value class semantics")
+ typer.context.warning(clazz.pos, s"Implementation of ${m.name} inherited from ${m.owner} overridden in $clazz to enforce value class semantics")
}
}
true
diff --git a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala
index 743bbe53bd..a7d48ceb89 100644
--- a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala
@@ -266,7 +266,6 @@ abstract class TreeCheckers extends Analyzer {
if (tree ne typed)
treesDiffer(tree, typed)
-
tree
}
@@ -301,8 +300,8 @@ abstract class TreeCheckers extends Analyzer {
checkSym(tree)
/* XXX: lots of syms show up here with accessed == NoSymbol. */
if (accessed != NoSymbol) {
- val agetter = accessed.getter(sym.owner)
- val asetter = accessed.setter(sym.owner)
+ val agetter = accessed.getterIn(sym.owner)
+ val asetter = accessed.setterIn(sym.owner)
assertFn(agetter == sym || asetter == sym,
sym + " is getter or setter, but accessed sym " + accessed + " shows " + agetter + " and " + asetter
@@ -312,7 +311,7 @@ abstract class TreeCheckers extends Analyzer {
}
case ValDef(_, _, _, _) =>
if (sym.hasGetter && !sym.isOuterField && !sym.isOuterAccessor) {
- assertFn(sym.getter(sym.owner) != NoSymbol, ownerstr(sym) + " has getter but cannot be found. " + sym.ownerChain)
+ assertFn(sym.getterIn(sym.owner) != NoSymbol, ownerstr(sym) + " has getter but cannot be found. " + sym.ownerChain)
}
case Apply(fn, args) =>
if (args exists (_ == EmptyTree))
diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala
index 0f90c6a478..059981aa37 100644
--- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala
@@ -141,8 +141,8 @@ trait TypeDiagnostics {
if (!member.hasAccessorFlag) member
else if (!member.isDeferred) member.accessed
else {
- val getter = if (member.isSetter) member.getter(member.owner) else member
- val flags = if (getter.setter(member.owner) != NoSymbol) DEFERRED.toLong | MUTABLE else DEFERRED
+ val getter = if (member.isSetter) member.getterIn(member.owner) else member
+ val flags = if (getter.setterIn(member.owner) != NoSymbol) DEFERRED.toLong | MUTABLE else DEFERRED
getter.owner.newValue(getter.name.toTermName, getter.pos, flags) setInfo getter.tpe.resultType
}
@@ -439,7 +439,7 @@ trait TypeDiagnostics {
context.warning(pos, "imported `%s' is permanently hidden by definition of %s".format(hidden, defn.fullLocationString))
object checkUnused {
- val ignoreNames = Set[TermName]("readResolve", "readObject", "writeObject", "writeReplace")
+ val ignoreNames: Set[TermName] = Set(TermName("readResolve"), TermName("readObject"), TermName("writeObject"), TermName("writeReplace"))
class UnusedPrivates extends Traverser {
val defnTrees = ListBuffer[MemberDef]()
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index 93ece4c8da..2c8cc897f7 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -14,7 +14,7 @@ package tools.nsc
package typechecker
import scala.collection.{mutable, immutable}
-import scala.reflect.internal.util.{ BatchSourceFile, Statistics, shortClassOfInstance }
+import scala.reflect.internal.util.{ BatchSourceFile, Statistics, shortClassOfInstance, ListOfNil }
import mutable.ListBuffer
import symtab.Flags._
import Mode._
@@ -76,7 +76,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case s : SilentTypeError => f(s.reportableErrors)
}
}
- class SilentTypeError private(val errors: List[AbsTypeError]) extends SilentResult[Nothing] {
+ class SilentTypeError private(val errors: List[AbsTypeError], val warnings: List[(Position, String)]) extends SilentResult[Nothing] {
override def isEmpty = true
def err: AbsTypeError = errors.head
def reportableErrors = errors match {
@@ -87,10 +87,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
}
object SilentTypeError {
- def apply(errors: AbsTypeError*): SilentTypeError = new SilentTypeError(errors.toList)
+ def apply(errors: AbsTypeError*): SilentTypeError = apply(errors.toList, Nil)
+ def apply(errors: List[AbsTypeError], warnings: List[(Position, String)]): SilentTypeError = new SilentTypeError(errors, warnings)
+ // todo: this extracts only one error, should be a separate extractor.
def unapply(error: SilentTypeError): Option[AbsTypeError] = error.errors.headOption
}
+ // todo: should include reporter warnings in SilentResultValue.
+ // e.g. tryTypedApply could print warnings on arguments when the typing succeeds.
case class SilentResultValue[+T](value: T) extends SilentResult[T] { override def isEmpty = false }
def newTyper(context: Context): Typer = new NormalTyper(context)
@@ -147,7 +151,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
for(ar <- argResultsBuff)
paramTp = paramTp.subst(ar.subst.from, ar.subst.to)
- val res = if (paramFailed || (paramTp.isError && {paramFailed = true; true})) SearchFailure else inferImplicit(fun, paramTp, context.reportErrors, isView = false, context)
+ val res = if (paramFailed || (paramTp.isErroneous && {paramFailed = true; true})) SearchFailure else inferImplicit(fun, paramTp, context.reportErrors, isView = false, context)
argResultsBuff += res
if (res.isSuccess) {
@@ -241,7 +245,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case TypeRef(_, sym, _) if sym.isAliasType =>
val tp0 = tp.dealias
if (tp eq tp0) {
- debugwarn(s"dropExistential did not progress dealiasing $tp, see SI-7126")
+ devWarning(s"dropExistential did not progress dealiasing $tp, see SI-7126")
tp
} else {
val tp1 = dropExistential(tp0)
@@ -665,7 +669,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
@inline def wrapResult(reporter: ContextReporter, result: T) =
if (reporter.hasErrors) {
stopStats()
- SilentTypeError(reporter.errors: _*)
+ SilentTypeError(reporter.errors.toList, reporter.warnings.toList)
} else SilentResultValue(result)
try {
@@ -829,6 +833,16 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
orElse { _ =>
val resetTree = resetAttrs(original)
+ resetTree match {
+ case treeInfo.Applied(fun, targs, args) =>
+ if (fun.symbol != null && fun.symbol.isError)
+ // SI-9041 Without this, we leak error symbols past the typer!
+ // because the fallback typechecking notices the error-symbol,
+ // refuses to re-attempt typechecking, and presumes that someone
+ // else was responsible for issuing the related type error!
+ fun.setSymbol(NoSymbol)
+ case _ =>
+ }
debuglog(s"fallback on implicits: ${tree}/$resetTree")
val tree1 = typed(resetTree, mode)
// Q: `typed` already calls `pluginsTyped` and `adapt`. the only difference here is that
@@ -1012,11 +1026,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// to non-continuation types.
if (tree.tpe <:< AnyTpe) pt.dealias match {
case TypeRef(_, UnitClass, _) => // (12)
- if (settings.warnValueDiscard)
+ if (!isPastTyper && settings.warnValueDiscard)
context.warning(tree.pos, "discarded non-Unit value")
return typedPos(tree.pos, mode, pt)(Block(List(tree), Literal(Constant(()))))
case TypeRef(_, sym, _) if isNumericValueClass(sym) && isNumericSubType(tree.tpe, pt) =>
- if (settings.warnNumericWiden)
+ if (!isPastTyper && settings.warnNumericWiden)
context.warning(tree.pos, "implicit numeric widening")
return typedPos(tree.pos, mode, pt)(Select(tree, "to" + sym.name))
case _ =>
@@ -1164,7 +1178,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
def instantiatePossiblyExpectingUnit(tree: Tree, mode: Mode, pt: Type): Tree = {
- if (mode.typingExprNotFun && pt.typeSymbol == UnitClass)
+ if (mode.typingExprNotFun && pt.typeSymbol == UnitClass && !tree.tpe.isInstanceOf[MethodType])
instantiateExpectingUnit(tree, mode)
else
instantiate(tree, mode, pt)
@@ -1523,7 +1537,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val cbody1 = treeCopy.Block(cbody, preSuperStats, superCall1)
val clazz = context.owner
assert(clazz != NoSymbol, templ)
- val dummy = context.outer.owner.newLocalDummy(templ.pos)
+ // SI-9086 The position of this symbol is material: implicit search will avoid triggering
+ // cyclic errors in an implicit search in argument to the super constructor call on
+ // account of the "ignore symbols without complete info that succeed the implicit search"
+ // in this source file. See `ImplicitSearch#isValid` and `ImplicitInfo#isCyclicOrErroneous`.
+ val dummy = context.outer.owner.newLocalDummy(context.owner.pos)
val cscope = context.outer.makeNewScope(ctor, dummy)
if (dummy.isTopLevel) currentRun.symSource(dummy) = currentUnit.source.file
val cbody2 = { // called both during completion AND typing.
@@ -2013,7 +2031,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (mexists(vparamss)(_.symbol == superArg.symbol)) {
val alias = (
superAcc.initialize.alias
- orElse (superAcc getter superAcc.owner)
+ orElse (superAcc getterIn superAcc.owner)
filter (alias => superClazz.info.nonPrivateMember(alias.name) == alias)
)
if (alias.exists && !alias.accessed.isVariable && !isRepeatedParamType(alias.accessed.info)) {
@@ -2022,7 +2040,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case acc => acc
}
ownAcc match {
- case acc: TermSymbol if !acc.isVariable =>
+ case acc: TermSymbol if !acc.isVariable && !isByNameParamType(acc.info) =>
debuglog(s"$acc has alias ${alias.fullLocationString}")
acc setAlias alias
case _ =>
@@ -2534,7 +2552,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val default = methodSym newValueParameter (newTermName("default"), tree.pos.focus, SYNTHETIC) setInfo functionType(List(A1.tpe), B1.tpe)
val paramSyms = List(x, default)
- methodSym setInfo polyType(List(A1, B1), MethodType(paramSyms, B1.tpe))
+ methodSym setInfo genPolyType(List(A1, B1), MethodType(paramSyms, B1.tpe))
val methodBodyTyper = newTyper(context.makeNewScope(context.tree, methodSym))
if (!paramSynthetic) methodBodyTyper.context.scope enter x
@@ -2834,7 +2852,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
ClassDef(Modifiers(FINAL), tpnme.ANON_FUN_NAME, tparams = Nil,
gen.mkTemplate(
parents = TypeTree(samClassTpFullyDefined) :: serializableParentAddendum,
- self = emptyValDef,
+ self = noSelfType,
constrMods = NoMods,
vparamss = ListOfNil,
body = List(samDef),
@@ -2903,7 +2921,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
var issuedMissingParameterTypeError = false
foreach2(fun.vparams, argpts) { (vparam, argpt) =>
if (vparam.tpt.isEmpty) {
- vparam.tpt.tpe =
+ val vparamType =
if (isFullyDefined(argpt)) argpt
else {
fun match {
@@ -2922,6 +2940,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
issuedMissingParameterTypeError = true
ErrorType
}
+ vparam.tpt.setType(vparamType)
if (!vparam.tpt.pos.isDefined) vparam.tpt setPos vparam.pos.focus
}
}
@@ -3356,7 +3375,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// defaults are needed. they are added to the argument list in named style as
// calls to the default getters. Example:
// foo[Int](a)() ==> foo[Int](a)(b = foo$qual.foo$default$2[Int](a))
- checkNotMacro()
// SI-8111 transformNamedApplication eagerly shuffles around the application to preserve
// evaluation order. During this process, it calls `changeOwner` on symbols that
@@ -3403,6 +3421,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
duplErrTree
} else if (lencmp2 == 0) {
// useful when a default doesn't match parameter type, e.g. def f[T](x:T="a"); f[Int]()
+ checkNotMacro()
context.diagUsedDefaults = true
doTypedApply(tree, if (blockIsEmpty) fun else fun1, allArgs, mode, pt)
} else {
@@ -4334,7 +4353,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
def narrowRhs(tp: Type) = { val sym = context.tree.symbol
context.tree match {
case ValDef(mods, _, _, Apply(Select(`tree`, _), _)) if !mods.isMutable && sym != null && sym != NoSymbol =>
- val sym1 = if (sym.owner.isClass && sym.getter(sym.owner) != NoSymbol) sym.getter(sym.owner)
+ val sym1 = if (sym.owner.isClass && sym.getterIn(sym.owner) != NoSymbol) sym.getterIn(sym.owner)
else sym.lazyAccessorOrSelf
val pre = if (sym1.owner.isClass) sym1.owner.thisType else NoPrefix
intersectionType(List(tp, singleType(pre, sym1)))
@@ -4408,7 +4427,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
def tryTypedApply(fun: Tree, args: List[Tree]): Tree = {
val start = if (Statistics.canEnable) Statistics.startTimer(failedApplyNanos) else null
- def onError(typeErrors: Seq[AbsTypeError]): Tree = {
+ def onError(typeErrors: Seq[AbsTypeError], warnings: Seq[(Position, String)]): Tree = {
if (Statistics.canEnable) Statistics.stopTimer(failedApplyNanos, start)
// If the problem is with raw types, copnvert to existentials and try again.
@@ -4456,10 +4475,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
}
typeErrors foreach context.issue
+ warnings foreach { case (p, m) => context.warning(p, m) }
setError(treeCopy.Apply(tree, fun, args))
}
- silent(_.doTypedApply(tree, fun, args, mode, pt)) orElse onError
+ silent(_.doTypedApply(tree, fun, args, mode, pt)) match {
+ case SilentResultValue(value) => value
+ case e: SilentTypeError => onError(e.errors, e.warnings)
+ }
}
def normalTypedApply(tree: Tree, fun: Tree, args: List[Tree]) = {
@@ -4510,6 +4533,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case err: SilentTypeError =>
onError({
err.reportableErrors foreach context.issue
+ err.warnings foreach { case (p, m) => context.warning(p, m) }
args foreach (arg => typed(arg, mode, ErrorType))
setError(tree)
})
@@ -5162,15 +5186,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
def warn(message: String) = context.warning(lit.pos, s"possible missing interpolator: $message")
def suspiciousSym(name: TermName) = context.lookupSymbol(name, _ => true).symbol
def suspiciousExpr = InterpolatorCodeRegex findFirstIn s
- def suspiciousIdents = InterpolatorIdentRegex findAllIn s map (s => suspiciousSym(s drop 1))
+ def suspiciousIdents = InterpolatorIdentRegex findAllIn s map (s => suspiciousSym(TermName(s drop 1)))
- // heuristics - no warning on e.g. a string with only "$asInstanceOf"
- if (s contains ' ') (
- if (suspiciousExpr.nonEmpty)
- warn("detected an interpolated expression") // "${...}"
- else
- suspiciousIdents find isPlausible foreach (sym => warn(s"detected interpolated identifier `$$${sym.name}`")) // "$id"
- )
+ if (suspiciousExpr.nonEmpty)
+ warn("detected an interpolated expression") // "${...}"
+ else
+ suspiciousIdents find isPlausible foreach (sym => warn(s"detected interpolated identifier `$$${sym.name}`")) // "$id"
}
lit match {
case Literal(Constant(s: String)) if !isRecognizablyNotForInterpolation => maybeWarn(s)
@@ -5348,8 +5369,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
)
def runTyper(): Tree = {
if (retypingOk) {
- tree.tpe = null
- if (tree.hasSymbol) tree.symbol = NoSymbol
+ tree.setType(null)
+ if (tree.hasSymbolField) tree.symbol = NoSymbol
}
val alreadyTyped = tree.tpe ne null
val shouldPrint = !alreadyTyped && !phase.erasedTypes
diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
index fc1f45e358..22fb0728e6 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
@@ -7,6 +7,7 @@ package scala.tools.nsc
package typechecker
import symtab.Flags._
+import scala.reflect.internal.util.ListOfNil
/*
* @author Martin Odersky
diff --git a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala
index 1643e0061f..47c88f2c00 100644
--- a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala
+++ b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala
@@ -7,7 +7,7 @@ import scala.tools.nsc.Global
import scala.tools.nsc.reporters._
import scala.tools.nsc.CompilerCommand
import scala.tools.nsc.io.{AbstractFile, VirtualDirectory}
-import scala.tools.nsc.util.AbstractFileClassLoader
+import scala.reflect.internal.util.AbstractFileClassLoader
import scala.reflect.internal.Flags._
import scala.reflect.internal.util.{BatchSourceFile, NoSourceFile, NoFile}
import java.lang.{Class => jClass}