summaryrefslogtreecommitdiff
path: root/src/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler')
-rw-r--r--src/compiler/rootdoc.txt7
-rw-r--r--src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala3
-rw-r--r--src/compiler/scala/reflect/macros/contexts/Parsers.scala3
-rw-r--r--src/compiler/scala/reflect/macros/contexts/Reifiers.scala4
-rw-r--r--src/compiler/scala/reflect/macros/contexts/Typers.scala39
-rw-r--r--src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala15
-rw-r--r--src/compiler/scala/reflect/quasiquotes/Holes.scala3
-rw-r--r--src/compiler/scala/reflect/quasiquotes/Parsers.scala3
-rw-r--r--src/compiler/scala/reflect/quasiquotes/Placeholders.scala2
-rw-r--r--src/compiler/scala/reflect/reify/codegen/GenAnnotationInfos.scala2
-rw-r--r--src/compiler/scala/reflect/reify/codegen/GenTypes.scala10
-rw-r--r--src/compiler/scala/reflect/reify/codegen/GenUtils.scala2
-rw-r--r--src/compiler/scala/reflect/reify/phases/Reify.scala6
-rw-r--r--src/compiler/scala/reflect/reify/phases/Reshape.scala37
-rw-r--r--src/compiler/scala/reflect/reify/utils/NodePrinters.scala2
-rw-r--r--src/compiler/scala/tools/ant/FastScalac.scala15
-rw-r--r--src/compiler/scala/tools/ant/Pack200Task.scala8
-rw-r--r--src/compiler/scala/tools/ant/ScalaMatchingTask.scala6
-rw-r--r--src/compiler/scala/tools/ant/ScalaTool.scala3
-rw-r--r--src/compiler/scala/tools/ant/Scalac.scala16
-rw-r--r--src/compiler/scala/tools/ant/sabbus/ScalacFork.scala2
-rw-r--r--src/compiler/scala/tools/ant/sabbus/Use.scala1
-rwxr-xr-xsrc/compiler/scala/tools/ant/templates/tool-unix.tmpl12
-rw-r--r--src/compiler/scala/tools/ant/templates/tool-windows.tmpl2
-rw-r--r--src/compiler/scala/tools/cmd/CommandLine.scala4
-rw-r--r--src/compiler/scala/tools/cmd/FromString.scala2
-rw-r--r--src/compiler/scala/tools/cmd/Opt.scala2
-rw-r--r--src/compiler/scala/tools/cmd/Property.scala7
-rw-r--r--src/compiler/scala/tools/cmd/Reference.scala6
-rw-r--r--src/compiler/scala/tools/cmd/Spec.scala2
-rw-r--r--src/compiler/scala/tools/cmd/gen/AnyVals.scala484
-rw-r--r--src/compiler/scala/tools/cmd/gen/Codegen.scala39
-rw-r--r--src/compiler/scala/tools/cmd/gen/CodegenSpec.scala25
-rw-r--r--src/compiler/scala/tools/nsc/CompilationUnits.scala12
-rw-r--r--src/compiler/scala/tools/nsc/CompileServer.scala10
-rw-r--r--src/compiler/scala/tools/nsc/CompileSocket.scala12
-rw-r--r--src/compiler/scala/tools/nsc/CompilerCommand.scala10
-rw-r--r--src/compiler/scala/tools/nsc/Driver.scala26
-rw-r--r--src/compiler/scala/tools/nsc/GenericRunnerSettings.scala4
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala510
-rw-r--r--src/compiler/scala/tools/nsc/Main.scala3
-rw-r--r--src/compiler/scala/tools/nsc/PhaseAssembly.scala2
-rw-r--r--src/compiler/scala/tools/nsc/Reporting.scala61
-rw-r--r--src/compiler/scala/tools/nsc/ScriptRunner.scala9
-rw-r--r--src/compiler/scala/tools/nsc/ast/DocComments.scala19
-rw-r--r--src/compiler/scala/tools/nsc/ast/Printers.scala2
-rw-r--r--src/compiler/scala/tools/nsc/ast/TreeBrowsers.scala5
-rw-r--r--src/compiler/scala/tools/nsc/ast/TreeDSL.scala1
-rw-r--r--src/compiler/scala/tools/nsc/ast/TreeGen.scala173
-rw-r--r--src/compiler/scala/tools/nsc/ast/TreeInfo.scala8
-rw-r--r--src/compiler/scala/tools/nsc/ast/Trees.scala7
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala4
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Parsers.scala174
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Scanners.scala133
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala2
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala2
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala4
-rw-r--r--src/compiler/scala/tools/nsc/backend/JavaPlatform.scala35
-rw-r--r--src/compiler/scala/tools/nsc/backend/Platform.scala10
-rw-r--r--src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala56
-rw-r--r--src/compiler/scala/tools/nsc/backend/WorklistAlgorithm.scala51
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala553
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/CheckerException.scala10
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/ExceptionHandlers.scala71
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/GenICode.scala2239
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala711
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/ICodes.scala129
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala201
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Members.scala296
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala767
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Primitives.scala247
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Printers.scala126
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Repository.scala47
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala438
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/TypeStacks.scala82
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/CopyPropagation.scala553
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/DataFlowAnalysis.scala92
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/Liveness.scala102
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/LubException.scala12
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/ProgramPoint.scala18
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/ReachingDefinitions.scala250
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/SemiLattice.scala49
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala725
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala62
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala465
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala784
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala696
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeICodeCommon.scala25
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala110
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala152
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala60
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala174
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala370
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala144
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala1
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala4
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala445
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala3350
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala62
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala556
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala514
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala273
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala191
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala (renamed from src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala)80
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/TypeFlowInterpreter.scala36
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/package.scala374
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala907
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala247
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala243
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala492
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala351
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala635
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala75
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala936
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala339
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala240
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala871
-rw-r--r--src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala235
-rw-r--r--src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala626
-rw-r--r--src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala450
-rw-r--r--src/compiler/scala/tools/nsc/backend/opt/InlineExceptionHandlers.scala392
-rw-r--r--src/compiler/scala/tools/nsc/backend/opt/Inliners.scala1075
-rw-r--r--src/compiler/scala/tools/nsc/classpath/AggregateClassPath.scala (renamed from src/compiler/scala/tools/nsc/classpath/AggregateFlatClassPath.scala)74
-rw-r--r--src/compiler/scala/tools/nsc/classpath/ClassPath.scala60
-rw-r--r--src/compiler/scala/tools/nsc/classpath/ClassPathFactory.scala69
-rw-r--r--src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala240
-rw-r--r--src/compiler/scala/tools/nsc/classpath/DirectoryFlatClassPath.scala162
-rw-r--r--src/compiler/scala/tools/nsc/classpath/FileUtils.scala8
-rw-r--r--src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala101
-rw-r--r--src/compiler/scala/tools/nsc/classpath/FlatClassPathFactory.scala38
-rw-r--r--src/compiler/scala/tools/nsc/classpath/PackageNameUtils.scala2
-rw-r--r--src/compiler/scala/tools/nsc/classpath/VirtualDirectoryClassPath.scala40
-rw-r--r--src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala51
-rw-r--r--src/compiler/scala/tools/nsc/classpath/ZipArchiveFileLookup.scala11
-rw-r--r--src/compiler/scala/tools/nsc/io/Jar.scala5
-rw-r--r--src/compiler/scala/tools/nsc/io/Socket.scala2
-rw-r--r--src/compiler/scala/tools/nsc/io/SourceReader.scala2
-rw-r--r--src/compiler/scala/tools/nsc/javac/JavaParsers.scala76
-rw-r--r--src/compiler/scala/tools/nsc/javac/JavaScanners.scala45
-rw-r--r--src/compiler/scala/tools/nsc/plugins/Plugin.scala6
-rw-r--r--src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala97
-rw-r--r--src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala3
-rw-r--r--src/compiler/scala/tools/nsc/settings/AbsSettings.scala6
-rw-r--r--src/compiler/scala/tools/nsc/settings/FscSettings.scala4
-rw-r--r--src/compiler/scala/tools/nsc/settings/MutableSettings.scala52
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaSettings.scala232
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaVersion.scala48
-rw-r--r--src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala4
-rw-r--r--src/compiler/scala/tools/nsc/settings/Warnings.scala49
-rw-r--r--src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala2
-rw-r--r--src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala102
-rw-r--r--src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala282
-rw-r--r--src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala1130
-rw-r--r--src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala403
-rw-r--r--src/compiler/scala/tools/nsc/transform/AddInterfaces.scala376
-rw-r--r--src/compiler/scala/tools/nsc/transform/CleanUp.scala162
-rw-r--r--src/compiler/scala/tools/nsc/transform/Constructors.scala624
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala755
-rw-r--r--src/compiler/scala/tools/nsc/transform/Erasure.scala197
-rw-r--r--src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala160
-rw-r--r--src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala12
-rw-r--r--src/compiler/scala/tools/nsc/transform/Fields.scala787
-rw-r--r--src/compiler/scala/tools/nsc/transform/Flatten.scala4
-rw-r--r--src/compiler/scala/tools/nsc/transform/InlineErasure.scala11
-rw-r--r--src/compiler/scala/tools/nsc/transform/LambdaLift.scala281
-rw-r--r--src/compiler/scala/tools/nsc/transform/LazyVals.scala293
-rw-r--r--src/compiler/scala/tools/nsc/transform/Mixin.scala1305
-rw-r--r--src/compiler/scala/tools/nsc/transform/OverridingPairs.scala4
-rw-r--r--src/compiler/scala/tools/nsc/transform/SampleTransform.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala103
-rw-r--r--src/compiler/scala/tools/nsc/transform/Statics.scala59
-rw-r--r--src/compiler/scala/tools/nsc/transform/TailCalls.scala10
-rw-r--r--src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala177
-rw-r--r--src/compiler/scala/tools/nsc/transform/TypingTransformers.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala241
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/Logic.scala10
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala16
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala14
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala4
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala22
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala62
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala6
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Adaptations.scala8
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Analyzer.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Checkable.scala8
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala30
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala103
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Contexts.scala171
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Duplicators.scala21
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala54
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Implicits.scala140
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Infer.scala75
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Macros.scala21
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala523
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Namers.scala911
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala109
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala5
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/RefChecks.scala342
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala38
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala64
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Tags.scala11
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala8
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala269
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala1366
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/TypersTracking.scala3
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Unapplies.scala3
-rw-r--r--src/compiler/scala/tools/nsc/util/ClassFileLookup.scala57
-rw-r--r--src/compiler/scala/tools/nsc/util/ClassPath.scala381
-rw-r--r--src/compiler/scala/tools/nsc/util/StackTracing.scala44
-rw-r--r--src/compiler/scala/tools/nsc/util/package.scala2
-rw-r--r--src/compiler/scala/tools/reflect/FastTrack.scala1
-rw-r--r--src/compiler/scala/tools/reflect/FormatInterpolator.scala13
-rw-r--r--src/compiler/scala/tools/reflect/ReflectGlobal.scala3
-rw-r--r--src/compiler/scala/tools/reflect/ReflectMain.scala4
-rw-r--r--src/compiler/scala/tools/reflect/ToolBoxFactory.scala52
-rw-r--r--src/compiler/scala/tools/reflect/WrappedProperties.scala5
-rw-r--r--src/compiler/scala/tools/util/PathResolver.scala93
-rw-r--r--src/compiler/scala/tools/util/VerifyClass.scala2
221 files changed, 14605 insertions, 26327 deletions
diff --git a/src/compiler/rootdoc.txt b/src/compiler/rootdoc.txt
index 173f604098..25808dec89 100644
--- a/src/compiler/rootdoc.txt
+++ b/src/compiler/rootdoc.txt
@@ -1,6 +1 @@
-The Scala compiler API.
-
-The following resources are useful for Scala plugin/compiler development:
- - [[http://www.scala-lang.org/node/215 Scala development tutorials]] on [[http://www.scala-lang.org www.scala-lang.org]]
- - [[https://wiki.scala-lang.org/display/SIW/ Scala Internals wiki]]
- - [[http://lampwww.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ Scala compiler corner]], maintained by Miguel
+The Scala compiler and reflection APIs.
diff --git a/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala b/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala
index b8384851da..a3bf894b25 100644
--- a/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala
+++ b/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala
@@ -12,7 +12,6 @@ abstract class DefaultMacroCompiler extends Resolvers
import treeInfo._
import definitions._
val runDefinitions = currentRun.runDefinitions
- import runDefinitions.Predef_???
val typer: global.analyzer.Typer
val context = typer.context
@@ -92,4 +91,4 @@ abstract class DefaultMacroCompiler extends Resolvers
EmptyTree
}
}
-} \ No newline at end of file
+}
diff --git a/src/compiler/scala/reflect/macros/contexts/Parsers.scala b/src/compiler/scala/reflect/macros/contexts/Parsers.scala
index f4584f3627..cc3f01e53b 100644
--- a/src/compiler/scala/reflect/macros/contexts/Parsers.scala
+++ b/src/compiler/scala/reflect/macros/contexts/Parsers.scala
@@ -16,8 +16,9 @@ trait Parsers {
val tree = gen.mkTreeOrBlock(parser.parseStatsOrPackages())
sreporter.infos.foreach {
case sreporter.Info(pos, msg, sreporter.ERROR) => throw ParseException(pos, msg)
+ case _ =>
}
tree
} finally global.reporter = oldReporter
}
-} \ No newline at end of file
+}
diff --git a/src/compiler/scala/reflect/macros/contexts/Reifiers.scala b/src/compiler/scala/reflect/macros/contexts/Reifiers.scala
index ecef1c7289..010829f6ab 100644
--- a/src/compiler/scala/reflect/macros/contexts/Reifiers.scala
+++ b/src/compiler/scala/reflect/macros/contexts/Reifiers.scala
@@ -61,9 +61,9 @@ trait Reifiers {
// logging free vars only when they are untyped prevents avalanches of duplicate messages
symtab.syms map (sym => symtab.symDef(sym)) foreach {
case FreeTermDef(_, _, binding, _, origin) if universe.settings.logFreeTerms && binding.tpe == null =>
- reporter.echo(position, "free term: %s %s".format(showRaw(binding), origin))
+ reporter.echo(position, s"free term: ${showRaw(binding)} $origin")
case FreeTypeDef(_, _, binding, _, origin) if universe.settings.logFreeTypes && binding.tpe == null =>
- reporter.echo(position, "free type: %s %s".format(showRaw(binding), origin))
+ reporter.echo(position, s"free type: ${showRaw(binding)} $origin")
case _ =>
// do nothing
}
diff --git a/src/compiler/scala/reflect/macros/contexts/Typers.scala b/src/compiler/scala/reflect/macros/contexts/Typers.scala
index 28c1e3ddb3..a0dfbf5df1 100644
--- a/src/compiler/scala/reflect/macros/contexts/Typers.scala
+++ b/src/compiler/scala/reflect/macros/contexts/Typers.scala
@@ -18,31 +18,34 @@ trait Typers {
* @see [[scala.tools.reflect.ToolBox.typeCheck]]
*/
def typecheck(tree: Tree, mode: TypecheckMode = TERMmode, pt: Type = universe.WildcardType, silent: Boolean = false, withImplicitViewsDisabled: Boolean = false, withMacrosDisabled: Boolean = false): Tree = {
- macroLogVerbose("typechecking %s with expected type %s, implicit views = %s, macros = %s".format(tree, pt, !withImplicitViewsDisabled, !withMacrosDisabled))
- val context = callsiteTyper.context
- val withImplicitFlag = if (!withImplicitViewsDisabled) (context.withImplicitsEnabled[Tree] _) else (context.withImplicitsDisabled[Tree] _)
- val withMacroFlag = if (!withMacrosDisabled) (context.withMacrosEnabled[Tree] _) else (context.withMacrosDisabled[Tree] _)
- def withContext(tree: => Tree) = withImplicitFlag(withMacroFlag(tree))
- def withWrapping(tree: Tree)(op: Tree => Tree) = if (mode == TERMmode) universe.wrappingIntoTerm(tree)(op) else op(tree)
- def typecheckInternal(tree: Tree) = callsiteTyper.silent(_.typed(universe.duplicateAndKeepPositions(tree), mode, pt), reportAmbiguousErrors = false)
- withWrapping(tree)(wrappedTree => withContext(typecheckInternal(wrappedTree) match {
- case universe.analyzer.SilentResultValue(result) =>
- macroLogVerbose(result)
- result
- case error @ universe.analyzer.SilentTypeError(_) =>
- macroLogVerbose(error.err.errMsg)
- if (!silent) throw new TypecheckException(error.err.errPos, error.err.errMsg)
- universe.EmptyTree
- }))
+ macroLogVerbose(s"typechecking $tree with expected type $pt, implicit views = ${!withImplicitViewsDisabled}, macros = ${!withMacrosDisabled}")
+ import callsiteTyper.context
+ def doTypecheck(wrapped: Tree): Tree =
+ context.withImplicits(enabled = !withImplicitViewsDisabled) {
+ context.withMacros(enabled = !withMacrosDisabled) {
+ callsiteTyper.silent(_.typed(universe.duplicateAndKeepPositions(wrapped), mode, pt), reportAmbiguousErrors = false) match {
+ case universe.analyzer.SilentResultValue(result) =>
+ macroLogVerbose(result)
+ result
+ case error@universe.analyzer.SilentTypeError(_) =>
+ macroLogVerbose(error.err.errMsg)
+ if (!silent) throw new TypecheckException(error.err.errPos, error.err.errMsg)
+ universe.EmptyTree
+ }
+ }
+ }
+
+ if (mode == TERMmode) universe.wrappingIntoTerm(tree)(doTypecheck)
+ else doTypecheck(tree)
}
def inferImplicitValue(pt: Type, silent: Boolean = true, withMacrosDisabled: Boolean = false, pos: Position = enclosingPosition): Tree = {
- macroLogVerbose("inferring implicit value of type %s, macros = %s".format(pt, !withMacrosDisabled))
+ macroLogVerbose(s"inferring implicit value of type $pt, macros = ${!withMacrosDisabled}")
universe.analyzer.inferImplicit(universe.EmptyTree, pt, false, callsiteTyper.context, silent, withMacrosDisabled, pos, (pos, msg) => throw TypecheckException(pos, msg))
}
def inferImplicitView(tree: Tree, from: Type, to: Type, silent: Boolean = true, withMacrosDisabled: Boolean = false, pos: Position = enclosingPosition): Tree = {
- macroLogVerbose("inferring implicit view from %s to %s for %s, macros = %s".format(from, to, tree, !withMacrosDisabled))
+ macroLogVerbose(s"inferring implicit view from $from to $to for $tree, macros = ${!withMacrosDisabled}")
val viewTpe = universe.appliedType(universe.definitions.FunctionClass(1).toTypeConstructor, List(from, to))
universe.analyzer.inferImplicit(tree, viewTpe, true, callsiteTyper.context, silent, withMacrosDisabled, pos, (pos, msg) => throw TypecheckException(pos, msg))
}
diff --git a/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala b/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala
index 5fd9c0db34..7e700a524c 100644
--- a/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala
+++ b/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala
@@ -1,8 +1,11 @@
package scala.reflect.macros
package runtime
+import java.net.URLClassLoader
+
import scala.reflect.internal.Flags._
import scala.reflect.runtime.ReflectionUtils
+import scala.reflect.internal.util.AbstractFileClassLoader
trait MacroRuntimes extends JavaReflectionRuntimes {
self: scala.tools.nsc.typechecker.Analyzer =>
@@ -44,7 +47,15 @@ trait MacroRuntimes extends JavaReflectionRuntimes {
* which compiles implementations into a virtual directory (very much like REPL does) and then conjures
* a classloader mapped to that virtual directory.
*/
- lazy val defaultMacroClassloader: ClassLoader = findMacroClassLoader()
+ private lazy val defaultMacroClassloaderCache = {
+ def attemptClose(loader: ClassLoader): Unit = loader match {
+ case u: URLClassLoader => debuglog("Closing macro runtime classloader"); u.close()
+ case afcl: AbstractFileClassLoader => attemptClose(afcl.getParent)
+ case _ => ???
+ }
+ perRunCaches.newGeneric(findMacroClassLoader, attemptClose _)
+ }
+ def defaultMacroClassloader: ClassLoader = defaultMacroClassloaderCache()
/** Abstracts away resolution of macro runtimes.
*/
@@ -72,4 +83,4 @@ trait MacroRuntimes extends JavaReflectionRuntimes {
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/compiler/scala/reflect/quasiquotes/Holes.scala b/src/compiler/scala/reflect/quasiquotes/Holes.scala
index 47084fc317..63be500061 100644
--- a/src/compiler/scala/reflect/quasiquotes/Holes.scala
+++ b/src/compiler/scala/reflect/quasiquotes/Holes.scala
@@ -1,7 +1,6 @@
package scala.reflect
package quasiquotes
-import scala.collection.{immutable, mutable}
import scala.reflect.internal.Flags._
import scala.reflect.macros.TypecheckException
@@ -222,7 +221,7 @@ trait Holes { self: Quasiquotes =>
else if (rank == NoDot) Some(unlifter)
else {
val idx = records.indexWhere { p => p._1 =:= tpe && p._2 == rank }
- val resIdx = if (idx != -1) idx else { records +:= (tpe, rank); records.length - 1}
+ val resIdx = if (idx != -1) idx else { records +:= ((tpe, rank)); records.length - 1}
Some(Ident(TermName(nme.QUASIQUOTE_UNLIFT_HELPER + resIdx)))
}
}
diff --git a/src/compiler/scala/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/reflect/quasiquotes/Parsers.scala
index 108ad0bc2e..c695f438a4 100644
--- a/src/compiler/scala/reflect/quasiquotes/Parsers.scala
+++ b/src/compiler/scala/reflect/quasiquotes/Parsers.scala
@@ -3,10 +3,7 @@ package quasiquotes
import scala.tools.nsc.ast.parser.{Parsers => ScalaParser}
import scala.tools.nsc.ast.parser.Tokens._
-import scala.compat.Platform.EOL
import scala.reflect.internal.util.{BatchSourceFile, SourceFile, FreshNameCreator}
-import scala.collection.mutable.ListBuffer
-import scala.util.Try
/** Builds upon the vanilla Scala parser and teams up together with Placeholders.scala to emulate holes.
* A principled solution to splicing into Scala syntax would be a parser that natively supports holes.
diff --git a/src/compiler/scala/reflect/quasiquotes/Placeholders.scala b/src/compiler/scala/reflect/quasiquotes/Placeholders.scala
index a5b42f8a1f..bc4f954275 100644
--- a/src/compiler/scala/reflect/quasiquotes/Placeholders.scala
+++ b/src/compiler/scala/reflect/quasiquotes/Placeholders.scala
@@ -2,7 +2,7 @@ package scala.reflect
package quasiquotes
import java.util.UUID.randomUUID
-import scala.collection.{immutable, mutable}
+import scala.collection.mutable
/** Emulates hole support (see Holes.scala) in the quasiquote parser (see Parsers.scala).
* A principled solution to splicing into Scala syntax would be a parser that natively supports holes.
diff --git a/src/compiler/scala/reflect/reify/codegen/GenAnnotationInfos.scala b/src/compiler/scala/reflect/reify/codegen/GenAnnotationInfos.scala
index ce26232e5f..089f07de06 100644
--- a/src/compiler/scala/reflect/reify/codegen/GenAnnotationInfos.scala
+++ b/src/compiler/scala/reflect/reify/codegen/GenAnnotationInfos.scala
@@ -10,7 +10,7 @@ trait GenAnnotationInfos {
// however, when reifying free and tough types, we're forced to reify annotation infos as is
// why is that bad? take a look inside
def reifyAnnotationInfo(ann: AnnotationInfo): Tree = {
- val reifiedArgs = ann.args map { arg =>
+ ann.args.foreach { arg =>
val saved1 = reifyTreeSymbols
val saved2 = reifyTreeTypes
diff --git a/src/compiler/scala/reflect/reify/codegen/GenTypes.scala b/src/compiler/scala/reflect/reify/codegen/GenTypes.scala
index d007df75e3..b2948f8161 100644
--- a/src/compiler/scala/reflect/reify/codegen/GenTypes.scala
+++ b/src/compiler/scala/reflect/reify/codegen/GenTypes.scala
@@ -106,14 +106,10 @@ trait GenTypes {
private def spliceAsManifest(tpe: Type): Tree = {
def isSynthetic(manifest: Tree) = manifest exists (sub => sub.symbol != null && (sub.symbol == FullManifestModule || sub.symbol.owner == FullManifestModule))
def searchForManifest(typer: analyzer.Typer): Tree =
- analyzer.inferImplicit(
- EmptyTree,
+ analyzer.inferImplicitByTypeSilent(
appliedType(FullManifestClass.toTypeConstructor, List(tpe)),
- reportAmbiguous = false,
- isView = false,
- context = typer.context,
- saveAmbiguousDivergent = false,
- pos = defaultErrorPosition) match {
+ typer.context,
+ defaultErrorPosition) match {
case success if !success.tree.isEmpty && !isSynthetic(success.tree) =>
val manifestInScope = success.tree
// todo. write a test for this
diff --git a/src/compiler/scala/reflect/reify/codegen/GenUtils.scala b/src/compiler/scala/reflect/reify/codegen/GenUtils.scala
index b5b0f93750..242e5d60b3 100644
--- a/src/compiler/scala/reflect/reify/codegen/GenUtils.scala
+++ b/src/compiler/scala/reflect/reify/codegen/GenUtils.scala
@@ -55,7 +55,7 @@ trait GenUtils {
mirrorCall(TermName("" + prefix), args: _*)
def scalaFactoryCall(name: TermName, args: Tree*): Tree =
- call(s"scala.$name.apply", args: _*)
+ call(s"_root_.scala.$name.apply", args: _*)
def scalaFactoryCall(name: String, args: Tree*): Tree =
scalaFactoryCall(TermName(name), args: _*)
diff --git a/src/compiler/scala/reflect/reify/phases/Reify.scala b/src/compiler/scala/reflect/reify/phases/Reify.scala
index 143424dac5..93f6f99d81 100644
--- a/src/compiler/scala/reflect/reify/phases/Reify.scala
+++ b/src/compiler/scala/reflect/reify/phases/Reify.scala
@@ -1,7 +1,6 @@
package scala.reflect.reify
package phases
-import scala.runtime.ScalaRunTime.isAnyVal
import scala.reflect.reify.codegen._
trait Reify extends GenSymbols
@@ -57,4 +56,9 @@ trait Reify extends GenSymbols
case _ =>
throw new Error("reifee %s of type %s is not supported".format(reifee, reifee.getClass))
})
+
+ private def isAnyVal(x: Any) = x match {
+ case _: Byte | _: Short | _: Char | _: Int | _: Long | _: Float | _: Double | _: Boolean | _: Unit => true
+ case _ => false
+ }
}
diff --git a/src/compiler/scala/reflect/reify/phases/Reshape.scala b/src/compiler/scala/reflect/reify/phases/Reshape.scala
index 6c073c0b4c..581ce8256a 100644
--- a/src/compiler/scala/reflect/reify/phases/Reshape.scala
+++ b/src/compiler/scala/reflect/reify/phases/Reshape.scala
@@ -49,13 +49,13 @@ trait Reshape {
if (discard) hk else ta
case classDef @ ClassDef(mods, name, params, impl) =>
val Template(parents, self, body) = impl
- var body1 = trimAccessors(classDef, reshapeLazyVals(body))
+ var body1 = trimAccessors(classDef, body)
body1 = trimSyntheticCaseClassMembers(classDef, body1)
val impl1 = Template(parents, self, body1).copyAttrs(impl)
ClassDef(mods, name, params, impl1).copyAttrs(classDef)
case moduledef @ ModuleDef(mods, name, impl) =>
val Template(parents, self, body) = impl
- var body1 = trimAccessors(moduledef, reshapeLazyVals(body))
+ var body1 = trimAccessors(moduledef, body)
body1 = trimSyntheticCaseClassMembers(moduledef, body1)
val impl1 = Template(parents, self, body1).copyAttrs(impl)
ModuleDef(mods, name, impl1).copyAttrs(moduledef)
@@ -63,10 +63,10 @@ trait Reshape {
val discardedParents = parents collect { case tt: TypeTree => tt } filter isDiscarded
if (reifyDebug && discardedParents.length > 0) println("discarding parents in Template: " + discardedParents.mkString(", "))
val parents1 = parents diff discardedParents
- val body1 = reshapeLazyVals(trimSyntheticCaseClassCompanions(body))
+ val body1 = trimSyntheticCaseClassCompanions(body)
Template(parents1, self, body1).copyAttrs(template)
case block @ Block(stats, expr) =>
- val stats1 = reshapeLazyVals(trimSyntheticCaseClassCompanions(stats))
+ val stats1 = trimSyntheticCaseClassCompanions(stats)
Block(stats1, expr).copyAttrs(block)
case unapply @ UnApply(Unapplied(Select(fun, nme.unapply | nme.unapplySeq)), args) =>
if (reifyDebug) println("unapplying unapply: " + tree)
@@ -306,35 +306,6 @@ trait Reshape {
stats1
}
- private def reshapeLazyVals(stats: List[Tree]): List[Tree] = {
- val lazyvaldefs:Map[Symbol, DefDef] = stats.collect({ case ddef: DefDef if ddef.mods.isLazy => ddef }).
- map((ddef: DefDef) => ddef.symbol -> ddef).toMap
- // lazy valdef and defdef are in the same block.
- // only that valdef needs to have its rhs rebuilt from defdef
- stats flatMap (stat => stat match {
- case vdef: ValDef if vdef.symbol.isLazy =>
- if (reifyDebug) println(s"reconstructing original lazy value for $vdef")
- val ddefSym = vdef.symbol.lazyAccessor
- val vdef1 = lazyvaldefs.get(ddefSym) match {
- case Some(ddef) =>
- toPreTyperLazyVal(ddef)
- case None =>
- if (reifyDebug) println("couldn't find corresponding lazy val accessor")
- vdef
- }
- if (reifyDebug) println(s"reconstructed lazy val is $vdef1")
- vdef1::Nil
- case ddef: DefDef if ddef.symbol.isLazy =>
- def hasUnitType(sym: Symbol) = (sym.tpe.typeSymbol == UnitClass) && sym.tpe.annotations.isEmpty
- if (hasUnitType(ddef.symbol)) {
- // since lazy values of type Unit don't have val's
- // we need to create them from scratch
- toPreTyperLazyVal(ddef) :: Nil
- } else Nil
- case _ => stat::Nil
- })
- }
-
private def trimSyntheticCaseClassMembers(deff: Tree, stats: List[Tree]): List[Tree] =
stats filterNot (memberDef => memberDef.isDef && {
val isSynthetic = memberDef.symbol.isSynthetic
diff --git a/src/compiler/scala/reflect/reify/utils/NodePrinters.scala b/src/compiler/scala/reflect/reify/utils/NodePrinters.scala
index 3b91d28360..a5c4c7e0a3 100644
--- a/src/compiler/scala/reflect/reify/utils/NodePrinters.scala
+++ b/src/compiler/scala/reflect/reify/utils/NodePrinters.scala
@@ -28,7 +28,7 @@ trait NodePrinters {
var s = line substring 2
s = s.replace(nme.UNIVERSE_PREFIX.toString, "")
s = s.replace(".apply", "")
- s = "([^\"])scala\\.collection\\.immutable\\.".r.replaceAllIn(s, "$1")
+ s = "([^\"])(_root_\\.)?scala\\.collection\\.immutable\\.".r.replaceAllIn(s, "$1")
s = "List\\[List\\[.*?\\].*?\\]".r.replaceAllIn(s, "List")
s = "List\\[.*?\\]".r.replaceAllIn(s, "List")
s = s.replace("immutable.this.Nil", "List()")
diff --git a/src/compiler/scala/tools/ant/FastScalac.scala b/src/compiler/scala/tools/ant/FastScalac.scala
index 6f0a30aa9d..3b62c493d3 100644
--- a/src/compiler/scala/tools/ant/FastScalac.scala
+++ b/src/compiler/scala/tools/ant/FastScalac.scala
@@ -8,7 +8,7 @@
package scala.tools.ant
-import org.apache.tools.ant.{AntClassLoader, Project}
+import org.apache.tools.ant.AntClassLoader
import org.apache.tools.ant.taskdefs.Java
import org.apache.tools.ant.types.Path
@@ -109,7 +109,7 @@ class FastScalac extends Scalac {
List(
/*scalac*/
s.debuginfo, s.target
- ) filter (x => x.value != x.default) map (x => "%s:%s".format(x.name, x.value))
+ ) filter (x => x.value != x.default) map (x => s"${x.name}:${x.value}")
val booleanSettings =
List(
@@ -129,7 +129,7 @@ class FastScalac extends Scalac {
val phaseSetting = {
val s = settings.log
if (s.value.isEmpty) Nil
- else List("%s:%s".format(s.name, s.value.mkString(",")))
+ else List(s"${s.name}:${s.value.mkString(",")}")
}
val fscOptions =
@@ -147,8 +147,7 @@ class FastScalac extends Scalac {
case cl: AntClassLoader =>
path add new Path(getProject, cl.getClasspath)
case _ =>
- buildError("Compilation failed because of an internal compiler error;"+
- " see the error output for details.")
+ buildError("Compilation failed because of an internal compiler error; see the error output for details.")
}
path
}
@@ -160,8 +159,7 @@ class FastScalac extends Scalac {
File(url.getFile).jfile.getParentFile.getParentFile.getAbsolutePath
} catch {
case _: Throwable =>
- buildError("Compilation failed because of an internal compiler error;"+
- " couldn't determine value for -Dscala.home=<value>")
+ buildError("Compilation failed because of an internal compiler error; couldn't determine value for -Dscala.home=<value>")
}
java.createJvmarg() setValue "-Dscala.usejavacp=true"
java.createJvmarg() setValue ("-Dscala.home="+scalaHome)
@@ -186,7 +184,6 @@ class FastScalac extends Scalac {
val res = execWithArgFiles(java, paths)
if (failonerror && res != 0)
- buildError("Compilation failed because of an internal compiler error;"+
- " see the error output for details.")
+ buildError("Compilation failed because of an internal compiler error; see the error output for details.")
}
}
diff --git a/src/compiler/scala/tools/ant/Pack200Task.scala b/src/compiler/scala/tools/ant/Pack200Task.scala
index 3c1bc8cad9..df162d734a 100644
--- a/src/compiler/scala/tools/ant/Pack200Task.scala
+++ b/src/compiler/scala/tools/ant/Pack200Task.scala
@@ -8,14 +8,10 @@
package scala.tools.ant
-import java.io.{BufferedOutputStream, File, FileInputStream,
- FileOutputStream, PipedInputStream, PipedOutputStream}
-import java.util.jar.{JarFile, JarInputStream, JarOutputStream, Pack200}
+import java.io.{BufferedOutputStream, File, FileOutputStream}
+import java.util.jar.{JarFile, JarOutputStream, Pack200}
import java.util.jar.Pack200.Packer._
-import org.apache.tools.ant.{BuildException, DirectoryScanner}
-import org.apache.tools.ant.types.FileSet
-
/** An [[http://ant.apache.org Ant]] task that applies the pack200 encoding
* to a JAR file.
*
diff --git a/src/compiler/scala/tools/ant/ScalaMatchingTask.scala b/src/compiler/scala/tools/ant/ScalaMatchingTask.scala
index 68a84bed0c..43b9010509 100644
--- a/src/compiler/scala/tools/ant/ScalaMatchingTask.scala
+++ b/src/compiler/scala/tools/ant/ScalaMatchingTask.scala
@@ -8,11 +8,8 @@
package scala.tools.ant
-import java.io.{ File, InputStream, FileWriter }
-
import org.apache.tools.ant.{ Task, BuildException }
import org.apache.tools.ant.taskdefs.MatchingTask
-import org.apache.tools.ant.types.{ Path, Reference }
trait ScalaTask {
self: Task =>
@@ -27,5 +24,4 @@ trait ScalaTask {
throw new BuildException(message, getLocation())
}
-abstract class ScalaMatchingTask extends MatchingTask with ScalaTask {
-}
+abstract class ScalaMatchingTask extends MatchingTask with ScalaTask
diff --git a/src/compiler/scala/tools/ant/ScalaTool.scala b/src/compiler/scala/tools/ant/ScalaTool.scala
index bb6a933d3f..67879d6de3 100644
--- a/src/compiler/scala/tools/ant/ScalaTool.scala
+++ b/src/compiler/scala/tools/ant/ScalaTool.scala
@@ -8,8 +8,7 @@
package scala.tools.ant
-import java.io.{File, InputStream, FileWriter}
-import org.apache.tools.ant.BuildException
+import java.io.{File, FileWriter}
import org.apache.tools.ant.types.{Path, Reference}
/** An Ant task that generates a shell or batch script to execute a
diff --git a/src/compiler/scala/tools/ant/Scalac.scala b/src/compiler/scala/tools/ant/Scalac.scala
index a6024d4388..511572f6f3 100644
--- a/src/compiler/scala/tools/ant/Scalac.scala
+++ b/src/compiler/scala/tools/ant/Scalac.scala
@@ -10,13 +10,11 @@ package scala.tools.ant
import java.io.{File, PrintWriter, BufferedWriter, FileWriter}
-import org.apache.tools.ant.{ BuildException, Project, AntClassLoader }
+import org.apache.tools.ant.{ Project, AntClassLoader}
import org.apache.tools.ant.taskdefs.Java
import org.apache.tools.ant.types.{Path, Reference}
-import org.apache.tools.ant.util.{FileUtils, GlobPatternMapper,
- SourceFileScanner, facade}
-import org.apache.tools.ant.util.facade.{FacadeTaskHelper,
- ImplementationSpecificArgument}
+import org.apache.tools.ant.util.{FileUtils, GlobPatternMapper, SourceFileScanner}
+import org.apache.tools.ant.util.facade.{FacadeTaskHelper, ImplementationSpecificArgument}
import scala.tools.nsc.{Global, Settings, CompilerCommand}
import scala.tools.nsc.io.{Path => SPath}
@@ -90,9 +88,9 @@ class Scalac extends ScalaMatchingTask with ScalacShared {
object CompilerPhase extends PermissibleValue {
val values = List("namer", "typer", "pickler", "refchecks",
"uncurry", "tailcalls", "specialize", "explicitouter",
- "erasure", "lazyvals", "lambdalift", "constructors",
- "flatten", "mixin", "delambdafy", "cleanup", "icode", "inliner",
- "closelim", "dce", "jvm", "terminal")
+ "erasure", "fields", "lambdalift", "constructors",
+ "flatten", "mixin", "delambdafy", "cleanup",
+ "jvm", "terminal")
}
/** Defines valid values for the `target` property. */
@@ -553,7 +551,7 @@ class Scalac extends ScalaMatchingTask with ScalacShared {
val str =
if (javaFiles.isEmpty) "%d source file%s".format(list.length, plural(list))
else "%d scala and %d java source files".format(scalaFiles.length, javaFiles.length)
- log("Compiling %s to %s".format(str, getDestination.toString))
+ log(s"Compiling $str to $getDestination")
}
else log("No files selected for compilation", Project.MSG_VERBOSE)
diff --git a/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala b/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala
index cde827ba54..c31f55c9b6 100644
--- a/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala
+++ b/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala
@@ -10,7 +10,7 @@ package scala
package tools.ant
package sabbus
-import java.io.{ File, FileWriter }
+import java.io.File
import org.apache.tools.ant.Project
import org.apache.tools.ant.taskdefs.Java
import org.apache.tools.ant.util.{ GlobPatternMapper, SourceFileScanner }
diff --git a/src/compiler/scala/tools/ant/sabbus/Use.scala b/src/compiler/scala/tools/ant/sabbus/Use.scala
index a8736f228b..cb514e35b3 100644
--- a/src/compiler/scala/tools/ant/sabbus/Use.scala
+++ b/src/compiler/scala/tools/ant/sabbus/Use.scala
@@ -13,7 +13,6 @@ package sabbus
import java.io.File
-import org.apache.tools.ant.types.{Path, Reference}
import org.apache.tools.ant.util.{GlobPatternMapper, SourceFileScanner}
class Use extends ScalaMatchingTask {
diff --git a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl
index 5e6b3c041e..70ae9af444 100755
--- a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl
+++ b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl
@@ -160,7 +160,7 @@ while [[ $# -gt 0 ]]; do
shift 2
;;
-nobootcp)
- unset usebootcp
+ usebootcp="false"
shift
;;
-usebootcp)
@@ -189,15 +189,19 @@ declare -a classpath_args
# default to the boot classpath for speed, except on cygwin/mingw/msys because
# JLine on Windows requires a custom DLL to be loaded.
-unset usebootcp
-if [[ -z "$cygwin$mingw$msys" ]]; then
+if [[ "$usebootcp" != "false" && -z "$cygwin$mingw$msys" ]]; then
usebootcp="true"
fi
# If using the boot classpath, also pass an empty classpath
# to java to suppress "." from materializing.
-if [[ -n $usebootcp ]]; then
+if [[ "$usebootcp" == "true" ]]; then
classpath_args=("-Xbootclasspath/a:$TOOL_CLASSPATH" -classpath "\"\"")
+ # Java 9 removed sun.boot.class.path, and the supposed replacement to at least see
+ # the appended boot classpath (jdk.boot.class.path.append) is not visible.
+ # So we have to pass a custom system property that PathResolver will find.
+ # We do this for all JVM versions, rather than getting into the business of JVM version detection.
+ classpath_args+=("-Dscala.boot.class.path=$TOOL_CLASSPATH")
else
classpath_args=(-classpath "$TOOL_CLASSPATH")
fi
diff --git a/src/compiler/scala/tools/ant/templates/tool-windows.tmpl b/src/compiler/scala/tools/ant/templates/tool-windows.tmpl
index 50e44fb669..338f2f1375 100644
--- a/src/compiler/scala/tools/ant/templates/tool-windows.tmpl
+++ b/src/compiler/scala/tools/ant/templates/tool-windows.tmpl
@@ -156,7 +156,7 @@ rem (see http://support.microsoft.com/?kbid=833431)
rem set _SCALA_HOME=%~dps0..
:set_home
set _BIN_DIR=
- for %%i in (%~sf0) do set _BIN_DIR=%_BIN_DIR%%%~dpsi
+ for %%i in ("%~sf0") do set _BIN_DIR=%_BIN_DIR%%%~dpsi
set _SCALA_HOME=%_BIN_DIR%..
goto :eof
diff --git a/src/compiler/scala/tools/cmd/CommandLine.scala b/src/compiler/scala/tools/cmd/CommandLine.scala
index 781cc564cb..3a36a7d345 100644
--- a/src/compiler/scala/tools/cmd/CommandLine.scala
+++ b/src/compiler/scala/tools/cmd/CommandLine.scala
@@ -51,7 +51,7 @@ class CommandLine(val spec: Reference, val originalArgs: List[String]) extends C
/* Assumes known options have all been ruled out already. */
def isUnknown(opt: String) =
onlyKnownOptions && (opt startsWith "-") && {
- errorFn("Option '%s' not recognized.".format(opt))
+ errorFn(s"Option '$opt' not recognized.")
true
}
@@ -61,7 +61,7 @@ class CommandLine(val spec: Reference, val originalArgs: List[String]) extends C
case x :: Nil =>
expand(x) foreach (exp => return loop(exp))
if (isBinaryOption(x) && enforceArity)
- errorFn("Option '%s' requires argument, found EOF instead.".format(x))
+ errorFn(s"Option '$x' requires argument, found EOF instead.")
if (isUnaryOption(x)) mapForUnary(x)
else if (isUnknown(x)) Map()
diff --git a/src/compiler/scala/tools/cmd/FromString.scala b/src/compiler/scala/tools/cmd/FromString.scala
index 0b074efc0f..ab49c7507c 100644
--- a/src/compiler/scala/tools/cmd/FromString.scala
+++ b/src/compiler/scala/tools/cmd/FromString.scala
@@ -6,7 +6,7 @@
package scala.tools
package cmd
-import nsc.io.{ Path, File, Directory }
+import nsc.io.Directory
import scala.reflect.OptManifest
/** A general mechanism for defining how a command line argument
diff --git a/src/compiler/scala/tools/cmd/Opt.scala b/src/compiler/scala/tools/cmd/Opt.scala
index df3d0c4462..70756c5bb2 100644
--- a/src/compiler/scala/tools/cmd/Opt.scala
+++ b/src/compiler/scala/tools/cmd/Opt.scala
@@ -20,7 +20,7 @@ object Opt {
self: Implicit =>
protected def fail(msg: String) = runAndExit(println(programInfo.runner + ": " + msg))
- protected def failOption(arg: String, why: String) = fail("%s: '%s' is %s".format(opt, arg, why))
+ protected def failOption(arg: String, why: String) = fail(s"$opt: '$arg' is $why")
}
trait Implicit {
diff --git a/src/compiler/scala/tools/cmd/Property.scala b/src/compiler/scala/tools/cmd/Property.scala
index b1d951a5c4..18bedd6f7e 100644
--- a/src/compiler/scala/tools/cmd/Property.scala
+++ b/src/compiler/scala/tools/cmd/Property.scala
@@ -9,6 +9,7 @@ package cmd
import nsc.io._
import java.util.Properties
import java.io.FileInputStream
+import scala.sys.SystemProperties
/** Contains logic for translating a property key/value pair into
* equivalent command line arguments. The default settings will
@@ -58,14 +59,14 @@ trait Property extends Reference {
returning(new Properties)(_ load new FileInputStream(file.path))
def systemPropertiesToOptions: List[String] =
- propertiesToOptions(System.getProperties)
+ propertiesToOptions(new SystemProperties().toList)
def propertiesToOptions(file: File): List[String] =
propertiesToOptions(loadProperties(file))
def propertiesToOptions(props: java.util.Properties): List[String] = {
- import scala.collection.JavaConversions._
- propertiesToOptions(props.toList)
+ import scala.collection.JavaConverters._
+ propertiesToOptions(props.asScala.toList)
}
def propertiesToOptions(props: List[(String, String)]) = props flatMap propMapper
}
diff --git a/src/compiler/scala/tools/cmd/Reference.scala b/src/compiler/scala/tools/cmd/Reference.scala
index 62b6c893cf..25a16b1e3e 100644
--- a/src/compiler/scala/tools/cmd/Reference.scala
+++ b/src/compiler/scala/tools/cmd/Reference.scala
@@ -70,18 +70,18 @@ object Reference {
def addHelpAlias(f: () => String) = mapHelp { s =>
val str = "alias for '%s'" format f()
def noHelp = (helpFormatStr.format("", "")).length == s.length
- val str2 = if (noHelp) str else " (" + str + ")"
+ val str2 = if (noHelp) str else s" ($str)"
s + str2
}
def addHelpDefault(f: () => String): Unit = mapHelp { s =>
val str = "(default: %s)" format f()
- if (s.length + str.length < MaxLine) s + " " + str
+ if (s.length + str.length < MaxLine) s"$s $str"
else defaultFormatStr.format(s, str)
}
def addHelpEnvDefault(name: String): Unit = mapHelp { s =>
- val line1 = "%s (default: %s)".format(s, name)
+ val line1 = s"$s (default: $name)"
val envNow = envOrNone(name) map ("'" + _ + "'") getOrElse "unset"
val line2 = defaultFormatStr.format("Currently " + envNow)
diff --git a/src/compiler/scala/tools/cmd/Spec.scala b/src/compiler/scala/tools/cmd/Spec.scala
index a1cb31f911..069a7a89a1 100644
--- a/src/compiler/scala/tools/cmd/Spec.scala
+++ b/src/compiler/scala/tools/cmd/Spec.scala
@@ -47,6 +47,6 @@ object Spec {
}
class EnvironmentVar(val name: String) {
- override def toString = "${%s}" format name
+ override def toString = s"$${$name}"
}
}
diff --git a/src/compiler/scala/tools/cmd/gen/AnyVals.scala b/src/compiler/scala/tools/cmd/gen/AnyVals.scala
deleted file mode 100644
index e78589908c..0000000000
--- a/src/compiler/scala/tools/cmd/gen/AnyVals.scala
+++ /dev/null
@@ -1,484 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2007-2013 LAMP/EPFL
- * @author Paul Phillips
- */
-
-package scala.tools.cmd
-package gen
-
-/** Code generation of the AnyVal types and their companions. */
-trait AnyValReps {
- self: AnyVals =>
-
- sealed abstract class AnyValNum(name: String, repr: Option[String], javaEquiv: String)
- extends AnyValRep(name,repr,javaEquiv) {
-
- case class Op(op : String, doc : String)
-
- private def companionCoercions(tos: AnyValRep*) = {
- tos.toList map (to =>
- s"implicit def @javaequiv@2${to.javaEquiv}(x: @name@): ${to.name} = x.to${to.name}"
- )
- }
- def coercionComment =
-"""/** Language mandated coercions from @name@ to "wider" types. */
-import scala.language.implicitConversions"""
-
- def implicitCoercions: List[String] = {
- val coercions = this match {
- case B => companionCoercions(S, I, L, F, D)
- case S | C => companionCoercions(I, L, F, D)
- case I => companionCoercions(L, F, D)
- case L => companionCoercions(F, D)
- case F => companionCoercions(D)
- case _ => Nil
- }
- if (coercions.isEmpty) Nil
- else coercionComment.lines.toList ++ coercions
- }
-
- def isCardinal: Boolean = isIntegerType(this)
- def unaryOps = {
- val ops = List(
- Op("+", "/** Returns this value, unmodified. */"),
- Op("-", "/** Returns the negation of this value. */"))
-
- if(isCardinal)
- Op("~", "/**\n" +
- " * Returns the bitwise negation of this value.\n" +
- " * @example {{{\n" +
- " * ~5 == -6\n" +
- " * // in binary: ~00000101 ==\n" +
- " * // 11111010\n" +
- " * }}}\n" +
- " */") :: ops
- else ops
- }
-
- def bitwiseOps =
- if (isCardinal)
- List(
- Op("|", "/**\n" +
- " * Returns the bitwise OR of this value and `x`.\n" +
- " * @example {{{\n" +
- " * (0xf0 | 0xaa) == 0xfa\n" +
- " * // in binary: 11110000\n" +
- " * // | 10101010\n" +
- " * // --------\n" +
- " * // 11111010\n" +
- " * }}}\n" +
- " */"),
- Op("&", "/**\n" +
- " * Returns the bitwise AND of this value and `x`.\n" +
- " * @example {{{\n" +
- " * (0xf0 & 0xaa) == 0xa0\n" +
- " * // in binary: 11110000\n" +
- " * // & 10101010\n" +
- " * // --------\n" +
- " * // 10100000\n" +
- " * }}}\n" +
- " */"),
- Op("^", "/**\n" +
- " * Returns the bitwise XOR of this value and `x`.\n" +
- " * @example {{{\n" +
- " * (0xf0 ^ 0xaa) == 0x5a\n" +
- " * // in binary: 11110000\n" +
- " * // ^ 10101010\n" +
- " * // --------\n" +
- " * // 01011010\n" +
- " * }}}\n" +
- " */"))
- else Nil
-
- def shiftOps =
- if (isCardinal)
- List(
- Op("<<", "/**\n" +
- " * Returns this value bit-shifted left by the specified number of bits,\n" +
- " * filling in the new right bits with zeroes.\n" +
- " * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}}\n" +
- " */"),
-
- Op(">>>", "/**\n" +
- " * Returns this value bit-shifted right by the specified number of bits,\n" +
- " * filling the new left bits with zeroes.\n" +
- " * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}}\n" +
- " * @example {{{\n" +
- " * -21 >>> 3 == 536870909\n" +
- " * // in binary: 11111111 11111111 11111111 11101011 >>> 3 ==\n" +
- " * // 00011111 11111111 11111111 11111101\n" +
- " * }}}\n" +
- " */"),
-
- Op(">>", "/**\n" +
- " * Returns this value bit-shifted right by the specified number of bits,\n" +
- " * filling in the left bits with the same value as the left-most bit of this.\n" +
- " * The effect of this is to retain the sign of the value.\n" +
- " * @example {{{\n" +
- " * -21 >> 3 == -3\n" +
- " * // in binary: 11111111 11111111 11111111 11101011 >> 3 ==\n" +
- " * // 11111111 11111111 11111111 11111101\n" +
- " * }}}\n" +
- " */"))
- else Nil
-
- def comparisonOps = List(
- Op("==", "/** Returns `true` if this value is equal to x, `false` otherwise. */"),
- Op("!=", "/** Returns `true` if this value is not equal to x, `false` otherwise. */"),
- Op("<", "/** Returns `true` if this value is less than x, `false` otherwise. */"),
- Op("<=", "/** Returns `true` if this value is less than or equal to x, `false` otherwise. */"),
- Op(">", "/** Returns `true` if this value is greater than x, `false` otherwise. */"),
- Op(">=", "/** Returns `true` if this value is greater than or equal to x, `false` otherwise. */"))
-
- def otherOps = List(
- Op("+", "/** Returns the sum of this value and `x`. */"),
- Op("-", "/** Returns the difference of this value and `x`. */"),
- Op("*", "/** Returns the product of this value and `x`. */"),
- Op("/", "/** Returns the quotient of this value and `x`. */"),
- Op("%", "/** Returns the remainder of the division of this value by `x`. */"))
-
- // Given two numeric value types S and T , the operation type of S and T is defined as follows:
- // If both S and T are subrange types then the operation type of S and T is Int.
- // Otherwise the operation type of S and T is the larger of the two types wrt ranking.
- // Given two numeric values v and w the operation type of v and w is the operation type
- // of their run-time types.
- def opType(that: AnyValNum): AnyValNum = {
- val rank = IndexedSeq(I, L, F, D)
- (rank indexOf this, rank indexOf that) match {
- case (-1, -1) => I
- case (r1, r2) => rank apply (r1 max r2)
- }
- }
-
- def mkCoercions = numeric map (x => "def to%s: %s".format(x, x))
- def mkUnaryOps = unaryOps map (x => "%s\n def unary_%s : %s".format(x.doc, x.op, this opType I))
- def mkStringOps = List("def +(x: String): String")
- def mkShiftOps = (
- for (op <- shiftOps ; arg <- List(I, L)) yield
- "%s\n def %s(x: %s): %s".format(op.doc, op.op, arg, this opType I)
- )
-
- def clumps: List[List[String]] = {
- val xs1 = List(mkCoercions, mkUnaryOps, mkStringOps, mkShiftOps) map (xs => if (xs.isEmpty) xs else xs :+ "")
- val xs2 = List(
- mkBinOpsGroup(comparisonOps, numeric, _ => Z),
- mkBinOpsGroup(bitwiseOps, cardinal, this opType _),
- mkBinOpsGroup(otherOps, numeric, this opType _)
- )
- xs1 ++ xs2
- }
- def classLines = (clumps :+ commonClassLines).foldLeft(List[String]()) {
- case (res, Nil) => res
- case (res, lines) =>
- val xs = lines map {
- case "" => ""
- case s => interpolate(s)
- }
- res ++ xs
- }
- def objectLines = {
- val comp = if (isCardinal) cardinalCompanion else floatingCompanion
- interpolate(comp + allCompanions + "\n" + nonUnitCompanions).trim.lines.toList ++ (implicitCoercions map interpolate)
- }
-
- /** Makes a set of binary operations based on the given set of ops, args, and resultFn.
- *
- * @param ops list of function names e.g. List(">>", "%")
- * @param args list of types which should appear as arguments
- * @param resultFn function which calculates return type based on arg type
- * @return list of function definitions
- */
- def mkBinOpsGroup(ops: List[Op], args: List[AnyValNum], resultFn: AnyValNum => AnyValRep): List[String] = (
- ops flatMap (op =>
- args.map(arg =>
- "%s\n def %s(x: %s): %s".format(op.doc, op.op, arg, resultFn(arg))) :+ ""
- )
- ).toList
- }
-
- sealed abstract class AnyValRep(val name: String, val repr: Option[String], val javaEquiv: String) {
- def classLines: List[String]
- def objectLines: List[String]
- def commonClassLines = List(
- "override def getClass(): Class[@name@] = null"
- )
-
- def lcname = name.toLowerCase
- def boxedSimpleName = this match {
- case C => "Character"
- case I => "Integer"
- case _ => name
- }
- def boxedName = this match {
- case U => "scala.runtime.BoxedUnit"
- case _ => "java.lang." + boxedSimpleName
- }
- def zeroRep = this match {
- case L => "0L"
- case F => "0.0f"
- case D => "0.0d"
- case _ => "0"
- }
-
- def representation = repr.map(", a " + _).getOrElse("")
-
- def indent(s: String) = if (s == "") "" else " " + s
- def indentN(s: String) = s.lines map indent mkString "\n"
-
- def boxUnboxImpls = Map(
- "@boxRunTimeDoc@" -> """
- * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]].
- *""".format(boxedSimpleName),
- "@boxImpl@" -> "%s.valueOf(x)".format(boxedName),
- "@unboxRunTimeDoc@" -> """
- * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]].
- *""".format(name),
- "@unboxImpl@" -> "x.asInstanceOf[%s].%sValue()".format(boxedName, lcname),
- "@unboxDoc@" -> "the %s resulting from calling %sValue() on `x`".format(name, lcname)
- )
- def interpolations = Map(
- "@name@" -> name,
- "@representation@" -> representation,
- "@javaequiv@" -> javaEquiv,
- "@boxed@" -> boxedName,
- "@lcname@" -> lcname,
- "@zero@" -> zeroRep
- ) ++ boxUnboxImpls
-
- def interpolate(s: String): String = interpolations.foldLeft(s) {
- case (str, (key, value)) => str.replaceAll(key, value)
- }
- def classDoc = interpolate(classDocTemplate)
- def objectDoc = ""
- def mkImports = ""
-
- def mkClass = assemble("final abstract class " + name + " private extends AnyVal", classLines)
- def mkObject = assemble("object " + name + " extends AnyValCompanion", objectLines)
- def make() = List[String](
- headerTemplate,
- mkImports,
- classDoc,
- mkClass,
- objectDoc,
- mkObject
- ) mkString ""
-
- def assemble(decl: String, lines: List[String]): String = {
- val body = if (lines.isEmpty) " { }\n\n" else lines map indent mkString (" {\n", "\n", "\n}\n")
-
- decl + body + "\n"
- }
- override def toString = name
- }
-}
-
-trait AnyValTemplates {
- def headerTemplate = """/* __ *\
-** ________ ___ / / ___ Scala API **
-** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL **
-** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
-** /____/\___/_/ |_/____/_/ | | **
-** |/ **
-\* */
-
-// DO NOT EDIT, CHANGES WILL BE LOST
-// This auto-generated code can be modified in scala.tools.cmd.gen.
-// Afterwards, running tools/codegen-anyvals regenerates this source file.
-
-package scala
-
-"""
-
- def classDocTemplate = ("""
-/** `@name@`@representation@ (equivalent to Java's `@javaequiv@` primitive type) is a
- * subtype of [[scala.AnyVal]]. Instances of `@name@` are not
- * represented by an object in the underlying runtime system.
- *
- * There is an implicit conversion from [[scala.@name@]] => [[scala.runtime.Rich@name@]]
- * which provides useful non-primitive operations.
- */
-""".trim + "\n")
-
- def allCompanions = """
-/** Transform a value type into a boxed reference type.
- *@boxRunTimeDoc@
- * @param x the @name@ to be boxed
- * @return a @boxed@ offering `x` as its underlying value.
- */
-def box(x: @name@): @boxed@ = @boxImpl@
-
-/** Transform a boxed type into a value type. Note that this
- * method is not typesafe: it accepts any Object, but will throw
- * an exception if the argument is not a @boxed@.
- *@unboxRunTimeDoc@
- * @param x the @boxed@ to be unboxed.
- * @throws ClassCastException if the argument is not a @boxed@
- * @return @unboxDoc@
- */
-def unbox(x: java.lang.Object): @name@ = @unboxImpl@
-
-/** The String representation of the scala.@name@ companion object. */
-override def toString = "object scala.@name@"
-"""
-
- def nonUnitCompanions = "" // todo
-
- def cardinalCompanion = """
-/** The smallest value representable as a @name@. */
-final val MinValue = @boxed@.MIN_VALUE
-
-/** The largest value representable as a @name@. */
-final val MaxValue = @boxed@.MAX_VALUE
-"""
-
- def floatingCompanion = """
-/** The smallest positive value greater than @zero@ which is
- * representable as a @name@.
- */
-final val MinPositiveValue = @boxed@.MIN_VALUE
-final val NaN = @boxed@.NaN
-final val PositiveInfinity = @boxed@.POSITIVE_INFINITY
-final val NegativeInfinity = @boxed@.NEGATIVE_INFINITY
-
-/** The negative number with the greatest (finite) absolute value which is representable
- * by a @name@. Note that it differs from [[java.lang.@name@.MIN_VALUE]], which
- * is the smallest positive value representable by a @name@. In Scala that number
- * is called @name@.MinPositiveValue.
- */
-final val MinValue = -@boxed@.MAX_VALUE
-
-/** The largest finite positive number representable as a @name@. */
-final val MaxValue = @boxed@.MAX_VALUE
-"""
-}
-
-class AnyVals extends AnyValReps with AnyValTemplates {
- object B extends AnyValNum("Byte", Some("8-bit signed integer"), "byte")
- object S extends AnyValNum("Short", Some("16-bit signed integer"), "short")
- object C extends AnyValNum("Char", Some("16-bit unsigned integer"), "char")
- object I extends AnyValNum("Int", Some("32-bit signed integer"), "int")
- object L extends AnyValNum("Long", Some("64-bit signed integer"), "long")
- object F extends AnyValNum("Float", Some("32-bit IEEE-754 floating point number"), "float")
- object D extends AnyValNum("Double", Some("64-bit IEEE-754 floating point number"), "double")
- object Z extends AnyValRep("Boolean", None, "boolean") {
- def classLines = """
-/** Negates a Boolean expression.
- *
- * - `!a` results in `false` if and only if `a` evaluates to `true` and
- * - `!a` results in `true` if and only if `a` evaluates to `false`.
- *
- * @return the negated expression
- */
-def unary_! : Boolean
-
-/** Compares two Boolean expressions and returns `true` if they evaluate to the same value.
- *
- * `a == b` returns `true` if and only if
- * - `a` and `b` are `true` or
- * - `a` and `b` are `false`.
- */
-def ==(x: Boolean): Boolean
-
-/**
- * Compares two Boolean expressions and returns `true` if they evaluate to a different value.
- *
- * `a != b` returns `true` if and only if
- * - `a` is `true` and `b` is `false` or
- * - `a` is `false` and `b` is `true`.
- */
-def !=(x: Boolean): Boolean
-
-/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true.
- *
- * `a || b` returns `true` if and only if
- * - `a` is `true` or
- * - `b` is `true` or
- * - `a` and `b` are `true`.
- *
- * @note This method uses 'short-circuit' evaluation and
- * behaves as if it was declared as `def ||(x: => Boolean): Boolean`.
- * If `a` evaluates to `true`, `true` is returned without evaluating `b`.
- */
-def ||(x: Boolean): Boolean
-
-/** Compares two Boolean expressions and returns `true` if both of them evaluate to true.
- *
- * `a && b` returns `true` if and only if
- * - `a` and `b` are `true`.
- *
- * @note This method uses 'short-circuit' evaluation and
- * behaves as if it was declared as `def &&(x: => Boolean): Boolean`.
- * If `a` evaluates to `false`, `false` is returned without evaluating `b`.
- */
-def &&(x: Boolean): Boolean
-
-// Compiler won't build with these seemingly more accurate signatures
-// def ||(x: => Boolean): Boolean
-// def &&(x: => Boolean): Boolean
-
-/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true.
- *
- * `a | b` returns `true` if and only if
- * - `a` is `true` or
- * - `b` is `true` or
- * - `a` and `b` are `true`.
- *
- * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`.
- */
-def |(x: Boolean): Boolean
-
-/** Compares two Boolean expressions and returns `true` if both of them evaluate to true.
- *
- * `a & b` returns `true` if and only if
- * - `a` and `b` are `true`.
- *
- * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`.
- */
-def &(x: Boolean): Boolean
-
-/** Compares two Boolean expressions and returns `true` if they evaluate to a different value.
- *
- * `a ^ b` returns `true` if and only if
- * - `a` is `true` and `b` is `false` or
- * - `a` is `false` and `b` is `true`.
- */
-def ^(x: Boolean): Boolean
-
-override def getClass(): Class[Boolean] = null
- """.trim.lines.toList
-
- def objectLines = interpolate(allCompanions + "\n" + nonUnitCompanions).lines.toList
- }
- object U extends AnyValRep("Unit", None, "void") {
- override def classDoc = """
-/** `Unit` is a subtype of [[scala.AnyVal]]. There is only one value of type
- * `Unit`, `()`, and it is not represented by any object in the underlying
- * runtime system. A method with return type `Unit` is analogous to a Java
- * method which is declared `void`.
- */
-"""
- def classLines = List(
- """override def getClass(): Class[Unit] = null"""
- )
- def objectLines = interpolate(allCompanions).lines.toList
-
- override def boxUnboxImpls = Map(
- "@boxRunTimeDoc@" -> "",
- "@boxImpl@" -> "scala.runtime.BoxedUnit.UNIT",
- "@unboxRunTimeDoc@" -> "",
- "@unboxImpl@" -> "()",
- "@unboxDoc@" -> "the Unit value ()"
- )
- }
-
- def isSubrangeType = Set(B, S, C)
- def isIntegerType = Set(B, S, C, I, L)
- def isFloatingType = Set(F, D)
- def isWideType = Set(L, D)
-
- def cardinal = numeric filter isIntegerType
- def numeric = List(B, S, C, I, L, F, D)
- def values = List(U, Z) ++ numeric
-
- def make() = values map (x => (x.name, x.make()))
-}
diff --git a/src/compiler/scala/tools/cmd/gen/Codegen.scala b/src/compiler/scala/tools/cmd/gen/Codegen.scala
deleted file mode 100644
index c3aa527ef2..0000000000
--- a/src/compiler/scala/tools/cmd/gen/Codegen.scala
+++ /dev/null
@@ -1,39 +0,0 @@
-/* NEST (New Scala Test)
- * Copyright 2007-2013 LAMP/EPFL
- * @author Paul Phillips
- */
-
-package scala.tools.cmd
-package gen
-
-class Codegen(args: List[String]) extends {
- val parsed = CodegenSpec(args: _*)
-} with CodegenSpec with Instance
-
-object Codegen {
- def echo(msg: String) = Console println msg
-
- def main(args0: Array[String]): Unit = {
- val runner = new Codegen(args0.toList)
- import runner._
-
- if (args0.isEmpty)
- return println (CodegenSpec.helpMsg)
-
- val out = outDir getOrElse { return println("--out is required.") }
- val all = genall || !anyvals
-
- echo("Generating sources into " + out)
-
- if (anyvals || all) {
- val av = new AnyVals { }
-
- av.make() foreach { case (name, code ) =>
- val file = (out / (name + ".scala")).toFile
- echo("Writing: " + file)
- file writeAll code
- }
- }
- }
-}
-
diff --git a/src/compiler/scala/tools/cmd/gen/CodegenSpec.scala b/src/compiler/scala/tools/cmd/gen/CodegenSpec.scala
deleted file mode 100644
index 4b4a1e482d..0000000000
--- a/src/compiler/scala/tools/cmd/gen/CodegenSpec.scala
+++ /dev/null
@@ -1,25 +0,0 @@
-/* NEST (New Scala Test)
- * Copyright 2007-2013 LAMP/EPFL
- * @author Paul Phillips
- */
-
-package scala.tools.cmd
-package gen
-
-import FromString.ExistingDir
-
-trait CodegenSpec extends Spec with Meta.StdOpts with Interpolation {
- def referenceSpec = CodegenSpec
- def programInfo = Spec.Info("codegen", "", "scala.tools.cmd.gen.Codegen")
-
- help("Usage: codegen [<options>]")
-
- val outDir = "out" / "directory for generated files" --^ ExistingDir
- val anyvals = "anyvals" / "generate sources for AnyVal types" --?
- val genall = "all" / "generate sources for everything" --?
-}
-
-object CodegenSpec extends CodegenSpec with Reference {
- type ThisCommandLine = CommandLine
- def creator(args: List[String]): ThisCommandLine = new CommandLine(CodegenSpec, args)
-}
diff --git a/src/compiler/scala/tools/nsc/CompilationUnits.scala b/src/compiler/scala/tools/nsc/CompilationUnits.scala
index 6be1fda1b5..5b09504fd6 100644
--- a/src/compiler/scala/tools/nsc/CompilationUnits.scala
+++ b/src/compiler/scala/tools/nsc/CompilationUnits.scala
@@ -52,7 +52,7 @@ trait CompilationUnits { global: Global =>
* To get their sourcefiles, you need to dereference with .sourcefile
*/
private[this] val _depends = mutable.HashSet[Symbol]()
- // SBT compatibility (SI-6875)
+ // sbt compatibility (SI-6875)
//
// imagine we have a file named A.scala, which defines a trait named Foo and a module named Main
// Main contains a call to a macro, which calls compileLate to define a mock for Foo
@@ -63,12 +63,12 @@ trait CompilationUnits { global: Global =>
// * Virt35af32 depends on A (because it extends Foo from A)
// * A depends on Virt35af32 (because it contains a macro expansion referring to FooMock from Virt35af32)
//
- // after compiling A.scala, SBT will notice that it has a new source file named Virt35af32.
+ // after compiling A.scala, sbt will notice that it has a new source file named Virt35af32.
// it will also think that this file hasn't yet been compiled and since A depends on it
// it will think that A needs to be recompiled.
//
// recompilation will lead to another macro expansion. that another macro expansion might choose to create a fresh mock,
- // producing another virtual file, say, Virtee509a, which will again trick SBT into thinking that A needs a recompile,
+ // producing another virtual file, say, Virtee509a, which will again trick sbt into thinking that A needs a recompile,
// which will lead to another macro expansion, which will produce another virtual file and so on
def depends = if (exists && !source.file.isVirtual) _depends else mutable.HashSet[Symbol]()
@@ -117,9 +117,7 @@ trait CompilationUnits { global: Global =>
*/
def targetPos: Position = NoPosition
- /** The icode representation of classes in this compilation unit.
- * It is empty up to phase 'icode'.
- */
+ /** For sbt compatibility (https://github.com/scala/scala/pull/4588) */
val icode: LinkedHashSet[icodes.IClass] = new LinkedHashSet
@deprecated("Call global.reporter.echo directly instead.", "2.11.2")
@@ -130,7 +128,7 @@ trait CompilationUnits { global: Global =>
final def warning(pos: Position, msg: String): Unit = reporter.warning(pos, msg)
@deprecated("Call global.currentRun.reporting.deprecationWarning directly instead.", "2.11.2")
- final def deprecationWarning(pos: Position, msg: String): Unit = currentRun.reporting.deprecationWarning(pos, msg)
+ final def deprecationWarning(pos: Position, msg: String, since: String): Unit = currentRun.reporting.deprecationWarning(pos, msg, since)
@deprecated("Call global.currentRun.reporting.uncheckedWarning directly instead.", "2.11.2")
final def uncheckedWarning(pos: Position, msg: String): Unit = currentRun.reporting.uncheckedWarning(pos, msg)
diff --git a/src/compiler/scala/tools/nsc/CompileServer.scala b/src/compiler/scala/tools/nsc/CompileServer.scala
index aa02957a6c..ffe95ba9dc 100644
--- a/src/compiler/scala/tools/nsc/CompileServer.scala
+++ b/src/compiler/scala/tools/nsc/CompileServer.scala
@@ -8,7 +8,7 @@ package scala.tools.nsc
import java.io.PrintStream
import io.Directory
import scala.tools.nsc.reporters.{Reporter, ConsoleReporter}
-import scala.reflect.internal.util.{FakePos, Position}
+import scala.reflect.internal.util.FakePos
import scala.tools.util.SocketServer
import settings.FscSettings
@@ -47,7 +47,7 @@ class StandardCompileServer(fixPort: Int = 0) extends SocketServer(fixPort) {
}
def printMemoryStats() {
- def mb(bytes: Long) = "%dMB".format(bytes / 1000000)
+ def mb(bytes: Long) = "%10.2fMB".format(bytes / 1048576.0)
info("New session: total memory = %s, max memory = %s, free memory = %s".format(
mb(totalMemory), mb(maxMemory), mb(freeMemory)))
}
@@ -193,14 +193,14 @@ object CompileServer {
val i = args.indexOf("-p")
if (i >= 0 && args.length > i + 1) {
scala.util.control.Exception.ignoring(classOf[NumberFormatException]) {
- port = args(i + 1).toInt
+ port = args(i + 1).toInt
}
}
-
+
// Create instance rather than extend to pass a port parameter.
val server = new StandardCompileServer(port)
val redirectDir = (server.compileSocket.tmpDir / "output-redirects").createDirectory()
-
+
if (debug) {
server.echo("Starting CompileServer on port " + server.port)
server.echo("Redirect dir is " + redirectDir)
diff --git a/src/compiler/scala/tools/nsc/CompileSocket.scala b/src/compiler/scala/tools/nsc/CompileSocket.scala
index 27a14141fa..01c7d72d4f 100644
--- a/src/compiler/scala/tools/nsc/CompileSocket.scala
+++ b/src/compiler/scala/tools/nsc/CompileSocket.scala
@@ -5,9 +5,9 @@
package scala.tools.nsc
-import java.io.{ FileNotFoundException, PrintWriter, FileOutputStream }
+import java.io.FileNotFoundException
import java.security.SecureRandom
-import io.{ File, Path, Directory, Socket }
+import io.{ File, Path, Socket }
import scala.tools.util.CompileOutputCommon
import scala.reflect.internal.util.StringOps.splitWhere
import scala.sys.process._
@@ -46,7 +46,7 @@ trait HasCompileSocket {
class CompileSocket extends CompileOutputCommon {
protected lazy val compileClient: StandardCompileClient = CompileClient
def verbose = compileClient.verbose
-
+
/* Fixes the port where to start the server, 0 yields some free port */
var fixPort = 0
@@ -67,7 +67,7 @@ class CompileSocket extends CompileOutputCommon {
/** The class name of the scala compile server */
protected val serverClass = "scala.tools.nsc.CompileServer"
- protected def serverClassArgs = (if (verbose) List("-v") else Nil) ::: (if (fixPort > 0) List("-p", fixPort.toString) else Nil)
+ protected def serverClassArgs = (if (verbose) List("-v") else Nil) ::: (if (fixPort > 0) List("-p", fixPort.toString) else Nil)
/** A temporary directory to use */
val tmpDir = {
@@ -196,7 +196,7 @@ class CompileSocket extends CompileOutputCommon {
catch { case _: NumberFormatException => None }
def getSocket(serverAdr: String): Option[Socket] = (
- for ((name, portStr) <- splitWhere(serverAdr, _ == ':', doDropIndex = true) ; port <- parseInt(portStr)) yield
+ for ((name, portStr) <- splitWhere(serverAdr, _ == ':', doDropIndex = true) ; port <- parseInt(portStr)) yield
getSocket(name, port)
) getOrElse fatal("Malformed server address: %s; exiting" format serverAdr)
@@ -205,7 +205,7 @@ class CompileSocket extends CompileOutputCommon {
if (sock.isEmpty) warn("Unable to establish connection to server %s:%d".format(hostName, port))
sock
}
-
+
def getPassword(port: Int): String = {
val ff = portFile(port)
val f = ff.bufferedReader()
diff --git a/src/compiler/scala/tools/nsc/CompilerCommand.scala b/src/compiler/scala/tools/nsc/CompilerCommand.scala
index 9b8e9fa330..24da6ba487 100644
--- a/src/compiler/scala/tools/nsc/CompilerCommand.scala
+++ b/src/compiler/scala/tools/nsc/CompilerCommand.scala
@@ -103,15 +103,7 @@ class CompilerCommand(arguments: List[String], val settings: Settings) {
val components = global.phaseNames // global.phaseDescriptors // one initializes
s"Phase graph of ${components.size} components output to ${genPhaseGraph.value}*.dot."
}
- // would be nicer if we could ask all the options for their helpful messages
- else {
- val sb = new StringBuilder
- allSettings foreach {
- case s: MultiChoiceSetting[_] if s.isHelping => sb append s.help
- case _ =>
- }
- sb.toString
- }
+ else allSettings.filter(_.isHelping).map(_.help).mkString("\n\n")
}
/**
diff --git a/src/compiler/scala/tools/nsc/Driver.scala b/src/compiler/scala/tools/nsc/Driver.scala
index 6befa76b3f..b30744c4df 100644
--- a/src/compiler/scala/tools/nsc/Driver.scala
+++ b/src/compiler/scala/tools/nsc/Driver.scala
@@ -1,7 +1,7 @@
package scala
package tools.nsc
-import scala.tools.nsc.reporters.ConsoleReporter
+import scala.tools.nsc.reporters.{ ConsoleReporter, Reporter }
import Properties.{ versionMsg, residentPromptString }
import scala.reflect.internal.util.FakePos
@@ -9,39 +9,43 @@ abstract class Driver {
val prompt = residentPromptString
- var reporter: ConsoleReporter = _
+ var reporter: Reporter = _
protected var command: CompilerCommand = _
protected var settings: Settings = _
+ /** Forward errors to the (current) reporter. */
protected def scalacError(msg: String): Unit = {
reporter.error(FakePos("scalac"), msg + "\n scalac -help gives more information")
}
+ /** True to continue compilation. */
protected def processSettingsHook(): Boolean = {
- if (settings.version) { reporter echo versionMsg ; false } else true
+ if (settings.version) { reporter echo versionMsg ; false }
+ else !reporter.hasErrors
}
protected def newCompiler(): Global
- protected def doCompile(compiler: Global) {
+ protected def doCompile(compiler: Global): Unit = {
if (command.files.isEmpty) {
reporter.echo(command.usageMsg)
reporter.echo(compiler.pluginOptionsHelp)
} else {
val run = new compiler.Run()
run compile command.files
- reporter.printSummary()
+ reporter.finish()
}
}
- def process(args: Array[String]) {
+ def process(args: Array[String]): Boolean = {
val ss = new Settings(scalacError)
- reporter = new ConsoleReporter(ss)
+ reporter = new ConsoleReporter(ss) // for reporting early config errors, before compiler is constructed
command = new CompilerCommand(args.toList, ss)
settings = command.settings
if (processSettingsHook()) {
val compiler = newCompiler()
+ reporter = compiler.reporter // adopt the configured reporter
try {
if (reporter.hasErrors)
reporter.flush()
@@ -57,11 +61,9 @@ abstract class Driver {
case _ => throw ex // unexpected error, tell the outside world.
}
}
- }
+ } else if (reporter.hasErrors) reporter.flush()
+ !reporter.hasErrors
}
- def main(args: Array[String]) {
- process(args)
- sys.exit(if (reporter.hasErrors) 1 else 0)
- }
+ def main(args: Array[String]): Unit = sys.exit(if (process(args)) 0 else 1)
}
diff --git a/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala b/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala
index d1f8db048b..113c02e558 100644
--- a/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala
+++ b/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala
@@ -6,10 +6,10 @@
package scala.tools.nsc
import java.net.URL
-import scala.tools.util.PathResolverFactory
+import scala.tools.util.PathResolver
class GenericRunnerSettings(error: String => Unit) extends Settings(error) {
- def classpathURLs: Seq[URL] = PathResolverFactory.create(this).resultAsURLs
+ lazy val classpathURLs: Seq[URL] = new PathResolver(this).resultAsURLs
val howtorun =
ChoiceSetting(
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala
index a54b92cef8..56ad4738d9 100644
--- a/src/compiler/scala/tools/nsc/Global.scala
+++ b/src/compiler/scala/tools/nsc/Global.scala
@@ -7,17 +7,17 @@ package scala
package tools
package nsc
-import java.io.{ File, FileOutputStream, PrintWriter, IOException, FileNotFoundException }
+import java.io.{File, FileNotFoundException, IOException}
import java.net.URL
-import java.nio.charset.{ Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException }
-import scala.collection.{ mutable, immutable }
-import io.{ SourceReader, AbstractFile, Path }
-import reporters.{ Reporter, ConsoleReporter }
-import util.{ ClassFileLookup, ClassPath, MergedClassPath, StatisticsInfo, returning }
+import java.nio.charset.{Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException}
+import scala.collection.{immutable, mutable}
+import io.{AbstractFile, Path, SourceReader}
+import reporters.Reporter
+import util.{ClassPath, StatisticsInfo, returning}
import scala.reflect.ClassTag
-import scala.reflect.internal.util.{ SourceFile, NoSourceFile, BatchSourceFile, ScriptSourceFile }
+import scala.reflect.internal.util.{BatchSourceFile, NoSourceFile, ScalaClassLoader, ScriptSourceFile, SourceFile}
import scala.reflect.internal.pickling.PickleBuffer
-import symtab.{ Flags, SymbolTable, SymbolTrackers }
+import symtab.{Flags, SymbolTable, SymbolTrackers}
import symtab.classfile.Pickler
import plugins.Plugins
import ast._
@@ -25,16 +25,11 @@ import ast.parser._
import typechecker._
import transform.patmat.PatternMatching
import transform._
-import backend.icode.{ ICodes, GenICode, ICodeCheckers }
-import backend.{ ScalaPrimitives, JavaPlatform }
+import backend.{JavaPlatform, ScalaPrimitives}
import backend.jvm.GenBCode
-import backend.jvm.GenASM
-import backend.opt.{ Inliners, InlineExceptionHandlers, ConstantOptimization, ClosureElimination, DeadCodeElimination }
-import backend.icode.analysis._
import scala.language.postfixOps
import scala.tools.nsc.ast.{TreeGen => AstTreeGen}
-import scala.tools.nsc.classpath.FlatClassPath
-import scala.tools.nsc.settings.ClassPathRepresentationType
+import scala.tools.nsc.classpath._
class Global(var currentSettings: Settings, var reporter: Reporter)
extends SymbolTable
@@ -58,12 +53,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
class GlobalMirror extends Roots(NoSymbol) {
val universe: self.type = self
- def rootLoader: LazyType = {
- settings.YclasspathImpl.value match {
- case ClassPathRepresentationType.Flat => new loaders.PackageLoaderUsingFlatClassPath(FlatClassPath.RootPackage, flatClassPath)
- case ClassPathRepresentationType.Recursive => new loaders.PackageLoader(recursiveClassPath)
- }
- }
+ def rootLoader: LazyType = new loaders.PackageLoader(ClassPath.RootPackage, classPath)
override def toString = "compiler mirror"
}
implicit val MirrorTag: ClassTag[Mirror] = ClassTag[Mirror](classOf[GlobalMirror])
@@ -90,7 +80,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
this(new Settings(err => reporter.error(null, err)), reporter)
def this(settings: Settings) =
- this(settings, new ConsoleReporter(settings))
+ this(settings, Global.reporter(settings))
def picklerPhase: Phase = if (currentRun.isDefined) currentRun.picklerPhase else NoPhase
@@ -100,9 +90,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
* to the last tree to typer, whose position is the trigger of stub errors. */
override def newStubSymbol(owner: Symbol,
name: Name,
- missingMessage: String,
- isPackage: Boolean = false): Symbol = {
- val stubSymbol = super.newStubSymbol(owner, name, missingMessage, isPackage)
+ missingMessage: String): Symbol = {
+ val stubSymbol = super.newStubSymbol(owner, name, missingMessage)
val stubErrorPosition = {
val lastTreeToTyper = analyzer.lastTreeToTyper
if (lastTreeToTyper != EmptyTree) lastTreeToTyper.pos else stubSymbol.pos
@@ -119,18 +108,11 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
type ThisPlatform = JavaPlatform { val global: Global.this.type }
lazy val platform: ThisPlatform = new GlobalPlatform
+ /* A hook for the REPL to add a classpath entry containing products of previous runs to inliner's bytecode repository*/
+ // Fixes SI-8779
+ def optimizerClassPath(base: ClassPath): ClassPath = base
- type PlatformClassPath = ClassPath[AbstractFile]
- type OptClassPath = Option[PlatformClassPath]
-
- def classPath: ClassFileLookup[AbstractFile] = settings.YclasspathImpl.value match {
- case ClassPathRepresentationType.Flat => flatClassPath
- case ClassPathRepresentationType.Recursive => recursiveClassPath
- }
-
- private def recursiveClassPath: ClassPath[AbstractFile] = platform.classPath
-
- private def flatClassPath: FlatClassPath = platform.flatClassPath
+ def classPath: ClassPath = platform.classPath
// sub-components --------------------------------------------------
@@ -154,12 +136,12 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
val global: Global.this.type = Global.this
} with ConstantFolder
- /** ICode generator */
- object icodes extends {
- val global: Global.this.type = Global.this
- } with ICodes
+ /** For sbt compatibility (https://github.com/scala/scala/pull/4588) */
+ object icodes {
+ class IClass(val symbol: Symbol)
+ }
- /** Scala primitives, used in genicode */
+ /** Scala primitives, used the backend */
object scalaPrimitives extends {
val global: Global.this.type = Global.this
} with ScalaPrimitives
@@ -171,18 +153,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
type SymbolPair = overridingPairs.SymbolPair
- // Optimizer components
-
- /** ICode analysis for optimization */
- object analysis extends {
- val global: Global.this.type = Global.this
- } with TypeFlowAnalysis
-
- /** Copy propagation for optimization */
- object copyPropagation extends {
- val global: Global.this.type = Global.this
- } with CopyPropagation
-
// Components for collecting and generating output
/** Some statistics (normally disabled) set with -Ystatistics */
@@ -329,7 +299,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
// Over 200 closure objects are eliminated by inlining this.
@inline final def log(msg: => AnyRef) {
if (shouldLogAtThisPhase)
- inform("[log %s%s] %s".format(globalPhase, atPhaseStackMessage, msg))
+ inform(s"[log $globalPhase$atPhaseStackMessage] $msg")
}
@inline final override def debuglog(msg: => String) {
@@ -351,10 +321,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
try Some(Charset.forName(name))
catch {
case _: IllegalCharsetNameException =>
- globalError("illegal charset name '" + name + "'")
+ globalError(s"illegal charset name '$name'")
None
case _: UnsupportedCharsetException =>
- globalError("unsupported charset '" + name + "'")
+ globalError(s"unsupported charset '$name'")
None
}
@@ -384,14 +354,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
s"[search path for class files: ${classPath.asClassPathString}]"
)
- // The current division between scala.reflect.* and scala.tools.nsc.* is pretty
- // clunky. It is often difficult to have a setting influence something without having
- // to create it on that side. For this one my strategy is a constant def at the file
- // where I need it, and then an override in Global with the setting.
- override protected val etaExpandKeepsStar = settings.etaExpandKeepsStar.value
- // Here comes another one...
- override protected val enableTypeVarExperimentals = settings.Xexperimental.value
-
def getSourceFile(f: AbstractFile): BatchSourceFile = new BatchSourceFile(f, reader read f)
def getSourceFile(name: String): SourceFile = {
@@ -427,15 +389,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
def apply(unit: CompilationUnit): Unit
- private val isErased = prev.name == "erasure" || prev.erasedTypes
- override def erasedTypes: Boolean = isErased
- private val isFlat = prev.name == "flatten" || prev.flatClasses
- override def flatClasses: Boolean = isFlat
- private val isSpecialized = prev.name == "specialize" || prev.specialized
- override def specialized: Boolean = isSpecialized
- private val isRefChecked = prev.name == "refchecks" || prev.refChecked
- override def refChecked: Boolean = isRefChecked
-
/** Is current phase cancelled on this unit? */
def cancelled(unit: CompilationUnit) = {
// run the typer only if in `createJavadoc` mode
@@ -449,15 +402,18 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
if (settings.debug && (settings.verbose || currentRun.size < 5))
inform("[running phase " + name + " on " + unit + "]")
+ if (!cancelled(unit)) {
+ currentRun.informUnitStarting(this, unit)
+ try withCurrentUnitNoLog(unit)(task)
+ finally currentRun.advanceUnit()
+ }
+ }
+ final def withCurrentUnitNoLog(unit: CompilationUnit)(task: => Unit) {
val unit0 = currentUnit
try {
currentRun.currentUnit = unit
- if (!cancelled(unit)) {
- currentRun.informUnitStarting(this, unit)
- task
- }
- currentRun.advanceUnit()
+ task
} finally {
//assert(currentUnit == unit)
currentRun.currentUnit = unit0
@@ -476,7 +432,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
override val initial = true
}
- import syntaxAnalyzer.{ UnitScanner, UnitParser }
+ import syntaxAnalyzer.{ UnitScanner, UnitParser, JavaUnitParser }
// !!! I think we're overdue for all these phase objects being lazy vals.
// There's no way for a Global subclass to provide a custom typer
@@ -521,7 +477,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
} with Pickler
// phaseName = "refchecks"
- override object refChecks extends {
+ object refChecks extends {
val global: Global.this.type = Global.this
val runsAfter = List("pickler")
val runsRightAfter = None
@@ -541,10 +497,22 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
val runsRightAfter = None
} with TailCalls
+ // phaseName = "fields"
+ object fields extends {
+ val global: Global.this.type = Global.this
+ // after refchecks, so it doesn't have to make weird exceptions for synthetic accessors
+ // after uncurry as it produces more work for the fields phase as well as being confused by it:
+ // - sam expansion synthesizes classes, which may need trait fields mixed in
+ // - the fields phase adds synthetic abstract methods to traits that should not disqualify them from being a SAM type
+ // before erasure: correct signatures & bridges for accessors
+ val runsAfter = List("uncurry")
+ val runsRightAfter = None
+ } with Fields
+
// phaseName = "explicitouter"
object explicitOuter extends {
val global: Global.this.type = Global.this
- val runsAfter = List("tailcalls")
+ val runsAfter = List("fields")
val runsRightAfter = None
} with ExplicitOuter
@@ -569,17 +537,11 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
val runsRightAfter = Some("erasure")
} with PostErasure
- // phaseName = "lazyvals"
- object lazyVals extends {
- val global: Global.this.type = Global.this
- val runsAfter = List("erasure")
- val runsRightAfter = None
- } with LazyVals
// phaseName = "lambdalift"
object lambdaLift extends {
val global: Global.this.type = Global.this
- val runsAfter = List("lazyvals")
+ val runsAfter = List("erasure")
val runsRightAfter = None
} with LambdaLift
@@ -618,59 +580,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
val runsRightAfter = None
} with Delambdafy
- // phaseName = "icode"
- object genicode extends {
- val global: Global.this.type = Global.this
- val runsAfter = List("cleanup")
- val runsRightAfter = None
- } with GenICode
-
- // phaseName = "inliner"
- object inliner extends {
- val global: Global.this.type = Global.this
- val runsAfter = List("icode")
- val runsRightAfter = None
- } with Inliners
-
- // phaseName = "inlinehandlers"
- object inlineExceptionHandlers extends {
- val global: Global.this.type = Global.this
- val runsAfter = List("inliner")
- val runsRightAfter = None
- } with InlineExceptionHandlers
-
- // phaseName = "closelim"
- object closureElimination extends {
- val global: Global.this.type = Global.this
- val runsAfter = List("inlinehandlers")
- val runsRightAfter = None
- } with ClosureElimination
-
- // phaseName = "constopt"
- object constantOptimization extends {
- val global: Global.this.type = Global.this
- val runsAfter = List("closelim")
- val runsRightAfter = None
- } with ConstantOptimization
-
- // phaseName = "dce"
- object deadCode extends {
- val global: Global.this.type = Global.this
- val runsAfter = List("closelim")
- val runsRightAfter = None
- } with DeadCodeElimination
-
- // phaseName = "jvm", ASM-based version
- object genASM extends {
- val global: Global.this.type = Global.this
- val runsAfter = List("dce")
- val runsRightAfter = None
- } with GenASM
-
// phaseName = "bcode"
object genBCode extends {
val global: Global.this.type = Global.this
- val runsAfter = List("dce")
+ val runsAfter = List("cleanup")
val runsRightAfter = None
} with GenBCode
@@ -701,13 +614,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
val global: Global.this.type = Global.this
} with TreeCheckers
- /** Icode verification */
- object icodeCheckers extends {
- val global: Global.this.type = Global.this
- } with ICodeCheckers
-
- object icodeChecker extends icodeCheckers.ICodeChecker()
-
object typer extends analyzer.Typer(
analyzer.NoContext.make(EmptyTree, RootClass, newScope)
)
@@ -716,7 +622,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
* This implementation creates a description map at the same time.
*/
protected def computeInternalPhases(): Unit = {
- // Note: this fits -Xshow-phases into 80 column width, which it is
+ // Note: this fits -Xshow-phases into 80 column width, which is
// desirable to preserve.
val phs = List(
syntaxAnalyzer -> "parse source into ASTs, perform simple desugaring",
@@ -729,23 +635,17 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
pickler -> "serialize symbol tables",
refChecks -> "reference/override checking, translate nested objects",
uncurry -> "uncurry, translate function values to anonymous classes",
+ fields -> "synthesize accessors and fields, add bitmaps for lazy vals",
tailCalls -> "replace tail calls by jumps",
specializeTypes -> "@specialized-driven class and method specialization",
explicitOuter -> "this refs to outer pointers",
erasure -> "erase types, add interfaces for traits",
postErasure -> "clean up erased inline classes",
- lazyVals -> "allocate bitmaps, translate lazy vals into lazified defs",
lambdaLift -> "move nested functions to top level",
constructors -> "move field definitions into constructors",
mixer -> "mixin composition",
delambdafy -> "remove lambdas",
cleanup -> "platform-specific cleanups, generate reflective calls",
- genicode -> "generate portable intermediate code",
- inliner -> "optimization: do inlining",
- inlineExceptionHandlers -> "optimization: inline exception handlers",
- closureElimination -> "optimization: eliminate uncalled closures",
- constantOptimization -> "optimization: optimize null and other constants",
- deadCode -> "optimization: eliminate dead code",
terminal -> "the last phase during a compilation run"
)
@@ -797,7 +697,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
}
/** A description of the phases that will run in this configuration, or all if -Ydebug. */
- def phaseDescriptions: String = phaseHelp("description", elliptically = true, phasesDescMap)
+ def phaseDescriptions: String = phaseHelp("description", elliptically = !settings.debug, phasesDescMap)
/** Summary of the per-phase values of nextFlags and newFlags, shown under -Xshow-phases -Ydebug. */
def phaseFlagDescriptions: String = {
@@ -808,7 +708,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
else if (ph.phaseNewFlags != 0L && ph.phaseNextFlags != 0L) fstr1 + " " + fstr2
else fstr1 + fstr2
}
- phaseHelp("new flags", elliptically = false, fmt)
+ phaseHelp("new flags", elliptically = !settings.debug, fmt)
}
/** Emit a verbose phase table.
@@ -820,7 +720,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
* @param elliptically whether to truncate the description with an ellipsis (...)
* @param describe how to describe a component
*/
- def phaseHelp(title: String, elliptically: Boolean, describe: SubComponent => String) = {
+ private def phaseHelp(title: String, elliptically: Boolean, describe: SubComponent => String): String = {
val Limit = 16 // phase names should not be absurdly long
val MaxCol = 80 // because some of us edit on green screens
val maxName = phaseNames map (_.length) max
@@ -835,13 +735,13 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
// built-in string precision merely truncates
import java.util.{ Formattable, FormattableFlags, Formatter }
def dotfmt(s: String) = new Formattable {
- def elliptically(s: String, max: Int) = (
+ def foreshortened(s: String, max: Int) = (
if (max < 0 || s.length <= max) s
else if (max < 4) s.take(max)
else s.take(max - 3) + "..."
)
override def formatTo(formatter: Formatter, flags: Int, width: Int, precision: Int) {
- val p = elliptically(s, precision)
+ val p = foreshortened(s, precision)
val w = if (width > 0 && p.length < width) {
import FormattableFlags.LEFT_JUSTIFY
val leftly = (flags & LEFT_JUSTIFY) == LEFT_JUSTIFY
@@ -867,7 +767,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
else (p.phaseName, describe(p))
fmt.format(name, idOf(p), text)
}
- line1 :: line2 :: (phaseDescriptors map mkText) mkString
+ (line1 :: line2 :: (phaseDescriptors map mkText)).mkString
}
/** Returns List of (phase, value) pairs, including only those
@@ -885,12 +785,9 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
/** Extend classpath of `platform` and rescan updated packages. */
def extendCompilerClassPath(urls: URL*): Unit = {
- if (settings.YclasspathImpl.value == ClassPathRepresentationType.Flat)
- throw new UnsupportedOperationException("Flat classpath doesn't support extending the compiler classpath")
-
- val newClassPath = platform.classPath.mergeUrlsIntoClassPath(urls: _*)
+ val urlClasspaths = urls.map(u => ClassPathFactory.newClassPath(AbstractFile.getURL(u), settings))
+ val newClassPath = AggregateClassPath.createAggregate(platform.classPath +: urlClasspaths : _*)
platform.currentClassPath = Some(newClassPath)
- // Reload all specified jars into this compiler instance
invalidateClassPathEntries(urls.map(_.getPath): _*)
}
@@ -923,43 +820,54 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
* entries on the classpath.
*/
def invalidateClassPathEntries(paths: String*): Unit = {
- if (settings.YclasspathImpl.value == ClassPathRepresentationType.Flat)
- throw new UnsupportedOperationException("Flat classpath doesn't support the classpath invalidation")
-
- implicit object ClassPathOrdering extends Ordering[PlatformClassPath] {
- def compare(a:PlatformClassPath, b:PlatformClassPath) = a.asClassPathString compare b.asClassPathString
+ implicit object ClassPathOrdering extends Ordering[ClassPath] {
+ def compare(a: ClassPath, b: ClassPath): Int = a.asClassPathString compareTo b.asClassPathString
}
val invalidated, failed = new mutable.ListBuffer[ClassSymbol]
- classPath match {
- case cp: MergedClassPath[_] =>
- def assoc(path: String): List[(PlatformClassPath, PlatformClassPath)] = {
- val dir = AbstractFile.getDirectory(path)
- val canonical = dir.canonicalPath
- def matchesCanonical(e: ClassPath[_]) = e.origin match {
- case Some(opath) =>
- AbstractFile.getDirectory(opath).canonicalPath == canonical
- case None =>
- false
- }
- cp.entries find matchesCanonical match {
- case Some(oldEntry) =>
- List(oldEntry -> cp.context.newClassPath(dir))
- case None =>
- error(s"Error adding entry to classpath. During invalidation, no entry named $path in classpath $classPath")
- List()
- }
- }
- val subst = immutable.TreeMap(paths flatMap assoc: _*)
- if (subst.nonEmpty) {
- platform updateClassPath subst
- informProgress(s"classpath updated on entries [${subst.keys mkString ","}]")
- def mkClassPath(elems: Iterable[PlatformClassPath]): PlatformClassPath =
- if (elems.size == 1) elems.head
- else new MergedClassPath(elems, recursiveClassPath.context)
- val oldEntries = mkClassPath(subst.keys)
- val newEntries = mkClassPath(subst.values)
- mergeNewEntries(newEntries, RootClass, Some(recursiveClassPath), Some(oldEntries), invalidated, failed)
- }
+
+ def assoc(path: String): Option[(ClassPath, ClassPath)] = {
+ def origin(lookup: ClassPath): Option[String] = lookup match {
+ case cp: JFileDirectoryLookup[_] => Some(cp.dir.getPath)
+ case cp: ZipArchiveFileLookup[_] => Some(cp.zipFile.getPath)
+ case _ => None
+ }
+
+ def entries(lookup: ClassPath): Seq[ClassPath] = lookup match {
+ case cp: AggregateClassPath => cp.aggregates
+ case cp: ClassPath => Seq(cp)
+ }
+
+ val dir = AbstractFile.getDirectory(path) // if path is a `jar`, this is a FileZipArchive (isDirectory is true)
+ val canonical = dir.canonicalPath // this is the canonical path of the .jar
+ def matchesCanonical(e: ClassPath) = origin(e) match {
+ case Some(opath) =>
+ AbstractFile.getDirectory(opath).canonicalPath == canonical
+ case None =>
+ false
+ }
+ entries(classPath) find matchesCanonical match {
+ case Some(oldEntry) =>
+ Some(oldEntry -> ClassPathFactory.newClassPath(dir, settings))
+ case None =>
+ error(s"Error adding entry to classpath. During invalidation, no entry named $path in classpath $classPath")
+ None
+ }
+ }
+ val subst = immutable.TreeMap(paths flatMap assoc: _*)
+ if (subst.nonEmpty) {
+ platform updateClassPath subst
+ informProgress(s"classpath updated on entries [${subst.keys mkString ","}]")
+ def mkClassPath(elems: Iterable[ClassPath]): ClassPath =
+ if (elems.size == 1) elems.head
+ else AggregateClassPath.createAggregate(elems.toSeq: _*)
+ val oldEntries = mkClassPath(subst.keys)
+ val newEntries = mkClassPath(subst.values)
+ classPath match {
+ case cp: ClassPath => mergeNewEntries(
+ RootClass, "",
+ oldEntries, newEntries, cp,
+ invalidated, failed)
+ }
}
def show(msg: String, syms: scala.collection.Traversable[Symbol]) =
if (syms.nonEmpty)
@@ -968,66 +876,61 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
show("could not invalidate system packages", failed)
}
- /** Merges new classpath entries into the symbol table
+ /**
+ * Merges new classpath entries into the symbol table
*
- * @param newEntries The new classpath entries
- * @param root The root symbol to be resynced (a package class)
- * @param allEntries Optionally, the corresponding package in the complete current classpath
- * @param oldEntries Optionally, the corresponding package in the old classpath entries
- * @param invalidated A listbuffer collecting the invalidated package classes
- * @param failed A listbuffer collecting system package classes which could not be invalidated
+ * @param packageClass The ClassSymbol for the package being updated
+ * @param fullPackageName The full name of the package being updated
+ * @param oldEntries The classpath that was removed, it is no longer part of fullClasspath
+ * @param newEntries The classpath that was added, it is already part of fullClasspath
+ * @param fullClasspath The full classpath, equivalent to global.classPath
+ * @param invalidated A ListBuffer collecting the invalidated package classes
+ * @param failed A ListBuffer collecting system package classes which could not be invalidated
*
- * The merging strategy is determined by the absence or presence of classes and packages.
+ * If either oldEntries or newEntries contains classes in the current package, the package symbol
+ * is re-initialized to a fresh package loader, provided that a corresponding package exists in
+ * fullClasspath. Otherwise it is removed.
*
- * If either oldEntries or newEntries contains classes, root is invalidated provided that a corresponding package
- * exists in allEntries. Otherwise it is removed.
- * Otherwise, the action is determined by the following matrix, with columns:
- *
- * old sym action
- * + + recurse into all child packages of newEntries
- * - + invalidate root
- * - - create and enter root
- *
- * Here, old means classpath, and sym means symboltable. + is presence of an entry in its column, - is absence.
+ * Otherwise, sub-packages in newEntries are looked up in the symbol table (created if
+ * non-existent) and the merge function is called recursively.
*/
- private def mergeNewEntries(newEntries: PlatformClassPath, root: ClassSymbol,
- allEntries: OptClassPath, oldEntries: OptClassPath,
- invalidated: mutable.ListBuffer[ClassSymbol], failed: mutable.ListBuffer[ClassSymbol]) {
- ifDebug(informProgress(s"syncing $root, $oldEntries -> $newEntries"))
-
- val getName: ClassPath[AbstractFile] => String = (_.name)
- def hasClasses(cp: OptClassPath) = cp.isDefined && cp.get.classes.nonEmpty
- def invalidateOrRemove(root: ClassSymbol) = {
- allEntries match {
- case Some(cp) => root setInfo new loaders.PackageLoader(cp)
- case None => root.owner.info.decls unlink root.sourceModule
- }
- invalidated += root
+ private def mergeNewEntries(packageClass: ClassSymbol, fullPackageName: String,
+ oldEntries: ClassPath, newEntries: ClassPath, fullClasspath: ClassPath,
+ invalidated: mutable.ListBuffer[ClassSymbol], failed: mutable.ListBuffer[ClassSymbol]): Unit = {
+ ifDebug(informProgress(s"syncing $packageClass, $oldEntries -> $newEntries"))
+
+ def packageExists(cp: ClassPath): Boolean = {
+ val (parent, _) = PackageNameUtils.separatePkgAndClassNames(fullPackageName)
+ cp.packages(parent).exists(_.name == fullPackageName)
}
- def subPackage(cp: PlatformClassPath, name: String): OptClassPath =
- cp.packages find (cp1 => getName(cp1) == name)
- val classesFound = hasClasses(oldEntries) || newEntries.classes.nonEmpty
- if (classesFound && !isSystemPackageClass(root)) {
- invalidateOrRemove(root)
- } else {
- if (classesFound) {
- if (root.isRoot) invalidateOrRemove(EmptyPackageClass)
- else failed += root
- }
- if (!oldEntries.isDefined) invalidateOrRemove(root)
+ def invalidateOrRemove(pkg: ClassSymbol) = {
+ if (packageExists(fullClasspath))
+ pkg setInfo new loaders.PackageLoader(fullPackageName, fullClasspath)
else
- for (pstr <- newEntries.packages.map(getName)) {
- val pname = newTermName(pstr)
- val pkg = (root.info decl pname) orElse {
- // package does not exist in symbol table, create symbol to track it
- assert(!subPackage(oldEntries.get, pstr).isDefined)
- loaders.enterPackage(root, pstr, new loaders.PackageLoader(allEntries.get))
- }
- mergeNewEntries(subPackage(newEntries, pstr).get, pkg.moduleClass.asClass,
- subPackage(allEntries.get, pstr), subPackage(oldEntries.get, pstr),
- invalidated, failed)
+ pkg.owner.info.decls unlink pkg.sourceModule
+ invalidated += pkg
+ }
+
+ val classesFound = oldEntries.classes(fullPackageName).nonEmpty || newEntries.classes(fullPackageName).nonEmpty
+ if (classesFound) {
+ // if the package contains classes either in oldEntries or newEntries, the package is invalidated (or removed if there are no more classes in it)
+ if (!isSystemPackageClass(packageClass)) invalidateOrRemove(packageClass)
+ else if (packageClass.isRoot) invalidateOrRemove(EmptyPackageClass)
+ else failed += packageClass
+ } else {
+ // no new or removed classes in the current package
+ for (p <- newEntries.packages(fullPackageName)) {
+ val (_, subPackageName) = PackageNameUtils.separatePkgAndClassNames(p.name)
+ val subPackage = packageClass.info.decl(newTermName(subPackageName)) orElse {
+ // package does not exist in symbol table, create a new symbol
+ loaders.enterPackage(packageClass, subPackageName, new loaders.PackageLoader(p.name, fullClasspath))
}
+ mergeNewEntries(
+ subPackage.moduleClass.asClass, p.name,
+ oldEntries, newEntries, fullClasspath,
+ invalidated, failed)
+ }
}
}
@@ -1063,10 +966,11 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
definitions.isDefinitionsInitialized
&& rootMirror.isMirrorInitialized
)
- override def isPastTyper = (
+ override def isPastTyper = isPast(currentRun.typerPhase)
+ def isPast(phase: Phase) = (
(curRun ne null)
&& isGlobalInitialized // defense against init order issues
- && (globalPhase.id > currentRun.typerPhase.id)
+ && (globalPhase.id > phase.id)
)
// TODO - trim these to the absolute minimum.
@@ -1084,9 +988,9 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
@inline final def enteringErasure[T](op: => T): T = enteringPhase(currentRun.erasurePhase)(op)
@inline final def enteringExplicitOuter[T](op: => T): T = enteringPhase(currentRun.explicitouterPhase)(op)
@inline final def enteringFlatten[T](op: => T): T = enteringPhase(currentRun.flattenPhase)(op)
- @inline final def enteringIcode[T](op: => T): T = enteringPhase(currentRun.icodePhase)(op)
@inline final def enteringMixin[T](op: => T): T = enteringPhase(currentRun.mixinPhase)(op)
@inline final def enteringDelambdafy[T](op: => T): T = enteringPhase(currentRun.delambdafyPhase)(op)
+ @inline final def enteringJVM[T](op: => T): T = enteringPhase(currentRun.jvmPhase)(op)
@inline final def enteringPickler[T](op: => T): T = enteringPhase(currentRun.picklerPhase)(op)
@inline final def enteringSpecialize[T](op: => T): T = enteringPhase(currentRun.specializePhase)(op)
@inline final def enteringTyper[T](op: => T): T = enteringPhase(currentRun.typerPhase)(op)
@@ -1099,7 +1003,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
)
private def formatExplain(pairs: (String, Any)*): String = (
- pairs.toList collect { case (k, v) if v != null => "%20s: %s".format(k, v) } mkString "\n"
+ pairs collect { case (k, v) if v != null => f"$k%20s: $v" } mkString "\n"
)
/** Don't want to introduce new errors trying to report errors,
@@ -1112,9 +1016,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
val site = lastSeenContext.enclClassOrMethod.owner
val pos_s = if (tree.pos.isDefined) s"line ${tree.pos.line} of ${tree.pos.source.file}" else "<unknown>"
val context_s = try {
+ import scala.reflect.io.{File => SFile}
// Taking 3 before, 3 after the fingered line.
- val start = 0 max (tree.pos.line - 3)
- val xs = scala.reflect.io.File(tree.pos.source.file.file).lines drop start take 7
+ val start = 1 max (tree.pos.line - 3)
+ val xs = SFile(tree.pos.source.file.file).lines.drop(start-1).take(7)
val strs = xs.zipWithIndex map { case (line, idx) => f"${start + idx}%6d $line" }
strs.mkString("== Source file context for tree position ==\n\n", "\n", "")
}
@@ -1166,6 +1071,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
def newUnitParser(code: String, filename: String = "<console>"): UnitParser =
newUnitParser(newCompilationUnit(code, filename))
+ def newJavaUnitParser(unit: CompilationUnit): JavaUnitParser = new JavaUnitParser(unit)
+
/** A Run is a single execution of the compiler on a set of units.
*/
class Run extends RunContextApi with RunReporting with RunParsing {
@@ -1178,9 +1085,9 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
var currentUnit: CompilationUnit = NoCompilationUnit
// used in sbt
- def uncheckedWarnings: List[(Position, String)] = reporting.uncheckedWarnings
+ def uncheckedWarnings: List[(Position, String)] = reporting.uncheckedWarnings.map{case (pos, (msg, since)) => (pos, msg)}
// used in sbt
- def deprecationWarnings: List[(Position, String)] = reporting.deprecationWarnings
+ def deprecationWarnings: List[(Position, String)] = reporting.deprecationWarnings.map{case (pos, (msg, since)) => (pos, msg)}
private class SyncedCompilationBuffer { self =>
private val underlying = new mutable.ArrayBuffer[CompilationUnit]
@@ -1305,7 +1212,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
first
}
- // --------------- Miscellania -------------------------------
+ // --------------- Miscellanea -------------------------------
/** Progress tracking. Measured in "progress units" which are 1 per
* compilation unit per phase completed.
@@ -1360,35 +1267,23 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
// val superaccessorsPhase = phaseNamed("superaccessors")
val picklerPhase = phaseNamed("pickler")
val refchecksPhase = phaseNamed("refchecks")
- // val selectiveanfPhase = phaseNamed("selectiveanf")
- // val selectivecpsPhase = phaseNamed("selectivecps")
val uncurryPhase = phaseNamed("uncurry")
+ // val fieldsPhase = phaseNamed("fields")
// val tailcallsPhase = phaseNamed("tailcalls")
val specializePhase = phaseNamed("specialize")
val explicitouterPhase = phaseNamed("explicitouter")
val erasurePhase = phaseNamed("erasure")
val posterasurePhase = phaseNamed("posterasure")
- // val lazyvalsPhase = phaseNamed("lazyvals")
val lambdaliftPhase = phaseNamed("lambdalift")
// val constructorsPhase = phaseNamed("constructors")
val flattenPhase = phaseNamed("flatten")
val mixinPhase = phaseNamed("mixin")
val delambdafyPhase = phaseNamed("delambdafy")
val cleanupPhase = phaseNamed("cleanup")
- val icodePhase = phaseNamed("icode")
- val inlinerPhase = phaseNamed("inliner")
- val inlineExceptionHandlersPhase = phaseNamed("inlinehandlers")
- val closelimPhase = phaseNamed("closelim")
- val dcePhase = phaseNamed("dce")
- // val jvmPhase = phaseNamed("jvm")
+ val jvmPhase = phaseNamed("jvm")
def runIsAt(ph: Phase) = globalPhase.id == ph.id
- def runIsAtOptimiz = {
- runIsAt(inlinerPhase) || // listing phases in full for robustness when -Ystop-after has been given.
- runIsAt(inlineExceptionHandlersPhase) ||
- runIsAt(closelimPhase) ||
- runIsAt(dcePhase)
- }
+ def runIsAtOptimiz = runIsAt(jvmPhase)
isDefined = true
@@ -1400,13 +1295,17 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
unitbuf += unit
compiledFiles += unit.source.file.path
}
- private def checkDeprecatedSettings(unit: CompilationUnit) {
+ private def warnDeprecatedAndConflictingSettings(unit: CompilationUnit) {
// issue warnings for any usage of deprecated settings
settings.userSetSettings filter (_.isDeprecated) foreach { s =>
- currentRun.reporting.deprecationWarning(NoPosition, s.name + " is deprecated: " + s.deprecationMessage.get)
+ currentRun.reporting.deprecationWarning(NoPosition, s.name + " is deprecated: " + s.deprecationMessage.get, "")
+ }
+ val supportedTarget = "jvm-1.8"
+ if (settings.target.value != supportedTarget) {
+ currentRun.reporting.deprecationWarning(NoPosition, settings.target.name + ":" + settings.target.value + " is deprecated and has no effect, setting to " + supportedTarget, "2.12.0")
+ settings.target.value = supportedTarget
}
- if (settings.target.value.contains("jvm-1.5"))
- currentRun.reporting.deprecationWarning(NoPosition, settings.target.name + ":" + settings.target.value + " is deprecated: use target for Java 1.6 or above.")
+ settings.conflictWarning.foreach(reporter.warning(NoPosition, _))
}
/* An iterator returning all the units being compiled in this run */
@@ -1421,7 +1320,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
/** does this run compile given class, module, or case factory? */
// NOTE: Early initialized members temporarily typechecked before the enclosing class, see typedPrimaryConstrBody!
- // Here we work around that wrinkle by claiming that a early-initialized member is compiled in
+ // Here we work around that wrinkle by claiming that a pre-initialized member is compiled in
// *every* run. This approximation works because this method is exclusively called with `this` == `currentRun`.
def compiles(sym: Symbol): Boolean =
if (sym == NoSymbol) false
@@ -1447,8 +1346,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
if (canCheck) {
phase = globalPhase
- if (globalPhase.id >= icodePhase.id) icodeChecker.checkICodes()
- else treeChecker.checkTrees()
+ if (globalPhase.id <= cleanupPhase.id)
+ treeChecker.checkTrees()
}
}
@@ -1497,7 +1396,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
def compileSources(sources: List[SourceFile]) = if (!reporter.hasErrors) {
def checkDeprecations() = {
- checkDeprecatedSettings(newCompilationUnit(""))
+ warnDeprecatedAndConflictingSettings(newCompilationUnit(""))
reporting.summarizeErrors()
}
@@ -1519,7 +1418,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
val startTime = currentTime
reporter.reset()
- checkDeprecatedSettings(unitbuf.head)
+ warnDeprecatedAndConflictingSettings(unitbuf.head)
globalPhase = fromPhase
while (globalPhase.hasNext && !reporter.hasErrors) {
@@ -1529,14 +1428,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
// progress update
informTime(globalPhase.description, startTime)
- val shouldWriteIcode = (
- (settings.writeICode.isSetByUser && (settings.writeICode containsPhase globalPhase))
- || (!settings.Xprint.doAllPhases && (settings.Xprint containsPhase globalPhase) && runIsAtOptimiz)
- )
- if (shouldWriteIcode) {
- // Write *.icode files when -Xprint-icode or -Xprint:<some-optimiz-phase> was given.
- writeICode()
- } else if ((settings.Xprint containsPhase globalPhase) || settings.printLate && runIsAt(cleanupPhase)) {
+ if ((settings.Xprint containsPhase globalPhase) || settings.printLate && runIsAt(cleanupPhase)) {
// print trees
if (settings.Xshowtrees || settings.XshowtreesCompact || settings.XshowtreesStringified) nodePrinters.printAll()
else printAllUnits()
@@ -1557,7 +1449,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
// move the pointer
globalPhase = globalPhase.next
- // run tree/icode checkers
+ // run tree checkers
if (settings.check containsPhase globalPhase.prev)
runCheckers()
@@ -1701,33 +1593,17 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
/** Returns the file with the given suffix for the given class. Used for icode writing. */
def getFile(clazz: Symbol, suffix: String): File = getFile(clazz.sourceFile, clazz.fullName split '.', suffix)
- private def writeICode() {
- val printer = new icodes.TextPrinter(writer = null, icodes.linearizer)
- icodes.classes.values foreach { cls =>
- val file = {
- val module = if (cls.symbol.hasModuleFlag) "$" else ""
- val faze = if (settings.debug) phase.name else f"${phase.id}%02d" // avoid breaking windows build with long filename
- getFile(cls.symbol, s"$module-$faze.icode")
- }
-
- try {
- val stream = new FileOutputStream(file)
- printer.setWriter(new PrintWriter(stream, true))
- try
- printer.printClass(cls)
- finally
- stream.close()
- informProgress(s"wrote $file")
- } catch {
- case e: IOException =>
- if (settings.debug) e.printStackTrace()
- globalError(s"could not write file $file")
- }
- }
- }
def createJavadoc = false
}
object Global {
def apply(settings: Settings, reporter: Reporter): Global = new Global(settings, reporter)
+
+ def apply(settings: Settings): Global = new Global(settings, reporter(settings))
+
+ private def reporter(settings: Settings): Reporter = {
+ //val loader = ScalaClassLoader(getClass.getClassLoader) // apply does not make delegate
+ val loader = new ClassLoader(getClass.getClassLoader) with ScalaClassLoader
+ loader.create[Reporter](settings.reporter.value, settings.errorFn)(settings)
+ }
}
diff --git a/src/compiler/scala/tools/nsc/Main.scala b/src/compiler/scala/tools/nsc/Main.scala
index a66ee572a9..e2cf49907b 100644
--- a/src/compiler/scala/tools/nsc/Main.scala
+++ b/src/compiler/scala/tools/nsc/Main.scala
@@ -17,7 +17,8 @@ class MainClass extends Driver with EvalLoop {
new compiler.Run() compile command.files
}
- override def newCompiler(): Global = Global(settings, reporter)
+ override def newCompiler(): Global = Global(settings)
+
override def doCompile(compiler: Global) {
if (settings.resident) resident(compiler)
else super.doCompile(compiler)
diff --git a/src/compiler/scala/tools/nsc/PhaseAssembly.scala b/src/compiler/scala/tools/nsc/PhaseAssembly.scala
index ef9818c62d..df72c37e53 100644
--- a/src/compiler/scala/tools/nsc/PhaseAssembly.scala
+++ b/src/compiler/scala/tools/nsc/PhaseAssembly.scala
@@ -17,7 +17,7 @@ trait PhaseAssembly {
self: Global =>
/**
- * Aux datastructure for solving the constraint system
+ * Aux data structure for solving the constraint system
* The dependency graph container with helper methods for node and edge creation
*/
private class DependencyGraph {
diff --git a/src/compiler/scala/tools/nsc/Reporting.scala b/src/compiler/scala/tools/nsc/Reporting.scala
index e01c536ad1..5635e678de 100644
--- a/src/compiler/scala/tools/nsc/Reporting.scala
+++ b/src/compiler/scala/tools/nsc/Reporting.scala
@@ -7,7 +7,7 @@ package scala
package tools
package nsc
-import scala.collection.{ mutable, immutable }
+import scala.collection.mutable
import scala.reflect.internal.util.StringOps.countElementsAsString
/** Provides delegates to the reporter doing the actual work.
@@ -26,31 +26,50 @@ 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)(reRunFlag: String = option.name) {
- val warnings = mutable.LinkedHashMap[Position, String]()
- def warn(pos: Position, msg: String) =
- if (option) reporter.warning(pos, msg)
- else if (!(warnings contains pos)) warnings += ((pos, msg))
+ private class ConditionalWarning(what: String, doReport: () => Boolean, setting: Settings#Setting) {
+ def this(what: String, booleanSetting: Settings#BooleanSetting) {
+ this(what, () => booleanSetting, booleanSetting)
+ }
+ val warnings = mutable.LinkedHashMap[Position, (String, String)]()
+ def warn(pos: Position, msg: String, since: String = "") =
+ if (doReport()) reporter.warning(pos, msg)
+ else if (!(warnings contains pos)) warnings += ((pos, (msg, since)))
def summarize() =
- if (warnings.nonEmpty && (option.isDefault || option)) {
- val numWarnings = warnings.size
- 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 $reRunFlag for details")
+ if (warnings.nonEmpty && (setting.isDefault || doReport())) {
+ val sinceAndAmount = mutable.TreeMap[String, Int]()
+ warnings.valuesIterator.foreach { case (_, since) =>
+ val value = sinceAndAmount.get(since)
+ if (value.isDefined) sinceAndAmount += ((since, value.get + 1))
+ else sinceAndAmount += ((since, 1))
+ }
+ val deprecationSummary = sinceAndAmount.size > 1
+ sinceAndAmount.foreach { case (since, numWarnings) =>
+ val warningsSince = if (since.nonEmpty) s" (since $since)" else ""
+ val warningVerb = if (numWarnings == 1) "was" else "were"
+ val warningCount = countElementsAsString(numWarnings, s"$what warning")
+ val rerun = if (deprecationSummary) "" else reporter.rerunWithDetails(setting, setting.name)
+ reporter.warning(NoPosition, s"there ${warningVerb} ${warningCount}${warningsSince}${rerun}")
+ }
+ if (deprecationSummary) {
+ val numWarnings = warnings.size
+ val warningVerb = if (numWarnings == 1) "was" else "were"
+ val warningCount = countElementsAsString(numWarnings, s"$what warning")
+ val rerun = reporter.rerunWithDetails(setting, setting.name)
+ reporter.warning(NoPosition, s"there ${warningVerb} ${warningCount} in total${rerun}")
+ }
}
}
// 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)(if (settings.isBCodeActive) settings.YoptWarnings.name else settings.YinlinerWarnings.name)
+ 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.optWarningsSummaryOnly, settings.optWarnings)
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)
- def deprecationWarning(pos: Position, msg: String): Unit = _deprecationWarnings.warn(pos, msg)
+ def deprecationWarning(pos: Position, msg: String, since: String): Unit = _deprecationWarnings.warn(pos, msg, since)
def uncheckedWarning(pos: Position, msg: String): Unit = _uncheckedWarnings.warn(pos, msg)
def featureWarning(pos: Position, msg: String): Unit = _featureWarnings.warn(pos, msg)
def inlinerWarning(pos: Position, msg: String): Unit = _inlinerWarnings.warn(pos, msg)
@@ -63,10 +82,12 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w
def allConditionalWarnings = _allConditionalWarnings flatMap (_.warnings)
// behold! the symbol that caused the deprecation warning (may not be deprecated itself)
- def deprecationWarning(pos: Position, sym: Symbol, msg: String): Unit = _deprecationWarnings.warn(pos, msg)
+ def deprecationWarning(pos: Position, sym: Symbol, msg: String, since: String): Unit = _deprecationWarnings.warn(pos, msg, since)
def deprecationWarning(pos: Position, sym: Symbol): Unit = {
- val suffix = sym.deprecationMessage match { case Some(msg) => ": "+ msg case _ => "" }
- deprecationWarning(pos, sym, s"$sym${sym.locationString} is deprecated$suffix")
+ val version = sym.deprecationVersion.getOrElse("")
+ val since = if (version.isEmpty) version else s" (since $version)"
+ val message = sym.deprecationMessage match { case Some(msg) => s": $msg" case _ => "" }
+ deprecationWarning(pos, sym, s"$sym${sym.locationString} is deprecated$since$message", version)
}
private[this] var reportedFeature = Set[Symbol]()
diff --git a/src/compiler/scala/tools/nsc/ScriptRunner.scala b/src/compiler/scala/tools/nsc/ScriptRunner.scala
index bf93ad30bc..1f66657d8d 100644
--- a/src/compiler/scala/tools/nsc/ScriptRunner.scala
+++ b/src/compiler/scala/tools/nsc/ScriptRunner.scala
@@ -8,10 +8,8 @@ package tools.nsc
import io.{ AbstractFile, Directory, File, Path }
import java.io.IOException
-import scala.tools.nsc.classpath.DirectoryFlatClassPath
+import scala.tools.nsc.classpath.DirectoryClassPath
import scala.tools.nsc.reporters.{Reporter,ConsoleReporter}
-import scala.tools.nsc.settings.ClassPathRepresentationType
-import scala.tools.nsc.util.ClassPath.DefaultJavaContext
import util.Exceptional.unwrap
/** An object that runs Scala code in script files.
@@ -115,10 +113,7 @@ class ScriptRunner extends HasCompileSocket {
}
def hasClassToRun(d: Directory): Boolean = {
- val cp = settings.YclasspathImpl.value match {
- case ClassPathRepresentationType.Recursive => DefaultJavaContext.newClassPath(AbstractFile.getDirectory(d))
- case ClassPathRepresentationType.Flat => DirectoryFlatClassPath(d.jfile)
- }
+ val cp = DirectoryClassPath(d.jfile)
cp.findClass(mainClass).isDefined
}
diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala
index 6442ef2d54..c70690e697 100644
--- 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/Printers.scala b/src/compiler/scala/tools/nsc/ast/Printers.scala
index f3def3c80c..8b37948e9b 100644
--- a/src/compiler/scala/tools/nsc/ast/Printers.scala
+++ b/src/compiler/scala/tools/nsc/ast/Printers.scala
@@ -6,7 +6,7 @@
package scala.tools.nsc
package ast
-import java.io.{ OutputStream, PrintWriter, StringWriter, Writer }
+import java.io.{ OutputStream, PrintWriter }
trait Printers extends scala.reflect.internal.Printers { this: Global =>
diff --git a/src/compiler/scala/tools/nsc/ast/TreeBrowsers.scala b/src/compiler/scala/tools/nsc/ast/TreeBrowsers.scala
index eafecf9462..105bdee256 100644
--- a/src/compiler/scala/tools/nsc/ast/TreeBrowsers.scala
+++ b/src/compiler/scala/tools/nsc/ast/TreeBrowsers.scala
@@ -7,7 +7,9 @@ package scala
package tools.nsc
package ast
-import java.awt.{List => awtList, _}
+import scala.language.implicitConversions
+
+import java.awt.{List => _, _}
import java.awt.event._
import java.io.StringWriter
@@ -17,7 +19,6 @@ import javax.swing.tree._
import scala.concurrent.Lock
import scala.text._
-import scala.language.implicitConversions
/**
* Tree browsers can show the AST in a graphical and interactive
diff --git a/src/compiler/scala/tools/nsc/ast/TreeDSL.scala b/src/compiler/scala/tools/nsc/ast/TreeDSL.scala
index 6dda30b5e7..9e1498cf3e 100644
--- a/src/compiler/scala/tools/nsc/ast/TreeDSL.scala
+++ b/src/compiler/scala/tools/nsc/ast/TreeDSL.scala
@@ -7,7 +7,6 @@
package scala.tools.nsc
package ast
-import symtab.Flags
import scala.language.implicitConversions
/** A DSL for generating scala code. The goal is that the
diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala
index 332acf4a26..b073cb828c 100644
--- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala
+++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala
@@ -91,7 +91,7 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
)
/** Make a synchronized block on 'monitor'. */
- def mkSynchronized(monitor: Tree, body: Tree): Tree =
+ def mkSynchronized(monitor: Tree)(body: Tree): Tree =
Apply(Select(monitor, Object_synchronized), List(body))
def mkAppliedTypeForCase(clazz: Symbol): Tree = {
@@ -145,6 +145,7 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
override def mkCast(tree: Tree, pt: Type): Tree = {
debuglog("casting " + tree + ":" + tree.tpe + " to " + pt + " at phase: " + phase)
assert(!tree.tpe.isInstanceOf[MethodType], tree)
+ assert(!pt.isInstanceOf[MethodType], tree)
assert(pt eq pt.normalize, tree +" : "+ debugString(pt) +" ~>"+ debugString(pt.normalize))
atPos(tree.pos) {
mkAsInstanceOf(tree, pt, any = !phase.next.erasedTypes, wrapInApply = isAtPhaseAfter(currentRun.uncurryPhase))
@@ -232,22 +233,6 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
else Block(prefix, containing) setPos (prefix.head.pos union containing.pos)
}
- /** Return the synchronized part of the double-checked locking idiom around the syncBody tree. It guards with `cond` and
- * synchronizes on `clazz.this`. Additional statements can be included after initialization,
- * (outside the synchronized block).
- *
- * The idiom works only if the condition is using a volatile field.
- * @see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
- */
- def mkSynchronizedCheck(clazz: Symbol, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree =
- mkSynchronizedCheck(mkAttributedThis(clazz), cond, syncBody, stats)
-
- def mkSynchronizedCheck(attrThis: Tree, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree =
- Block(mkSynchronized(
- attrThis,
- If(cond, Block(syncBody: _*), EmptyTree)) ::
- stats: _*)
-
/** Creates a tree representing new Object { stats }.
* To make sure an anonymous subclass of Object is created,
* if there are no stats, a () is added.
@@ -257,43 +242,135 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
mkNew(Nil, noSelfType, stats1, NoPosition, NoPosition)
}
- /**
- * Create a method based on a Function
- *
- * Used both to under `-Ydelambdafy:method` create a lifted function and
- * under `-Ydelambdafy:inline` to create the apply method on the anonymous
- * class.
- *
- * It creates a method definition with value params cloned from the
- * original lambda. Then it calls a supplied function to create
- * the body and types the result. Finally
- * everything is wrapped up in a DefDef
- *
- * @param owner The owner for the new method
- * @param name name for the new method
- * @param additionalFlags flags to be put on the method in addition to FINAL
- */
- def mkMethodFromFunction(localTyper: analyzer.Typer)
- (fun: Function, owner: Symbol, name: TermName, additionalFlags: FlagSet = NoFlags) = {
- val funParams = fun.vparams map (_.symbol)
- val formals :+ restpe = fun.tpe.typeArgs
- val methSym = owner.newMethod(name, fun.pos, FINAL | additionalFlags)
+ // Construct a method to implement `fun`'s single abstract method (`apply`, when `fun.tpe` is a built-in function type)
+ def mkMethodFromFunction(localTyper: analyzer.Typer)(owner: Symbol, fun: Function) = {
+ // TODO: treat FunctionN like any other SAM -- drop `&& !isFunctionType(fun.tpe)`
+ val sam = if (!isFunctionType(fun.tpe)) samOf(fun.tpe) else NoSymbol
+ if (!sam.exists) mkMethodForFunctionBody(localTyper)(owner, fun, nme.apply)()
+ else {
+ val samMethType = fun.tpe memberInfo sam
+ mkMethodForFunctionBody(localTyper)(owner, fun, sam.name.toTermName)(methParamProtos = samMethType.params, resTp = samMethType.resultType)
+ }
+ }
- val paramSyms = map2(formals, fun.vparams) {
- (tp, vparam) => methSym.newSyntheticValueParam(tp, vparam.name)
+ // used to create the lifted method that holds a function's body
+ def mkLiftedFunctionBodyMethod(localTyper: global.analyzer.Typer)(owner: global.Symbol, fun: global.Function) = {
+ def nonLocalEnclosingMember(sym: Symbol): Symbol = {
+ if (sym.isLocalDummy) sym.enclClass.primaryConstructor
+ else if (sym.isLocalToBlock) nonLocalEnclosingMember(sym.originalOwner)
+ else sym
}
+ val ownerName = nonLocalEnclosingMember(fun.symbol.originalOwner).name match {
+ case nme.CONSTRUCTOR => nme.NEWkw // do as javac does for the suffix, prefer "new" to "$lessinit$greater$1"
+ case x => x.dropLocal
+ }
+ val newName = nme.ANON_FUN_NAME.append(nme.NAME_JOIN_STRING).append(ownerName)
+ mkMethodForFunctionBody(localTyper)(owner, fun, newName)(additionalFlags = ARTIFACT)
+ }
- methSym setInfo MethodType(paramSyms, restpe.deconst)
- fun.body.substituteSymbols(funParams, paramSyms)
- fun.body changeOwner (fun.symbol -> methSym)
+ // the result type of a function or corresponding SAM type
+ private def functionResultType(tp: Type): Type = {
+ val dealiased = tp.dealiasWiden
+ if (isFunctionTypeDirect(dealiased)) dealiased.typeArgs.last
+ else samOf(tp) match {
+ case samSym if samSym.exists => tp.memberInfo(samSym).resultType.deconst
+ case _ => NoType
+ }
+ }
- val methDef = DefDef(methSym, fun.body)
+ /**
+ * Lift a Function's body to a method. For use during Uncurry, where Function nodes have type FunctionN[T1, ..., Tn, R]
+ *
+ * It creates a method definition with value params derived from the original lambda
+ * or `methParamProtos` (used to create the correct override for sam methods).
+ *
+ * Replace the `fun.vparams` symbols by the newly created method params,
+ * changes owner of `fun.body` from `fun.symbol` to resulting method's symbol.
+ *
+ * @param owner The owner for the new method
+ * @param fun the function to take the body from
+ * @param name name for the new method
+ * @param additionalFlags flags to be put on the method in addition to FINAL
+ */
+ private def mkMethodForFunctionBody(localTyper: analyzer.Typer)
+ (owner: Symbol, fun: Function, name: TermName)
+ (methParamProtos: List[Symbol] = fun.vparams.map(_.symbol),
+ resTp: Type = functionResultType(fun.tpe),
+ additionalFlags: FlagSet = NoFlags): DefDef = {
+ val methSym = owner.newMethod(name, fun.pos, FINAL | additionalFlags)
+ // for sams, methParamProtos is the parameter symbols for the sam's method, so that we generate the correct override (based on parameter types)
+ val methParamSyms = methParamProtos.map { param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName) }
+ methSym setInfo MethodType(methParamSyms, resTp)
+
+ // we must rewire reference to the function's param symbols -- and not methParamProtos -- to methParamSyms
+ val useMethodParams = new TreeSymSubstituter(fun.vparams.map(_.symbol), methParamSyms)
+ // we're now owned by the method that holds the body, and not the function
+ val moveToMethod = new ChangeOwnerTraverser(fun.symbol, methSym)
+
+ newDefDef(methSym, moveToMethod(useMethodParams(fun.body)))(tpt = TypeTree(resTp))
+ }
+
+ /**
+ * Create a new `DefDef` based on `orig` with an explicit self parameter.
+ *
+ * Details:
+ * - Must by run after erasure
+ * - If `maybeClone` is the identity function, this runs "in place"
+ * and mutates the symbol of `orig`. `orig` should be discarded
+ * - Symbol owners and returns are substituted, as are parameter symbols
+ * - Recursive calls are not rewritten. This is correct if we assume
+ * that we either:
+ * - are in "in-place" mode, but can guarantee that no recursive calls exists
+ * - are associating the RHS with a cloned symbol, but intend for the original
+ * method to remain and for recursive calls to target it.
+ */
+ final def mkStatic(orig: DefDef, newName: Name, maybeClone: Symbol => Symbol): DefDef = {
+ assert(phase.erasedTypes, phase)
+ assert(!orig.symbol.hasFlag(SYNCHRONIZED), orig.symbol.defString)
+ val origSym = orig.symbol
+ val origParams = orig.symbol.info.params
+ val newSym = maybeClone(orig.symbol)
+ newSym.setName(newName)
+ newSym.setFlag(STATIC)
+ // Add an explicit self parameter
+ val selfParamSym = newSym.newSyntheticValueParam(newSym.owner.typeConstructor, nme.SELF).setFlag(ARTIFACT)
+ newSym.updateInfo(newSym.info match {
+ case mt @ MethodType(params, res) => copyMethodType(mt, selfParamSym :: params, res)
+ })
+ val selfParam = ValDef(selfParamSym)
+ val rhs = orig.rhs.substituteThis(newSym.owner, gen.mkAttributedIdent(selfParamSym)) // SD-186 intentionally leaving Ident($this) is unpositioned
+ .substituteSymbols(origParams, newSym.info.params.drop(1)).changeOwner(origSym -> newSym)
+ treeCopy.DefDef(orig, orig.mods, orig.name, orig.tparams, (selfParam :: orig.vparamss.head) :: Nil, orig.tpt, rhs).setSymbol(newSym)
+ }
+
+ def expandFunction(localTyper: analyzer.Typer)(fun: Function, inConstructorFlag: Long): Tree = {
+ val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag)
+ val parents = if (isFunctionType(fun.tpe)) {
+ anonClass addAnnotation SerialVersionUIDAnnotation
+ addSerializable(abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.body.tpe.deconst))
+ } else {
+ if (fun.tpe.typeSymbol.isSubClass(JavaSerializableClass))
+ anonClass addAnnotation SerialVersionUIDAnnotation
+ fun.tpe :: Nil
+ }
+ anonClass setInfo ClassInfoType(parents, newScope, anonClass)
- // Have to repack the type to avoid mismatches when existentials
- // appear in the result - see SI-4869.
- methDef.tpt setType localTyper.packedType(fun.body, methSym).deconst
- methDef
+ // 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)
+
+ val samDef = mkMethodFromFunction(localTyper)(anonClass, fun)
+ anonClass.info.decls enter samDef.symbol
+
+ localTyper.typedPos(fun.pos) {
+ Block(
+ ClassDef(anonClass, NoMods, ListOfNil, List(samDef), fun.pos),
+ Typed(New(anonClass.tpe), TypeTree(fun.tpe)))
+ }
}
+
+ override def isPatVarWarnable = settings.warnUnusedPatVars
}
diff --git a/src/compiler/scala/tools/nsc/ast/TreeInfo.scala b/src/compiler/scala/tools/nsc/ast/TreeInfo.scala
index 689e6405d0..b78c5acc4f 100644
--- a/src/compiler/scala/tools/nsc/ast/TreeInfo.scala
+++ b/src/compiler/scala/tools/nsc/ast/TreeInfo.scala
@@ -97,4 +97,12 @@ abstract class TreeInfo extends scala.reflect.internal.TreeInfo {
case DocDef(_, definition) => isPureDef(definition)
case _ => super.isPureDef(tree)
}
+
+ override def firstConstructor(stats: List[Tree]): Tree = {
+ def unwrap(stat: Tree): Tree = stat match {
+ case DocDef(_, defn) => unwrap(defn)
+ case tree => tree
+ }
+ super.firstConstructor(stats map unwrap)
+ }
}
diff --git a/src/compiler/scala/tools/nsc/ast/Trees.scala b/src/compiler/scala/tools/nsc/ast/Trees.scala
index 934257092f..2d47e254e5 100644
--- a/src/compiler/scala/tools/nsc/ast/Trees.scala
+++ b/src/compiler/scala/tools/nsc/ast/Trees.scala
@@ -7,13 +7,6 @@ package scala.tools.nsc
package ast
import scala.reflect.ClassTag
-import scala.reflect.internal.Flags.BYNAMEPARAM
-import scala.reflect.internal.Flags.DEFAULTPARAM
-import scala.reflect.internal.Flags.IMPLICIT
-import scala.reflect.internal.Flags.PARAM
-import scala.reflect.internal.Flags.PARAMACCESSOR
-import scala.reflect.internal.Flags.PRESUPER
-import scala.reflect.internal.Flags.TRAIT
import scala.compat.Platform.EOL
trait Trees extends scala.reflect.internal.Trees { self: Global =>
diff --git a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala
index 52b8a51a79..46d533b037 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala
@@ -12,7 +12,7 @@ import mutable.{ Buffer, ArrayBuffer, ListBuffer }
import scala.util.control.ControlThrowable
import scala.tools.nsc.util.CharArrayReader
import scala.tools.nsc.ast.parser.xml.{MarkupParserCommon, Utility}
-import scala.reflect.internal.Chars.{ SU, LF }
+import scala.reflect.internal.Chars.SU
// XXX/Note: many/most of the functions in here are almost direct cut and pastes
// from another file - scala.xml.parsing.MarkupParser, it looks like.
@@ -261,7 +261,7 @@ trait MarkupParsers {
def coalesce(): ArrayBuffer[Tree] = {
def copy() = {
val buf = new ArrayBuffer[Tree]
- var acc = new StringBuilder
+ val acc = new StringBuilder
var pos: Position = NoPosition
def emit() = if (acc.nonEmpty) {
appendText(pos, buf, acc.toString)
diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
index 308669256d..707fe15f91 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
@@ -9,10 +9,9 @@
package scala.tools.nsc
package ast.parser
-import scala.collection.{ mutable, immutable }
-import mutable.{ ListBuffer, StringBuilder }
+import scala.collection.mutable
+import mutable.ListBuffer
import scala.reflect.internal.{ Precedence, ModifierFlags => Flags }
-import scala.reflect.internal.Chars.{ isScalaLetter }
import scala.reflect.internal.util.{ SourceFile, Position, FreshNameCreator, ListOfNil }
import Tokens._
@@ -40,7 +39,7 @@ trait ParsersCommon extends ScannersCommon { self =>
*/
abstract class ParserCommon {
val in: ScannerCommon
- def deprecationWarning(off: Offset, msg: String): Unit
+ def deprecationWarning(off: Offset, msg: String, since: String): Unit
def accept(token: Token): Int
/** Methods inParensOrError and similar take a second argument which, should
@@ -155,7 +154,7 @@ self =>
// suppress warnings; silent abort on errors
def warning(offset: Offset, msg: String): Unit = ()
- def deprecationWarning(offset: Offset, msg: String): Unit = ()
+ def deprecationWarning(offset: Offset, msg: String, since: String): Unit = ()
def syntaxError(offset: Offset, msg: String): Unit = throw new MalformedInput(offset, msg)
def incompleteInputError(msg: String): Unit = throw new MalformedInput(source.content.length - 1, msg)
@@ -207,8 +206,8 @@ self =>
override def warning(offset: Offset, msg: String): Unit =
reporter.warning(o2p(offset), msg)
- override def deprecationWarning(offset: Offset, msg: String): Unit =
- currentRun.reporting.deprecationWarning(o2p(offset), msg)
+ override def deprecationWarning(offset: Offset, msg: String, since: String): Unit =
+ currentRun.reporting.deprecationWarning(o2p(offset), msg, since)
private var smartParsing = false
@inline private def withSmartParsing[T](body: => T): T = {
@@ -235,7 +234,7 @@ self =>
else currentRun.parsing.incompleteInputError(o2p(offset), msg)
}
- /** parse unit. If there are inbalanced braces,
+ /** parse unit. If there are unbalanced braces,
* try to correct them and reparse.
*/
def smartParse(): Tree = withSmartParsing {
@@ -685,6 +684,15 @@ self =>
}
def isLiteral = isLiteralToken(in.token)
+ def isSimpleExprIntroToken(token: Token): Boolean = isLiteralToken(token) || (token match {
+ case IDENTIFIER | BACKQUOTED_IDENT |
+ THIS | SUPER | NEW | USCORE |
+ LPAREN | LBRACE | XMLSTART => true
+ case _ => false
+ })
+
+ def isSimpleExprIntro: Boolean = isExprIntroToken(in.token)
+
def isExprIntroToken(token: Token): Boolean = isLiteralToken(token) || (token match {
case IDENTIFIER | BACKQUOTED_IDENT |
THIS | SUPER | IF | FOR | NEW | USCORE | TRY | WHILE |
@@ -804,7 +812,7 @@ self =>
false
} else true
- /** Strip the artifitial `Parens` node to create a tuple term Tree. */
+ /** Strip the artificial `Parens` node to create a tuple term Tree. */
def stripParens(t: Tree) = t match {
case Parens(ts) => atPos(t.pos) { makeSafeTupleTerm(ts, t.pos.point) }
case _ => t
@@ -1255,8 +1263,8 @@ self =>
case CHARLIT => in.charVal
case INTLIT => in.intVal(isNegated).toInt
case LONGLIT => in.intVal(isNegated)
- case FLOATLIT => in.floatVal(isNegated).toFloat
- case DOUBLELIT => in.floatVal(isNegated)
+ case FLOATLIT => in.floatVal(isNegated)
+ case DOUBLELIT => in.doubleVal(isNegated)
case STRINGLIT | STRINGPART => in.strVal.intern()
case TRUE => true
case FALSE => false
@@ -1636,11 +1644,14 @@ self =>
def prefixExpr(): Tree = {
if (isUnaryOp) {
atPos(in.offset) {
- val name = nme.toUnaryName(rawIdent().toTermName)
- if (name == nme.UNARY_- && isNumericLit)
- simpleExprRest(literal(isNegated = true), canApply = true)
- else
- Select(stripParens(simpleExpr()), name)
+ if (lookingAhead(isSimpleExprIntro)) {
+ val uname = nme.toUnaryName(rawIdent().toTermName)
+ if (uname == nme.UNARY_- && isNumericLit)
+ simpleExprRest(literal(isNegated = true), canApply = true)
+ else
+ Select(stripParens(simpleExpr()), uname)
+ }
+ else simpleExpr()
}
}
else simpleExpr()
@@ -1722,9 +1733,7 @@ self =>
}
simpleExprRest(app, canApply = true)
case USCORE =>
- atPos(t.pos.start, in.skipToken()) {
- Typed(stripParens(t), Function(Nil, EmptyTree))
- }
+ atPos(t.pos.start, in.skipToken()) { makeMethodValue(stripParens(t)) }
case _ =>
t
}
@@ -1833,7 +1842,7 @@ self =>
val hasEq = in.token == EQUALS
if (hasVal) {
- if (hasEq) deprecationWarning(in.offset, "val keyword in for comprehension is deprecated")
+ if (hasEq) deprecationWarning(in.offset, "val keyword in for comprehension is deprecated", "2.10.0")
else syntaxError(in.offset, "val in for comprehension must be followed by assignment")
}
@@ -1912,19 +1921,20 @@ self =>
}
/** {{{
- * Pattern1 ::= varid `:' TypePat
+ * Pattern1 ::= boundvarid `:' TypePat
* | `_' `:' TypePat
* | Pattern2
- * SeqPattern1 ::= varid `:' TypePat
+ * SeqPattern1 ::= boundvarid `:' TypePat
* | `_' `:' TypePat
* | [SeqPattern2]
* }}}
*/
def pattern1(): Tree = pattern2() match {
case p @ Ident(name) if in.token == COLON =>
- if (treeInfo.isVarPattern(p))
+ if (nme.isVariableName(name)) {
+ p.removeAttachment[BackquotedIdentifierAttachment.type]
atPos(p.pos.start, in.skipToken())(Typed(p, compoundType()))
- else {
+ } else {
syntaxError(in.offset, "Pattern variables must start with a lower-case letter. (SLS 8.1.1.)")
p
}
@@ -1932,25 +1942,27 @@ self =>
}
/** {{{
- * Pattern2 ::= varid [ @ Pattern3 ]
+ * Pattern2 ::= id @ Pattern3
+ * | `_' @ Pattern3
* | Pattern3
- * SeqPattern2 ::= varid [ @ SeqPattern3 ]
- * | SeqPattern3
* }}}
*/
- def pattern2(): Tree = {
- val p = pattern3()
-
- if (in.token != AT) p
- else p match {
- case Ident(nme.WILDCARD) =>
- in.nextToken()
- pattern3()
- case Ident(name) if treeInfo.isVarPattern(p) =>
- in.nextToken()
- atPos(p.pos.start) { Bind(name, pattern3()) }
- case _ => p
- }
+ def pattern2(): Tree = (pattern3(), in.token) match {
+ case (Ident(nme.WILDCARD), AT) =>
+ in.nextToken()
+ pattern3()
+ case (p @ Ident(name), AT) =>
+ in.nextToken()
+ val body = pattern3()
+ atPos(p.pos.start, p.pos.start, body.pos.end) {
+ val t = Bind(name, body)
+ body match {
+ case Ident(nme.WILDCARD) => t updateAttachment AtBoundIdentifierAttachment
+ case _ if !settings.warnUnusedPatVars => t updateAttachment AtBoundIdentifierAttachment
+ case _ => t
+ }
+ }
+ case (p, _) => p
}
/** {{{
@@ -1975,8 +1987,8 @@ self =>
case _ => EmptyTree
}
def loop(top: Tree): Tree = reducePatternStack(base, top) match {
- case next if isIdentExcept(raw.BAR) => pushOpInfo(next) ; loop(simplePattern(badPattern3))
- case next => next
+ case next if isIdent && !isRawBar => pushOpInfo(next) ; loop(simplePattern(badPattern3))
+ case next => next
}
checkWildStar orElse stripParens(loop(top))
}
@@ -2227,31 +2239,57 @@ self =>
* }}}
*/
def paramClauses(owner: Name, contextBounds: List[Tree], ofCaseClass: Boolean): List[List[ValDef]] = {
- var implicitmod = 0
- var caseParam = ofCaseClass
- def paramClause(): List[ValDef] = {
- if (in.token == RPAREN)
- return Nil
-
- if (in.token == IMPLICIT) {
- in.nextToken()
- implicitmod = Flags.IMPLICIT
- }
- commaSeparated(param(owner, implicitmod, caseParam ))
- }
- val vds = new ListBuffer[List[ValDef]]
+ var implicitSection = -1
+ var implicitOffset = -1
+ var warnAt = -1
+ var caseParam = ofCaseClass
+ val vds = new ListBuffer[List[ValDef]]
val start = in.offset
+ def paramClause(): List[ValDef] = if (in.token == RPAREN) Nil else {
+ val implicitmod =
+ if (in.token == IMPLICIT) {
+ if (implicitOffset == -1) { implicitOffset = in.offset ; implicitSection = vds.length }
+ else if (warnAt == -1) warnAt = in.offset
+ in.nextToken()
+ Flags.IMPLICIT
+ } else 0
+ commaSeparated(param(owner, implicitmod, caseParam))
+ }
newLineOptWhenFollowedBy(LPAREN)
- if (ofCaseClass && in.token != LPAREN)
- syntaxError(in.lastOffset, "case classes without a parameter list are not allowed;\n"+
- "use either case objects or case classes with an explicit `()' as a parameter list.")
- while (implicitmod == 0 && in.token == LPAREN) {
+ while (in.token == LPAREN) {
in.nextToken()
vds += paramClause()
accept(RPAREN)
caseParam = false
newLineOptWhenFollowedBy(LPAREN)
}
+ if (ofCaseClass) {
+ if (vds.isEmpty)
+ syntaxError(start, s"case classes must have a parameter list; try 'case class ${owner.encoded
+ }()' or 'case object ${owner.encoded}'")
+ else if (vds.head.nonEmpty && vds.head.head.mods.isImplicit) {
+ if (settings.isScala213)
+ syntaxError(start, s"case classes must have a non-implicit parameter list; try 'case class ${
+ owner.encoded}()${ vds.map(vs => "(...)").mkString }'")
+ else {
+ deprecationWarning(start, s"case classes should have a non-implicit parameter list; adapting to 'case class ${
+ owner.encoded}()${ vds.map(vs => "(...)").mkString }'", "2.12.2")
+ vds.insert(0, List.empty[ValDef])
+ vds(1) = vds(1).map(vd => copyValDef(vd)(mods = vd.mods & ~Flags.CASEACCESSOR))
+ if (implicitSection != -1) implicitSection += 1
+ }
+ }
+ }
+ if (implicitSection != -1 && implicitSection != vds.length - 1)
+ syntaxError(implicitOffset, "an implicit parameter section must be last")
+ if (warnAt != -1)
+ syntaxError(warnAt, "multiple implicit parameter sections are not allowed")
+ else if (settings.warnExtraImplicit) {
+ // guard against anomalous class C(private implicit val x: Int)(implicit s: String)
+ val ttl = vds.count { case ValDef(mods, _, _, _) :: _ => mods.isImplicit ; case _ => false }
+ if (ttl > 1)
+ warning(in.offset, s"$ttl parameter sections are effectively implicit")
+ }
val result = vds.toList
if (owner == nme.CONSTRUCTOR && (result.isEmpty || (result.head take 1 exists (_.mods.isImplicit)))) {
in.token match {
@@ -2369,7 +2407,7 @@ self =>
while (in.token == VIEWBOUND) {
val msg = "Use an implicit parameter instead.\nExample: Instead of `def f[A <% Int](a: A)` use `def f[A](a: A)(implicit ev: A => Int)`."
if (settings.future)
- deprecationWarning(in.offset, s"View bounds are deprecated. $msg")
+ deprecationWarning(in.offset, s"View bounds are deprecated. $msg", "2.12.0")
contextBoundBuf += atPos(in.skipToken())(makeFunctionTypeTree(List(Ident(pname)), typ()))
}
while (in.token == COLON) {
@@ -2663,14 +2701,14 @@ self =>
if (isStatSep || in.token == RBRACE) {
if (restype.isEmpty) {
if (settings.future)
- deprecationWarning(in.lastOffset, s"Procedure syntax is deprecated. Convert procedure `$name` to method by adding `: Unit`.")
+ deprecationWarning(in.lastOffset, s"Procedure syntax is deprecated. Convert procedure `$name` to method by adding `: Unit`.", "2.12.0")
restype = scalaUnitConstr
}
newmods |= Flags.DEFERRED
EmptyTree
} else if (restype.isEmpty && in.token == LBRACE) {
if (settings.future)
- deprecationWarning(in.offset, s"Procedure syntax is deprecated. Convert procedure `$name` to method by adding `: Unit =`.")
+ deprecationWarning(in.offset, s"Procedure syntax is deprecated. Convert procedure `$name` to method by adding `: Unit =`.", "2.12.0")
restype = scalaUnitConstr
blockExpr()
} else {
@@ -2819,14 +2857,8 @@ self =>
val (constrMods, vparamss) =
if (mods.isTrait) (Modifiers(Flags.TRAIT), List())
else (accessModifierOpt(), paramClauses(name, classContextBounds, ofCaseClass = mods.isCase))
- var mods1 = mods
- if (mods.isTrait) {
- if (settings.YvirtClasses && in.token == SUBTYPE) mods1 |= Flags.DEFERRED
- } else if (in.token == SUBTYPE) {
- syntaxError("classes are not allowed to be virtual", skipIt = false)
- }
- val template = templateOpt(mods1, name, constrMods withAnnotations constrAnnots, vparamss, tstart)
- val result = gen.mkClassDef(mods1, name, tparams, template)
+ val template = templateOpt(mods, name, constrMods withAnnotations constrAnnots, vparamss, tstart)
+ val result = gen.mkClassDef(mods, name, tparams, template)
// Context bounds generate implicit parameters (part of the template) with types
// from tparams: we need to ensure these don't overlap
if (!classContextBounds.isEmpty)
@@ -2937,7 +2969,7 @@ self =>
case vdef @ ValDef(mods, _, _, _) if !mods.isDeferred =>
copyValDef(vdef)(mods = mods | Flags.PRESUPER)
case tdef @ TypeDef(mods, name, tparams, rhs) =>
- deprecationWarning(tdef.pos.point, "early type members are deprecated. Move them to the regular body: the semantics are the same.")
+ deprecationWarning(tdef.pos.point, "early type members are deprecated. Move them to the regular body: the semantics are the same.", "2.11.0")
treeCopy.TypeDef(tdef, mods | Flags.PRESUPER, name, tparams, rhs)
case docdef @ DocDef(comm, rhs) =>
treeCopy.DocDef(docdef, comm, rhs)
diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
index cd41c75298..0618f5d06e 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
@@ -10,7 +10,7 @@ import scala.reflect.internal.util._
import scala.reflect.internal.Chars._
import Tokens._
import scala.annotation.{ switch, tailrec }
-import scala.collection.{ mutable, immutable }
+import scala.collection.mutable
import mutable.{ ListBuffer, ArrayBuffer }
import scala.tools.nsc.ast.parser.xml.Utility.isNameStart
import scala.language.postfixOps
@@ -35,7 +35,28 @@ trait ScannersCommon {
// things to fill in, in addition to buf, decodeUni which come from CharArrayReader
def error(off: Offset, msg: String): Unit
def incompleteInputError(off: Offset, msg: String): Unit
- def deprecationWarning(off: Offset, msg: String): Unit
+ def deprecationWarning(off: Offset, msg: String, since: String): Unit
+ }
+
+ // Hooks for ScaladocUnitScanner and ScaladocJavaUnitScanner
+ trait DocScanner {
+ protected def beginDocComment(prefix: String): Unit = {}
+ protected def processCommentChar(): Unit = {}
+ protected def finishDocComment(): Unit = {}
+
+ private var lastDoc: DocComment = null
+ // get last doc comment
+ def flushDoc(): DocComment = try lastDoc finally lastDoc = null
+ def registerDocComment(raw: String, pos: Position) = {
+ lastDoc = DocComment(raw, pos)
+ signalParsedDocComment(raw, pos)
+ }
+
+ /** To prevent doc comments attached to expressions from leaking out of scope
+ * onto the next documentable entity, they are discarded upon passing a right
+ * brace, bracket, or parenthesis.
+ */
+ def discardDocBuffer(): Unit = {}
}
def createKeywordArray(keywords: Seq[(Name, Token)], defaultToken: Token): (Token, Array[Token]) = {
@@ -103,11 +124,11 @@ trait Scanners extends ScannersCommon {
}
}
- abstract class Scanner extends CharArrayReader with TokenData with ScannerData with ScannerCommon {
+ abstract class Scanner extends CharArrayReader with TokenData with ScannerData with ScannerCommon with DocScanner {
private def isDigit(c: Char) = java.lang.Character isDigit c
private var openComments = 0
- protected def putCommentChar(): Unit = nextChar()
+ final protected def putCommentChar(): Unit = { processCommentChar(); nextChar() }
@tailrec private def skipLineComment(): Unit = ch match {
case SU | CR | LF =>
@@ -134,8 +155,6 @@ trait Scanners extends ScannersCommon {
case SU => incompleteInputError("unclosed comment")
case _ => putCommentChar() ; skipNestedComments()
}
- def skipDocComment(): Unit = skipNestedComments()
- def skipBlockComment(): Unit = skipNestedComments()
private def skipToCommentEnd(isLineComment: Boolean): Unit = {
nextChar()
@@ -147,27 +166,23 @@ trait Scanners extends ScannersCommon {
// Check for the amazing corner case of /**/
if (ch == '/')
nextChar()
- else
- skipDocComment()
+ else {
+ beginDocComment("/**")
+ skipNestedComments()
+ }
}
- else skipBlockComment()
+ else skipNestedComments()
}
}
/** @pre ch == '/'
* Returns true if a comment was skipped.
*/
- def skipComment(): Boolean = ch match {
- case '/' | '*' => skipToCommentEnd(isLineComment = ch == '/') ; true
+ final def skipComment(): Boolean = ch match {
+ case '/' | '*' => skipToCommentEnd(isLineComment = ch == '/') ; finishDocComment(); true
case _ => false
}
- def flushDoc(): DocComment = null
- /** To prevent doc comments attached to expressions from leaking out of scope
- * onto the next documentable entity, they are discarded upon passing a right
- * brace, bracket, or parenthesis.
- */
- def discardDocBuffer(): Unit = ()
def isAtEnd = charOffset >= buf.length
@@ -208,7 +223,7 @@ trait Scanners extends ScannersCommon {
if (name == nme.MACROkw)
syntaxError(s"$name is now a reserved word; usage as an identifier is disallowed")
else if (emitIdentifierDeprecationWarnings)
- deprecationWarning(s"$name is now a reserved word; usage as an identifier is deprecated")
+ deprecationWarning(s"$name is a reserved word (since 2.10.0); usage as an identifier is deprecated", "2.10.0")
}
}
}
@@ -246,6 +261,14 @@ trait Scanners extends ScannersCommon {
private def inMultiLineInterpolation =
inStringInterpolation && sepRegions.tail.nonEmpty && sepRegions.tail.head == STRINGPART
+ /** Are we in a `${ }` block? such that RBRACE exits back into multiline string. */
+ private def inMultiLineInterpolatedExpression = {
+ sepRegions match {
+ case RBRACE :: STRINGLIT :: STRINGPART :: rest => true
+ case _ => false
+ }
+ }
+
/** read next token and return last offset
*/
def skipToken(): Offset = {
@@ -312,7 +335,7 @@ trait Scanners extends ScannersCommon {
lastOffset -= 1
}
if (inStringInterpolation) fetchStringPart() else fetchToken()
- if(token == ERROR) {
+ if (token == ERROR) {
if (inMultiLineInterpolation)
sepRegions = sepRegions.tail.tail
else if (inStringInterpolation)
@@ -363,6 +386,17 @@ trait Scanners extends ScannersCommon {
next copyFrom this
this copyFrom prev
}
+ } else if (token == COMMA) {
+ // SIP-27 Trailing Comma (multi-line only) support
+ // If a comma is followed by a new line & then a closing paren, bracket or brace
+ // then it is a trailing comma and is ignored
+ val saved = new ScannerData {} copyFrom this
+ fetchToken()
+ if (afterLineEnd() && (token == RPAREN || token == RBRACKET || token == RBRACE)) {
+ /* skip the trailing comma */
+ } else if (token == EOF) { // e.g. when the REPL is parsing "val List(x, y, _*,"
+ /* skip the trailing comma */
+ } else this copyFrom saved
}
// print("["+this+"]")
@@ -515,7 +549,7 @@ trait Scanners extends ScannersCommon {
charLitOr(getIdentRest)
else if (isOperatorPart(ch) && (ch != '\\'))
charLitOr(getOperatorRest)
- else {
+ else if (!isAtEnd && (ch != SU && ch != CR && ch != LF || isUnicodeEscape)) {
getLitChar()
if (ch == '\'') {
nextChar()
@@ -525,6 +559,8 @@ trait Scanners extends ScannersCommon {
syntaxError("unclosed character literal")
}
}
+ else
+ syntaxError("unclosed character literal")
}
fetchSingleQuote()
case '.' =>
@@ -545,7 +581,8 @@ trait Scanners extends ScannersCommon {
case ')' =>
nextChar(); token = RPAREN
case '}' =>
- nextChar(); token = RBRACE
+ if (inMultiLineInterpolatedExpression) nextRawChar() else nextChar()
+ token = RBRACE
case '[' =>
nextChar(); token = LBRACKET
case ']' =>
@@ -690,7 +727,7 @@ trait Scanners extends ScannersCommon {
private def unclosedStringLit(): Unit = syntaxError("unclosed string literal")
- private def getRawStringLit(): Unit = {
+ @tailrec private def getRawStringLit(): Unit = {
if (ch == '\"') {
nextRawChar()
if (isTripleQuote()) {
@@ -707,7 +744,7 @@ trait Scanners extends ScannersCommon {
}
}
- @scala.annotation.tailrec private def getStringPart(multiLine: Boolean): Unit = {
+ @tailrec private def getStringPart(multiLine: Boolean): Unit = {
def finishStringPart() = {
setStrVal()
token = STRINGPART
@@ -822,7 +859,7 @@ trait Scanners extends ScannersCommon {
if (settings.future)
syntaxError(start, msg("unsupported"))
else
- deprecationWarning(start, msg("deprecated"))
+ deprecationWarning(start, msg("deprecated"), "2.11.0")
putChar(oct.toChar)
} else {
ch match {
@@ -946,23 +983,45 @@ trait Scanners extends ScannersCommon {
def intVal: Long = intVal(negated = false)
- /** Convert current strVal, base to double value
+ private val zeroFloat = raw"[0.]+(?:[eE][+-]?[0-9]+)?[fFdD]?".r
+
+ /** Convert current strVal, base to float value.
*/
- def floatVal(negated: Boolean): Double = {
- val limit: Double = if (token == DOUBLELIT) Double.MaxValue else Float.MaxValue
+ def floatVal(negated: Boolean): Float = {
try {
- val value: Double = java.lang.Double.valueOf(strVal).doubleValue()
- if (value > limit)
+ val value: Float = java.lang.Float.parseFloat(strVal)
+ if (value > Float.MaxValue)
syntaxError("floating point number too large")
+ if (value == 0.0f && !zeroFloat.pattern.matcher(strVal).matches)
+ syntaxError("floating point number too small")
if (negated) -value else value
} catch {
case _: NumberFormatException =>
syntaxError("malformed floating point number")
+ 0.0f
+ }
+ }
+
+ def floatVal: Float = floatVal(negated = false)
+
+ /** Convert current strVal, base to double value.
+ */
+ def doubleVal(negated: Boolean): Double = {
+ try {
+ val value: Double = java.lang.Double.parseDouble(strVal)
+ if (value > Double.MaxValue)
+ syntaxError("double precision floating point number too large")
+ if (value == 0.0d && !zeroFloat.pattern.matcher(strVal).matches)
+ syntaxError("double precision floating point number too small")
+ if (negated) -value else value
+ } catch {
+ case _: NumberFormatException =>
+ syntaxError("malformed double precision floating point number")
0.0
}
}
- def floatVal: Double = floatVal(negated = false)
+ def doubleVal: Double = doubleVal(negated = false)
def checkNoLetter(): Unit = {
if (isIdentifierPart(ch) && ch >= ' ')
@@ -1032,7 +1091,7 @@ trait Scanners extends ScannersCommon {
/** generate an error at the current token offset */
def syntaxError(msg: String): Unit = syntaxError(offset, msg)
- def deprecationWarning(msg: String): Unit = deprecationWarning(offset, msg)
+ def deprecationWarning(msg: String, since: String): Unit = deprecationWarning(offset, msg, since)
/** signal an error where the input ended in the middle of a token */
def incompleteInputError(msg: String): Unit = {
@@ -1195,15 +1254,15 @@ trait Scanners extends ScannersCommon {
class MalformedInput(val offset: Offset, val msg: String) extends Exception
/** A scanner for a given source file not necessarily attached to a compilation unit.
- * Useful for looking inside source files that aren not currently compiled to see what's there
+ * Useful for looking inside source files that are not currently compiled to see what's there
*/
class SourceFileScanner(val source: SourceFile) extends Scanner {
val buf = source.content
override val decodeUni: Boolean = !settings.nouescape
// suppress warnings, throw exception on errors
- def deprecationWarning(off: Offset, msg: String): Unit = ()
- def error (off: Offset, msg: String): Unit = throw new MalformedInput(off, msg)
+ def deprecationWarning(off: Offset, msg: String, since: String): Unit = ()
+ def error(off: Offset, msg: String): Unit = throw new MalformedInput(off, msg)
def incompleteInputError(off: Offset, msg: String): Unit = throw new MalformedInput(off, msg)
}
@@ -1212,9 +1271,9 @@ trait Scanners extends ScannersCommon {
class UnitScanner(val unit: CompilationUnit, patches: List[BracePatch]) extends SourceFileScanner(unit.source) {
def this(unit: CompilationUnit) = this(unit, List())
- override def deprecationWarning(off: Offset, msg: String) = currentRun.reporting.deprecationWarning(unit.position(off), msg)
- override def error (off: Offset, msg: String) = reporter.error(unit.position(off), msg)
- override def incompleteInputError(off: Offset, msg: String) = currentRun.parsing.incompleteInputError(unit.position(off), msg)
+ override def deprecationWarning(off: Offset, msg: String, since: String) = currentRun.reporting.deprecationWarning(unit.position(off), msg, since)
+ override def error(off: Offset, msg: String) = reporter.error(unit.position(off), msg)
+ override def incompleteInputError(off: Offset, msg: String) = currentRun.parsing.incompleteInputError(unit.position(off), msg)
private var bracePatches: List[BracePatch] = patches
diff --git a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala
index 67241ef639..c3c3ee9d47 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala
@@ -6,7 +6,7 @@
package scala.tools.nsc
package ast.parser
-import scala.collection.{ mutable, immutable }
+import scala.collection.mutable
import symtab.Flags.MUTABLE
import scala.reflect.internal.util.ListOfNil
import scala.reflect.internal.util.StringOps.splitWhere
diff --git a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala
index df2073785b..e0667b5a3e 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala
@@ -82,7 +82,7 @@ abstract class SyntaxAnalyzer extends SubComponent with Parsers with MarkupParse
}
private def initialUnitBody(unit: CompilationUnit): Tree = {
- if (unit.isJava) new JavaUnitParser(unit).parse()
+ if (unit.isJava) newJavaUnitParser(unit).parse()
else if (currentRun.parsing.incompleteHandled) newUnitParser(unit).parse()
else newUnitParser(unit).smartParse()
}
diff --git a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala
index 45f731686a..7866fcf2dc 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala
@@ -7,7 +7,6 @@ package scala.tools.nsc
package ast.parser
import symtab.Flags._
-import scala.collection.mutable.ListBuffer
import scala.reflect.internal.util.{Position, SourceFile, FreshNameCreator}
/** Methods for building trees, used in the parser. All the trees
@@ -36,6 +35,9 @@ abstract class TreeBuilder {
def repeatedApplication(tpe: Tree): Tree =
AppliedTypeTree(rootScalaDot(tpnme.REPEATED_PARAM_CLASS_NAME), List(tpe))
+ // represents `expr _`, as specified in Method Values of spec/06-expressions.md
+ def makeMethodValue(expr: Tree): Tree = Typed(expr, Function(Nil, EmptyTree))
+
def makeImportSelector(name: Name, nameOffset: Int): ImportSelector =
ImportSelector(name, nameOffset, name, nameOffset)
diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala
index 6bd123c51f..dc63b335cc 100644
--- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala
+++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala
@@ -7,11 +7,9 @@ package scala.tools.nsc
package backend
import io.AbstractFile
-import scala.tools.nsc.classpath.FlatClassPath
-import scala.tools.nsc.settings.ClassPathRepresentationType
-import scala.tools.nsc.util.{ ClassPath, DeltaClassPath, MergedClassPath }
-import scala.tools.util.FlatClassPathResolver
+import scala.tools.nsc.classpath.AggregateClassPath
import scala.tools.util.PathResolver
+import scala.tools.nsc.util.ClassPath
trait JavaPlatform extends Platform {
val global: Global
@@ -19,34 +17,25 @@ trait JavaPlatform extends Platform {
import global._
import definitions._
- private[nsc] var currentClassPath: Option[MergedClassPath[AbstractFile]] = None
-
- def classPath: ClassPath[AbstractFile] = {
- assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Recursive,
- "To use recursive classpath representation you must enable it with -YclasspathImpl:recursive compiler option.")
+ private[nsc] var currentClassPath: Option[ClassPath] = None
+ private[nsc] def classPath: ClassPath = {
if (currentClassPath.isEmpty) currentClassPath = Some(new PathResolver(settings).result)
currentClassPath.get
}
- private[nsc] lazy val flatClassPath: FlatClassPath = {
- assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Flat,
- "To use flat classpath representation you must enable it with -YclasspathImpl:flat compiler option.")
-
- new FlatClassPathResolver(settings).result
- }
-
/** Update classpath with a substituted subentry */
- def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]) =
- currentClassPath = Some(new DeltaClassPath(currentClassPath.get, subst))
+ def updateClassPath(subst: Map[ClassPath, ClassPath]): Unit = global.classPath match {
+ case AggregateClassPath(entries) =>
+ currentClassPath = Some(AggregateClassPath(entries map (e => subst.getOrElse(e, e))))
- private def classEmitPhase =
- if (settings.isBCodeActive) genBCode
- else genASM
+ case cp: ClassPath =>
+ currentClassPath = Some(subst.getOrElse(cp, cp))
+ }
def platformPhases = List(
- flatten, // get rid of inner classes
- classEmitPhase // generate .class files
+ flatten, // get rid of inner classes
+ genBCode // generate .class files
)
lazy val externalEquals = getDecl(BoxesRunTimeClass, nme.equals_)
diff --git a/src/compiler/scala/tools/nsc/backend/Platform.scala b/src/compiler/scala/tools/nsc/backend/Platform.scala
index c3bc213be1..e464768bb3 100644
--- a/src/compiler/scala/tools/nsc/backend/Platform.scala
+++ b/src/compiler/scala/tools/nsc/backend/Platform.scala
@@ -6,9 +6,8 @@
package scala.tools.nsc
package backend
-import util.ClassPath
import io.AbstractFile
-import scala.tools.nsc.classpath.FlatClassPath
+import scala.tools.nsc.util.ClassPath
/** The platform dependent pieces of Global.
*/
@@ -16,14 +15,11 @@ trait Platform {
val symbolTable: symtab.SymbolTable
import symbolTable._
- /** The old, recursive implementation of compiler classpath. */
- def classPath: ClassPath[AbstractFile]
-
/** The new implementation of compiler classpath. */
- private[nsc] def flatClassPath: FlatClassPath
+ private[nsc] def classPath: ClassPath
/** Update classpath with a substitution that maps entries to entries */
- def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]])
+ def updateClassPath(subst: Map[ClassPath, ClassPath])
/** Any platform-specific phases. */
def platformPhases: List[SubComponent]
diff --git a/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala b/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala
index b8ddb65de9..c18f220d95 100644
--- a/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala
+++ b/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala
@@ -7,7 +7,7 @@ package scala
package tools.nsc
package backend
-import scala.collection.{ mutable, immutable }
+import scala.collection.mutable
/** Scala primitive operations are represented as methods in `Any` and
* `AnyVal` subclasses. Here we demultiplex them by providing a mapping
@@ -31,7 +31,6 @@ abstract class ScalaPrimitives {
import global._
import definitions._
- import global.icodes._
// Arithmetic unary operations
final val POS = 1 // +x
@@ -62,8 +61,8 @@ abstract class ScalaPrimitives {
final val NE = 43 // x != y
final val LT = 44 // x < y
final val LE = 45 // x <= y
- final val GE = 46 // x > y
- final val GT = 47 // x >= y
+ final val GT = 46 // x > y
+ final val GE = 47 // x >= y
// Boolean unary operations
final val ZNOT = 50 // !x
@@ -447,9 +446,10 @@ abstract class ScalaPrimitives {
inform(s"Unknown primitive method $cls.$method")
else alts foreach (s =>
addPrimitive(s,
- s.info.paramTypes match {
- case tp :: _ if code == ADD && tp =:= StringTpe => CONCAT
- case _ => code
+ if (code != ADD) code
+ else exitingTyper(s.info).paramTypes match {
+ case tp :: _ if tp =:= StringTpe => CONCAT
+ case _ => code
}
)
)
@@ -457,18 +457,6 @@ abstract class ScalaPrimitives {
def isCoercion(code: Int): Boolean = (code >= B2B) && (code <= D2D)
- final val typeOfArrayOp: Map[Int, TypeKind] = Map(
- (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++
- (List(BARRAY_LENGTH, BARRAY_GET, BARRAY_SET) map (_ -> BYTE)) ++
- (List(SARRAY_LENGTH, SARRAY_GET, SARRAY_SET) map (_ -> SHORT)) ++
- (List(CARRAY_LENGTH, CARRAY_GET, CARRAY_SET) map (_ -> CHAR)) ++
- (List(IARRAY_LENGTH, IARRAY_GET, IARRAY_SET) map (_ -> INT)) ++
- (List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++
- (List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++
- (List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++
- (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> REFERENCE(AnyRefClass))) : _*
- )
-
/** Check whether the given operation code is an array operation. */
def isArrayOp(code: Int): Boolean =
isArrayNew(code) | isArrayLength(code) | isArrayGet(code) | isArraySet(code)
@@ -535,24 +523,11 @@ abstract class ScalaPrimitives {
case _ => false
}
- /** If code is a coercion primitive, the result type */
- def generatedKind(code: Int): TypeKind = code match {
- case B2B | C2B | S2B | I2B | L2B | F2B | D2B => BYTE
- case B2C | C2C | S2C | I2C | L2C | F2C | D2C => CHAR
- case B2S | C2S | S2S | I2S | L2S | F2S | D2S => SHORT
- case B2I | C2I | S2I | I2I | L2I | F2I | D2I => INT
- case B2L | C2L | S2L | I2L | L2L | F2L | D2L => LONG
- case B2F | C2F | S2F | I2F | L2F | F2F | D2F => FLOAT
- case B2D | C2D | S2D | I2D | L2D | F2D | D2D => DOUBLE
- }
-
def isPrimitive(sym: Symbol): Boolean = primitives contains sym
/** Return the code for the given symbol. */
- def getPrimitive(sym: Symbol): Int = {
- assert(isPrimitive(sym), "Unknown primitive " + sym)
- primitives(sym)
- }
+ def getPrimitive(sym: Symbol): Int =
+ primitives.getOrElse(sym, throw new AssertionError(s"Unknown primitive $sym"))
/**
* Return the primitive code of the given operation. If the
@@ -565,6 +540,7 @@ abstract class ScalaPrimitives {
*/
def getPrimitive(fun: Symbol, tpe: Type): Int = {
import definitions._
+ import genBCode.bTypes._
val code = getPrimitive(fun)
def elementType = enteringTyper {
@@ -577,7 +553,7 @@ abstract class ScalaPrimitives {
code match {
case APPLY =>
- toTypeKind(elementType) match {
+ typeToBType(elementType) match {
case BOOL => ZARRAY_GET
case BYTE => BARRAY_GET
case SHORT => SARRAY_GET
@@ -586,13 +562,13 @@ abstract class ScalaPrimitives {
case LONG => LARRAY_GET
case FLOAT => FARRAY_GET
case DOUBLE => DARRAY_GET
- case REFERENCE(_) | ARRAY(_) => OARRAY_GET
+ case _: ClassBType | _: ArrayBType => OARRAY_GET
case _ =>
abort("Unexpected array element type: " + elementType)
}
case UPDATE =>
- toTypeKind(elementType) match {
+ typeToBType(elementType) match {
case BOOL => ZARRAY_SET
case BYTE => BARRAY_SET
case SHORT => SARRAY_SET
@@ -601,13 +577,13 @@ abstract class ScalaPrimitives {
case LONG => LARRAY_SET
case FLOAT => FARRAY_SET
case DOUBLE => DARRAY_SET
- case REFERENCE(_) | ARRAY(_) => OARRAY_SET
+ case _: ClassBType | _: ArrayBType => OARRAY_SET
case _ =>
abort("Unexpected array element type: " + elementType)
}
case LENGTH =>
- toTypeKind(elementType) match {
+ typeToBType(elementType) match {
case BOOL => ZARRAY_LENGTH
case BYTE => BARRAY_LENGTH
case SHORT => SARRAY_LENGTH
@@ -616,7 +592,7 @@ abstract class ScalaPrimitives {
case LONG => LARRAY_LENGTH
case FLOAT => FARRAY_LENGTH
case DOUBLE => DARRAY_LENGTH
- case REFERENCE(_) | ARRAY(_) => OARRAY_LENGTH
+ case _: ClassBType | _: ArrayBType => OARRAY_LENGTH
case _ =>
abort("Unexpected array element type: " + elementType)
}
diff --git a/src/compiler/scala/tools/nsc/backend/WorklistAlgorithm.scala b/src/compiler/scala/tools/nsc/backend/WorklistAlgorithm.scala
deleted file mode 100644
index 45ca39fee4..0000000000
--- a/src/compiler/scala/tools/nsc/backend/WorklistAlgorithm.scala
+++ /dev/null
@@ -1,51 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-
-import scala.collection.mutable
-
-/**
- * Simple implementation of a worklist algorithm. A processing
- * function is applied repeatedly to the first element in the
- * worklist, as long as the stack is not empty.
- *
- * The client class should mix-in this class and initialize the worklist
- * field and define the `processElement` method. Then call the `run` method
- * providing a function that initializes the worklist.
- *
- * @author Martin Odersky
- * @version 1.0
- * @see [[scala.tools.nsc.backend.icode.Linearizers]]
- */
-trait WorklistAlgorithm {
- type Elem
- type WList = mutable.Stack[Elem]
-
- val worklist: WList
-
- /**
- * Run the iterative algorithm until the worklist remains empty.
- * The initializer is run once before the loop starts and should
- * initialize the worklist.
- */
- def run(initWorklist: => Unit) = {
- initWorklist
-
- while (worklist.nonEmpty)
- processElement(dequeue)
- }
-
- /**
- * Process the current element from the worklist.
- */
- def processElement(e: Elem): Unit
-
- /**
- * Remove and return the first element to be processed from the worklist.
- */
- def dequeue: Elem
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala b/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala
deleted file mode 100644
index ad1975ef23..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala
+++ /dev/null
@@ -1,553 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-import scala.collection.{ mutable, immutable }
-import mutable.ListBuffer
-import backend.icode.analysis.ProgramPoint
-import scala.language.postfixOps
-
-trait BasicBlocks {
- self: ICodes =>
-
- import opcodes._
- import global._
-
- /** Override Array creation for efficiency (to not go through reflection). */
- private implicit val instructionTag: scala.reflect.ClassTag[Instruction] = new scala.reflect.ClassTag[Instruction] {
- def runtimeClass: java.lang.Class[Instruction] = classOf[Instruction]
- final override def newArray(len: Int): Array[Instruction] = new Array[Instruction](len)
- }
-
- object NoBasicBlock extends BasicBlock(-1, null)
-
- /** This class represents a basic block. Each
- * basic block contains a list of instructions that are
- * either executed all, or none. No jumps
- * to/from the "middle" of the basic block are allowed (modulo exceptions).
- */
- class BasicBlock(val label: Int, val method: IMethod) extends ProgramPoint[BasicBlock] {
- outer =>
-
- import BBFlags._
-
- def code = if (method eq null) NoCode else method.code
-
- private final class SuccessorList() {
- private var successors: List[BasicBlock] = Nil
- /** This method is very hot! Handle with care. */
- private def updateConserve() {
- var lb: ListBuffer[BasicBlock] = null
- var matches = 0
- var remaining = successors
- val direct = directSuccessors
- var scratchHandlers: List[ExceptionHandler] = method.exh
- var scratchBlocks: List[BasicBlock] = direct
-
- def addBlock(bb: BasicBlock) {
- if (matches < 0)
- lb += bb
- else if (remaining.isEmpty || bb != remaining.head) {
- lb = ListBuffer[BasicBlock]() ++= (successors take matches) += bb
- matches = -1
- }
- else {
- matches += 1
- remaining = remaining.tail
- }
- }
-
- while (scratchBlocks ne Nil) {
- addBlock(scratchBlocks.head)
- scratchBlocks = scratchBlocks.tail
- }
- /* Return a list of successors for 'b' that come from exception handlers
- * covering b's (non-exceptional) successors. These exception handlers
- * might not cover 'b' itself. This situation corresponds to an
- * exception being thrown as the first thing of one of b's successors.
- */
- while (scratchHandlers ne Nil) {
- val handler = scratchHandlers.head
- if (handler covers outer)
- addBlock(handler.startBlock)
-
- scratchBlocks = direct
- while (scratchBlocks ne Nil) {
- if (handler covers scratchBlocks.head)
- addBlock(handler.startBlock)
- scratchBlocks = scratchBlocks.tail
- }
- scratchHandlers = scratchHandlers.tail
- }
- // Blocks did not align: create a new list.
- if (matches < 0)
- successors = lb.toList
- // Blocks aligned, but more blocks remain. Take a prefix of the list.
- else if (remaining.nonEmpty)
- successors = successors take matches
- // Otherwise the list is unchanged, leave it alone.
- }
-
- /** This is called millions of times: it is performance sensitive. */
- def updateSuccs() {
- if (isEmpty) {
- if (successors.nonEmpty)
- successors = Nil
- }
- else updateConserve()
- }
- def toList = successors
- }
-
- /** Flags of this basic block. */
- private[this] var flags: Int = 0
-
- /** Does this block have the given flag? */
- def hasFlag(flag: Int): Boolean = (flags & flag) != 0
-
- /** Set the given flag. */
- private def setFlag(flag: Int): Unit = flags |= flag
- private def resetFlag(flag: Int) {
- flags &= ~flag
- }
-
- /** Is this block closed? */
- def closed: Boolean = hasFlag(CLOSED)
- def closed_=(b: Boolean) = if (b) setFlag(CLOSED) else resetFlag(CLOSED)
-
- /** When set, the `emit` methods will be ignored. */
- def ignore: Boolean = hasFlag(IGNORING)
- def ignore_=(b: Boolean) = if (b) setFlag(IGNORING) else resetFlag(IGNORING)
-
- /** Is this block the head of a while? */
- def loopHeader = hasFlag(LOOP_HEADER)
- def loopHeader_=(b: Boolean) =
- if (b) setFlag(LOOP_HEADER) else resetFlag(LOOP_HEADER)
-
- /** Is this block the start block of an exception handler? */
- def exceptionHandlerStart = hasFlag(EX_HEADER)
- def exceptionHandlerStart_=(b: Boolean) =
- if (b) setFlag(EX_HEADER) else resetFlag(EX_HEADER)
-
- /** Has this basic block been modified since the last call to 'successors'? */
- def touched = hasFlag(DIRTYSUCCS)
- def touched_=(b: Boolean) = if (b) {
- setFlag(DIRTYSUCCS | DIRTYPREDS)
- } else {
- resetFlag(DIRTYSUCCS | DIRTYPREDS)
- }
-
- // basic blocks start in a dirty state
- setFlag(DIRTYSUCCS | DIRTYPREDS)
-
- /** Cached predecessors. */
- var preds: List[BasicBlock] = Nil
-
- /** Local variables that are in scope at entry of this basic block. Used
- * for debugging information.
- */
- val varsInScope: mutable.Set[Local] = new mutable.LinkedHashSet()
-
- /** ICode instructions, used as temporary storage while emitting code.
- * Once closed is called, only the `instrs` array should be used.
- */
- private var instructionList: List[Instruction] = Nil
- private var instrs: Array[Instruction] = _
-
- def take(n: Int): Seq[Instruction] =
- if (closed) instrs take n else instructionList takeRight n reverse
-
- def toList: List[Instruction] =
- if (closed) instrs.toList else instructionList.reverse
-
- /** Return an iterator over the instructions in this basic block. */
- def iterator: Iterator[Instruction] =
- if (closed) instrs.iterator else instructionList.reverseIterator
-
- /** return the underlying array of instructions */
- def getArray: Array[Instruction] = {
- assert(closed, this)
- instrs
- }
-
- def fromList(is: List[Instruction]) {
- code.touched = true
- instrs = is.toArray
- closed = true
- }
-
- /** Return the index of inst. Uses reference equality.
- * Returns -1 if not found.
- */
- def indexOf(inst: Instruction): Int = {
- assert(closed, this)
- instrs indexWhere (_ eq inst)
- }
-
- /** Apply a function to all the instructions of the block. */
- final def foreach[U](f: Instruction => U) = {
- if (!closed) dumpMethodAndAbort(method, this)
- else instrs foreach f
-
- // !!! If I replace "instrs foreach f" with the following:
- // var i = 0
- // val len = instrs.length
- // while (i < len) {
- // f(instrs(i))
- // i += 1
- // }
- //
- // Then when compiling under -optimise, quick.plugins fails as follows:
- //
- // quick.plugins:
- // [mkdir] Created dir: /scratch/trunk6/build/quick/classes/continuations-plugin
- // [scalacfork] Compiling 5 files to /scratch/trunk6/build/quick/classes/continuations-plugin
- // [scalacfork] error: java.lang.VerifyError: (class: scala/tools/nsc/typechecker/Implicits$ImplicitSearch, method: typedImplicit0 signature: (Lscala/tools/nsc/typechecker/Implicits$ImplicitInfo;Z)Lscala/tools/nsc/typechecker/Implicits$SearchResult;) Incompatible object argument for function call
- // [scalacfork] at scala.tools.nsc.typechecker.Implicits$class.inferImplicit(Implicits.scala:67)
- // [scalacfork] at scala.tools.nsc.Global$$anon$1.inferImplicit(Global.scala:419)
- // [scalacfork] at scala.tools.nsc.typechecker.Typers$Typer.wrapImplicit$1(Typers.scala:170)
- // [scalacfork] at scala.tools.nsc.typechecker.Typers$Typer.inferView(Typers.scala:174)
- // [scalacfork] at scala.tools.nsc.typechecker.Typers$Typer.adapt(Typers.scala:963)
- // [scalacfork] at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4378)
- //
- // This is bad and should be understood/eliminated.
- }
-
- /** The number of instructions in this basic block so far. */
- def length = if (closed) instrs.length else instructionList.length
- def size = length
-
- /** Return the n-th instruction. */
- def apply(n: Int): Instruction =
- if (closed) instrs(n) else instructionList.reverse(n)
-
- ///////////////////// Substitutions ///////////////////////
-
- /**
- * Replace the instruction at the given position. Used by labels when they are anchored.
- * The replacing instruction is given the nsc.util.Position of the instruction it replaces.
- */
- def replaceInstruction(pos: Int, instr: Instruction): Boolean = {
- assert(closed, "Instructions can be replaced only after the basic block is closed")
- instr.setPos(instrs(pos).pos)
- instrs(pos) = instr
- code.touched = true
- true
- }
-
- /**
- * Replace the given instruction with the new one.
- * Returns `true` if it actually changed something.
- * The replacing instruction is given the nsc.util.Position of the instruction it replaces.
- */
- def replaceInstruction(oldInstr: Instruction, newInstr: Instruction): Boolean = {
- assert(closed, "Instructions can be replaced only after the basic block is closed")
-
- indexOf(oldInstr) match {
- case -1 => false
- case idx =>
- newInstr setPos oldInstr.pos
- instrs(idx) = newInstr
- code.touched = true
- true
- }
- }
-
- /** Replaces `oldInstr` with `is`. It does not update
- * the position field in the newly inserted instructions, so it behaves
- * differently than the one-instruction versions of this function.
- */
- def replaceInstruction(oldInstr: Instruction, is: List[Instruction]): Boolean = {
- assert(closed, "Instructions can be replaced only after the basic block is closed")
-
- indexOf(oldInstr) match {
- case -1 => false
- case idx =>
- instrs = instrs.patch(idx, is, 1)
- code.touched = true
- true
- }
- }
-
- /** Removes instructions found at the given positions.
- */
- def removeInstructionsAt(positions: Int*) {
- assert(closed, this)
- instrs = instrs.indices.toArray filterNot positions.toSet map instrs
- code.touched = true
- }
-
- /** Remove the last instruction of this basic block. It is
- * fast for an open block, but slower when the block is closed.
- */
- def removeLastInstruction() {
- if (closed)
- removeInstructionsAt(length)
- else {
- instructionList = instructionList.tail
- code.touched = true
- }
- }
-
- /** Replaces all instructions found in the map.
- */
- def subst(map: Map[Instruction, Instruction]): Unit =
- if (!closed)
- instructionList = instructionList map (x => map.getOrElse(x, x))
- else
- instrs.iterator.zipWithIndex foreach {
- case (oldInstr, i) =>
- if (map contains oldInstr) {
- // SI-6288 clone important here because `replaceInstruction` assigns
- // a position to `newInstr`. Without this, a single instruction can
- // be added twice, and the position last position assigned clobbers
- // all previous positions in other usages.
- val newInstr = map(oldInstr).clone()
- code.touched |= replaceInstruction(i, newInstr)
- }
- }
-
- ////////////////////// Emit //////////////////////
-
-
- /** Add a new instruction at the end of the block,
- * using the same source position as the last emitted instruction
- */
- def emit(instr: Instruction) {
- val pos = if (instructionList.isEmpty) NoPosition else instructionList.head.pos
- emit(instr, pos)
- }
-
- /** Emitting does not set touched to true. During code generation this is a hotspot and
- * setting the flag for each emit is a waste. Caching should happen only after a block
- * is closed, which sets the DIRTYSUCCS flag.
- */
- def emit(instr: Instruction, pos: Position) {
- assert(!closed || ignore, this)
-
- if (ignore) {
- if (settings.debug) {
- /* Trying to pin down what it's likely to see after a block has been
- * put into ignore mode so we hear about it if there's a problem.
- */
- instr match {
- case JUMP(_) | RETURN(_) | THROW(_) | SCOPE_EXIT(_) => // ok
- case STORE_LOCAL(local) if nme.isExceptionResultName(local.sym.name) => // ok
- case x => log("Ignoring instruction, possibly at our peril, at " + pos + ": " + x)
- }
- }
- }
- else {
- instr.setPos(pos)
- instructionList ::= instr
- }
- }
-
- def emit(is: Seq[Instruction]) {
- is foreach (i => emit(i, i.pos))
- }
-
- /** The semantics of this are a little odd but it's designed to work
- * seamlessly with the existing code. It emits each supplied instruction,
- * then closes the block. The odd part is that if the instruction has
- * pos == NoPosition, it calls the 1-arg emit, but otherwise it calls
- * the 2-arg emit. This way I could retain existing behavior exactly by
- * calling setPos on any instruction using the two arg version which
- * I wanted to include in a call to emitOnly.
- */
- def emitOnly(is: Instruction*) {
- is foreach (i => if (i.pos == NoPosition) emit(i) else emit(i, i.pos))
- this.close()
- }
-
- /** do nothing if block is already closed */
- def closeWith(instr: Instruction) {
- if (!closed) {
- emit(instr)
- close()
- }
- }
-
- def closeWith(instr: Instruction, pos: Position) {
- if (!closed) {
- emit(instr, pos)
- close()
- }
- }
-
- /** Close the block */
- def close() {
- assert(!closed || ignore, this)
- if (ignore && closed) { // redundant `ignore &&` for clarity -- we should never be in state `!ignore && closed`
- // not doing anything to this block is important...
- // because the else branch reverses innocent blocks, which is wrong when they're in ignore mode (and closed)
- // reversing the instructions when (closed && ignore) wreaks havoc for nested label jumps (see comments in genLoad)
- } else {
- closed = true
- setFlag(DIRTYSUCCS)
- instructionList = instructionList.reverse
- instrs = instructionList.toArray
- if (instructionList.isEmpty) {
- debuglog(s"Removing empty block $this")
- code removeBlock this
- }
- }
- }
-
- /**
- * if cond is true, closes this block, entersIgnoreMode, and removes the block from
- * its list of blocks. Used to allow a block to be started and then cancelled when it
- * is discovered to be unreachable.
- */
- def killIf(cond: Boolean) {
- if (!settings.YdisableUnreachablePrevention && cond) {
- debuglog(s"Killing block $this")
- assert(instructionList.isEmpty, s"Killing a non empty block $this")
- // only checked under debug because fetching predecessor list is moderately expensive
- if (settings.debug)
- assert(predecessors.isEmpty, s"Killing block $this which is referred to from ${predecessors.mkString}")
-
- close()
- enterIgnoreMode()
- }
- }
-
- /**
- * Same as killIf but with the logic of the condition reversed
- */
- def killUnless(cond: Boolean) {
- this killIf !cond
- }
-
- def open() {
- assert(closed, this)
- closed = false
- ignore = false
- touched = true
- instructionList = instructionList.reverse // prepare for appending to the head
- }
-
- def clear() {
- instructionList = Nil
- instrs = null
- preds = Nil
- }
-
- final def isEmpty = instructionList.isEmpty
- final def nonEmpty = !isEmpty
-
- /** Enter ignore mode: new 'emit'ted instructions will not be
- * added to this basic block. It makes the generation of THROW
- * and RETURNs easier.
- */
- def enterIgnoreMode() = {
- ignore = true
- }
-
- /** Return the last instruction of this basic block. */
- def lastInstruction =
- if (closed) instrs(instrs.length - 1)
- else instructionList.head
-
- def exceptionSuccessors: List[BasicBlock] =
- exceptionSuccessorsForBlock(this)
-
- def exceptionSuccessorsForBlock(block: BasicBlock): List[BasicBlock] =
- method.exh collect { case x if x covers block => x.startBlock }
-
- /** Cached value of successors. Must be recomputed whenever a block in the current method is changed. */
- private val succs = new SuccessorList
-
- def successors: List[BasicBlock] = {
- if (touched) {
- succs.updateSuccs()
- resetFlag(DIRTYSUCCS)
- }
- succs.toList
- }
-
- def directSuccessors: List[BasicBlock] =
- if (isEmpty) Nil else lastInstruction match {
- case JUMP(whereto) => whereto :: Nil
- case CJUMP(succ, fail, _, _) => fail :: succ :: Nil
- case CZJUMP(succ, fail, _, _) => fail :: succ :: Nil
- case SWITCH(_, labels) => labels
- case RETURN(_) => Nil
- case THROW(_) => Nil
- case _ =>
- if (closed)
- devWarning(s"$lastInstruction/${lastInstruction.getClass.getName} is not a control flow instruction")
-
- Nil
- }
-
- /** Returns the predecessors of this block. */
- def predecessors: List[BasicBlock] = {
- if (hasFlag(DIRTYPREDS)) {
- resetFlag(DIRTYPREDS)
- preds = code.blocks.iterator filter (_.successors contains this) toList
- }
- preds
- }
-
- override def equals(other: Any): Boolean = other match {
- case that: BasicBlock => (that.label == label) && (that.code == code)
- case _ => false
- }
-
- override def hashCode = label * 41 + code.hashCode
-
- private def succString = if (successors.isEmpty) "[S: N/A]" else successors.distinct.mkString("[S: ", ", ", "]")
- private def predString = if (predecessors.isEmpty) "[P: N/A]" else predecessors.distinct.mkString("[P: ", ", ", "]")
-
- override def toString(): String = "" + label
-
- def blockContents = {
- def posStr(p: Position) = if (p.isDefined) p.line.toString else "<??>"
- val xs = this.toList map (instr => posStr(instr.pos) + "\t" + instr)
- xs.mkString(fullString + " {\n ", "\n ", "\n}")
- }
- def predContents = predecessors.map(_.blockContents).mkString(predecessors.size + " preds:\n", "\n", "\n")
- def succContents = successors.map(_.blockContents).mkString(successors.size + " succs:\n", "\n", "\n")
-
- def fullString: String = List("Block", label, succString, predString, flagsString) mkString " "
- def flagsString: String = BBFlags.flagsToString(flags)
- }
-}
-
-object BBFlags {
- /** This block is a loop header (was translated from a while). */
- final val LOOP_HEADER = (1 << 0)
-
- /** Ignoring mode: emit instructions are dropped. */
- final val IGNORING = (1 << 1)
-
- /** This block is the header of an exception handler. */
- final val EX_HEADER = (1 << 2)
-
- /** This block is closed. No new instructions can be added. */
- final val CLOSED = (1 << 3)
-
- /** Code has been changed, recompute successors. */
- final val DIRTYSUCCS = (1 << 4)
-
- /** Code has been changed, recompute predecessors. */
- final val DIRTYPREDS = (1 << 5)
-
- val flagMap = Map[Int, String](
- LOOP_HEADER -> "loopheader",
- IGNORING -> "ignore",
- EX_HEADER -> "exheader",
- CLOSED -> "closed",
- DIRTYSUCCS -> "dirtysuccs",
- DIRTYPREDS -> "dirtypreds"
- )
- def flagsToString(flags: Int) = {
- flagMap collect { case (bit, name) if (bit & flags) != 0 => "<" + name + ">" } mkString " "
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/CheckerException.scala b/src/compiler/scala/tools/nsc/backend/icode/CheckerException.scala
deleted file mode 100644
index 8bcdb6dbd2..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/CheckerException.scala
+++ /dev/null
@@ -1,10 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-class CheckerException(s: String) extends Exception(s)
diff --git a/src/compiler/scala/tools/nsc/backend/icode/ExceptionHandlers.scala b/src/compiler/scala/tools/nsc/backend/icode/ExceptionHandlers.scala
deleted file mode 100644
index 7243264773..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/ExceptionHandlers.scala
+++ /dev/null
@@ -1,71 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-import scala.collection.immutable
-
-/**
- * Exception handlers are pieces of code that `handle` exceptions on
- * the covered basic blocks. Since Scala's exception handling uses
- * pattern matching instead of just class names to identify handlers,
- * all our handlers will catch `Throwable` and rely on proper ordering
- * in the generated code to preserve nesting.
- */
-trait ExceptionHandlers {
- self: ICodes =>
-
- import global._
- import definitions.{ ThrowableClass }
-
- class ExceptionHandler(val method: IMethod, val label: TermName, val cls: Symbol, val pos: Position) {
- def loadExceptionClass = if (cls == NoSymbol) ThrowableClass else cls
- private var _startBlock: BasicBlock = _
- var finalizer: Finalizer = _
-
- def setStartBlock(b: BasicBlock) = {
- _startBlock = b
- b.exceptionHandlerStart = true
- }
- def startBlock = _startBlock
-
- /** The list of blocks that are covered by this exception handler */
- var covered: immutable.Set[BasicBlock] = immutable.HashSet.empty[BasicBlock]
-
- def addCoveredBlock(b: BasicBlock): this.type = {
- covered = covered + b
- this
- }
-
- /** Is `b` covered by this exception handler? */
- def covers(b: BasicBlock): Boolean = covered(b)
-
- /** The body of this exception handler. May contain 'dead' blocks (which will not
- * make it into generated code because linearizers may not include them) */
- var blocks: List[BasicBlock] = Nil
-
- def addBlock(b: BasicBlock): Unit = blocks = b :: blocks
-
- override def toString() = "exh_" + label + "(" + cls.simpleName + ")"
-
- /** A standard copy constructor */
- def this(other: ExceptionHandler) = {
- this(other.method, other.label, other.cls, other.pos)
-
- covered = other.covered
- setStartBlock(other.startBlock)
- finalizer = other.finalizer
- }
-
- def dup: ExceptionHandler = new ExceptionHandler(this)
- }
-
- class Finalizer(method: IMethod, label: TermName, pos: Position) extends ExceptionHandler(method, label, NoSymbol, pos) {
- override def toString() = "finalizer_" + label
- override def dup: Finalizer = new Finalizer(method, label, pos)
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
deleted file mode 100644
index b6f9bcc9ab..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
+++ /dev/null
@@ -1,2239 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala
-package tools.nsc
-package backend
-package icode
-
-import scala.collection.{ mutable, immutable }
-import scala.collection.mutable.{ ListBuffer, Buffer }
-import scala.tools.nsc.symtab._
-import scala.annotation.switch
-
-/**
- * @author Iulian Dragos
- * @version 1.0
- */
-abstract class GenICode extends SubComponent {
- import global._
- import icodes._
- import icodes.opcodes._
- import definitions._
- import scalaPrimitives.{
- isArrayOp, isComparisonOp, isLogicalOp,
- isUniversalEqualityOp, isReferenceEqualityOp
- }
- import platform.isMaybeBoxed
-
- private val bCodeICodeCommon: jvm.BCodeICodeCommon[global.type] = new jvm.BCodeICodeCommon(global)
- import bCodeICodeCommon._
-
- val phaseName = "icode"
-
- override def newPhase(prev: Phase) = new ICodePhase(prev)
-
- @inline private def debugassert(cond: => Boolean, msg: => Any) {
- if (settings.debug)
- assert(cond, msg)
- }
-
- class ICodePhase(prev: Phase) extends StdPhase(prev) {
-
- override def description = "Generate ICode from the AST"
-
- var unit: CompilationUnit = NoCompilationUnit
-
- override def run() {
- if (!settings.isBCodeActive) {
- scalaPrimitives.init()
- classes.clear()
- }
- super.run()
- }
-
- override def apply(unit: CompilationUnit): Unit = {
- if (settings.isBCodeActive) { return }
- this.unit = unit
- unit.icode.clear()
- informProgress("Generating icode for " + unit)
- gen(unit.body)
- this.unit = NoCompilationUnit
- }
-
- def gen(tree: Tree): Context = gen(tree, new Context())
-
- def gen(trees: List[Tree], ctx: Context): Context = {
- var ctx1 = ctx
- for (t <- trees) ctx1 = gen(t, ctx1)
- ctx1
- }
-
- /** If the selector type has a member with the right name,
- * it is the host class; otherwise the symbol's owner.
- */
- def findHostClass(selector: Type, sym: Symbol) = selector member sym.name match {
- case NoSymbol => debuglog(s"Rejecting $selector as host class for $sym") ; sym.owner
- case _ => selector.typeSymbol
- }
-
- /////////////////// Code generation ///////////////////////
-
- def gen(tree: Tree, ctx: Context): Context = tree match {
- case EmptyTree => ctx
-
- case PackageDef(pid, stats) =>
- gen(stats, ctx setPackage pid.name)
-
- case ClassDef(mods, name, _, impl) =>
- debuglog("Generating class: " + tree.symbol.fullName)
- val outerClass = ctx.clazz
- ctx setClass (new IClass(tree.symbol) setCompilationUnit unit)
- addClassFields(ctx, tree.symbol)
- classes += (tree.symbol -> ctx.clazz)
- unit.icode += ctx.clazz
- gen(impl, ctx)
- ctx.clazz.methods = ctx.clazz.methods.reverse // preserve textual order
- ctx.clazz.fields = ctx.clazz.fields.reverse // preserve textual order
- ctx setClass outerClass
-
- // !! modules should be eliminated by refcheck... or not?
- case ModuleDef(mods, name, impl) =>
- abort("Modules should not reach backend! " + tree)
-
- case ValDef(mods, name, tpt, rhs) =>
- ctx // we use the symbol to add fields
-
- case DefDef(mods, name, tparams, vparamss, tpt, rhs) =>
- debuglog("Entering method " + name)
- val m = new IMethod(tree.symbol)
- m.sourceFile = unit.source
- m.returnType = if (tree.symbol.isConstructor) UNIT
- else toTypeKind(tree.symbol.info.resultType)
- ctx.clazz.addMethod(m)
-
- var ctx1 = ctx.enterMethod(m, tree.asInstanceOf[DefDef])
- addMethodParams(ctx1, vparamss)
- m.native = m.symbol.hasAnnotation(definitions.NativeAttr)
-
- if (!m.isAbstractMethod && !m.native) {
- ctx1 = genLoad(rhs, ctx1, m.returnType)
-
- // reverse the order of the local variables, to match the source-order
- m.locals = m.locals.reverse
-
- rhs match {
- case Block(_, Return(_)) => ()
- case Return(_) => ()
- case EmptyTree =>
- globalError("Concrete method has no definition: " + tree + (
- if (settings.debug) "(found: " + m.symbol.owner.info.decls.toList.mkString(", ") + ")"
- else "")
- )
- case _ => if (ctx1.bb.isEmpty)
- ctx1.bb.closeWith(RETURN(m.returnType), rhs.pos)
- else
- ctx1.bb.closeWith(RETURN(m.returnType))
- }
- if (!ctx1.bb.closed) ctx1.bb.close()
- prune(ctx1.method)
- } else
- ctx1.method.setCode(NoCode)
- ctx1
-
- case Template(_, _, body) =>
- gen(body, ctx)
-
- case _ =>
- abort("Illegal tree in gen: " + tree)
- }
-
- private def genStat(trees: List[Tree], ctx: Context): Context =
- trees.foldLeft(ctx)((currentCtx, t) => genStat(t, currentCtx))
-
- /**
- * Generate code for the given tree. The trees should contain statements
- * and not produce any value. Use genLoad for expressions which leave
- * a value on top of the stack.
- *
- * @return a new context. This is necessary for control flow instructions
- * which may change the current basic block.
- */
- private def genStat(tree: Tree, ctx: Context): Context = tree match {
- case Assign(lhs @ Select(_, _), rhs) =>
- val isStatic = lhs.symbol.isStaticMember
- var ctx1 = if (isStatic) ctx else genLoadQualifier(lhs, ctx)
-
- ctx1 = genLoad(rhs, ctx1, toTypeKind(lhs.symbol.info))
- ctx1.bb.emit(STORE_FIELD(lhs.symbol, isStatic), tree.pos)
- ctx1
-
- case Assign(lhs, rhs) =>
- val ctx1 = genLoad(rhs, ctx, toTypeKind(lhs.symbol.info))
- val Some(l) = ctx.method.lookupLocal(lhs.symbol)
- ctx1.bb.emit(STORE_LOCAL(l), tree.pos)
- ctx1
-
- case _ =>
- genLoad(tree, ctx, UNIT)
- }
-
- private def genThrow(expr: Tree, ctx: Context): (Context, TypeKind) = {
- require(expr.tpe <:< ThrowableTpe, expr.tpe)
-
- val thrownKind = toTypeKind(expr.tpe)
- val ctx1 = genLoad(expr, ctx, thrownKind)
- ctx1.bb.emit(THROW(expr.tpe.typeSymbol), expr.pos)
- ctx1.bb.enterIgnoreMode()
-
- (ctx1, NothingReference)
- }
-
- /**
- * Generate code for primitive arithmetic operations.
- * Returns (Context, Generated Type)
- */
- private def genArithmeticOp(tree: Tree, ctx: Context, code: Int): (Context, TypeKind) = {
- val Apply(fun @ Select(larg, _), args) = tree
- var ctx1 = ctx
- var resKind = toTypeKind(larg.tpe)
-
- debugassert(args.length <= 1,
- "Too many arguments for primitive function: " + fun.symbol)
- debugassert(resKind.isNumericType | resKind == BOOL,
- resKind.toString() + " is not a numeric or boolean type " +
- "[operation: " + fun.symbol + "]")
-
- args match {
- // unary operation
- case Nil =>
- ctx1 = genLoad(larg, ctx1, resKind)
- code match {
- case scalaPrimitives.POS =>
- () // nothing
- case scalaPrimitives.NEG =>
- ctx1.bb.emit(CALL_PRIMITIVE(Negation(resKind)), larg.pos)
- case scalaPrimitives.NOT =>
- ctx1.bb.emit(CALL_PRIMITIVE(Arithmetic(NOT, resKind)), larg.pos)
- case _ =>
- abort("Unknown unary operation: " + fun.symbol.fullName +
- " code: " + code)
- }
-
- // binary operation
- case rarg :: Nil =>
- resKind = getMaxType(larg.tpe :: rarg.tpe :: Nil)
- if (scalaPrimitives.isShiftOp(code) || scalaPrimitives.isBitwiseOp(code))
- assert(resKind.isIntegralType | resKind == BOOL,
- resKind.toString() + " incompatible with arithmetic modulo operation: " + ctx1)
-
- ctx1 = genLoad(larg, ctx1, resKind)
- ctx1 = genLoad(rarg,
- ctx1, // check .NET size of shift arguments!
- if (scalaPrimitives.isShiftOp(code)) INT else resKind)
-
- val primitiveOp = code match {
- case scalaPrimitives.ADD => Arithmetic(ADD, resKind)
- case scalaPrimitives.SUB => Arithmetic(SUB, resKind)
- case scalaPrimitives.MUL => Arithmetic(MUL, resKind)
- case scalaPrimitives.DIV => Arithmetic(DIV, resKind)
- case scalaPrimitives.MOD => Arithmetic(REM, resKind)
- case scalaPrimitives.OR => Logical(OR, resKind)
- case scalaPrimitives.XOR => Logical(XOR, resKind)
- case scalaPrimitives.AND => Logical(AND, resKind)
- case scalaPrimitives.LSL => Shift(LSL, resKind)
- case scalaPrimitives.LSR => Shift(LSR, resKind)
- case scalaPrimitives.ASR => Shift(ASR, resKind)
- case _ => abort("Unknown primitive: " + fun.symbol + "[" + code + "]")
- }
- ctx1.bb.emit(CALL_PRIMITIVE(primitiveOp), tree.pos)
-
- case _ =>
- abort("Too many arguments for primitive function: " + tree)
- }
- (ctx1, resKind)
- }
-
- /** Generate primitive array operations.
- */
- private def genArrayOp(tree: Tree, ctx: Context, code: Int, expectedType: TypeKind): (Context, TypeKind) = {
- import scalaPrimitives._
- val Apply(Select(arrayObj, _), args) = tree
- val k = toTypeKind(arrayObj.tpe)
- val ARRAY(elem) = k
- var ctx1 = genLoad(arrayObj, ctx, k)
- val elementType = typeOfArrayOp.getOrElse(code, abort("Unknown operation on arrays: " + tree + " code: " + code))
-
- var generatedType = expectedType
-
- if (scalaPrimitives.isArrayGet(code)) {
- // load argument on stack
- debugassert(args.length == 1,
- "Too many arguments for array get operation: " + tree)
- ctx1 = genLoad(args.head, ctx1, INT)
- generatedType = elem
- ctx1.bb.emit(LOAD_ARRAY_ITEM(elementType), tree.pos)
- // it's tempting to just drop array loads of type Null instead
- // of adapting them but array accesses can cause
- // ArrayIndexOutOfBounds so we can't. Besides, Array[Null]
- // probably isn't common enough to figure out an optimization
- adaptNullRef(generatedType, expectedType, ctx1, tree.pos)
- }
- else if (scalaPrimitives.isArraySet(code)) {
- debugassert(args.length == 2,
- "Too many arguments for array set operation: " + tree)
- ctx1 = genLoad(args.head, ctx1, INT)
- ctx1 = genLoad(args.tail.head, ctx1, toTypeKind(args.tail.head.tpe))
- // the following line should really be here, but because of bugs in erasure
- // we pretend we generate whatever type is expected from us.
- //generatedType = UNIT
-
- ctx1.bb.emit(STORE_ARRAY_ITEM(elementType), tree.pos)
- }
- else {
- generatedType = INT
- ctx1.bb.emit(CALL_PRIMITIVE(ArrayLength(elementType)), tree.pos)
- }
-
- (ctx1, generatedType)
- }
- private def genSynchronized(tree: Apply, ctx: Context, expectedType: TypeKind): (Context, TypeKind) = {
- val Apply(fun, args) = tree
- val monitor = ctx.makeLocal(tree.pos, ObjectTpe, "monitor")
- var monitorResult: Local = null
- val argTpe = args.head.tpe
- val hasResult = expectedType != UNIT
- if (hasResult)
- monitorResult = ctx.makeLocal(tree.pos, argTpe, "monitorResult")
-
- var ctx1 = genLoadQualifier(fun, ctx)
- ctx1.bb.emit(Seq(
- DUP(ObjectReference),
- STORE_LOCAL(monitor),
- MONITOR_ENTER() setPos tree.pos
- ))
- ctx1.enterSynchronized(monitor)
- debuglog("synchronized block start")
-
- ctx1 = ctx1.Try(
- bodyCtx => {
- val ctx2 = genLoad(args.head, bodyCtx, expectedType /* toTypeKind(tree.tpe.resultType) */)
- if (hasResult)
- ctx2.bb.emit(STORE_LOCAL(monitorResult))
- ctx2.bb.emit(Seq(
- LOAD_LOCAL(monitor),
- MONITOR_EXIT() setPos tree.pos
- ))
- ctx2
- }, List(
- // tree.tpe / fun.tpe is object, which is no longer true after this transformation
- (ThrowableClass, expectedType, exhCtx => {
- exhCtx.bb.emit(Seq(
- LOAD_LOCAL(monitor),
- MONITOR_EXIT() setPos tree.pos,
- THROW(ThrowableClass)
- ))
- exhCtx.bb.enterIgnoreMode()
- exhCtx
- })), EmptyTree, tree)
-
- debuglog("synchronized block end with block %s closed=%s".format(ctx1.bb, ctx1.bb.closed))
- ctx1.exitSynchronized(monitor)
- if (hasResult)
- ctx1.bb.emit(LOAD_LOCAL(monitorResult))
- (ctx1, expectedType)
- }
-
- private def genLoadIf(tree: If, ctx: Context, expectedType: TypeKind): (Context, TypeKind) = {
- val If(cond, thenp, elsep) = tree
-
- var thenCtx = ctx.newBlock()
- var elseCtx = ctx.newBlock()
- val contCtx = ctx.newBlock()
-
- genCond(cond, ctx, thenCtx, elseCtx)
-
- val ifKind = toTypeKind(tree.tpe)
- val thenKind = toTypeKind(thenp.tpe)
- val elseKind = if (elsep == EmptyTree) UNIT else toTypeKind(elsep.tpe)
-
- // we need to drop unneeded results, if one branch gives
- // unit and the other gives something on the stack, because
- // the type of 'if' is scala.Any, and its erasure would be Object.
- // But unboxed units are not Objects...
- def hasUnitBranch = thenKind == UNIT || elseKind == UNIT
- val resKind = if (hasUnitBranch) UNIT else ifKind
-
- if (hasUnitBranch)
- debuglog("Will drop result from an if branch")
-
- thenCtx = genLoad(thenp, thenCtx, resKind)
- elseCtx = genLoad(elsep, elseCtx, resKind)
-
- debugassert(!hasUnitBranch || expectedType == UNIT,
- "I produce UNIT in a context where " + expectedType + " is expected!")
-
- // alternatives may be already closed by a tail-recursive jump
- val contReachable = !(thenCtx.bb.ignore && elseCtx.bb.ignore)
- thenCtx.bb.closeWith(JUMP(contCtx.bb))
- elseCtx.bb.closeWith(
- if (elsep == EmptyTree) JUMP(contCtx.bb)
- else JUMP(contCtx.bb) setPos tree.pos
- )
-
- contCtx.bb killUnless contReachable
- (contCtx, resKind)
- }
- private def genLoadTry(tree: Try, ctx: Context, setGeneratedType: TypeKind => Unit): Context = {
- val Try(block, catches, finalizer) = tree
- val kind = toTypeKind(tree.tpe)
-
- val caseHandlers =
- for (CaseDef(pat, _, body) <- catches.reverse) yield {
- def genWildcardHandler(sym: Symbol): (Symbol, TypeKind, Context => Context) =
- (sym, kind, ctx => {
- ctx.bb.emit(DROP(REFERENCE(sym))) // drop the loaded exception
- genLoad(body, ctx, kind)
- })
-
- pat match {
- case Typed(Ident(nme.WILDCARD), tpt) => genWildcardHandler(tpt.tpe.typeSymbol)
- case Ident(nme.WILDCARD) => genWildcardHandler(ThrowableClass)
- case Bind(_, _) =>
- val exception = ctx.method addLocal new Local(pat.symbol, toTypeKind(pat.symbol.tpe), false) // the exception will be loaded and stored into this local
-
- (pat.symbol.tpe.typeSymbol, kind, {
- ctx: Context =>
- ctx.bb.emit(STORE_LOCAL(exception), pat.pos)
- genLoad(body, ctx, kind)
- })
- }
- }
-
- ctx.Try(
- bodyCtx => {
- setGeneratedType(kind)
- genLoad(block, bodyCtx, kind)
- },
- caseHandlers,
- finalizer,
- tree)
- }
-
- private def genPrimitiveOp(tree: Apply, ctx: Context, expectedType: TypeKind): (Context, TypeKind) = {
- val sym = tree.symbol
- val Apply(fun @ Select(receiver, _), _) = tree
- val code = scalaPrimitives.getPrimitive(sym, receiver.tpe)
-
- if (scalaPrimitives.isArithmeticOp(code))
- genArithmeticOp(tree, ctx, code)
- else if (code == scalaPrimitives.CONCAT)
- (genStringConcat(tree, ctx), StringReference)
- else if (code == scalaPrimitives.HASH)
- (genScalaHash(receiver, ctx), INT)
- else if (isArrayOp(code))
- genArrayOp(tree, ctx, code, expectedType)
- else if (isLogicalOp(code) || isComparisonOp(code)) {
- val trueCtx, falseCtx, afterCtx = ctx.newBlock()
-
- genCond(tree, ctx, trueCtx, falseCtx)
- trueCtx.bb.emitOnly(
- CONSTANT(Constant(true)) setPos tree.pos,
- JUMP(afterCtx.bb)
- )
- falseCtx.bb.emitOnly(
- CONSTANT(Constant(false)) setPos tree.pos,
- JUMP(afterCtx.bb)
- )
- (afterCtx, BOOL)
- }
- else if (code == scalaPrimitives.SYNCHRONIZED)
- genSynchronized(tree, ctx, expectedType)
- else if (scalaPrimitives.isCoercion(code)) {
- val ctx1 = genLoad(receiver, ctx, toTypeKind(receiver.tpe))
- genCoercion(tree, ctx1, code)
- (ctx1, scalaPrimitives.generatedKind(code))
- }
- else abort(
- "Primitive operation not handled yet: " + sym.fullName + "(" +
- fun.symbol.simpleName + ") " + " at: " + (tree.pos)
- )
- }
-
- /**
- * Generate code for trees that produce values on the stack
- *
- * @param tree The tree to be translated
- * @param ctx The current context
- * @param expectedType The type of the value to be generated on top of the
- * stack.
- * @return The new context. The only thing that may change is the current
- * basic block (as the labels map is mutable).
- */
- private def genLoad(tree: Tree, ctx: Context, expectedType: TypeKind): Context = {
- var generatedType = expectedType
- debuglog("at line: " + (if (tree.pos.isDefined) tree.pos.line else tree.pos))
-
- val resCtx: Context = tree match {
- case LabelDef(name, params, rhs) =>
- def genLoadLabelDef = {
- val ctx1 = ctx.newBlock() // note: we cannot kill ctx1 if ctx is in ignore mode because
- // label defs can be the target of jumps from other locations.
- // that means label defs can lead to unreachable code without
- // proper reachability analysis
-
- if (nme.isLoopHeaderLabel(name))
- ctx1.bb.loopHeader = true
-
- ctx1.labels.get(tree.symbol) match {
- case Some(label) =>
- debuglog("Found existing label for " + tree.symbol.fullLocationString)
- label.anchor(ctx1.bb)
- label.patch(ctx.method.code)
-
- case None =>
- val pair = (tree.symbol -> (new Label(tree.symbol) anchor ctx1.bb setParams (params map (_.symbol))))
- debuglog("Adding label " + tree.symbol.fullLocationString + " in genLoad.")
- ctx1.labels += pair
- ctx.method.addLocals(params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false)))
- }
-
- ctx.bb.closeWith(JUMP(ctx1.bb), tree.pos)
- genLoad(rhs, ctx1, expectedType /*toTypeKind(tree.symbol.info.resultType)*/)
- }
- genLoadLabelDef
-
- case ValDef(_, name, _, rhs) =>
- def genLoadValDef =
- if (name == nme.THIS) {
- debuglog("skipping trivial assign to _$this: " + tree)
- ctx
- } else {
- val sym = tree.symbol
- val local = ctx.method.addLocal(new Local(sym, toTypeKind(sym.info), false))
-
- if (rhs == EmptyTree) {
- debuglog("Uninitialized variable " + tree + " at: " + (tree.pos))
- ctx.bb.emit(getZeroOf(local.kind))
- }
-
- var ctx1 = ctx
- if (rhs != EmptyTree)
- ctx1 = genLoad(rhs, ctx, local.kind)
-
- ctx1.bb.emit(STORE_LOCAL(local), tree.pos)
- ctx1.scope.add(local)
- ctx1.bb.emit(SCOPE_ENTER(local))
- generatedType = UNIT
- ctx1
- }
- genLoadValDef
-
- case t @ If(cond, thenp, elsep) =>
- val (newCtx, resKind) = genLoadIf(t, ctx, expectedType)
- generatedType = resKind
- newCtx
-
- case Return(expr) =>
- def genLoadReturn = {
- val returnedKind = toTypeKind(expr.tpe)
- debuglog("Return(" + expr + ") with returnedKind = " + returnedKind)
-
- var ctx1 = genLoad(expr, ctx, returnedKind)
- lazy val tmp = ctx1.makeLocal(tree.pos, expr.tpe, "tmp")
- val saved = savingCleanups(ctx1) {
- var savedFinalizer = false
- ctx1.cleanups foreach {
- case MonitorRelease(m) =>
- debuglog("removing " + m + " from cleanups: " + ctx1.cleanups)
- ctx1.bb.emit(Seq(LOAD_LOCAL(m), MONITOR_EXIT()))
- ctx1.exitSynchronized(m)
-
- case Finalizer(f, finalizerCtx) =>
- debuglog("removing " + f + " from cleanups: " + ctx1.cleanups)
- if (returnedKind != UNIT && mayCleanStack(f)) {
- log("Emitting STORE_LOCAL for " + tmp + " to save finalizer.")
- ctx1.bb.emit(STORE_LOCAL(tmp))
- savedFinalizer = true
- }
-
- // duplicate finalizer (takes care of anchored labels)
- val f1 = duplicateFinalizer(Set.empty ++ ctx1.labels.keySet, ctx1, f)
-
- // we have to run this without the same finalizer in
- // the list, otherwise infinite recursion happens for
- // finalizers that contain 'return'
- val fctx = finalizerCtx.newBlock()
- fctx.bb killIf ctx1.bb.ignore
- ctx1.bb.closeWith(JUMP(fctx.bb))
- ctx1 = genLoad(f1, fctx, UNIT)
- }
- savedFinalizer
- }
-
- if (saved) {
- log("Emitting LOAD_LOCAL for " + tmp + " after saving finalizer.")
- ctx1.bb.emit(LOAD_LOCAL(tmp))
- }
- adapt(returnedKind, ctx1.method.returnType, ctx1, tree.pos)
- ctx1.bb.emit(RETURN(ctx.method.returnType), tree.pos)
- ctx1.bb.enterIgnoreMode()
- generatedType = expectedType
- ctx1
- }
- genLoadReturn
-
- case t @ Try(_, _, _) =>
- genLoadTry(t, ctx, generatedType = _)
-
- case Throw(expr) =>
- val (ctx1, expectedType) = genThrow(expr, ctx)
- generatedType = expectedType
- ctx1
-
- case New(tpt) =>
- abort("Unexpected New(" + tpt.summaryString + "/" + tpt + ") received in icode.\n" +
- " Call was genLoad" + ((tree, ctx, expectedType)))
-
- case Apply(TypeApply(fun, targs), _) =>
- def genLoadApply1 = {
- val sym = fun.symbol
- val cast = sym match {
- case Object_isInstanceOf => false
- case Object_asInstanceOf => true
- case _ => abort("Unexpected type application " + fun + "[sym: " + sym.fullName + "]" + " in: " + tree)
- }
-
- val Select(obj, _) = fun
- val l = toTypeKind(obj.tpe)
- val r = toTypeKind(targs.head.tpe)
- val ctx1 = genLoadQualifier(fun, ctx)
-
- if (l.isValueType && r.isValueType)
- genConversion(l, r, ctx1, cast)
- else if (l.isValueType) {
- ctx1.bb.emit(DROP(l), fun.pos)
- if (cast) {
- ctx1.bb.emit(Seq(
- NEW(REFERENCE(definitions.ClassCastExceptionClass)),
- DUP(ObjectReference),
- THROW(definitions.ClassCastExceptionClass)
- ))
- } else
- ctx1.bb.emit(CONSTANT(Constant(false)))
- } else if (r.isValueType && cast) {
- /* Erasure should have added an unboxing operation to prevent that. */
- abort("should have been unboxed by erasure: " + tree)
- } else if (r.isValueType) {
- ctx.bb.emit(IS_INSTANCE(REFERENCE(definitions.boxedClass(r.toType.typeSymbol))))
- } else {
- genCast(l, r, ctx1, cast)
- }
- generatedType = if (cast) r else BOOL
- ctx1
- }
- genLoadApply1
-
- // 'super' call: Note: since constructors are supposed to
- // return an instance of what they construct, we have to take
- // special care. On JVM they are 'void', and Scala forbids (syntactically)
- // to call super constructors explicitly and/or use their 'returned' value.
- // therefore, we can ignore this fact, and generate code that leaves nothing
- // on the stack (contrary to what the type in the AST says).
- case Apply(fun @ Select(Super(_, mix), _), args) =>
- def genLoadApply2 = {
- debuglog("Call to super: " + tree)
- val invokeStyle = SuperCall(mix)
- // if (fun.symbol.isConstructor) Static(true) else SuperCall(mix);
-
- ctx.bb.emit(THIS(ctx.clazz.symbol), tree.pos)
- val ctx1 = genLoadArguments(args, fun.symbol.info.paramTypes, ctx)
-
- ctx1.bb.emit(CALL_METHOD(fun.symbol, invokeStyle), tree.pos)
- generatedType =
- if (fun.symbol.isConstructor) UNIT
- else toTypeKind(fun.symbol.info.resultType)
- ctx1
- }
- genLoadApply2
-
- // 'new' constructor call: Note: since constructors are
- // thought to return an instance of what they construct,
- // we have to 'simulate' it by DUPlicating the freshly created
- // instance (on JVM, <init> methods return VOID).
- case Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) =>
- def genLoadApply3 = {
- val ctor = fun.symbol
- debugassert(ctor.isClassConstructor,
- "'new' call to non-constructor: " + ctor.name)
-
- generatedType = toTypeKind(tpt.tpe)
- debugassert(generatedType.isReferenceType || generatedType.isArrayType,
- "Non reference type cannot be instantiated: " + generatedType)
-
- generatedType match {
- case arr @ ARRAY(elem) =>
- val ctx1 = genLoadArguments(args, ctor.info.paramTypes, ctx)
- val dims = arr.dimensions
- var elemKind = arr.elementKind
- if (args.length > dims)
- reporter.error(tree.pos, "too many arguments for array constructor: found " + args.length +
- " but array has only " + dims + " dimension(s)")
- if (args.length != dims)
- for (i <- args.length until dims) elemKind = ARRAY(elemKind)
- ctx1.bb.emit(CREATE_ARRAY(elemKind, args.length), tree.pos)
- ctx1
-
- case rt @ REFERENCE(cls) =>
- debugassert(ctor.owner == cls,
- "Symbol " + ctor.owner.fullName + " is different than " + tpt)
-
- val nw = NEW(rt)
- ctx.bb.emit(nw, tree.pos)
- ctx.bb.emit(DUP(generatedType))
- val ctx1 = genLoadArguments(args, ctor.info.paramTypes, ctx)
-
- val init = CALL_METHOD(ctor, Static(onInstance = true))
- nw.init = init
- ctx1.bb.emit(init, tree.pos)
- ctx1
- case _ =>
- abort("Cannot instantiate " + tpt + " of kind: " + generatedType)
- }
- }
- genLoadApply3
-
- case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) =>
- def genLoadApply4 = {
- debuglog("BOX : " + fun.symbol.fullName)
- val ctx1 = genLoad(expr, ctx, toTypeKind(expr.tpe))
- val nativeKind = toTypeKind(expr.tpe)
- if (settings.Xdce) {
- // we store this boxed value to a local, even if not really needed.
- // boxing optimization might use it, and dead code elimination will
- // take care of unnecessary stores
- val loc1 = ctx.makeLocal(tree.pos, expr.tpe, "boxed")
- ctx1.bb.emit(STORE_LOCAL(loc1))
- ctx1.bb.emit(LOAD_LOCAL(loc1))
- }
- ctx1.bb.emit(BOX(nativeKind), expr.pos)
- generatedType = toTypeKind(fun.symbol.tpe.resultType)
- ctx1
- }
- genLoadApply4
-
- case Apply(fun @ _, List(expr)) if (currentRun.runDefinitions.isUnbox(fun.symbol)) =>
- debuglog("UNBOX : " + fun.symbol.fullName)
- val ctx1 = genLoad(expr, ctx, toTypeKind(expr.tpe))
- val boxType = toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe)
- generatedType = boxType
- ctx1.bb.emit(UNBOX(boxType), expr.pos)
- ctx1
-
- case app @ Apply(fun, args) =>
- def genLoadApply6 = {
- val sym = fun.symbol
-
- if (sym.isLabel) { // jump to a label
- val label = ctx.labels.getOrElse(sym, {
- // it is a forward jump, scan for labels
- resolveForwardLabel(ctx.defdef, ctx, sym)
- ctx.labels.get(sym) match {
- case Some(l) =>
- debuglog("Forward jump for " + sym.fullLocationString + ": scan found label " + l)
- l
- case _ =>
- abort("Unknown label target: " + sym + " at: " + (fun.pos) + ": ctx: " + ctx)
- }
- })
- // note: when one of the args to genLoadLabelArguments is a jump to a label,
- // it will call back into genLoad and arrive at this case, which will then set ctx1.bb.ignore to true,
- // this is okay, since we're jumping unconditionally, so the loads and jumps emitted by the outer
- // call to genLoad (by calling genLoadLabelArguments and emitOnly) can safely be ignored,
- // however, as emitOnly will close the block, which reverses its instructions (when it's still open),
- // we better not reverse when the block has already been closed but is in ignore mode
- // (if it's not in ignore mode, double-closing is an error)
- val ctx1 = genLoadLabelArguments(args, label, ctx)
- ctx1.bb.emitOnly(if (label.anchored) JUMP(label.block) else PJUMP(label))
- ctx1.bb.enterIgnoreMode()
- ctx1
- } else if (isPrimitive(sym)) { // primitive method call
- val (newCtx, resKind) = genPrimitiveOp(app, ctx, expectedType)
- generatedType = resKind
- newCtx
- } else { // normal method call
- debuglog("Gen CALL_METHOD with sym: " + sym + " isStaticSymbol: " + sym.isStaticMember)
- val invokeStyle =
- if (sym.isStaticMember)
- Static(onInstance = false)
- else if (sym.isPrivate || sym.isClassConstructor)
- Static(onInstance = true)
- else
- Dynamic
-
- var ctx1 = if (invokeStyle.hasInstance) genLoadQualifier(fun, ctx) else ctx
- ctx1 = genLoadArguments(args, sym.info.paramTypes, ctx1)
- val cm = CALL_METHOD(sym, invokeStyle)
-
- /* In a couple cases, squirrel away a little extra information in the
- * CALL_METHOD for use by GenASM.
- */
- fun match {
- case Select(qual, _) =>
- val qualSym = findHostClass(qual.tpe, sym)
- if (qualSym == ArrayClass) {
- val kind = toTypeKind(qual.tpe)
- cm setTargetTypeKind kind
- log(s"Stored target type kind for {$sym.fullName} as $kind")
- }
- else {
- cm setHostClass qualSym
- if (qual.tpe.typeSymbol != qualSym)
- log(s"Precisified host class for $sym from ${qual.tpe.typeSymbol.fullName} to ${qualSym.fullName}")
- }
- case _ =>
- }
- ctx1.bb.emit(cm, tree.pos)
- ctx1.method.updateRecursive(sym)
- generatedType =
- if (sym.isClassConstructor) UNIT
- else toTypeKind(sym.info.resultType)
- // deal with methods that return Null
- adaptNullRef(generatedType, expectedType, ctx1, tree.pos)
- ctx1
- }
- }
- genLoadApply6
-
- case ApplyDynamic(qual, args) =>
- // TODO - this is where we'd catch dynamic applies for invokedynamic.
- sys.error("No invokedynamic support yet.")
- // val ctx1 = genLoad(qual, ctx, ObjectReference)
- // genLoadArguments(args, tree.symbol.info.paramTypes, ctx1)
- // ctx1.bb.emit(CALL_METHOD(tree.symbol, InvokeDynamic), tree.pos)
- // ctx1
-
- case This(qual) =>
- def genLoadThis = {
- assert(tree.symbol == ctx.clazz.symbol || tree.symbol.isModuleClass,
- "Trying to access the this of another class: " +
- "tree.symbol = " + tree.symbol + ", ctx.clazz.symbol = " + ctx.clazz.symbol + " compilation unit:"+unit)
- if (tree.symbol.isModuleClass && tree.symbol != ctx.clazz.symbol) {
- genLoadModule(ctx, tree)
- generatedType = REFERENCE(tree.symbol)
- } else {
- ctx.bb.emit(THIS(ctx.clazz.symbol), tree.pos)
- generatedType = REFERENCE(
- if (tree.symbol == ArrayClass) ObjectClass else ctx.clazz.symbol
- )
- }
- ctx
- }
- genLoadThis
-
- case Select(Ident(nme.EMPTY_PACKAGE_NAME), module) =>
- debugassert(tree.symbol.isModule,
- "Selection of non-module from empty package: " + tree +
- " sym: " + tree.symbol + " at: " + (tree.pos)
- )
- genLoadModule(ctx, tree)
-
- case Select(qualifier, selector) =>
- def genLoadSelect = {
- val sym = tree.symbol
- generatedType = toTypeKind(sym.info)
- val hostClass = findHostClass(qualifier.tpe, sym)
- debuglog(s"Host class of $sym with qual $qualifier (${qualifier.tpe}) is $hostClass")
- val qualSafeToElide = treeInfo isQualifierSafeToElide qualifier
-
- def genLoadQualUnlessElidable: Context =
- if (qualSafeToElide) ctx else genLoadQualifier(tree, ctx)
-
- if (sym.isModule) {
- genLoadModule(genLoadQualUnlessElidable, tree)
- } else {
- val isStatic = sym.isStaticMember
- val ctx1 = if (isStatic) genLoadQualUnlessElidable
- else genLoadQualifier(tree, ctx)
- ctx1.bb.emit(LOAD_FIELD(sym, isStatic) setHostClass hostClass, tree.pos)
- // it's tempting to drop field accesses of type Null instead of adapting them,
- // but field access can cause static class init so we can't. Besides, fields
- // of type Null probably aren't common enough to figure out an optimization
- adaptNullRef(generatedType, expectedType, ctx1, tree.pos)
- ctx1
- }
- }
- genLoadSelect
-
- case Ident(name) =>
- def genLoadIdent = {
- val sym = tree.symbol
- if (!sym.hasPackageFlag) {
- if (sym.isModule) {
- genLoadModule(ctx, tree)
- generatedType = toTypeKind(sym.info)
- } else {
- 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 {
- val methodCode = unit.body.collect { case dd: DefDef
- if dd.symbol == ctx.method.symbol => showCode(dd);
- }.headOption.getOrElse("<unknown>")
- abort(s"symbol $sym does not exist in ${ctx.method}, which contains locals ${ctx.method.locals.mkString(",")}. \nMethod code: $methodCode")
- }
- finally settings.uniqid.value = saved
- }
- }
- }
- ctx
- }
- genLoadIdent
-
- case Literal(value) =>
- def genLoadLiteral = {
- if (value.tag != UnitTag) (value.tag, expectedType) match {
- case (IntTag, LONG) =>
- ctx.bb.emit(CONSTANT(Constant(value.longValue)), tree.pos)
- generatedType = LONG
- case (FloatTag, DOUBLE) =>
- ctx.bb.emit(CONSTANT(Constant(value.doubleValue)), tree.pos)
- generatedType = DOUBLE
- case (NullTag, _) =>
- ctx.bb.emit(CONSTANT(value), tree.pos)
- generatedType = NullReference
- case _ =>
- ctx.bb.emit(CONSTANT(value), tree.pos)
- generatedType = toTypeKind(tree.tpe)
- }
- ctx
- }
- genLoadLiteral
-
- case Block(stats, expr) =>
- ctx.enterScope()
- var ctx1 = genStat(stats, ctx)
- ctx1 = genLoad(expr, ctx1, expectedType)
- ctx1.exitScope()
- ctx1
-
- case Typed(Super(_, _), _) =>
- genLoad(This(ctx.clazz.symbol), ctx, expectedType)
-
- case Typed(expr, _) =>
- genLoad(expr, ctx, expectedType)
-
- case Assign(_, _) =>
- generatedType = UNIT
- genStat(tree, ctx)
-
- case ArrayValue(tpt @ TypeTree(), _elems) =>
- def genLoadArrayValue = {
- var ctx1 = ctx
- val elmKind = toTypeKind(tpt.tpe)
- generatedType = ARRAY(elmKind)
- val elems = _elems.toIndexedSeq
-
- ctx1.bb.emit(CONSTANT(new Constant(elems.length)), tree.pos)
- ctx1.bb.emit(CREATE_ARRAY(elmKind, 1))
- // inline array literals
- var i = 0
- while (i < elems.length) {
- ctx1.bb.emit(DUP(generatedType), tree.pos)
- ctx1.bb.emit(CONSTANT(new Constant(i)))
- ctx1 = genLoad(elems(i), ctx1, elmKind)
- ctx1.bb.emit(STORE_ARRAY_ITEM(elmKind))
- i = i + 1
- }
- ctx1
- }
- genLoadArrayValue
-
- case Match(selector, cases) =>
- def genLoadMatch = {
- debuglog("Generating SWITCH statement.")
- val ctx1 = genLoad(selector, ctx, INT) // TODO: Java 7 allows strings in switches (so, don't assume INT and don't convert the literals using intValue)
- val afterCtx = ctx1.newBlock()
- afterCtx.bb killIf ctx1.bb.ignore
- var afterCtxReachable = false
- var caseCtx: Context = null
- generatedType = toTypeKind(tree.tpe)
-
- var targets: List[BasicBlock] = Nil
- var tags: List[Int] = Nil
- var default: BasicBlock = afterCtx.bb
-
- for (caze @ CaseDef(pat, guard, body) <- cases) {
- assert(guard == EmptyTree, guard)
- val tmpCtx = ctx1.newBlock()
- tmpCtx.bb killIf ctx1.bb.ignore
- pat match {
- case Literal(value) =>
- tags = value.intValue :: tags
- targets = tmpCtx.bb :: targets
- case Ident(nme.WILDCARD) =>
- default = tmpCtx.bb
- case Alternative(alts) =>
- alts foreach {
- case Literal(value) =>
- tags = value.intValue :: tags
- targets = tmpCtx.bb :: targets
- case _ =>
- abort("Invalid case in alternative in switch-like pattern match: " +
- tree + " at: " + tree.pos)
- }
- case _ =>
- abort("Invalid case statement in switch-like pattern match: " +
- tree + " at: " + (tree.pos))
- }
-
- caseCtx = genLoad(body, tmpCtx, generatedType)
- afterCtxReachable ||= !caseCtx.bb.ignore
- // close the block unless it's already been closed by the body, which closes the block if it ends in a jump (which is emitted to have alternatives share their body)
- caseCtx.bb.closeWith(JUMP(afterCtx.bb) setPos caze.pos)
- }
- afterCtxReachable ||= (default == afterCtx)
- ctx1.bb.emitOnly(
- SWITCH(tags.reverse map (x => List(x)), (default :: targets).reverse) setPos tree.pos
- )
- afterCtx.bb killUnless afterCtxReachable
- afterCtx
- }
- genLoadMatch
-
- case EmptyTree =>
- if (expectedType != UNIT)
- ctx.bb.emit(getZeroOf(expectedType))
- ctx
-
- case _ =>
- abort("Unexpected tree in genLoad: " + tree + "/" + tree.getClass + " at: " + tree.pos)
- }
-
- // emit conversion
- if (generatedType != expectedType) {
- tree match {
- case Literal(Constant(null)) if generatedType == NullReference && expectedType != UNIT =>
- // literal null on the stack (as opposed to a boxed null, see SI-8233),
- // we can bypass `adapt` which would otherwise emit a redundant [DROP, CONSTANT(null)]
- // except one case: when expected type is UNIT (unboxed) where we need to emit just a DROP
- case _ =>
- adapt(generatedType, expectedType, resCtx, tree.pos)
- }
- }
-
- resCtx
- }
-
- /**
- * If we have a method call, field load, or array element load of type Null then
- * we need to convince the JVM that we have a null value because in Scala
- * land Null is a subtype of all ref types, but in JVM land scala.runtime.Null$
- * is not. Note we don't have to adapt loads of locals because the JVM type
- * system for locals does have a null type which it tracks internally. As
- * long as we adapt these other things, the JVM will know that a Scala local of
- * type Null is holding a null.
- */
- private def adaptNullRef(from: TypeKind, to: TypeKind, ctx: Context, pos: Position) {
- debuglog(s"GenICode#adaptNullRef($from, $to, $ctx, $pos)")
-
- // Don't need to adapt null to unit because we'll just drop it anyway. Don't
- // need to adapt to Object or AnyRef because the JVM is happy with
- // upcasting Null to them.
- // We do have to adapt from NullReference to NullReference because we could be storing
- // this value into a local of type Null and we want the JVM to see that it's
- // a null value so we don't have to also adapt local loads.
- if (from == NullReference && to != UNIT && to != ObjectReference && to != AnyRefReference) {
- assert(to.isRefOrArrayType, s"Attempt to adapt a null to a non reference type $to.")
- // adapt by dropping what we've got and pushing a null which
- // will convince the JVM we really do have null
- ctx.bb.emit(DROP(from), pos)
- ctx.bb.emit(CONSTANT(Constant(null)), pos)
- }
- }
-
- private def adapt(from: TypeKind, to: TypeKind, ctx: Context, pos: Position) {
- // An awful lot of bugs explode here - let's leave ourselves more clues.
- // A typical example is an overloaded type assigned after typer.
- debuglog(s"GenICode#adapt($from, $to, $ctx, $pos)")
-
- def coerce(from: TypeKind, to: TypeKind) = ctx.bb.emit(CALL_PRIMITIVE(Conversion(from, to)), pos)
-
- (from, to) match {
- // The JVM doesn't have a Nothing equivalent, so it doesn't know that a method of type Nothing can't actually return. So for instance, with
- // def f: String = ???
- // we need
- // 0: getstatic #25; //Field scala/Predef$.MODULE$:Lscala/Predef$;
- // 3: invokevirtual #29; //Method scala/Predef$.$qmark$qmark$qmark:()Lscala/runtime/Nothing$;
- // 6: athrow
- // So this case tacks on the ahtrow which makes the JVM happy because class Nothing is declared as a subclass of Throwable
- case (NothingReference, _) =>
- ctx.bb.emit(THROW(ThrowableClass))
- ctx.bb.enterIgnoreMode()
- case (NullReference, REFERENCE(_)) =>
- // SI-8223 we can't assume that the stack contains a `null`, it might contain a Null$
- ctx.bb.emit(Seq(DROP(from), CONSTANT(Constant(null))))
- case _ if from isAssignabledTo to =>
- ()
- case (_, UNIT) =>
- ctx.bb.emit(DROP(from), pos)
- // otherwise we'd better be doing a primitive -> primitive coercion or there's a problem
- case _ if !from.isRefOrArrayType && !to.isRefOrArrayType =>
- coerce(from, to)
- case _ =>
- assert(false, s"Can't convert from $from to $to in unit ${unit.source} at $pos")
- }
- }
-
- /** Load the qualifier of `tree` on top of the stack. */
- private def genLoadQualifier(tree: Tree, ctx: Context): Context =
- tree match {
- case Select(qualifier, _) =>
- genLoad(qualifier, ctx, toTypeKind(qualifier.tpe))
- case _ =>
- abort("Unknown qualifier " + tree)
- }
-
- /**
- * Generate code that loads args into label parameters.
- */
- private def genLoadLabelArguments(args: List[Tree], label: Label, ctx: Context): Context = {
- debugassert(
- args.length == label.params.length,
- "Wrong number of arguments in call to label " + label.symbol
- )
- var ctx1 = ctx
-
- def isTrivial(kv: (Tree, Symbol)) = kv match {
- case (This(_), p) if p.name == nme.THIS => true
- case (arg @ Ident(_), p) if arg.symbol == p => true
- case _ => false
- }
-
- val stores = args zip label.params filterNot isTrivial map {
- case (arg, param) =>
- val local = ctx.method.lookupLocal(param).get
- ctx1 = genLoad(arg, ctx1, local.kind)
-
- val store =
- if (param.name == nme.THIS) STORE_THIS(toTypeKind(ctx1.clazz.symbol.tpe))
- else STORE_LOCAL(local)
-
- store setPos arg.pos
- }
-
- // store arguments in reverse order on the stack
- ctx1.bb.emit(stores.reverse)
- ctx1
- }
-
- private def genLoadArguments(args: List[Tree], tpes: List[Type], ctx: Context): Context =
- (args zip tpes).foldLeft(ctx) {
- case (res, (arg, tpe)) =>
- genLoad(arg, res, toTypeKind(tpe))
- }
-
- private def genLoadModule(ctx: Context, tree: Tree): Context = {
- // Working around SI-5604. Rather than failing the compile when we see
- // a package here, check if there's a package object.
- val sym = (
- if (!tree.symbol.isPackageClass) tree.symbol
- else tree.symbol.info.member(nme.PACKAGE) match {
- case NoSymbol => abort("Cannot use package as value: " + tree)
- case s =>
- devWarning(s"Found ${tree.symbol} where a package object is required. Converting to ${s.moduleClass}")
- s.moduleClass
- }
- )
- debuglog("LOAD_MODULE from %s: %s".format(tree.shortClass, sym))
- ctx.bb.emit(LOAD_MODULE(sym), tree.pos)
- ctx
- }
-
- def genConversion(from: TypeKind, to: TypeKind, ctx: Context, cast: Boolean) = {
- if (cast)
- ctx.bb.emit(CALL_PRIMITIVE(Conversion(from, to)))
- else {
- ctx.bb.emit(DROP(from))
- ctx.bb.emit(CONSTANT(Constant(from == to)))
- }
- }
-
- def genCast(from: TypeKind, to: TypeKind, ctx: Context, cast: Boolean) =
- ctx.bb.emit(if (cast) CHECK_CAST(to) else IS_INSTANCE(to))
-
- def getZeroOf(k: TypeKind): Instruction = k match {
- case UNIT => CONSTANT(Constant(()))
- case BOOL => CONSTANT(Constant(false))
- case BYTE => CONSTANT(Constant(0: Byte))
- case SHORT => CONSTANT(Constant(0: Short))
- case CHAR => CONSTANT(Constant(0: Char))
- case INT => CONSTANT(Constant(0: Int))
- case LONG => CONSTANT(Constant(0: Long))
- case FLOAT => CONSTANT(Constant(0.0f))
- case DOUBLE => CONSTANT(Constant(0.0d))
- case REFERENCE(cls) => CONSTANT(Constant(null: Any))
- case ARRAY(elem) => CONSTANT(Constant(null: Any))
- case BOXED(_) => CONSTANT(Constant(null: Any))
- case ConcatClass => abort("no zero of ConcatClass")
- }
-
-
- /** Is the given symbol a primitive operation? */
- def isPrimitive(fun: Symbol): Boolean = scalaPrimitives.isPrimitive(fun)
-
- /** Generate coercion denoted by "code"
- */
- def genCoercion(tree: Tree, ctx: Context, code: Int) = {
- import scalaPrimitives._
- (code: @switch) match {
- case B2B => ()
- case B2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, CHAR)), tree.pos)
- case B2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, SHORT)), tree.pos)
- case B2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, INT)), tree.pos)
- case B2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, LONG)), tree.pos)
- case B2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, FLOAT)), tree.pos)
- case B2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, DOUBLE)), tree.pos)
-
- case S2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, BYTE)), tree.pos)
- case S2S => ()
- case S2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, CHAR)), tree.pos)
- case S2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, INT)), tree.pos)
- case S2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, LONG)), tree.pos)
- case S2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, FLOAT)), tree.pos)
- case S2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, DOUBLE)), tree.pos)
-
- case C2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, BYTE)), tree.pos)
- case C2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, SHORT)), tree.pos)
- case C2C => ()
- case C2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, INT)), tree.pos)
- case C2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, LONG)), tree.pos)
- case C2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, FLOAT)), tree.pos)
- case C2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, DOUBLE)), tree.pos)
-
- case I2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, BYTE)), tree.pos)
- case I2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, SHORT)), tree.pos)
- case I2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, CHAR)), tree.pos)
- case I2I => ()
- case I2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, LONG)), tree.pos)
- case I2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, FLOAT)), tree.pos)
- case I2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, DOUBLE)), tree.pos)
-
- case L2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, BYTE)), tree.pos)
- case L2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, SHORT)), tree.pos)
- case L2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, CHAR)), tree.pos)
- case L2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, INT)), tree.pos)
- case L2L => ()
- case L2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, FLOAT)), tree.pos)
- case L2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, DOUBLE)), tree.pos)
-
- case F2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, BYTE)), tree.pos)
- case F2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, SHORT)), tree.pos)
- case F2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, CHAR)), tree.pos)
- case F2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, INT)), tree.pos)
- case F2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, LONG)), tree.pos)
- case F2F => ()
- case F2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, DOUBLE)), tree.pos)
-
- case D2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, BYTE)), tree.pos)
- case D2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, SHORT)), tree.pos)
- case D2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, CHAR)), tree.pos)
- case D2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, INT)), tree.pos)
- case D2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, LONG)), tree.pos)
- case D2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, FLOAT)), tree.pos)
- case D2D => ()
-
- case _ => abort("Unknown coercion primitive: " + code)
- }
- }
-
- /** The Object => String overload.
- */
- private lazy val String_valueOf: Symbol = getMember(StringModule, nme.valueOf) filter (sym =>
- sym.info.paramTypes match {
- case List(pt) => pt.typeSymbol == ObjectClass
- case _ => false
- }
- )
-
- // I wrote it this way before I realized all the primitive types are
- // boxed at this point, so I'd have to unbox them. Keeping it around in
- // case we want to get more precise.
- //
- // private def valueOfForType(tp: Type): Symbol = {
- // val xs = getMember(StringModule, nme.valueOf) filter (sym =>
- // // We always exclude the Array[Char] overload because java throws an NPE if
- // // you pass it a null. It will instead find the Object one, which doesn't.
- // sym.info.paramTypes match {
- // case List(pt) => pt.typeSymbol != ArrayClass && (tp <:< pt)
- // case _ => false
- // }
- // )
- // xs.alternatives match {
- // case List(sym) => sym
- // case _ => NoSymbol
- // }
- // }
-
- /** Generate string concatenation.
- */
- def genStringConcat(tree: Tree, ctx: Context): Context = {
- liftStringConcat(tree) match {
- // Optimization for expressions of the form "" + x. We can avoid the StringBuilder.
- case List(Literal(Constant("")), arg) =>
- debuglog("Rewriting \"\" + x as String.valueOf(x) for: " + arg)
- val ctx1 = genLoad(arg, ctx, ObjectReference)
- ctx1.bb.emit(CALL_METHOD(String_valueOf, Static(onInstance = false)), arg.pos)
- ctx1
- case concatenations =>
- debuglog("Lifted string concatenations for " + tree + "\n to: " + concatenations)
- var ctx1 = ctx
- ctx1.bb.emit(CALL_PRIMITIVE(StartConcat), tree.pos)
- for (elem <- concatenations) {
- val kind = toTypeKind(elem.tpe)
- ctx1 = genLoad(elem, ctx1, kind)
- ctx1.bb.emit(CALL_PRIMITIVE(StringConcat(kind)), elem.pos)
- }
- ctx1.bb.emit(CALL_PRIMITIVE(EndConcat), tree.pos)
- ctx1
- }
- }
-
- /** Generate the scala ## method.
- */
- def genScalaHash(tree: Tree, ctx: Context): Context = {
- val hashMethod = {
- ctx.bb.emit(LOAD_MODULE(ScalaRunTimeModule))
- getMember(ScalaRunTimeModule, nme.hash_)
- }
-
- val ctx1 = genLoad(tree, ctx, ObjectReference)
- ctx1.bb.emit(CALL_METHOD(hashMethod, Static(onInstance = false)))
- ctx1
- }
-
- /**
- * Returns a list of trees that each should be concatenated, from
- * left to right. It turns a chained call like "a".+("b").+("c") into
- * a list of arguments.
- */
- def liftStringConcat(tree: Tree): List[Tree] = tree match {
- case Apply(fun @ Select(larg, method), rarg) =>
- if (isPrimitive(fun.symbol) &&
- scalaPrimitives.getPrimitive(fun.symbol) == scalaPrimitives.CONCAT)
- liftStringConcat(larg) ::: rarg
- else
- List(tree)
- case _ =>
- List(tree)
- }
-
- /**
- * Find the label denoted by `lsym` and enter it in context `ctx`.
- *
- * We only enter one symbol at a time, even though we might traverse the same
- * tree more than once per method. That's because we cannot enter labels that
- * might be duplicated (for instance, inside finally blocks).
- *
- * TODO: restrict the scanning to smaller subtrees than the whole method.
- * It is sufficient to scan the trees of the innermost enclosing block.
- */
- private def resolveForwardLabel(tree: Tree, ctx: Context, lsym: Symbol): Unit = tree foreachPartial {
- case t @ LabelDef(_, params, rhs) if t.symbol == lsym =>
- ctx.labels.getOrElseUpdate(t.symbol, {
- val locals = params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false))
- ctx.method addLocals locals
-
- new Label(t.symbol) setParams (params map (_.symbol))
- })
- rhs
- }
-
- /**
- * Generate code for conditional expressions. The two basic blocks
- * represent the continuation in case of success/failure of the
- * test.
- */
- private def genCond(tree: Tree,
- ctx: Context,
- thenCtx: Context,
- elseCtx: Context): Boolean =
- {
- /**
- * Generate the de-sugared comparison mechanism that will underly an '=='
- *
- * @param l left-hand side of the '=='
- * @param r right-hand side of the '=='
- * @param code the comparison operator to use
- * @return true if either branch can continue normally to a follow on block, false otherwise
- */
- def genComparisonOp(l: Tree, r: Tree, code: Int): Boolean = {
- val op: TestOp = code match {
- case scalaPrimitives.LT => LT
- case scalaPrimitives.LE => LE
- case scalaPrimitives.GT => GT
- case scalaPrimitives.GE => GE
- case scalaPrimitives.ID | scalaPrimitives.EQ => EQ
- case scalaPrimitives.NI | scalaPrimitives.NE => NE
-
- case _ => abort("Unknown comparison primitive: " + code)
- }
-
- // special-case reference (in)equality test for null (null eq x, x eq null)
- lazy val nonNullSide = ifOneIsNull(l, r)
- if (isReferenceEqualityOp(code) && nonNullSide != null) {
- val ctx1 = genLoad(nonNullSide, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- ctx1.bb.emitOnly(
- CZJUMP(thenCtx.bb, elseCtx.bb, op, ObjectReference)
- )
- branchesReachable
- }
- else {
- val kind = getMaxType(l.tpe :: r.tpe :: Nil)
- var ctx1 = genLoad(l, ctx, kind)
- ctx1 = genLoad(r, ctx1, kind)
- val branchesReachable = !ctx1.bb.ignore
-
- ctx1.bb.emitOnly(
- CJUMP(thenCtx.bb, elseCtx.bb, op, kind) setPos r.pos
- )
- branchesReachable
- }
- }
-
- debuglog("Entering genCond with tree: " + tree)
-
- // the default emission
- def default(): Boolean = {
- val ctx1 = genLoad(tree, ctx, BOOL)
- val branchesReachable = !ctx1.bb.ignore
- ctx1.bb.closeWith(CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL) setPos tree.pos)
- branchesReachable
- }
-
- tree match {
- // The comparison symbol is in ScalaPrimitives's "primitives" map
- case Apply(fun, args) if isPrimitive(fun.symbol) =>
- import scalaPrimitives.{ ZNOT, ZAND, ZOR, EQ, getPrimitive }
-
- // lhs and rhs of test
- lazy val Select(lhs, _) = fun
- lazy val rhs = args.head
-
- def genZandOrZor(and: Boolean): Boolean = {
- val ctxInterm = ctx.newBlock()
-
- val lhsBranchesReachable = if (and) genCond(lhs, ctx, ctxInterm, elseCtx)
- else genCond(lhs, ctx, thenCtx, ctxInterm)
- // If lhs is known to throw, we can kill the just created ctxInterm.
- ctxInterm.bb killUnless lhsBranchesReachable
-
- val rhsBranchesReachable = genCond(rhs, ctxInterm, thenCtx, elseCtx)
-
- // Reachable means "it does not always throw", i.e. "it might not throw".
- // In an expression (a && b) or (a || b), the b branch might not be evaluated.
- // Such an expression is therefore known to throw only if both expressions throw. Or,
- // successors are reachable if either of the two is reachable (SI-8625).
- lhsBranchesReachable || rhsBranchesReachable
- }
- def genRefEq(isEq: Boolean) = {
- val f = genEqEqPrimitive(lhs, rhs, ctx) _
- if (isEq) f(thenCtx, elseCtx)
- else f(elseCtx, thenCtx)
- }
-
- getPrimitive(fun.symbol) match {
- case ZNOT => genCond(lhs, ctx, elseCtx, thenCtx)
- case ZAND => genZandOrZor(and = true)
- case ZOR => genZandOrZor(and = false)
- case code =>
- // x == y where LHS is reference type
- if (isUniversalEqualityOp(code) && toTypeKind(lhs.tpe).isReferenceType) {
- if (code == EQ) genRefEq(isEq = true)
- else genRefEq(isEq = false)
- }
- else if (isComparisonOp(code))
- genComparisonOp(lhs, rhs, code)
- else
- default()
- }
-
- case _ => default()
- }
- }
-
- /**
- * Generate the "==" code for object references. It is equivalent of
- * if (l eq null) r eq null else l.equals(r);
- *
- * @param l left-hand side of the '=='
- * @param r right-hand side of the '=='
- * @param ctx current context
- * @param thenCtx target context if the comparison yields true
- * @param elseCtx target context if the comparison yields false
- * @return true if either branch can continue normally to a follow on block, false otherwise
- */
- def genEqEqPrimitive(l: Tree, r: Tree, ctx: Context)(thenCtx: Context, elseCtx: Context): Boolean = {
- def getTempLocal = ctx.method.lookupLocal(nme.EQEQ_LOCAL_VAR) getOrElse {
- ctx.makeLocal(l.pos, AnyRefTpe, nme.EQEQ_LOCAL_VAR.toString)
- }
-
- /* True if the equality comparison is between values that require the use of the rich equality
- * comparator (scala.runtime.Comparator.equals). This is the case when either side of the
- * comparison might have a run-time type subtype of java.lang.Number or java.lang.Character.
- * When it is statically known that both sides are equal and subtypes of Number of Character,
- * not using the rich equality is possible (their own equals method will do ok.)*/
- def mustUseAnyComparator: Boolean = {
- def areSameFinals = l.tpe.isFinalType && r.tpe.isFinalType && (l.tpe =:= r.tpe)
- !areSameFinals && isMaybeBoxed(l.tpe.typeSymbol) && isMaybeBoxed(r.tpe.typeSymbol)
- }
-
- if (mustUseAnyComparator) {
- // when -optimise is on we call the @inline-version of equals, found in ScalaRunTime
- val equalsMethod: Symbol = {
- if (!settings.optimise) {
- if (l.tpe <:< BoxedNumberClass.tpe) {
- if (r.tpe <:< BoxedNumberClass.tpe) platform.externalEqualsNumNum
- else if (r.tpe <:< BoxedCharacterClass.tpe) platform.externalEqualsNumObject // will be externalEqualsNumChar in 2.12, SI-9030
- else platform.externalEqualsNumObject
- } else platform.externalEquals
- } else {
- ctx.bb.emit(LOAD_MODULE(ScalaRunTimeModule))
- getMember(ScalaRunTimeModule, nme.inlinedEquals)
- }
- }
-
- val ctx1 = genLoad(l, ctx, ObjectReference)
- val ctx2 = genLoad(r, ctx1, ObjectReference)
- val branchesReachable = !ctx2.bb.ignore
- ctx2.bb.emitOnly(
- CALL_METHOD(equalsMethod, if (settings.optimise) Dynamic else Static(onInstance = false)),
- CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL)
- )
- branchesReachable
- }
- else {
- if (isNull(l)) {
- // null == expr -> expr eq null
- val ctx1 = genLoad(r, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- ctx1.bb emitOnly CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference)
- branchesReachable
- } else if (isNull(r)) {
- // expr == null -> expr eq null
- val ctx1 = genLoad(l, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- ctx1.bb emitOnly CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference)
- branchesReachable
- } else if (isNonNullExpr(l)) {
- // Avoid null check if L is statically non-null.
- //
- // "" == expr -> "".equals(expr)
- // Nil == expr -> Nil.equals(expr)
- //
- // Common enough (through pattern matching) to treat this specially here rather than
- // hoping that -Yconst-opt is enabled. The impossible branches for null checks lead
- // to spurious "branch not covered" warnings in Jacoco code coverage.
- var ctx1 = genLoad(l, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- ctx1 = genLoad(r, ctx1, ObjectReference)
- ctx1.bb emitOnly(
- CALL_METHOD(Object_equals, Dynamic),
- CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL)
- )
- branchesReachable
- } else {
- val eqEqTempLocal = getTempLocal
- var ctx1 = genLoad(l, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- lazy val nonNullCtx = {
- val block = ctx1.newBlock()
- block.bb killUnless branchesReachable
- block
- }
-
- // l == r -> if (l eq null) r eq null else l.equals(r)
- ctx1 = genLoad(r, ctx1, ObjectReference)
- val nullCtx = ctx1.newBlock()
- nullCtx.bb killUnless branchesReachable
-
- ctx1.bb.emitOnly(
- STORE_LOCAL(eqEqTempLocal) setPos l.pos,
- DUP(ObjectReference),
- CZJUMP(nullCtx.bb, nonNullCtx.bb, EQ, ObjectReference)
- )
- nullCtx.bb.emitOnly(
- DROP(ObjectReference) setPos l.pos, // type of AnyRef
- LOAD_LOCAL(eqEqTempLocal),
- CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference)
- )
- nonNullCtx.bb.emitOnly(
- LOAD_LOCAL(eqEqTempLocal) setPos l.pos,
- CALL_METHOD(Object_equals, Dynamic),
- CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL)
- )
- branchesReachable
- }
- }
- }
-
- /**
- * Add all fields of the given class symbol to the current ICode
- * class.
- */
- private def addClassFields(ctx: Context, cls: Symbol) {
- debugassert(ctx.clazz.symbol eq cls,
- "Classes are not the same: " + ctx.clazz.symbol + ", " + cls)
-
- /* Non-method term members are fields, except for module members. Module
- * members can only happen on .NET (no flatten) for inner traits. There,
- * a module symbol is generated (transformInfo in mixin) which is used
- * as owner for the members of the implementation class (so that the
- * backend emits them as static).
- * No code is needed for this module symbol.
- */
- for (f <- cls.info.decls ; if !f.isMethod && f.isTerm && !f.isModule)
- ctx.clazz addField new IField(f)
- }
-
- /**
- * Add parameters to the current ICode method. It is assumed the methods
- * have been uncurried, so the list of lists contains just one list.
- */
- private def addMethodParams(ctx: Context, vparamss: List[List[ValDef]]) {
- vparamss match {
- case Nil => ()
-
- case vparams :: Nil =>
- for (p <- vparams) {
- val lv = new Local(p.symbol, toTypeKind(p.symbol.info), true)
- ctx.method.addParam(lv)
- ctx.scope.add(lv)
- ctx.bb.varsInScope += lv
- }
- ctx.method.params = ctx.method.params.reverse
-
- case _ =>
- abort("Malformed parameter list: " + vparamss)
- }
- }
-
- /** Does this tree have a try-catch block? */
- def mayCleanStack(tree: Tree): Boolean = tree exists {
- case Try(_, _, _) => true
- case _ => false
- }
-
- /**
- * If the block consists of a single unconditional jump, prune
- * it by replacing the instructions in the predecessor to jump
- * directly to the JUMP target of the block.
- */
- def prune(method: IMethod) = {
- var changed = false
- var n = 0
-
- def prune0(block: BasicBlock): Unit = {
- val optCont = block.lastInstruction match {
- case JUMP(b) if (b != block) => Some(b)
- case _ => None
- }
- if (block.size == 1 && optCont.isDefined) {
- val Some(cont) = optCont
- val pred = block.predecessors
- debuglog("Preds: " + pred + " of " + block + " (" + optCont + ")")
- pred foreach { p =>
- changed = true
- p.lastInstruction match {
- case CJUMP(succ, fail, cond, kind) if (succ == block || fail == block) =>
- debuglog("Pruning empty if branch.")
- p.replaceInstruction(p.lastInstruction,
- if (block == succ)
- if (block == fail)
- CJUMP(cont, cont, cond, kind)
- else
- CJUMP(cont, fail, cond, kind)
- else if (block == fail)
- CJUMP(succ, cont, cond, kind)
- else
- abort("Could not find block in preds: " + method + " " + block + " " + pred + " " + p))
-
- case CZJUMP(succ, fail, cond, kind) if (succ == block || fail == block) =>
- debuglog("Pruning empty ifz branch.")
- p.replaceInstruction(p.lastInstruction,
- if (block == succ)
- if (block == fail)
- CZJUMP(cont, cont, cond, kind)
- else
- CZJUMP(cont, fail, cond, kind)
- else if (block == fail)
- CZJUMP(succ, cont, cond, kind)
- else
- abort("Could not find block in preds"))
-
- case JUMP(b) if (b == block) =>
- debuglog("Pruning empty JMP branch.")
- val replaced = p.replaceInstruction(p.lastInstruction, JUMP(cont))
- debugassert(replaced, "Didn't find p.lastInstruction")
-
- case SWITCH(tags, labels) if (labels contains block) =>
- debuglog("Pruning empty SWITCH branch.")
- p.replaceInstruction(p.lastInstruction,
- SWITCH(tags, labels map (l => if (l == block) cont else l)))
-
- // the last instr of the predecessor `p` is not a jump to the block `block`.
- // this happens when `block` is part of an exception handler covering `b`.
- case _ => ()
- }
- }
- if (changed) {
- debuglog("Removing block: " + block)
- method.code.removeBlock(block)
- for (e <- method.exh) {
- e.covered = e.covered filter (_ != block)
- e.blocks = e.blocks filter (_ != block)
- if (e.startBlock eq block)
- e setStartBlock cont
- }
- }
- }
- }
-
- do {
- changed = false
- n += 1
- method.blocks foreach prune0
- } while (changed)
-
- debuglog("Prune fixpoint reached in " + n + " iterations.")
- }
-
- def getMaxType(ts: List[Type]): TypeKind =
- ts map toTypeKind reduceLeft (_ maxType _)
-
- /** Tree transformer that duplicates code and at the same time creates
- * fresh symbols for existing labels. Since labels may be used before
- * they are defined (forward jumps), all labels found are mapped to fresh
- * symbols. References to the same label (use or definition) will remain
- * consistent after this transformation (both the use and the definition of
- * some label l will be mapped to the same label l').
- *
- * Note: If the tree fragment passed to the duplicator contains unbound
- * label names, the bind to the outer labeldef will be lost! That's because
- * a use of an unbound label l will be transformed to l', and the corresponding
- * label def, being outside the scope of this transformation, will not be updated.
- *
- * All LabelDefs are entered into the context label map, since it makes no sense
- * to delay it any more: they will be used at some point.
- */
- class DuplicateLabels(boundLabels: Set[Symbol]) extends Transformer {
- val labels = perRunCaches.newMap[Symbol, Symbol]()
- var method: Symbol = _
- var ctx: Context = _
-
- def apply(ctx: Context, t: Tree) = {
- this.method = ctx.method.symbol
- this.ctx = ctx
- transform(t)
- }
-
- override def transform(t: Tree): Tree = {
- val sym = t.symbol
- def getLabel(pos: Position, name: Name) =
- labels.getOrElseUpdate(sym,
- method.newLabel(unit.freshTermName(name.toString), sym.pos) setInfo sym.tpe
- )
-
- t match {
- case t @ Apply(_, args) if sym.isLabel && !boundLabels(sym) =>
- val newSym = getLabel(sym.pos, sym.name)
- Apply(global.gen.mkAttributedRef(newSym), transformTrees(args)) setPos t.pos setType t.tpe
-
- case t @ LabelDef(name, params, rhs) =>
- val newSym = getLabel(t.pos, name)
- val tree = treeCopy.LabelDef(t, newSym.name, params, transform(rhs))
- tree.symbol = newSym
-
- val pair = (newSym -> (new Label(newSym) setParams (params map (_.symbol))))
- log("Added " + pair + " to labels.")
- ctx.labels += pair
- ctx.method.addLocals(params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false)))
-
- tree
-
- case _ => super.transform(t)
- }
- }
- }
-
- /////////////////////// Context ////////////////////////////////
-
- sealed abstract class Cleanup(val value: AnyRef) {
- def contains(x: AnyRef) = value == x
- }
- case class MonitorRelease(m: Local) extends Cleanup(m) { }
- case class Finalizer(f: Tree, ctx: Context) extends Cleanup (f) { }
-
- def duplicateFinalizer(boundLabels: Set[Symbol], targetCtx: Context, finalizer: Tree) = {
- (new DuplicateLabels(boundLabels))(targetCtx, finalizer)
- }
-
- def savingCleanups[T](ctx: Context)(body: => T): T = {
- val saved = ctx.cleanups
- try body
- finally ctx.cleanups = saved
- }
-
- /**
- * The Context class keeps information relative to the current state
- * in code generation
- */
- class Context {
- /** The current package. */
- var packg: Name = _
-
- /** The current class. */
- var clazz: IClass = _
-
- /** The current method. */
- var method: IMethod = _
-
- /** The current basic block. */
- var bb: BasicBlock = _
-
- /** Map from label symbols to label objects. */
- var labels = perRunCaches.newMap[Symbol, Label]()
-
- /** Current method definition. */
- var defdef: DefDef = _
-
- /** current exception handlers */
- var handlers: List[ExceptionHandler] = Nil
-
- /** The current monitors or finalizers, to be cleaned up upon `return`. */
- var cleanups: List[Cleanup] = Nil
-
- /** The exception handlers we are currently generating code for */
- var currentExceptionHandlers: List[ExceptionHandler] = Nil
-
- /** The current local variable scope. */
- var scope: Scope = EmptyScope
-
- var handlerCount = 0
-
- override def toString =
- s"package $packg { class $clazz { def $method { bb=$bb } } }"
-
- def loadException(ctx: Context, exh: ExceptionHandler, pos: Position) = {
- debuglog("Emitting LOAD_EXCEPTION for class: " + exh.loadExceptionClass)
- ctx.bb.emit(LOAD_EXCEPTION(exh.loadExceptionClass) setPos pos, pos)
- }
-
- def this(other: Context) = {
- this()
- this.packg = other.packg
- this.clazz = other.clazz
- this.method = other.method
- this.bb = other.bb
- this.labels = other.labels
- this.defdef = other.defdef
- this.handlers = other.handlers
- this.handlerCount = other.handlerCount
- this.cleanups = other.cleanups
- this.currentExceptionHandlers = other.currentExceptionHandlers
- this.scope = other.scope
- }
-
- def setPackage(p: Name): this.type = {
- this.packg = p
- this
- }
-
- def setClass(c: IClass): this.type = {
- this.clazz = c
- this
- }
-
- def setMethod(m: IMethod): this.type = {
- this.method = m
- this
- }
-
- def setBasicBlock(b: BasicBlock): this.type = {
- this.bb = b
- this
- }
-
- def enterSynchronized(monitor: Local): this.type = {
- cleanups = MonitorRelease(monitor) :: cleanups
- this
- }
-
- def exitSynchronized(monitor: Local): this.type = {
- assert(cleanups.head contains monitor,
- "Bad nesting of cleanup operations: " + cleanups + " trying to exit from monitor: " + monitor)
- cleanups = cleanups.tail
- this
- }
-
- def addFinalizer(f: Tree, ctx: Context): this.type = {
- cleanups = Finalizer(f, ctx) :: cleanups
- this
- }
-
- /** Prepare a new context upon entry into a method.
- */
- def enterMethod(m: IMethod, d: DefDef): Context = {
- val ctx1 = new Context(this) setMethod(m)
- ctx1.labels = mutable.HashMap()
- ctx1.method.code = new Code(m)
- ctx1.bb = ctx1.method.startBlock
- ctx1.defdef = d
- ctx1.scope = EmptyScope
- ctx1.enterScope()
- ctx1
- }
-
- /** Return a new context for a new basic block. */
- def newBlock(): Context = {
- val block = method.code.newBlock()
- handlers foreach (_ addCoveredBlock block)
- currentExceptionHandlers foreach (_ addBlock block)
- block.varsInScope.clear()
- block.varsInScope ++= scope.varsInScope
- new Context(this) setBasicBlock block
- }
-
- def enterScope() {
- scope = new Scope(scope)
- }
-
- def exitScope() {
- if (bb.nonEmpty) {
- scope.locals foreach { lv => bb.emit(SCOPE_EXIT(lv)) }
- }
- scope = scope.outer
- }
-
- /** Create a new exception handler and adds it in the list
- * of current exception handlers. All new blocks will be
- * 'covered' by this exception handler (in addition to the
- * previously active handlers).
- */
- private def newExceptionHandler(cls: Symbol, pos: Position): ExceptionHandler = {
- handlerCount += 1
- val exh = new ExceptionHandler(method, newTermNameCached("" + handlerCount), cls, pos)
- method.addHandler(exh)
- handlers = exh :: handlers
- debuglog("added handler: " + exh)
-
- exh
- }
-
- /** Add an active exception handler in this context. It will cover all new basic blocks
- * created from now on. */
- private def addActiveHandler(exh: ExceptionHandler) {
- handlerCount += 1
- handlers = exh :: handlers
- debuglog("added handler: " + exh)
- }
-
- /** Return a new context for generating code for the given
- * exception handler.
- */
- private def enterExceptionHandler(exh: ExceptionHandler): Context = {
- currentExceptionHandlers ::= exh
- val ctx = newBlock()
- exh.setStartBlock(ctx.bb)
- ctx
- }
-
- def endHandler() {
- currentExceptionHandlers = currentExceptionHandlers.tail
- }
-
- /** Clone the current context */
- def dup: Context = new Context(this)
-
- /** Make a fresh local variable. It ensures the 'name' is unique. */
- def makeLocal(pos: Position, tpe: Type, name: String): Local = {
- val sym = method.symbol.newVariable(unit.freshTermName(name), pos, Flags.SYNTHETIC) setInfo tpe
- this.method.addLocal(new Local(sym, toTypeKind(tpe), false))
- }
-
-
- /**
- * Generate exception handlers for the body. Body is evaluated
- * with a context where all the handlers are active. Handlers are
- * evaluated in the 'outer' context.
- *
- * It returns the resulting context, with the same active handlers as
- * before the call. Use it like:
- *
- * ` ctx.Try( ctx => {
- * ctx.bb.emit(...) // protected block
- * }, (ThrowableClass,
- * ctx => {
- * ctx.bb.emit(...); // exception handler
- * }), (AnotherExceptionClass,
- * ctx => {...
- * } ))`
- *
- * The resulting structure will look something like
- *
- * outer:
- * // this 'useless' jump will be removed later,
- * // for now it separates the try body's blocks from previous
- * // code since the try body needs its own exception handlers
- * JUMP body
- *
- * body:
- * [ try body ]
- * JUMP normalExit
- *
- * catch[i]:
- * [ handler[i] body ]
- * JUMP normalExit
- *
- * catchAll:
- * STORE exception
- * [ finally body ]
- * THROW exception
- *
- * normalExit:
- * [ finally body ]
- *
- * each catch[i] will cover body. catchAll will cover both body and each catch[i]
- * Additional finally copies are created on the emission of every RETURN in the try body and exception handlers.
- *
- * This could result in unreachable code which has to be cleaned up later, e.g. if the try and all the exception
- * handlers always end in RETURN then there will be no "normal" flow out of the try/catch/finally.
- * Later reachability analysis will remove unreachable code.
- */
- def Try(body: Context => Context,
- handlers: List[(Symbol, TypeKind, Context => Context)],
- finalizer: Tree,
- tree: Tree) = {
-
- val outerCtx = this.dup // context for generating exception handlers, covered by the catch-all finalizer
- val finalizerCtx = this.dup // context for generating finalizer handler
- val normalExitCtx = outerCtx.newBlock() // context where flow will go on a "normal" (non-return, non-throw) exit from a try or catch handler
- var normalExitReachable = false
- var tmp: Local = null
- val kind = toTypeKind(tree.tpe)
- val guardResult = kind != UNIT && mayCleanStack(finalizer)
- // we need to save bound labels before any code generation is performed on
- // the current context (otherwise, any new labels in the finalizer that need to
- // be duplicated would be incorrectly considered bound -- see #2850).
- val boundLabels: Set[Symbol] = Set.empty ++ labels.keySet
-
- if (guardResult) {
- tmp = this.makeLocal(tree.pos, tree.tpe, "tmp")
- }
-
- def emitFinalizer(ctx: Context): Context = if (!finalizer.isEmpty) {
- val ctx1 = finalizerCtx.dup.newBlock()
- ctx1.bb killIf ctx.bb.ignore
- ctx.bb.closeWith(JUMP(ctx1.bb))
-
- if (guardResult) {
- ctx1.bb.emit(STORE_LOCAL(tmp))
- val ctx2 = genLoad(duplicateFinalizer(boundLabels, ctx1, finalizer), ctx1, UNIT)
- ctx2.bb.emit(LOAD_LOCAL(tmp))
- ctx2
- } else
- genLoad(duplicateFinalizer(boundLabels, ctx1, finalizer), ctx1, UNIT)
- } else ctx
-
-
- // Generate the catch-all exception handler that deals with uncaught exceptions coming
- // from the try or exception handlers. It catches the exception, runs the finally code, then rethrows
- // the exception
- if (settings.YdisableUnreachablePrevention || !outerCtx.bb.ignore) {
- if (finalizer != EmptyTree) {
- val exh = outerCtx.newExceptionHandler(NoSymbol, finalizer.pos) // finalizer covers exception handlers
- this.addActiveHandler(exh) // .. and body as well
- val exhStartCtx = finalizerCtx.enterExceptionHandler(exh)
- exhStartCtx.bb killIf outerCtx.bb.ignore
- val exception = exhStartCtx.makeLocal(finalizer.pos, ThrowableTpe, "exc")
- loadException(exhStartCtx, exh, finalizer.pos)
- exhStartCtx.bb.emit(STORE_LOCAL(exception))
- val exhEndCtx = genLoad(finalizer, exhStartCtx, UNIT)
- exhEndCtx.bb.emit(LOAD_LOCAL(exception))
- exhEndCtx.bb.closeWith(THROW(ThrowableClass))
- exhEndCtx.bb.enterIgnoreMode()
- finalizerCtx.endHandler()
- }
-
- // Generate each exception handler
- for ((sym, kind, handler) <- handlers) {
- val exh = this.newExceptionHandler(sym, tree.pos)
- val exhStartCtx = outerCtx.enterExceptionHandler(exh)
- exhStartCtx.bb killIf outerCtx.bb.ignore
- exhStartCtx.addFinalizer(finalizer, finalizerCtx)
- loadException(exhStartCtx, exh, tree.pos)
- val exhEndCtx = handler(exhStartCtx)
- normalExitReachable ||= !exhEndCtx.bb.ignore
- exhEndCtx.bb.closeWith(JUMP(normalExitCtx.bb))
- outerCtx.endHandler()
- }
- }
-
- val bodyCtx = this.newBlock()
- bodyCtx.bb killIf outerCtx.bb.ignore
- if (finalizer != EmptyTree)
- bodyCtx.addFinalizer(finalizer, finalizerCtx)
-
- val bodyEndCtx = body(bodyCtx)
-
- outerCtx.bb.closeWith(JUMP(bodyCtx.bb))
-
- normalExitReachable ||= !bodyEndCtx.bb.ignore
- normalExitCtx.bb killUnless normalExitReachable
- bodyEndCtx.bb.closeWith(JUMP(normalExitCtx.bb))
-
- emitFinalizer(normalExitCtx)
- }
- }
- }
-
- /**
- * Represent a label in the current method code. In order
- * to support forward jumps, labels can be created without
- * having a designated target block. They can later be attached
- * by calling `anchor`.
- */
- class Label(val symbol: Symbol) {
- var anchored = false
- var block: BasicBlock = _
- var params: List[Symbol] = _
-
- private var toPatch: List[Instruction] = Nil
-
- /** Fix this label to the given basic block. */
- def anchor(b: BasicBlock): Label = {
- assert(!anchored, "Cannot anchor an already anchored label!")
- anchored = true
- this.block = b
- this
- }
-
- def setParams(p: List[Symbol]): Label = {
- assert(params eq null, "Cannot set label parameters twice!")
- params = p
- this
- }
-
- /** Add an instruction that refers to this label. */
- def addCallingInstruction(i: Instruction) =
- toPatch = i :: toPatch
-
- /**
- * Patch the code by replacing pseudo call instructions with
- * jumps to the given basic block.
- */
- def patch(code: Code) {
- val map = mapFrom(toPatch)(patch)
- code.blocks foreach (_ subst map)
- }
-
- /**
- * Return the patched instruction. If the given instruction
- * jumps to this label, replace it with the basic block. Otherwise,
- * return the same instruction. Conditional jumps have more than one
- * label, so they are replaced only if all labels are anchored.
- */
- def patch(instr: Instruction): Instruction = {
- assert(anchored, "Cannot patch until this label is anchored: " + this)
-
- instr match {
- case PJUMP(self)
- if (self == this) => JUMP(block)
-
- case PCJUMP(self, failure, cond, kind)
- if (self == this && failure.anchored) =>
- CJUMP(block, failure.block, cond, kind)
-
- case PCJUMP(success, self, cond, kind)
- if (self == this && success.anchored) =>
- CJUMP(success.block, block, cond, kind)
-
- case PCZJUMP(self, failure, cond, kind)
- if (self == this && failure.anchored) =>
- CZJUMP(block, failure.block, cond, kind)
-
- case PCZJUMP(success, self, cond, kind)
- if (self == this && success.anchored) =>
- CZJUMP(success.block, block, cond, kind)
-
- case _ => instr
- }
- }
-
- override def toString() = symbol.toString()
- }
-
- ///////////////// Fake instructions //////////////////////////
-
- /**
- * Pseudo jump: it takes a Label instead of a basic block.
- * It is used temporarily during code generation. It is replaced
- * by a real JUMP instruction when all labels are resolved.
- */
- abstract class PseudoJUMP(label: Label) extends Instruction {
- override def toString = s"PJUMP(${label.symbol})"
- override def consumed = 0
- override def produced = 0
-
- // register with the given label
- if (!label.anchored)
- label.addCallingInstruction(this)
- }
-
- case class PJUMP(whereto: Label) extends PseudoJUMP(whereto)
-
- case class PCJUMP(success: Label, failure: Label, cond: TestOp, kind: TypeKind)
- extends PseudoJUMP(success) {
- override def toString(): String =
- "PCJUMP (" + kind + ") " + success.symbol.simpleName +
- " : " + failure.symbol.simpleName
-
- if (!failure.anchored)
- failure.addCallingInstruction(this)
- }
-
- case class PCZJUMP(success: Label, failure: Label, cond: TestOp, kind: TypeKind)
- extends PseudoJUMP(success) {
- override def toString(): String =
- "PCZJUMP (" + kind + ") " + success.symbol.simpleName +
- " : " + failure.symbol.simpleName
-
- if (!failure.anchored)
- failure.addCallingInstruction(this)
- }
-
- /** Local variable scopes. Keep track of line numbers for debugging info. */
- class Scope(val outer: Scope) {
- val locals: ListBuffer[Local] = new ListBuffer
-
- def add(l: Local) = locals += l
-
- /** Return all locals that are in scope. */
- def varsInScope: Buffer[Local] = outer.varsInScope.clone() ++= locals
-
- override def toString() = locals.mkString(outer.toString + "[", ", ", "]")
- }
-
- object EmptyScope extends Scope(null) {
- override def toString() = "[]"
- override def varsInScope: Buffer[Local] = new ListBuffer
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala b/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala
deleted file mode 100644
index 0f17b5d694..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala
+++ /dev/null
@@ -1,711 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-import scala.collection.mutable
-import scala.collection.mutable.ListBuffer
-
-abstract class ICodeCheckers {
- val global: Global
- import global._
-
- /** <p>
- * This class performs a set of checks similar to what the bytecode
- * verifier does. For each basic block, it checks that:
- * </p>
- * <ul>
- * <li>
- * for primitive operations: the type and number of operands match
- * the type of the operation
- * </li>
- * <li>
- * for method calls: the method exists in the type of the receiver
- * and the number and type of arguments match the declared type of
- * the method.
- * </li>
- * <li>
- * for object creation: the constructor can be called.
- * </li>
- * <li>
- * for load/stores: the field/local/param exists and the type
- * of the value matches that of the target.
- * </li>
- * </ul>
- * <p>
- * For a control flow graph it checks that type stacks at entry to
- * each basic block 'agree':
- * </p>
- * <ul>
- * <li>they have the same length</li>
- * <li>there exists a lub for all types at the same position in stacks.</li>
- * </ul>
- *
- * @author Iulian Dragos
- * @version 1.0, 06/09/2005
- *
- * @todo Better checks for `MONITOR_ENTER/EXIT`
- * Better checks for local var initializations
- *
- * @todo Iulian says: I think there's some outdated logic in the checker.
- * The issue with exception handlers being special for least upper
- * bounds pointed out some refactoring in the lattice class. Maybe
- * a worthwhile refactoring would be to make the checker use the
- * DataFlowAnalysis class, and use the lattice trait. In the
- * implementation of LUB, there's a flag telling if one of the
- * successors is 'exceptional'. The inliner is using this mechanism.
- */
- class ICodeChecker {
- import icodes._
- import opcodes._
-
- var clasz: IClass = _
- var method: IMethod = _
- var code: Code = _
-
- val in: mutable.Map[BasicBlock, TypeStack] = perRunCaches.newMap()
- val out: mutable.Map[BasicBlock, TypeStack] = perRunCaches.newMap()
- val emptyStack = new TypeStack() {
- override def toString = "<empty>"
- }
-
- /** The presence of emptyStack means that path has not yet been checked
- * (and may not be empty).
- */
- def notChecked(ts: TypeStack) = ts eq emptyStack
- def initMaps(bs: Seq[BasicBlock]): Unit = {
- in.clear()
- out.clear()
- bs foreach { b =>
- in(b) = emptyStack
- out(b) = emptyStack
- }
- }
-
- /** A wrapper to route log messages to debug output also.
- */
- def logChecker(msg: String) = {
- log(msg)
- checkerDebug(msg)
- }
-
- def checkICodes(): Unit = {
- if (settings.verbose)
- println("[[consistency check at the beginning of phase " + globalPhase.name + "]]")
- classes.values foreach check
- }
-
- private def posStr(p: Position) =
- if (p.isDefined) p.line.toString else "<??>"
-
- private def indent(s: String, prefix: String): String = {
- val lines = s split "\\n"
- lines map (prefix + _) mkString "\n"
- }
-
- /** Only called when m1 < m2, so already known that (m1 ne m2).
- */
- private def isConflict(m1: IMember, m2: IMember, canOverload: Boolean) = (
- (m1.symbol.name == m2.symbol.name) &&
- (!canOverload || (m1.symbol.tpe =:= m2.symbol.tpe))
- )
-
- def check(cls: IClass) {
- logChecker("\n<<-- Checking class " + cls + " -->>")
- clasz = cls
-
- for (f1 <- cls.fields ; f2 <- cls.fields ; if f1 < f2)
- if (isConflict(f1, f2, canOverload = false))
- icodeError("Repetitive field name: " + f1.symbol.fullName)
-
- for (m1 <- cls.methods ; m2 <- cls.methods ; if m1 < m2)
- if (isConflict(m1, m2, canOverload = true))
- icodeError("Repetitive method: " + m1.symbol.fullName)
-
- clasz.methods foreach check
- }
-
- def check(m: IMethod) {
- logChecker("\n<< Checking method " + m.symbol.name + " >>")
- method = m
- if (!m.isAbstractMethod)
- check(m.code)
- }
-
- def check(c: Code) {
- val worklist = new ListBuffer[BasicBlock]
- def append(elems: List[BasicBlock]) =
- worklist ++= (elems filterNot (worklist contains _))
-
- code = c
- worklist += c.startBlock
- initMaps(c.blocks)
-
- while (worklist.nonEmpty) {
- val block = worklist remove 0
- val output = check(block, in(block))
- if (output != out(block) || notChecked(out(block))) {
- if (block.successors.nonEmpty)
- logChecker("** Output change for %s: %s -> %s".format(block, out(block), output))
-
- out(block) = output
- append(block.successors)
- block.successors foreach meet
- }
- }
- }
-
- /**
- * Apply the meet operator of the stack lattice on bl's predecessors.
- * :-). Compute the input to bl by checking that all stacks have the
- * same length, and taking the lub of types at the same positions.
- */
- def meet(bl: BasicBlock) {
- val preds = bl.predecessors
-
- def hasNothingType(s: TypeStack) = s.nonEmpty && (s.head == NothingReference)
-
- /* XXX workaround #1: one stack empty, the other has BoxedUnit.
- * One example where this arises is:
- *
- * def f(b: Boolean): Unit = synchronized { if (b) () }
- */
- def allUnits(s: TypeStack) = s.types forall (_ == BoxedUnitReference)
-
- def ifAthenB[T](f: T => Boolean): PartialFunction[(T, T), T] = {
- case (x1, x2) if f(x1) => x2
- case (x1, x2) if f(x2) => x1
- }
-
- /* XXX workaround #2: different stacks heading into an exception
- * handler which will clear them anyway. Examples where it arises:
- *
- * var bippy: Int = synchronized { if (b) 5 else 10 }
- */
- def isHandlerBlock() = bl.exceptionHandlerStart
-
- def meet2(s1: TypeStack, s2: TypeStack): TypeStack = {
- def workaround(msg: String) = {
- checkerDebug(msg + ": " + method + " at block " + bl)
- checkerDebug(" s1: " + s1)
- checkerDebug(" s2: " + s2)
- new TypeStack()
- }
- def incompatibleString = (
- "Incompatible stacks: " + s1 + " and " + s2 + " in " + method + " at entry to block " + bl.label + ":\n" +
- indent(bl.predContents, "// ") +
- indent(bl.succContents, "// ") +
- indent(bl.blockContents, "// ")
- )
-
- val f: ((TypeStack, TypeStack)) => TypeStack = {
- ifAthenB(notChecked) orElse ifAthenB(hasNothingType) orElse {
- case (s1: TypeStack, s2: TypeStack) =>
- if (s1.length != s2.length) {
- if (allUnits(s1) && allUnits(s2))
- workaround("Ignoring mismatched boxed units")
- else if (isHandlerBlock())
- workaround("Ignoring mismatched stacks entering exception handler")
- else
- throw new CheckerException(incompatibleString)
- }
- else {
- val newStack: TypeStack = try {
- new TypeStack((s1.types, s2.types).zipped map lub)
- } catch {
- case t: Exception =>
- checkerDebug(t.toString + ": " + s1.types.toString + " vs " + s2.types.toString)
- new TypeStack(s1.types)
- }
- if (newStack.isEmpty || s1.types == s2.types) () // not interesting to report
- else checkerDebug("Checker created new stack:\n (%s, %s) => %s".format(s1, s2, newStack))
-
- newStack
- }
- }
- }
-
- f((s1, s2))
- }
-
- if (preds.nonEmpty) {
- in(bl) = (preds map out.apply) reduceLeft meet2
- log("Input changed for block: " + bl +" to: " + in(bl))
- }
- }
-
- private var instruction: Instruction = null
- private var basicBlock: BasicBlock = null
- private var stringConcatDepth = 0
- private def stringConcatIndent() = " " * stringConcatDepth
- private def currentInstrString: String = {
- val (indent, str) = this.instruction match {
- case CALL_PRIMITIVE(StartConcat) =>
- val x = stringConcatIndent()
- stringConcatDepth += 1
- (x, "concat(")
- case CALL_PRIMITIVE(EndConcat) =>
- if (stringConcatDepth > 0) {
- stringConcatDepth -= 1
- (stringConcatIndent(), ") // end concat")
- }
- else ("", "")
- case _ =>
- (stringConcatIndent(), this.instruction match {
- case CALL_PRIMITIVE(StringConcat(el)) => "..."
- case null => "null"
- case cm @ CALL_METHOD(_, _) => if (clasz.symbol == cm.hostClass) cm.toShortString else cm.toString
- case x => x.toString
- })
- }
- indent + str
- }
- /** A couple closure creators to reduce noise in the output: when multiple
- * items are pushed or popped, this lets us print something short and sensible
- * for those beyond the first.
- */
- def mkInstrPrinter(f: Int => String): () => String = {
- var counter = -1
- val indent = stringConcatIndent()
- () => {
- counter += 1
- if (counter == 0) currentInstrString
- else indent + f(counter)
- }
- }
- def defaultInstrPrinter: () => String = mkInstrPrinter(_ => "\"\"\"")
-
- /**
- * Check the basic block to be type correct and return the
- * produced type stack.
- */
- def check(b: BasicBlock, initial: TypeStack): TypeStack = {
- this.basicBlock = b
-
- logChecker({
- val prefix = "** Checking " + b.fullString
-
- if (initial.isEmpty) prefix
- else prefix + " with initial stack " + initial.types.mkString("[", ", ", "]")
- })
-
- val stack = new TypeStack(initial)
- def checkStack(len: Int) {
- if (stack.length < len)
- ICodeChecker.this.icodeError("Expected at least " + len + " elements on the stack", stack)
- }
-
- def sizeString(push: Boolean) = {
- val arrow = if (push) "-> " else "<- "
- val sp = " " * stack.length
-
- sp + stack.length + arrow
- }
- def printStackString(isPush: Boolean, value: TypeKind, instrString: String) = {
- val pushString = if (isPush) "+" else "-"
- val posString = posStr(this.instruction.pos)
-
- checkerDebug("%-70s %-4s %s %s".format(sizeString(isPush) + value, posString, pushString, instrString))
- }
- def _popStack: TypeKind = {
- if (stack.isEmpty) {
- icodeError("Popped empty stack in " + b.fullString + ", throwing a Unit")
- return UNIT
- }
- stack.pop
- }
- def popStackN(num: Int, instrFn: () => String = defaultInstrPrinter) = {
- List.range(0, num) map { _ =>
- val res = _popStack
- printStackString(isPush = false, res, instrFn())
- res
- }
- }
- def pushStackN(xs: Seq[TypeKind], instrFn: () => String) = {
- xs foreach { x =>
- stack push x
- printStackString(isPush = true, x, instrFn())
- }
- }
-
- def popStack = { checkStack(1) ; (popStackN(1): @unchecked) match { case List(x) => x } }
- def popStack2 = { checkStack(2) ; (popStackN(2): @unchecked) match { case List(x, y) => (x, y) } }
- def popStack3 = { checkStack(3) ; (popStackN(3): @unchecked) match { case List(x, y, z) => (x, y, z) } }
-
- /* Called by faux instruction LOAD_EXCEPTION to wipe out the stack. */
- def clearStack() = {
- if (stack.nonEmpty)
- logChecker("Wiping out the " + stack.length + " element stack for exception handler: " + stack)
-
- 1 to stack.length foreach (_ => popStack)
- }
-
- def pushStack(xs: TypeKind*): Unit = {
- pushStackN(xs filterNot (_ == UNIT), defaultInstrPrinter)
- }
-
- def typeError(k1: TypeKind, k2: TypeKind) {
- icodeError("\n expected: " + k1 + "\n found: " + k2)
- }
- def isSubtype(k1: TypeKind, k2: TypeKind) = (k1 isAssignabledTo k2) || {
- import platform.isMaybeBoxed
-
- (k1, k2) match {
- case (REFERENCE(_), REFERENCE(_)) if k1.isInterfaceType || k2.isInterfaceType =>
- logChecker("Considering %s <:< %s because at least one is an interface".format(k1, k2))
- true
- case (REFERENCE(cls1), REFERENCE(cls2)) if isMaybeBoxed(cls1) || isMaybeBoxed(cls2) =>
- logChecker("Considering %s <:< %s because at least one might be a boxed primitive".format(cls1, cls2))
- true
- case _ =>
- false
- }
- }
-
- def subtypeTest(k1: TypeKind, k2: TypeKind): Unit =
- if (isSubtype(k1, k2)) ()
- else typeError(k2, k1)
-
- for (instr <- b) {
- this.instruction = instr
-
- def checkLocal(local: Local) {
- if ((method lookupLocal local.sym.name).isEmpty)
- icodeError(s" $local is not defined in method $method")
- }
- def checkField(obj: TypeKind, field: Symbol): Unit = obj match {
- case REFERENCE(sym) =>
- if (sym.info.member(field.name) == NoSymbol)
- icodeError(" " + field + " is not defined in class " + clasz)
- case _ =>
- icodeError(" expected reference type, but " + obj + " found")
- }
-
- /* Checks that tpe is a subtype of one of the allowed types */
- def checkType(tpe: TypeKind, allowed: TypeKind*) = (
- if (allowed exists (k => isSubtype(tpe, k))) ()
- else icodeError(tpe + " is not one of: " + allowed.mkString("{ ", ", ", " }"))
- )
- def checkNumeric(tpe: TypeKind) =
- checkType(tpe, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE)
-
- /* Checks that the 2 topmost elements on stack are of the kind TypeKind. */
- def checkBinop(kind: TypeKind) {
- val (a, b) = popStack2
- checkType(a, kind)
- checkType(b, kind)
- }
-
- /* Check that arguments on the stack match method params. */
- def checkMethodArgs(method: Symbol) {
- val params = method.info.paramTypes
- checkStack(params.length)
- (
- popStackN(params.length, mkInstrPrinter(num => "<arg" + num + ">")),
- params.reverse map toTypeKind).zipped foreach ((x, y) => checkType(x, y)
- )
- }
-
- /* Checks that the object passed as receiver has a method
- * `method` and that it is callable from the current method.
- */
- def checkMethod(receiver: TypeKind, method: Symbol) =
- receiver match {
- case REFERENCE(sym) =>
- checkBool(sym.info.member(method.name) != NoSymbol,
- "Method " + method + " does not exist in " + sym.fullName)
- if (method.isPrivate)
- checkBool(method.owner == clasz.symbol,
- "Cannot call private method of " + method.owner.fullName
- + " from " + clasz.symbol.fullName)
- else if (method.isProtected) {
- val isProtectedOK = (
- (clasz.symbol isSubClass method.owner) ||
- (clasz.symbol.typeOfThis.typeSymbol isSubClass method.owner) // see pos/bug780.scala
- )
-
- checkBool(isProtectedOK,
- "Cannot call protected method of " + method.owner.fullName
- + " from " + clasz.symbol.fullName)
- }
-
- case ARRAY(_) =>
- checkBool(receiver.toType.member(method.name) != NoSymbol,
- "Method " + method + " does not exist in " + receiver)
-
- case t =>
- icodeError("Not a reference type: " + t)
- }
-
- def checkBool(cond: Boolean, msg: String) =
- if (!cond) icodeError(msg)
-
- if (settings.debug) {
- log("PC: " + instr)
- log("stack: " + stack)
- log("================")
- }
- instr match {
- case THIS(clasz) =>
- pushStack(toTypeKind(clasz.tpe))
-
- case CONSTANT(const) =>
- pushStack(toTypeKind(const.tpe))
-
- case LOAD_ARRAY_ITEM(kind) =>
- popStack2 match {
- case (INT, ARRAY(elem)) =>
- subtypeTest(elem, kind)
- pushStack(elem)
- case (a, b) =>
- icodeError(" expected an INT and an array reference, but " +
- a + ", " + b + " found")
- }
-
- case LOAD_LOCAL(local) =>
- checkLocal(local)
- pushStack(local.kind)
-
- case LOAD_FIELD(field, isStatic) =>
- // the symbol's owner should contain its field, but
- // this is already checked by the type checker, no need
- // to redo that here
- if (isStatic) ()
- else checkField(popStack, field)
-
- pushStack(toTypeKind(field.tpe))
-
- case LOAD_MODULE(module) =>
- checkBool((module.isModule || module.isModuleClass),
- "Expected module: " + module + " flags: " + module.flagString)
- pushStack(toTypeKind(module.tpe))
-
- case STORE_THIS(kind) =>
- val actualType = popStack
- if (actualType.isReferenceType) subtypeTest(actualType, kind)
- else icodeError("Expected this reference but found: " + actualType)
-
- case STORE_ARRAY_ITEM(kind) =>
- popStack3 match {
- case (k, INT, ARRAY(elem)) =>
- subtypeTest(k, kind)
- subtypeTest(k, elem)
- case (a, b, c) =>
- icodeError(" expected and array reference, and int and " + kind +
- " but " + a + ", " + b + ", " + c + " found")
- }
-
- case STORE_LOCAL(local) =>
- checkLocal(local)
- val actualType = popStack
- if (local.kind != NullReference)
- subtypeTest(actualType, local.kind)
-
- case STORE_FIELD(field, true) => // static
- val fieldType = toTypeKind(field.tpe)
- val actualType = popStack
- subtypeTest(actualType, fieldType)
-
- case STORE_FIELD(field, false) => // not static
- val (value, obj) = popStack2
- checkField(obj, field)
- val fieldType = toTypeKind(field.tpe)
- if (fieldType == NullReference) ()
- else subtypeTest(value, fieldType)
-
- case CALL_PRIMITIVE(primitive) =>
- checkStack(instr.consumed)
- primitive match {
- case Negation(kind) =>
- checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE)
- checkType(popStack, kind)
- pushStack(kind)
-
- case Test(op, kind, zero) =>
- if (zero) checkType(popStack, kind)
- else checkBinop(kind)
-
- pushStack(BOOL)
-
- case Comparison(op, kind) =>
- checkNumeric(kind)
- checkBinop(kind)
- pushStack(INT)
-
- case Arithmetic(op, kind) =>
- checkNumeric(kind)
- if (op == NOT)
- checkType(popStack, kind)
- else
- checkBinop(kind)
- pushStack(kind)
-
- case Logical(op, kind) =>
- checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG)
- checkBinop(kind)
- pushStack(kind)
-
- case Shift(op, kind) =>
- checkType(kind, BYTE, CHAR, SHORT, INT, LONG)
- val (a, b) = popStack2
- checkType(a, INT)
- checkType(b, kind)
- pushStack(kind)
-
- case Conversion(src, dst) =>
- checkNumeric(src)
- checkNumeric(dst)
- checkType(popStack, src)
- pushStack(dst)
-
- case ArrayLength(kind) =>
- popStack match {
- case ARRAY(elem) => checkType(elem, kind)
- case arr => icodeError(" array reference expected, but " + arr + " found")
- }
- pushStack(INT)
-
- case StartConcat =>
- pushStack(ConcatClass)
-
- case EndConcat =>
- checkType(popStack, ConcatClass)
- pushStack(StringReference)
-
- case StringConcat(el) =>
- checkType(popStack, el)
- checkType(popStack, ConcatClass)
- pushStack(ConcatClass)
- }
-
- case CALL_METHOD(method, style) =>
- // PP to ID: I moved the if (!method.isConstructor) check to cover all
- // the styles to address checker failure. Can you confirm if the change
- // was correct? If I remember right it's a matter of whether some brand
- // of supercall should leave a value on the stack, and I know there is some
- // trickery performed elsewhere regarding this.
- val paramCount = method.info.paramTypes.length match {
- case x if style.hasInstance => x + 1
- case x => x
- }
- if (style == Static(onInstance = true))
- checkBool(method.isPrivate || method.isConstructor, "Static call to non-private method.")
-
- checkStack(paramCount)
- checkMethodArgs(method)
- if (style.hasInstance)
- checkMethod(popStack, method)
- if (!method.isConstructor)
- pushStack(toTypeKind(method.info.resultType))
-
- case NEW(kind) =>
- pushStack(kind)
-
- case CREATE_ARRAY(elem, dims) =>
- checkStack(dims)
- stack.pop(dims) foreach (checkType(_, INT))
- pushStack(ARRAY(elem))
-
- case IS_INSTANCE(tpe) =>
- val ref = popStack
- checkBool(!ref.isValueType, "IS_INSTANCE on primitive type: " + ref)
- checkBool(!tpe.isValueType, "IS_INSTANCE on primitive type: " + tpe)
- pushStack(BOOL)
-
- case CHECK_CAST(tpe) =>
- val ref = popStack
- checkBool(!ref.isValueType, "CHECK_CAST to primitive type: " + ref)
- checkBool(!tpe.isValueType, "CHECK_CAST to primitive type: " + tpe)
- pushStack(tpe)
-
- case SWITCH(tags, labels) =>
- checkType(popStack, INT)
- checkBool(tags.length == labels.length - 1,
- "The number of tags and labels does not coincide.")
- checkBool(labels forall (b => code.blocks contains b),
- "Switch target cannot be found in code.")
-
- case JUMP(whereto) =>
- checkBool(code.blocks contains whereto,
- "Jump to non-existant block " + whereto)
-
- case CJUMP(success, failure, cond, kind) =>
- checkBool(code.blocks contains success,
- "Jump to non-existant block " + success)
- checkBool(code.blocks contains failure,
- "Jump to non-existant block " + failure)
- checkBinop(kind)
-
- case CZJUMP(success, failure, cond, kind) =>
- checkBool(code.blocks contains success,
- "Jump to non-existant block " + success)
- checkBool(code.blocks contains failure,
- "Jump to non-existant block " + failure)
- checkType(popStack, kind)
-
- case RETURN(UNIT) => ()
- case RETURN(kind) =>
- val top = popStack
- if (kind.isValueType) checkType(top, kind)
- else checkBool(!top.isValueType, "" + kind + " is a reference type, but " + top + " is not")
-
- case THROW(clasz) =>
- checkType(popStack, toTypeKind(clasz.tpe))
- pushStack(NothingReference)
-
- case DROP(kind) =>
- checkType(popStack, kind)
-
- case DUP(kind) =>
- val top = popStack
- checkType(top, kind)
- pushStack(top)
- pushStack(top)
-
- case MONITOR_ENTER() =>
- checkBool(popStack.isReferenceType, "MONITOR_ENTER on non-reference type")
-
- case MONITOR_EXIT() =>
- checkBool(popStack.isReferenceType, "MONITOR_EXIT on non-reference type")
-
- case BOX(kind) =>
- checkType(popStack, kind)
- pushStack(REFERENCE(definitions.boxedClass(kind.toType.typeSymbol)))
-
- case UNBOX(kind) =>
- popStack
- pushStack(kind)
-
- case LOAD_EXCEPTION(clasz) =>
- clearStack()
- pushStack(REFERENCE(clasz))
-
- case SCOPE_ENTER(_) | SCOPE_EXIT(_) =>
- ()
-
- case _ =>
- abort("Unknown instruction: " + instr)
- }
- }
- stack
- }
-
- //////////////// Error reporting /////////////////////////
-
- def icodeError(msg: String) {
- ICodeCheckers.this.global.warning(
- "!! ICode checker fatality in " + method +
- "\n at: " + basicBlock.fullString +
- "\n error message: " + msg
- )
- }
-
- def icodeError(msg: String, stack: TypeStack) {
- icodeError(msg + "\n type stack: " + stack)
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala b/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala
deleted file mode 100644
index 10f0c6ee00..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala
+++ /dev/null
@@ -1,129 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-import java.io.PrintWriter
-import analysis.{ Liveness, ReachingDefinitions }
-import scala.tools.nsc.symtab.classfile.ICodeReader
-import scala.reflect.io.AbstractFile
-
-/** Glue together ICode parts.
- *
- * @author Iulian Dragos
- */
-abstract class ICodes extends AnyRef
- with Members
- with BasicBlocks
- with Opcodes
- with TypeStacks
- with TypeKinds
- with ExceptionHandlers
- with Primitives
- with Linearizers
- with Printers
- with Repository
-{
- val global: Global
- import global.{ log, definitions, settings, perRunCaches, devWarning }
-
- /** The ICode representation of classes */
- val classes = perRunCaches.newMap[global.Symbol, IClass]()
-
- /** Debugging flag */
- def shouldCheckIcode = settings.check contains global.genicode.phaseName
- def checkerDebug(msg: String) = if (shouldCheckIcode && global.settings.debug) println(msg)
-
- /** The ICode linearizer. */
- val linearizer: Linearizer = settings.Xlinearizer.value match {
- case "rpo" => new ReversePostOrderLinearizer()
- case "dfs" => new DepthFirstLinerizer()
- case "normal" => new NormalLinearizer()
- case "dump" => new DumpLinearizer()
- case x => global.abort("Unknown linearizer: " + x)
- }
-
- def newTextPrinter() =
- new TextPrinter(new PrintWriter(Console.out, true), new DumpLinearizer)
-
- /** Have to be careful because dump calls around, possibly
- * re-entering methods which initiated the dump (like foreach
- * in BasicBlocks) which leads to the icode output olympics.
- */
- private var alreadyDumping = false
-
- /** Print all classes and basic blocks. Used for debugging. */
-
- def dumpClassesAndAbort(msg: String): Nothing = {
- if (alreadyDumping) global.abort(msg)
- else alreadyDumping = true
-
- Console.println(msg)
- val printer = newTextPrinter()
- classes.values foreach printer.printClass
- global.abort(msg)
- }
-
- def dumpMethodAndAbort(m: IMethod, msg: String): Nothing = {
- Console.println("Fatal bug in inlinerwhile traversing " + m + ": " + msg)
- m.dump()
- global.abort("" + m)
- }
- def dumpMethodAndAbort(m: IMethod, b: BasicBlock): Nothing =
- dumpMethodAndAbort(m, "found open block " + b + " " + b.flagsString)
-
- def checkValid(m: IMethod) {
- // always slightly dicey to iterate over mutable structures
- m foreachBlock { b =>
- if (!b.closed) {
- // Something is leaving open/empty blocks around (see SI-4840) so
- // let's not kill the deal unless it's nonempty.
- if (b.isEmpty) {
- devWarning(s"Found open but empty block while inlining $m: removing from block list.")
- m.code removeBlock b
- }
- else dumpMethodAndAbort(m, b)
- }
- }
- }
-
- object liveness extends Liveness {
- val global: ICodes.this.global.type = ICodes.this.global
- }
-
- object reachingDefinitions extends ReachingDefinitions {
- val global: ICodes.this.global.type = ICodes.this.global
- }
-
- lazy val AnyRefReference: TypeKind = REFERENCE(definitions.AnyRefClass)
- lazy val BoxedUnitReference: TypeKind = REFERENCE(definitions.BoxedUnitClass)
- lazy val NothingReference: TypeKind = REFERENCE(definitions.NothingClass)
- lazy val NullReference: TypeKind = REFERENCE(definitions.NullClass)
- lazy val ObjectReference: TypeKind = REFERENCE(definitions.ObjectClass)
- lazy val StringReference: TypeKind = REFERENCE(definitions.StringClass)
-
- object icodeReader extends ICodeReader {
- lazy val global: ICodes.this.global.type = ICodes.this.global
- import global._
- def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol =
- global.loaders.lookupMemberAtTyperPhaseIfPossible(sym, name)
- lazy val symbolTable: global.type = global
- lazy val loaders: global.loaders.type = global.loaders
-
- def classFileLookup: util.ClassFileLookup[AbstractFile] = global.classPath
- }
-
- /** A phase which works on icode. */
- abstract class ICodePhase(prev: Phase) extends global.GlobalPhase(prev) {
- override def erasedTypes = true
- override def apply(unit: global.CompilationUnit): Unit =
- unit.icode foreach apply
-
- def apply(cls: global.icodes.IClass): Unit
- }
-}
-
diff --git a/src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala b/src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala
deleted file mode 100644
index 54be9d18f1..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala
+++ /dev/null
@@ -1,201 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala
-package tools.nsc
-package backend
-package icode
-
-import scala.collection.{ mutable, immutable }
-import mutable.ListBuffer
-
-trait Linearizers {
- self: ICodes =>
-
- import global.debuglog
- import opcodes._
-
- abstract class Linearizer {
- def linearize(c: IMethod): List[BasicBlock]
- def linearizeAt(c: IMethod, start: BasicBlock): List[BasicBlock]
- }
-
- /**
- * A simple linearizer which predicts all branches to
- * take the 'success' branch and tries to schedule those
- * blocks immediately after the test. This is in sync with
- * how 'while' statements are translated (if the test is
- * 'true', the loop continues).
- */
- class NormalLinearizer extends Linearizer with WorklistAlgorithm {
- type Elem = BasicBlock
- val worklist: WList = new mutable.Stack()
- var blocks: List[BasicBlock] = Nil
-
- def linearize(m: IMethod): List[BasicBlock] = {
- val b = m.startBlock
- blocks = Nil
-
- run {
- worklist pushAll (m.exh map (_.startBlock))
- worklist.push(b)
- }
-
- blocks.reverse
- }
-
- def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = {
- blocks = Nil
- worklist.clear()
- linearize(start)
- }
-
- /** Linearize another subtree and append it to the existing blocks. */
- def linearize(startBlock: BasicBlock): List[BasicBlock] = {
- //blocks = startBlock :: Nil;
- run( { worklist.push(startBlock); } )
- blocks.reverse
- }
-
- def processElement(b: BasicBlock) =
- if (b.nonEmpty) {
- add(b)
- b.lastInstruction match {
- case JUMP(whereto) =>
- add(whereto)
- case CJUMP(success, failure, _, _) =>
- add(success)
- add(failure)
- case CZJUMP(success, failure, _, _) =>
- add(success)
- add(failure)
- case SWITCH(_, labels) =>
- add(labels)
- case RETURN(_) => ()
- case THROW(clasz) => ()
- }
- }
-
- def dequeue: Elem = worklist.pop()
-
- /**
- * Prepend b to the list, if not already scheduled.
- * TODO: use better test than linear search
- */
- def add(b: BasicBlock) {
- if (blocks.contains(b))
- ()
- else {
- blocks = b :: blocks
- worklist push b
- }
- }
-
- def add(bs: List[BasicBlock]): Unit = bs foreach add
- }
-
- /**
- * Linearize code using a depth first traversal.
- */
- class DepthFirstLinerizer extends Linearizer {
- var blocks: List[BasicBlock] = Nil
-
- def linearize(m: IMethod): List[BasicBlock] = {
- blocks = Nil
-
- dfs(m.startBlock)
- m.exh foreach (b => dfs(b.startBlock))
-
- blocks.reverse
- }
-
- def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = {
- blocks = Nil
- dfs(start)
- blocks.reverse
- }
-
- def dfs(b: BasicBlock): Unit =
- if (b.nonEmpty && add(b))
- b.successors foreach dfs
-
- /**
- * Prepend b to the list, if not already scheduled.
- * TODO: use better test than linear search
- * @return Returns true if the block was added.
- */
- def add(b: BasicBlock): Boolean =
- !(blocks contains b) && {
- blocks = b :: blocks
- true
- }
- }
-
- /**
- * Linearize code in reverse post order. In fact, it does
- * a post order traversal, prepending visited nodes to the list.
- * This way, it is constructed already in reverse post order.
- */
- class ReversePostOrderLinearizer extends Linearizer {
- var blocks: List[BasicBlock] = Nil
- val visited = new mutable.HashSet[BasicBlock]
- val added = new mutable.BitSet
-
- def linearize(m: IMethod): List[BasicBlock] = {
- blocks = Nil
- visited.clear()
- added.clear()
-
- m.exh foreach (b => rpo(b.startBlock))
- rpo(m.startBlock)
-
- // if the start block has predecessors, it won't be the first one
- // in the linearization, so we need to enforce it here
- if (m.startBlock.predecessors eq Nil)
- blocks
- else
- m.startBlock :: (blocks.filterNot(_ == m.startBlock))
- }
-
- def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = {
- blocks = Nil
- visited.clear()
- added.clear()
-
- rpo(start)
- blocks
- }
-
- def rpo(b: BasicBlock): Unit =
- if (b.nonEmpty && !visited(b)) {
- visited += b
- b.successors foreach rpo
- add(b)
- }
-
- /**
- * Prepend b to the list, if not already scheduled.
- * @return Returns true if the block was added.
- */
- def add(b: BasicBlock) = {
- debuglog("Linearizer adding block " + b.label)
-
- if (!added(b.label)) {
- added += b.label
- blocks = b :: blocks
- }
- }
- }
-
- /** A 'dump' of the blocks in this method, which does not
- * require any well-formedness of the basic blocks (like
- * the last instruction being a jump).
- */
- class DumpLinearizer extends Linearizer {
- def linearize(m: IMethod): List[BasicBlock] = m.blocks
- def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = sys.error("not implemented")
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/Members.scala b/src/compiler/scala/tools/nsc/backend/icode/Members.scala
deleted file mode 100644
index 64146585e5..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/Members.scala
+++ /dev/null
@@ -1,296 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala
-package tools.nsc
-package backend
-package icode
-
-import scala.collection.{ mutable, immutable }
-import scala.reflect.internal.util.{ SourceFile, NoSourceFile }
-
-trait ReferenceEquality {
- override def hashCode = System.identityHashCode(this)
- override def equals(that: Any) = this eq that.asInstanceOf[AnyRef]
-}
-
-trait Members {
- self: ICodes =>
-
- import global._
-
- object NoCode extends Code(null, TermName("NoCode")) {
- override def blocksList: List[BasicBlock] = Nil
- }
-
- /**
- * This class represents the intermediate code of a method or
- * other multi-block piece of code, like exception handlers.
- */
- class Code(method: IMethod, name: Name) {
- def this(method: IMethod) = this(method, method.symbol.name)
- /** The set of all blocks */
- val blocks = mutable.ListBuffer[BasicBlock]()
-
- /** The start block of the method */
- var startBlock: BasicBlock = NoBasicBlock
-
- private var currentLabel: Int = 0
- private var _touched = false
-
- def blocksList: List[BasicBlock] = blocks.toList
- def instructions = blocksList flatMap (_.iterator)
- def blockCount = blocks.size
- def instructionCount = (blocks map (_.length)).sum
-
- def touched = _touched
- def touched_=(b: Boolean): Unit = {
- @annotation.tailrec def loop(xs: List[BasicBlock]) {
- xs match {
- case Nil =>
- case x :: xs => x.touched = true ; loop(xs)
- }
- }
- if (b) loop(blocks.toList)
-
- _touched = b
- }
-
- // Constructor code
- startBlock = newBlock()
-
- def removeBlock(b: BasicBlock) {
- if (settings.debug) {
- // only do this sanity check when debug is turned on because it's moderately expensive
- val referers = blocks filter (_.successors contains b)
- assert(referers.isEmpty, s"Trying to removing block $b (with preds ${b.predecessors.mkString}) but it is still refered to from block(s) ${referers.mkString}")
- }
-
- if (b == startBlock) {
- assert(b.successors.length == 1,
- s"Removing start block ${b} with ${b.successors.length} successors (${b.successors.mkString})."
- )
- startBlock = b.successors.head
- }
-
- blocks -= b
- assert(!blocks.contains(b))
- method.exh filter (_ covers b) foreach (_.covered -= b)
- touched = true
- }
-
- /** This methods returns a string representation of the ICode */
- override def toString = "ICode '" + name.decoded + "'"
-
- /* Compute a unique new label */
- def nextLabel: Int = {
- currentLabel += 1
- currentLabel
- }
-
- /* Create a new block and append it to the list
- */
- def newBlock(): BasicBlock = {
- touched = true
- val block = new BasicBlock(nextLabel, method)
- blocks += block
- block
- }
- }
-
- /** Common interface for IClass/IField/IMethod. */
- trait IMember extends Ordered[IMember] {
- def symbol: Symbol
-
- def compare(other: IMember) =
- if (symbol eq other.symbol) 0
- else if (symbol isLess other.symbol) -1
- else 1
-
- override def equals(other: Any): Boolean =
- other match {
- case other: IMember => (this compare other) == 0
- case _ => false
- }
-
- override def hashCode = symbol.##
- }
-
- /** Represent a class in ICode */
- class IClass(val symbol: Symbol) extends IMember {
- var fields: List[IField] = Nil
- var methods: List[IMethod] = Nil
- var cunit: CompilationUnit = _
-
- def addField(f: IField): this.type = {
- fields = f :: fields
- this
- }
-
- def addMethod(m: IMethod): this.type = {
- methods = m :: methods
- this
- }
-
- def setCompilationUnit(unit: CompilationUnit): this.type = {
- this.cunit = unit
- this
- }
-
- override def toString() = symbol.fullName
-
- def lookupMethod(s: Symbol) = methods find (_.symbol == s)
-
- /* returns this methods static ctor if it has one. */
- def lookupStaticCtor: Option[IMethod] = methods find (_.symbol.isStaticConstructor)
- }
-
- /** Represent a field in ICode */
- class IField(val symbol: Symbol) extends IMember { }
-
- object NoIMethod extends IMethod(NoSymbol) { }
-
- /**
- * Represents a method in ICode. Local variables contain
- * both locals and parameters, similar to the way the JVM
- * 'sees' them.
- *
- * Locals and parameters are added in reverse order, as they
- * are kept in cons-lists. The 'builder' is responsible for
- * reversing them and putting them back, when the generation is
- * finished (GenICode does that).
- */
- class IMethod(val symbol: Symbol) extends IMember {
- var code: Code = NoCode
-
- def newBlock() = code.newBlock()
- def startBlock = code.startBlock
- def lastBlock = { assert(blocks.nonEmpty, symbol); blocks.last }
- def blocks = code.blocksList
- def linearizedBlocks(lin: Linearizer = self.linearizer): List[BasicBlock] = lin linearize this
-
- def foreachBlock[U](f: BasicBlock => U): Unit = blocks foreach f
-
- var native = false
-
- /** The list of exception handlers, ordered from innermost to outermost. */
- var exh: List[ExceptionHandler] = Nil
- var sourceFile: SourceFile = NoSourceFile
- var returnType: TypeKind = _
- var recursive: Boolean = false
- var bytecodeHasEHs = false // set by ICodeReader only, used by Inliner to prevent inlining (SI-6188)
- var bytecodeHasInvokeDynamic = false // set by ICodeReader only, used by Inliner to prevent inlining until we have proper invoke dynamic support
-
- /** local variables and method parameters */
- var locals: List[Local] = Nil
-
- /** method parameters */
- var params: List[Local] = Nil
-
- def hasCode = code ne NoCode
- def setCode(code: Code): IMethod = {
- this.code = code
- this
- }
-
- final def updateRecursive(called: Symbol): Unit = {
- recursive ||= (called == symbol)
- }
-
- def addLocal(l: Local): Local = findOrElse(locals)(_ == l) { locals ::= l ; l }
-
- def addParam(p: Local): Unit =
- if (params contains p) ()
- else {
- params ::= p
- locals ::= p
- }
-
- def addLocals(ls: List[Local]) = ls foreach addLocal
-
- def lookupLocal(n: Name): Option[Local] = locals find (_.sym.name == n)
- def lookupLocal(sym: Symbol): Option[Local] = locals find (_.sym == sym)
-
- def addHandler(e: ExceptionHandler) = exh ::= e
-
- /** Is this method deferred ('abstract' in Java sense)?
- */
- def isAbstractMethod = symbol.isDeferred || symbol.owner.isInterface || native
-
- def isStatic: Boolean = symbol.isStaticMember
-
- override def toString() = symbol.fullName
-
- import opcodes._
-
- /** Merge together blocks that have a single successor which has a
- * single predecessor. Exception handlers are taken into account (they
- * might force to break a block of straight line code like that).
- *
- * This method should be most effective after heavy inlining.
- */
- def normalize(): Unit = if (this.hasCode) {
- val nextBlock: mutable.Map[BasicBlock, BasicBlock] = mutable.HashMap.empty
- for (b <- code.blocks.toList
- if b.successors.length == 1;
- succ = b.successors.head
- if succ ne b
- if succ.predecessors.length == 1
- if succ.predecessors.head eq b
- if !(exh.exists { (e: ExceptionHandler) =>
- (e.covers(succ) && !e.covers(b)) || (e.covers(b) && !e.covers(succ)) })) {
- nextBlock(b) = succ
- }
-
- var bb = code.startBlock
- while (!nextBlock.isEmpty) {
- if (nextBlock.isDefinedAt(bb)) {
- bb.open()
- var succ = bb
- do {
- succ = nextBlock(succ)
- val lastInstr = bb.lastInstruction
- /* Ticket SI-5672
- * Besides removing the control-flow instruction at the end of `bb` (usually a JUMP), we have to pop any values it pushes.
- * Examples:
- * `SWITCH` consisting of just the default case, or
- * `CJUMP(targetBlock, targetBlock, _, _)` ie where success and failure targets coincide (this one consumes two stack values).
- */
- val oldTKs = lastInstr.consumedTypes
- assert(lastInstr.consumed == oldTKs.size, "Someone forgot to override consumedTypes() in " + lastInstr)
-
- bb.removeLastInstruction()
- for(tk <- oldTKs.reverse) { bb.emit(DROP(tk), lastInstr.pos) }
- succ.toList foreach { i => bb.emit(i, i.pos) }
- code.removeBlock(succ)
- exh foreach { e => e.covered = e.covered - succ }
-
- nextBlock -= bb
- } while (nextBlock.isDefinedAt(succ))
- bb.close()
- } else
- bb = nextBlock.keysIterator.next()
- }
- checkValid(this)
- }
-
- def dump() {
- Console.println("dumping IMethod(" + symbol + ")")
- newTextPrinter() printMethod this
- }
- }
-
- /** Represent local variables and parameters */
- class Local(val sym: Symbol, val kind: TypeKind, val arg: Boolean) {
- var index: Int = -1
-
- override def equals(other: Any): Boolean = other match {
- case x: Local => sym == x.sym
- case _ => false
- }
- override def hashCode = sym.hashCode
- override def toString(): String = sym.toString
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala
deleted file mode 100644
index 351a8e33d3..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala
+++ /dev/null
@@ -1,767 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala
-package tools.nsc
-package backend
-package icode
-
-import scala.reflect.internal.util.{Position,NoPosition}
-
-/*
- A pattern match
-
- // locals
- case THIS(clasz) =>
- case STORE_THIS(kind) =>
- case LOAD_LOCAL(local) =>
- case STORE_LOCAL(local) =>
- case SCOPE_ENTER(lv) =>
- case SCOPE_EXIT(lv) =>
- // stack
- case LOAD_MODULE(module) =>
- case LOAD_EXCEPTION(clasz) =>
- case DROP(kind) =>
- case DUP(kind) =>
- // constants
- case CONSTANT(const) =>
- // arithlogic
- case CALL_PRIMITIVE(primitive) =>
- // casts
- case IS_INSTANCE(tpe) =>
- case CHECK_CAST(tpe) =>
- // objs
- case NEW(kind) =>
- case MONITOR_ENTER() =>
- case MONITOR_EXIT() =>
- case BOX(boxType) =>
- case UNBOX(tpe) =>
- // flds
- case LOAD_FIELD(field, isStatic) =>
- case STORE_FIELD(field, isStatic) =>
- // mthds
- case CALL_METHOD(method, style) =>
- // arrays
- case LOAD_ARRAY_ITEM(kind) =>
- case STORE_ARRAY_ITEM(kind) =>
- case CREATE_ARRAY(elem, dims) =>
- // jumps
- case SWITCH(tags, labels) =>
- case JUMP(whereto) =>
- case CJUMP(success, failure, cond, kind) =>
- case CZJUMP(success, failure, cond, kind) =>
- // ret
- case RETURN(kind) =>
- case THROW(clasz) =>
-*/
-
-
-/**
- * The ICode intermediate representation. It is a stack-based
- * representation, very close to the JVM and .NET. It uses the
- * erased types of Scala and references Symbols to refer named entities
- * in the source files.
- */
-trait Opcodes { self: ICodes =>
- import global.{Symbol, NoSymbol, Name, Constant}
-
- // categories of ICode instructions
- final val localsCat = 1
- final val stackCat = 2
- final val constCat = 3
- final val arilogCat = 4
- final val castsCat = 5
- final val objsCat = 6
- final val fldsCat = 7
- final val mthdsCat = 8
- final val arraysCat = 9
- final val jumpsCat = 10
- final val retCat = 11
-
- private lazy val ObjectReferenceList = ObjectReference :: Nil
-
- /** This class represents an instruction of the intermediate code.
- * Each case subclass will represent a specific operation.
- */
- abstract class Instruction extends Cloneable {
- // Vlad: I used these for checking the quality of the implementation, and we should regularly run a build with them
- // enabled. But for production these should definitely be disabled, unless we enjoy getting angry emails from Greg :)
- //if (!this.isInstanceOf[opcodes.LOAD_EXCEPTION])
- // assert(consumed == consumedTypes.length)
- //assert(produced == producedTypes.length)
-
- def category: Int = 0 // undefined
-
- /** This abstract method returns the number of used elements on the stack */
- def consumed : Int = 0
-
- /** This abstract method returns the number of produced elements on the stack */
- def produced : Int = 0
-
- /** This instruction consumes these types from the top of the stack, the first
- * element in the list is the deepest element on the stack.
- */
- def consumedTypes: List[TypeKind] = Nil
-
- /** This instruction produces these types on top of the stack. */
- // Vlad: I wonder why we keep producedTypes around -- it looks like an useless thing to have
- def producedTypes: List[TypeKind] = Nil
-
- /** The corresponding position in the source file */
- private var _pos: Position = NoPosition
-
- def pos: Position = _pos
-
- def setPos(p: Position): this.type = {
- _pos = p
- this
- }
-
- /** Clone this instruction. */
- override def clone(): Instruction =
- super.clone.asInstanceOf[Instruction]
- }
-
- object opcodes {
- /** Loads "this" on top of the stack.
- * Stack: ...
- * ->: ...:ref
- */
- case class THIS(clasz: Symbol) extends Instruction {
- /** Returns a string representation of this constant */
- override def toString = "THIS(" + clasz.name + ")"
-
- override def consumed = 0
- override def produced = 1
-
- override def producedTypes =
- // we're not allowed to have REFERENCE(Array), but what about compiling the Array class? Well, we use object for it.
- if (clasz != global.definitions.ArrayClass)
- REFERENCE(clasz) :: Nil
- else
- ObjectReference :: Nil
-
- override def category = localsCat
- }
-
- /** Loads a constant on the stack.
- * Stack: ...
- * ->: ...:constant
- */
- case class CONSTANT(constant: Constant) extends Instruction {
- override def toString = "CONSTANT(" + constant.escapedStringValue + ")"
- override def consumed = 0
- override def produced = 1
-
- override def producedTypes = toTypeKind(constant.tpe) :: Nil
-
- override def category = constCat
- }
-
- /** Loads an element of an array. The array and the index should
- * be on top of the stack.
- * Stack: ...:array[a](Ref):index(Int)
- * ->: ...:element(a)
- */
- case class LOAD_ARRAY_ITEM(kind: TypeKind) extends Instruction {
- override def consumed = 2
- override def produced = 1
-
- override def consumedTypes = ARRAY(kind) :: INT :: Nil
- override def producedTypes = kind :: Nil
-
- override def category = arraysCat
- }
-
- /** Load a local variable on the stack. It can be a method argument.
- * Stack: ...
- * ->: ...:value
- */
- case class LOAD_LOCAL(local: Local) extends Instruction {
- override def consumed = 0
- override def produced = 1
-
- override def producedTypes = local.kind :: Nil
-
- override def category = localsCat
- }
-
- /** Load a field on the stack. The object to which it refers should be
- * on the stack.
- * Stack: ...:ref (assuming isStatic = false)
- * ->: ...:value
- */
- case class LOAD_FIELD(field: Symbol, isStatic: Boolean) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String =
- "LOAD_FIELD " + (if (isStatic) field.fullName else field.toString())
-
- override def consumed = if (isStatic) 0 else 1
- override def produced = 1
-
- override def consumedTypes = if (isStatic) Nil else REFERENCE(field.owner) :: Nil
- override def producedTypes = toTypeKind(field.tpe) :: Nil
-
- // more precise information about how to load this field
- // see #4283
- var hostClass: Symbol = field.owner
- def setHostClass(cls: Symbol): this.type = { hostClass = cls; this }
-
- override def category = fldsCat
- }
-
- case class LOAD_MODULE(module: Symbol) extends Instruction {
- assert(module != NoSymbol, "Invalid module symbol")
- /** Returns a string representation of this instruction */
- override def toString(): String = "LOAD_MODULE " + module
-
- override def consumed = 0
- override def produced = 1
-
- override def producedTypes = REFERENCE(module) :: Nil
-
- override def category = stackCat
- }
-
- /** Store a value into an array at a specified index.
- * Stack: ...:array[a](Ref):index(Int):value(a)
- * ->: ...
- */
- case class STORE_ARRAY_ITEM(kind: TypeKind) extends Instruction {
- override def consumed = 3
- override def produced = 0
-
- override def consumedTypes = ARRAY(kind) :: INT :: kind :: Nil
-
- override def category = arraysCat
- }
-
- /** Store a value into a local variable. It can be an argument.
- * Stack: ...:value
- * ->: ...
- */
- case class STORE_LOCAL(local: Local) extends Instruction {
- override def consumed = 1
- override def produced = 0
-
- override def consumedTypes = local.kind :: Nil
-
- override def category = localsCat
- }
-
- /** Store a value into a field.
- * Stack: ...:ref:value (assuming isStatic=false)
- * ->: ...
- */
- case class STORE_FIELD(field: Symbol, isStatic: Boolean) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String =
- "STORE_FIELD "+field + (if (isStatic) " (static)" else " (dynamic)")
-
- override def consumed = if(isStatic) 1 else 2
-
- override def produced = 0
-
- override def consumedTypes =
- if (isStatic)
- toTypeKind(field.tpe) :: Nil
- else
- REFERENCE(field.owner) :: toTypeKind(field.tpe) :: Nil
-
- override def category = fldsCat
- }
-
- /** Store a value into the 'this' pointer.
- * Stack: ...:ref
- * ->: ...
- */
- case class STORE_THIS(kind: TypeKind) extends Instruction {
- override def consumed = 1
- override def produced = 0
- override def consumedTypes = kind :: Nil
- override def category = localsCat
- }
-
- /** Call a primitive function.
- * Stack: ...:arg1:arg2:...:argn
- * ->: ...:result
- */
- case class CALL_PRIMITIVE(primitive: Primitive) extends Instruction {
- override def consumed = primitive match {
- case Negation(_) => 1
- case Test(_,_, true) => 1
- case Test(_,_, false) => 2
- case Comparison(_,_) => 2
- case Arithmetic(NOT,_) => 1
- case Arithmetic(_,_) => 2
- case Logical(_,_) => 2
- case Shift(_,_) => 2
- case Conversion(_,_) => 1
- case ArrayLength(_) => 1
- case StringConcat(_) => 2
- case StartConcat => 0
- case EndConcat => 1
- }
- override def produced = 1
-
- override def consumedTypes = primitive match {
- case Negation(kind) => kind :: Nil
- case Test(_, kind, true) => kind :: Nil
- case Test(_, kind, false) => kind :: kind :: Nil
- case Comparison(_, kind) => kind :: kind :: Nil
- case Arithmetic(NOT, kind) => kind :: Nil
- case Arithmetic(_, kind) => kind :: kind :: Nil
- case Logical(_, kind) => kind :: kind :: Nil
- case Shift(_, kind) => kind :: INT :: Nil
- case Conversion(from, _) => from :: Nil
- case ArrayLength(kind) => ARRAY(kind) :: Nil
- case StringConcat(kind) => ConcatClass :: kind :: Nil
- case StartConcat => Nil
- case EndConcat => ConcatClass :: Nil
- }
-
- override def producedTypes = primitive match {
- case Negation(kind) => kind :: Nil
- case Test(_, _, true) => BOOL :: Nil
- case Test(_, _, false) => BOOL :: Nil
- case Comparison(_, _) => INT :: Nil
- case Arithmetic(_, kind) => kind :: Nil
- case Logical(_, kind) => kind :: Nil
- case Shift(_, kind) => kind :: Nil
- case Conversion(_, to) => to :: Nil
- case ArrayLength(_) => INT :: Nil
- case StringConcat(_) => ConcatClass :: Nil
- case StartConcat => ConcatClass :: Nil
- case EndConcat => REFERENCE(global.definitions.StringClass) :: Nil
- }
-
- override def category = arilogCat
- }
-
- /** This class represents a CALL_METHOD instruction
- * STYLE: dynamic / static(StaticInstance)
- * Stack: ...:ref:arg1:arg2:...:argn
- * ->: ...:result
- *
- * STYLE: static(StaticClass)
- * Stack: ...:arg1:arg2:...:argn
- * ->: ...:result
- *
- */
- case class CALL_METHOD(method: Symbol, style: InvokeStyle) extends Instruction with ReferenceEquality {
- def toShortString =
- "CALL_METHOD " + method.name +" ("+style+")"
-
- /** Returns a string representation of this instruction */
- override def toString(): String =
- "CALL_METHOD " + method.fullName +" ("+style+")"
-
- var hostClass: Symbol = method.owner
- def setHostClass(cls: Symbol): this.type = { hostClass = cls; this }
-
- /** This is specifically for preserving the target native Array type long
- * enough that clone() can generate the right call.
- */
- var targetTypeKind: TypeKind = UNIT // the default should never be used, so UNIT should fail fast.
- def setTargetTypeKind(tk: TypeKind) = targetTypeKind = tk
-
- private def params = method.info.paramTypes
- private def consumesInstance = style match {
- case Static(false) => 0
- case _ => 1
- }
-
- override def consumed = params.length + consumesInstance
- override def consumedTypes = {
- val args = params map toTypeKind
- if (consumesInstance > 0) ObjectReference :: args
- else args
- }
-
- private val producedList = toTypeKind(method.info.resultType) match {
- case UNIT => Nil
- case _ if method.isConstructor => Nil
- case kind => kind :: Nil
- }
- override def produced = producedList.size
- override def producedTypes = producedList
-
- /** object identity is equality for CALL_METHODs. Needed for
- * being able to store such instructions into maps, when more
- * than one CALL_METHOD to the same method might exist.
- */
-
- override def category = mthdsCat
- }
-
- /**
- * A place holder entry that allows us to parse class files with invoke dynamic
- * instructions. Because the compiler doesn't yet really understand the
- * behavior of invokeDynamic, this op acts as a poison pill. Any attempt to analyze
- * this instruction will cause a failure. The only optimization that
- * should ever look at non-Scala generated icode is the inliner, and it
- * has been modified to not examine any method with invokeDynamic
- * instructions. So if this poison pill ever causes problems then
- * there's been a serious misunderstanding
- */
- // TODO do the real thing
- case class INVOKE_DYNAMIC(poolEntry: Int) extends Instruction {
- private def error = sys.error("INVOKE_DYNAMIC is not fully implemented and should not be analyzed")
- override def consumed = error
- override def produced = error
- override def producedTypes = error
- override def category = error
- }
-
- case class BOX(boxType: TypeKind) extends Instruction {
- assert(boxType.isValueType && (boxType ne UNIT)) // documentation
- override def toString(): String = "BOX " + boxType
- override def consumed = 1
- override def consumedTypes = boxType :: Nil
- override def produced = 1
- override def producedTypes = BOXED(boxType) :: Nil
- override def category = objsCat
- }
-
- case class UNBOX(boxType: TypeKind) extends Instruction {
- assert(boxType.isValueType && !boxType.isInstanceOf[BOXED] && (boxType ne UNIT)) // documentation
- override def toString(): String = "UNBOX " + boxType
- override def consumed = 1
- override def consumedTypes = ObjectReferenceList
- override def produced = 1
- override def producedTypes = boxType :: Nil
- override def category = objsCat
- }
-
- /** Create a new instance of a class through the specified constructor
- * Stack: ...:arg1:arg2:...:argn
- * ->: ...:ref
- */
- case class NEW(kind: REFERENCE) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String = "NEW "+ kind
-
- override def consumed = 0
-
- override def produced = 1
-
- override def producedTypes = kind :: Nil
-
- /** The corresponding constructor call. */
- var init: CALL_METHOD = _
-
- override def category = objsCat
- }
-
-
- /** This class represents a CREATE_ARRAY instruction
- * Stack: ...:size_1:size_2:..:size_n
- * ->: ...:arrayref
- */
- case class CREATE_ARRAY(elem: TypeKind, dims: Int) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="CREATE_ARRAY "+elem + " x " + dims
-
- override def consumed = dims
-
- override def consumedTypes = List.fill(dims)(INT)
- override def produced = 1
-
- override def producedTypes = ARRAY(elem) :: Nil
-
- override def category = arraysCat
- }
-
- /** This class represents a IS_INSTANCE instruction
- * Stack: ...:ref
- * ->: ...:result(boolean)
- */
- case class IS_INSTANCE(typ: TypeKind) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="IS_INSTANCE "+typ
-
- override def consumed = 1
- override def produced = 1
- override def consumedTypes = ObjectReferenceList
- override def producedTypes = BOOL :: Nil
-
- override def category = castsCat
- }
-
- /** This class represents a CHECK_CAST instruction
- * Stack: ...:ref(oldtype)
- * ->: ...:ref(typ <=: oldtype)
- */
- case class CHECK_CAST(typ: TypeKind) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="CHECK_CAST "+typ
-
- override def consumed = 1
- override def produced = 1
- override def consumedTypes = ObjectReferenceList
- override def producedTypes = typ :: Nil
-
- override def category = castsCat
- }
-
- /** This class represents a SWITCH instruction
- * Stack: ...:index(int)
- * ->: ...:
- *
- * The tags array contains one entry per label, each entry consisting of
- * an array of ints, any of which will trigger the jump to the corresponding label.
- * labels should contain an extra label, which is the 'default' jump.
- */
- case class SWITCH(tags: List[List[Int]], labels: List[BasicBlock]) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="SWITCH ..."
-
- override def consumed = 1
- override def produced = 0
-
- override def consumedTypes = INT :: Nil
-
- def flatTagsCount: Int = { var acc = 0; var rest = tags; while(rest.nonEmpty) { acc += rest.head.length; rest = rest.tail }; acc } // a one-liner
-
- override def category = jumpsCat
- }
-
- /** This class represents a JUMP instruction
- * Stack: ...
- * ->: ...
- */
- case class JUMP(whereto: BasicBlock) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="JUMP "+whereto.label
-
- override def consumed = 0
- override def produced = 0
-
- override def category = jumpsCat
- }
-
- /** This class represents a CJUMP instruction
- * It compares the two values on the stack with the 'cond' test operator
- * Stack: ...:value1:value2
- * ->: ...
- */
- case class CJUMP(successBlock: BasicBlock,
- failureBlock: BasicBlock,
- cond: TestOp,
- kind: TypeKind) extends Instruction
- {
-
- /** Returns a string representation of this instruction */
- override def toString(): String = (
- "CJUMP (" + kind + ")" +
- cond + " ? "+successBlock.label+" : "+failureBlock.label
- )
-
- override def consumed = 2
- override def produced = 0
-
- override def consumedTypes = kind :: kind :: Nil
-
- override def category = jumpsCat
- }
-
- /** This class represents a CZJUMP instruction
- * It compares the one value on the stack and zero with the 'cond' test operator
- * Stack: ...:value:
- * ->: ...
- */
- case class CZJUMP(successBlock: BasicBlock,
- failureBlock: BasicBlock,
- cond: TestOp,
- kind: TypeKind) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String = (
- "CZJUMP (" + kind + ")" +
- cond + " ? "+successBlock.label+" : "+failureBlock.label
- )
-
- override def consumed = 1
- override def produced = 0
-
- override def consumedTypes = kind :: Nil
- override def category = jumpsCat
- }
-
-
- /** This class represents a RETURN instruction
- * Stack: ...
- * ->: ...
- */
- case class RETURN(kind: TypeKind) extends Instruction {
- override def consumed = if (kind == UNIT) 0 else 1
- override def produced = 0
-
- override def consumedTypes = if (kind == UNIT) Nil else kind :: Nil
-
- override def category = retCat
- }
-
- /** This class represents a THROW instruction
- * Stack: ...:Throwable(Ref)
- * ->: ...:
- */
- case class THROW(clasz: Symbol) extends Instruction {
- /** PP to ID: We discussed parameterizing LOAD_EXCEPTION but
- * not THROW, which came about organically. It seems like the
- * right thing, but can you confirm?
- */
- override def toString = "THROW(" + clasz.name + ")"
-
- override def consumed = 1
- override def produced = 0
-
- override def consumedTypes = toTypeKind(clasz.tpe) :: Nil
-
- override def category = retCat
- }
-
- /** This class represents a DROP instruction
- * Stack: ...:something
- * ->: ...
- */
- case class DROP (typ: TypeKind) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="DROP "+typ
-
- override def consumed = 1
- override def produced = 0
-
- override def consumedTypes = typ :: Nil
-
- override def category = stackCat
- }
-
- /** This class represents a DUP instruction
- * Stack: ...:something
- * ->: ...:something:something
- */
- case class DUP (typ: TypeKind) extends Instruction {
- override def consumed = 1
- override def produced = 2
- override def consumedTypes = typ :: Nil
- override def producedTypes = typ :: typ :: Nil
- override def category = stackCat
- }
-
- /** This class represents a MONITOR_ENTER instruction
- * Stack: ...:object(ref)
- * ->: ...:
- */
- case class MONITOR_ENTER() extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="MONITOR_ENTER"
-
- override def consumed = 1
- override def produced = 0
-
- override def consumedTypes = ObjectReference :: Nil
-
- override def category = objsCat
- }
-
- /** This class represents a MONITOR_EXIT instruction
- * Stack: ...:object(ref)
- * ->: ...:
- */
- case class MONITOR_EXIT() extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="MONITOR_EXIT"
-
- override def consumed = 1
-
- override def produced = 0
-
- override def consumedTypes = ObjectReference :: Nil
-
- override def category = objsCat
- }
-
- /** A local variable becomes visible at this point in code.
- * Used only for generating precise local variable tables as
- * debugging information.
- */
- case class SCOPE_ENTER(lv: Local) extends Instruction {
- override def toString(): String = "SCOPE_ENTER " + lv
- override def consumed = 0
- override def produced = 0
- override def category = localsCat
- }
-
- /** A local variable leaves its scope at this point in code.
- * Used only for generating precise local variable tables as
- * debugging information.
- */
- case class SCOPE_EXIT(lv: Local) extends Instruction {
- override def toString(): String = "SCOPE_EXIT " + lv
- override def consumed = 0
- override def produced = 0
- override def category = localsCat
- }
-
- /** Fake instruction. It designates the VM who pushes an exception
- * on top of the /empty/ stack at the beginning of each exception handler.
- * Note: Unlike other instructions, it consumes all elements on the stack!
- * then pushes one exception instance.
- */
- case class LOAD_EXCEPTION(clasz: Symbol) extends Instruction {
- override def consumed = sys.error("LOAD_EXCEPTION does clean the whole stack, no idea how many things it consumes!")
- override def produced = 1
- override def producedTypes = REFERENCE(clasz) :: Nil
- override def category = stackCat
- }
-
- /** This class represents a method invocation style. */
- sealed abstract class InvokeStyle {
- /** Is this a dynamic method call? */
- def isDynamic: Boolean = false
-
- /** Is this a static method call? */
- def isStatic: Boolean = false
-
- def isSuper: Boolean = false
-
- /** Is this an instance method call? */
- def hasInstance: Boolean = true
-
- /** Returns a string representation of this style. */
- override def toString(): String
- }
-
- /** Virtual calls.
- * On JVM, translated to either `invokeinterface` or `invokevirtual`.
- */
- case object Dynamic extends InvokeStyle {
- override def isDynamic = true
- override def toString(): String = "dynamic"
- }
-
- /**
- * Special invoke:
- * Static(true) is used for calls to private members, ie `invokespecial` on JVM.
- * Static(false) is used for calls to class-level instance-less static methods, ie `invokestatic` on JVM.
- */
- case class Static(onInstance: Boolean) extends InvokeStyle {
- override def isStatic = true
- override def hasInstance = onInstance
- override def toString(): String = {
- if(onInstance) "static-instance"
- else "static-class"
- }
- }
-
- /** Call through super[mix].
- * On JVM, translated to `invokespecial`.
- */
- case class SuperCall(mix: Name) extends InvokeStyle {
- override def isSuper = true
- override def toString(): String = { "super(" + mix + ")" }
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala b/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala
deleted file mode 100644
index dd930ba52f..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala
+++ /dev/null
@@ -1,247 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala.tools.nsc
-package backend
-package icode
-
-import java.io.PrintWriter
-
-trait Primitives { self: ICodes =>
-
- /** This class represents a primitive operation. */
- class Primitive {
- }
-
-
- // type : (type) => type
- // range: type <- { BOOL, Ix, Ux, Rx }
- // jvm : {i, l, f, d}neg
- case class Negation(kind: TypeKind) extends Primitive
-
- // type : zero ? (type) => BOOL : (type,type) => BOOL
- // range: type <- { BOOL, Ix, Ux, Rx, REF }
- // jvm : if{eq, ne, lt, ge, le, gt}, if{null, nonnull}
- // if_icmp{eq, ne, lt, ge, le, gt}, if_acmp{eq,ne}
- case class Test(op: TestOp, kind: TypeKind, zero: Boolean) extends Primitive
-
- // type : (type,type) => I4
- // range: type <- { Ix, Ux, Rx }
- // jvm : lcmp, {f, d}cmp{l, g}
- case class Comparison(op: ComparisonOp, kind: TypeKind) extends Primitive
-
- // type : (type,type) => type
- // range: type <- { Ix, Ux, Rx }
- // jvm : {i, l, f, d}{add, sub, mul, div, rem}
- case class Arithmetic(op: ArithmeticOp, kind: TypeKind) extends Primitive
-
- // type : (type,type) => type
- // range: type <- { BOOL, Ix, Ux }
- // jvm : {i, l}{and, or, xor}
- case class Logical(op: LogicalOp, kind: TypeKind) extends Primitive
-
- // type : (type,I4) => type
- // range: type <- { Ix, Ux }
- // jvm : {i, l}{shl, ushl, shr}
- case class Shift(op: ShiftOp, kind: TypeKind) extends Primitive
-
- // type : (src) => dst
- // range: src,dst <- { Ix, Ux, Rx }
- // jvm : i2{l, f, d}, l2{i, f, d}, f2{i, l, d}, d2{i, l, f}, i2{b, c, s}
- case class Conversion(src: TypeKind, dst: TypeKind) extends Primitive
-
- // type : (Array[REF]) => I4
- // range: type <- { BOOL, Ix, Ux, Rx, REF }
- // jvm : arraylength
- case class ArrayLength(kind: TypeKind) extends Primitive
-
- // type : (buf,el) => buf
- // range: lf,rg <- { BOOL, Ix, Ux, Rx, REF, STR }
- // jvm : It should call the appropriate 'append' method on StringBuffer
- case class StringConcat(el: TypeKind) extends Primitive
-
- /** Signals the beginning of a series of concatenations.
- * On the JVM platform, it should create a new StringBuffer
- */
- case object StartConcat extends Primitive
-
- /**
- * type: (buf) => STR
- * jvm : It should turn the StringBuffer into a String.
- */
- case object EndConcat extends Primitive
-
- /** Pretty printer for primitives */
- class PrimitivePrinter(out: PrintWriter) {
- def print(s: String): PrimitivePrinter = {
- out.print(s)
- this
- }
- }
-
- /** This class represents a comparison operation. */
- class ComparisonOp {
-
- /** Returns a string representation of this operation. */
- override def toString(): String = this match {
- case CMPL => "CMPL"
- case CMP => "CMP"
- case CMPG => "CMPG"
- case _ => throw new RuntimeException("ComparisonOp unknown case")
- }
- }
-
- /** A comparison operation with -1 default for NaNs */
- case object CMPL extends ComparisonOp
-
- /** A comparison operation with no default for NaNs */
- case object CMP extends ComparisonOp
-
- /** A comparison operation with +1 default for NaNs */
- case object CMPG extends ComparisonOp
-
-
- /** This class represents a test operation. */
- sealed abstract class TestOp {
-
- /** Returns the negation of this operation. */
- def negate(): TestOp
-
- /** Returns a string representation of this operation. */
- override def toString(): String
-
- /** used only from GenASM */
- def opcodeIF(): Int
-
- /** used only from GenASM */
- def opcodeIFICMP(): Int
-
- }
-
- /** An equality test */
- case object EQ extends TestOp {
- def negate() = NE
- override def toString() = "EQ"
- override def opcodeIF() = scala.tools.asm.Opcodes.IFEQ
- override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPEQ
- }
-
- /** A non-equality test */
- case object NE extends TestOp {
- def negate() = EQ
- override def toString() = "NE"
- override def opcodeIF() = scala.tools.asm.Opcodes.IFNE
- override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPNE
- }
-
- /** A less-than test */
- case object LT extends TestOp {
- def negate() = GE
- override def toString() = "LT"
- override def opcodeIF() = scala.tools.asm.Opcodes.IFLT
- override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPLT
- }
-
- /** A greater-than-or-equal test */
- case object GE extends TestOp {
- def negate() = LT
- override def toString() = "GE"
- override def opcodeIF() = scala.tools.asm.Opcodes.IFGE
- override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPGE
- }
-
- /** A less-than-or-equal test */
- case object LE extends TestOp {
- def negate() = GT
- override def toString() = "LE"
- override def opcodeIF() = scala.tools.asm.Opcodes.IFLE
- override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPLE
- }
-
- /** A greater-than test */
- case object GT extends TestOp {
- def negate() = LE
- override def toString() = "GT"
- override def opcodeIF() = scala.tools.asm.Opcodes.IFGT
- override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPGT
- }
-
- /** This class represents an arithmetic operation. */
- class ArithmeticOp {
-
- /** Returns a string representation of this operation. */
- override def toString(): String = this match {
- case ADD => "ADD"
- case SUB => "SUB"
- case MUL => "MUL"
- case DIV => "DIV"
- case REM => "REM"
- case NOT => "NOT"
- case _ => throw new RuntimeException("ArithmeticOp unknown case")
- }
- }
-
- /** An arithmetic addition operation */
- case object ADD extends ArithmeticOp
-
- /** An arithmetic subtraction operation */
- case object SUB extends ArithmeticOp
-
- /** An arithmetic multiplication operation */
- case object MUL extends ArithmeticOp
-
- /** An arithmetic division operation */
- case object DIV extends ArithmeticOp
-
- /** An arithmetic remainder operation */
- case object REM extends ArithmeticOp
-
- /** Bitwise negation. */
- case object NOT extends ArithmeticOp
-
- /** This class represents a shift operation. */
- class ShiftOp {
-
- /** Returns a string representation of this operation. */
- override def toString(): String = this match {
- case LSL => "LSL"
- case ASR => "ASR"
- case LSR => "LSR"
- case _ => throw new RuntimeException("ShiftOp unknown case")
- }
- }
-
- /** A logical shift to the left */
- case object LSL extends ShiftOp
-
- /** An arithmetic shift to the right */
- case object ASR extends ShiftOp
-
- /** A logical shift to the right */
- case object LSR extends ShiftOp
-
- /** This class represents a logical operation. */
- class LogicalOp {
-
- /** Returns a string representation of this operation. */
- override def toString(): String = this match {
- case AND => "AND"
- case OR => "OR"
- case XOR => "XOR"
- case _ => throw new RuntimeException("LogicalOp unknown case")
- }
- }
-
- /** A bitwise AND operation */
- case object AND extends LogicalOp
-
- /** A bitwise OR operation */
- case object OR extends LogicalOp
-
- /** A bitwise XOR operation */
- case object XOR extends LogicalOp
-}
-
diff --git a/src/compiler/scala/tools/nsc/backend/icode/Printers.scala b/src/compiler/scala/tools/nsc/backend/icode/Printers.scala
deleted file mode 100644
index 1fe33f78e7..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/Printers.scala
+++ /dev/null
@@ -1,126 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-import java.io.PrintWriter
-
-trait Printers { self: ICodes =>
- import global._
-
- class TextPrinter(writer: PrintWriter, lin: Linearizer) {
- private var margin = 0
- private var out = writer
-
- final val TAB = 2
-
- def setWriter(w: PrintWriter) { out = w }
-
- def indent() { margin += TAB }
- def undent() { margin -= TAB }
-
- def print(s: String) { out.print(s) }
- def print(o: Any) { print(o.toString()) }
-
- def println(s: String) {
- print(s)
- println()
- }
-
- def println() {
- out.println()
- var i = 0
- while (i < margin) {
- print(" ")
- i += 1
- }
- }
-
- def printList[A](l: List[A], sep: String): Unit = l match {
- case Nil =>
- case x :: Nil => print(x)
- case x :: xs => print(x); print(sep); printList(xs, sep)
- }
-
- def printList[A](pr: A => Unit)(l: List[A], sep: String): Unit = l match {
- case Nil =>
- case x :: Nil => pr(x)
- case x :: xs => pr(x); print(sep); printList(pr)(xs, sep)
- }
-
- def printClass(cls: IClass) {
- print(cls.symbol.toString()); print(" extends ")
- printList(cls.symbol.info.parents, ", ")
- indent(); println(" {")
- println("// fields:")
- cls.fields.foreach(printField); println()
- println("// methods")
- cls.methods.foreach(printMethod)
- undent(); println()
- println("}")
- }
-
- def printField(f: IField) {
- print(f.symbol.keyString); print(" ")
- print(f.symbol.nameString); print(": ")
- println(f.symbol.info.toString())
- }
-
- def printMethod(m: IMethod) {
- print("def "); print(m.symbol.name)
- print("("); printList(printParam)(m.params, ", "); print(")")
- print(": "); print(m.symbol.info.resultType)
-
- if (!m.isAbstractMethod) {
- println(" {")
- println("locals: " + m.locals.mkString("", ", ", ""))
- println("startBlock: " + m.startBlock)
- println("blocks: " + m.code.blocks.mkString("[", ",", "]"))
- println()
- lin.linearize(m) foreach printBlock
- println("}")
-
- indent(); println("Exception handlers: ")
- m.exh foreach printExceptionHandler
-
- undent(); println()
- } else
- println()
- }
-
- def printParam(p: Local) {
- print(p.sym.name); print(": "); print(p.sym.info)
- print(" ("); print(p.kind); print(")")
- }
-
- def printExceptionHandler(e: ExceptionHandler) {
- indent()
- println("catch (" + e.cls.simpleName + ") in " + e.covered.toSeq.sortBy(_.label) + " starting at: " + e.startBlock)
- println("consisting of blocks: " + e.blocks)
- undent()
- println("with finalizer: " + e.finalizer)
- // linearizer.linearize(e.startBlock) foreach printBlock;
- }
-
- def printBlock(bb: BasicBlock) {
- print(bb.label)
- if (bb.loopHeader) print("[loop header]")
- print(": ")
- if (settings.debug) print("pred: " + bb.predecessors + " succs: " + bb.successors + " flags: " + bb.flagsString)
- indent(); println()
- bb.toList foreach printInstruction
- undent(); println()
- }
-
- def printInstruction(i: Instruction) {
-// if (settings.Xdce.value)
-// print(if (i.useful) " " else " * ");
- if (i.pos.isDefined) print(i.pos.line.toString + "\t") else print("?\t")
- println(i.toString())
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/Repository.scala b/src/compiler/scala/tools/nsc/backend/icode/Repository.scala
deleted file mode 100644
index 10d57df4a3..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/Repository.scala
+++ /dev/null
@@ -1,47 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala.tools.nsc
-package backend
-package icode
-
-import scala.collection._
-
-/**
- * @author Iulian Dragos
- */
-trait Repository {
- val global: Global
- import global._
- import icodes._
-
- val loaded: mutable.Map[Symbol, IClass] = perRunCaches.newMap()
-
- /** Is the given class available as icode? */
- def available(sym: Symbol) = classes.contains(sym) || loaded.contains(sym)
-
- /** The icode of the given class, if available */
- def icode(sym: Symbol): Option[IClass] = (classes get sym) orElse (loaded get sym)
-
- /** Load bytecode for given symbol. */
- def load(sym: Symbol): Boolean = {
- try {
- val (c1, c2) = icodeReader.readClass(sym)
-
- assert(c1.symbol == sym || c2.symbol == sym, "c1.symbol = %s, c2.symbol = %s, sym = %s".format(c1.symbol, c2.symbol, sym))
- loaded += (c1.symbol -> c1)
- loaded += (c2.symbol -> c2)
-
- true
- } catch {
- case e: Throwable => // possible exceptions are MissingRequirementError, IOException and TypeError -> no better common supertype
- log("Failed to load %s. [%s]".format(sym.fullName, e.getMessage))
- if (settings.debug) { e.printStackTrace }
-
- false
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala b/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala
deleted file mode 100644
index a6d0d3b9fa..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala
+++ /dev/null
@@ -1,438 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-/* A type case
-
- case UNIT =>
- case BOOL =>
- case BYTE =>
- case SHORT =>
- case CHAR =>
- case INT =>
- case LONG =>
- case FLOAT =>
- case DOUBLE =>
- case REFERENCE(cls) =>
- case ARRAY(elem) =>
-
-*/
-
-trait TypeKinds { self: ICodes =>
- import global._
- import definitions.{ ArrayClass, AnyRefClass, ObjectClass, NullClass, NothingClass, arrayType }
-
- /** A map from scala primitive Types to ICode TypeKinds */
- lazy val primitiveTypeMap: Map[Symbol, TypeKind] = {
- import definitions._
- Map(
- UnitClass -> UNIT,
- BooleanClass -> BOOL,
- CharClass -> CHAR,
- ByteClass -> BYTE,
- ShortClass -> SHORT,
- IntClass -> INT,
- LongClass -> LONG,
- FloatClass -> FLOAT,
- DoubleClass -> DOUBLE
- )
- }
- /** Reverse map for toType */
- private lazy val reversePrimitiveMap: Map[TypeKind, Symbol] =
- (primitiveTypeMap map (_.swap)).toMap
-
- /** This class represents a type kind. Type kinds
- * represent the types that the VM know (or the ICode
- * view of what VMs know).
- */
- sealed abstract class TypeKind {
- def maxType(other: TypeKind): TypeKind
-
- def toType: Type = reversePrimitiveMap get this map (_.tpe) getOrElse {
- this match {
- case REFERENCE(cls) => cls.tpe_*
- case ARRAY(elem) => arrayType(elem.toType)
- case _ => abort("Unknown type kind.")
- }
- }
-
- def isReferenceType = false
- def isArrayType = false
- def isValueType = false
- def isBoxedType = false
- final def isRefOrArrayType = isReferenceType || isArrayType
- final def isNothingType = this == NothingReference
- final def isNullType = this == NullReference
- final def isInterfaceType = this match {
- case REFERENCE(cls) if cls.isInterface || cls.isTrait => true
- case _ => false
- }
-
- /** On the JVM,
- * BOOL, BYTE, CHAR, SHORT, and INT
- * are like Ints for the purposes of calculating the lub.
- */
- def isIntSizedType: Boolean = false
-
- /** On the JVM, similar to isIntSizedType except that BOOL isn't integral while LONG is. */
- def isIntegralType: Boolean = false
-
- /** On the JVM, FLOAT and DOUBLE. */
- def isRealType: Boolean = false
-
- final def isNumericType: Boolean = isIntegralType | isRealType
-
- /** Simple subtyping check */
- def <:<(other: TypeKind): Boolean
-
- /**
- * this is directly assignable to other if no coercion or
- * casting is needed to convert this to other. It's a distinct
- * relationship from <:< because on the JVM, BOOL, BYTE, CHAR,
- * SHORT need no coercion to INT even though JVM arrays
- * are covariant, ARRAY[SHORT] is not a subtype of ARRAY[INT]
- */
- final def isAssignabledTo(other: TypeKind): Boolean = other match {
- case INT => this.isIntSizedType
- case _ => this <:< other
- }
-
- /** Is this type a category 2 type in JVM terms? (ie, is it LONG or DOUBLE?) */
- def isWideType: Boolean = false
-
- /** The number of dimensions for array types. */
- def dimensions: Int = 0
-
- protected def uncomparable(thisKind: String, other: TypeKind): Nothing =
- abort("Uncomparable type kinds: " + thisKind + " with " + other)
-
- protected def uncomparable(other: TypeKind): Nothing =
- uncomparable(this.toString, other)
- }
-
- sealed abstract class ValueTypeKind extends TypeKind {
- override def isValueType = true
- override def toString = {
- this.getClass.getName stripSuffix "$" dropWhile (_ != '$') drop 1
- }
- def <:<(other: TypeKind): Boolean = this eq other
- }
-
- /**
- * The least upper bound of two typekinds. They have to be either
- * REFERENCE or ARRAY kinds.
- *
- * The lub is based on the lub of scala types.
- */
- def lub(a: TypeKind, b: TypeKind): TypeKind = {
- /* The compiler's lub calculation does not order classes before traits.
- * This is apparently not wrong but it is inconvenient, and causes the
- * icode checker to choke when things don't match up. My attempts to
- * alter the calculation at the compiler level were failures, so in the
- * interests of a working icode checker I'm making the adjustment here.
- *
- * Example where we'd like a different answer:
- *
- * abstract class Tom
- * case object Bob extends Tom
- * case object Harry extends Tom
- * List(Bob, Harry) // compiler calculates "Product with Tom" rather than "Tom with Product"
- *
- * Here we make the adjustment by rewinding to a pre-erasure state and
- * sifting through the parents for a class type.
- */
- def lub0(tk1: TypeKind, tk2: TypeKind): Type = enteringUncurry {
- val tp = global.lub(List(tk1.toType, tk2.toType))
- val (front, rest) = tp.parents span (_.typeSymbol.isTrait)
-
- if (front.isEmpty || rest.isEmpty || rest.head.typeSymbol == ObjectClass) tp
- else rest.head
- }
-
- def isIntLub = (
- (a == INT && b.isIntSizedType) ||
- (b == INT && a.isIntSizedType)
- )
-
- if (a == b) a
- else if (a.isNothingType) b
- else if (b.isNothingType) a
- else if (a.isBoxedType || b.isBoxedType) AnyRefReference // we should do better
- else if (isIntLub) INT
- else if (a.isRefOrArrayType && b.isRefOrArrayType) {
- if (a.isNullType) b
- else if (b.isNullType) a
- else toTypeKind(lub0(a, b))
- }
- else throw new CheckerException("Incompatible types: " + a + " with " + b)
- }
-
- /** The unit value */
- case object UNIT extends ValueTypeKind {
- def maxType(other: TypeKind) = other match {
- case UNIT | REFERENCE(NothingClass) => UNIT
- case _ => uncomparable(other)
- }
- }
-
- /** A boolean value */
- case object BOOL extends ValueTypeKind {
- override def isIntSizedType = true
- def maxType(other: TypeKind) = other match {
- case BOOL | REFERENCE(NothingClass) => BOOL
- case _ => uncomparable(other)
- }
- }
-
- /** Note that the max of Char/Byte and Char/Short is Int, because
- * neither strictly encloses the other due to unsignedness.
- * See ticket #2087 for a consequence.
- */
-
- /** A 1-byte signed integer */
- case object BYTE extends ValueTypeKind {
- override def isIntSizedType = true
- override def isIntegralType = true
- def maxType(other: TypeKind) = {
- if (other == BYTE || other.isNothingType) BYTE
- else if (other == CHAR) INT
- else if (other.isNumericType) other
- else uncomparable(other)
- }
- }
-
- /** A 2-byte signed integer */
- case object SHORT extends ValueTypeKind {
- override def isIntSizedType = true
- override def isIntegralType = true
- override def maxType(other: TypeKind) = other match {
- case BYTE | SHORT | REFERENCE(NothingClass) => SHORT
- case CHAR => INT
- case INT | LONG | FLOAT | DOUBLE => other
- case _ => uncomparable(other)
- }
- }
-
- /** A 2-byte UNSIGNED integer */
- case object CHAR extends ValueTypeKind {
- override def isIntSizedType = true
- override def isIntegralType = true
- override def maxType(other: TypeKind) = other match {
- case CHAR | REFERENCE(NothingClass) => CHAR
- case BYTE | SHORT => INT
- case INT | LONG | FLOAT | DOUBLE => other
- case _ => uncomparable(other)
- }
- }
-
- /** A 4-byte signed integer */
- case object INT extends ValueTypeKind {
- override def isIntSizedType = true
- override def isIntegralType = true
- override def maxType(other: TypeKind) = other match {
- case BYTE | SHORT | CHAR | INT | REFERENCE(NothingClass) => INT
- case LONG | FLOAT | DOUBLE => other
- case _ => uncomparable(other)
- }
- }
-
- /** An 8-byte signed integer */
- case object LONG extends ValueTypeKind {
- override def isIntegralType = true
- override def isWideType = true
- override def maxType(other: TypeKind): TypeKind =
- if (other.isIntegralType || other.isNothingType) LONG
- else if (other.isRealType) DOUBLE
- else uncomparable(other)
- }
-
- /** A 4-byte floating point number */
- case object FLOAT extends ValueTypeKind {
- override def isRealType = true
- override def maxType(other: TypeKind): TypeKind =
- if (other == DOUBLE) DOUBLE
- else if (other.isNumericType || other.isNothingType) FLOAT
- else uncomparable(other)
- }
-
- /** An 8-byte floating point number */
- case object DOUBLE extends ValueTypeKind {
- override def isRealType = true
- override def isWideType = true
- override def maxType(other: TypeKind): TypeKind =
- if (other.isNumericType || other.isNothingType) DOUBLE
- else uncomparable(other)
- }
-
- /** A class type. */
- final case class REFERENCE(cls: Symbol) extends TypeKind {
- override def toString = "REF(" + cls + ")"
- assert(cls ne null,
- "REFERENCE to null class symbol.")
- assert(cls != ArrayClass,
- "REFERENCE to Array is not allowed, should be ARRAY[..] instead")
- assert(cls != NoSymbol,
- "REFERENCE to NoSymbol not allowed!")
-
- /**
- * Approximate `lub`. The common type of two references is
- * always AnyRef. For 'real' least upper bound wrt to subclassing
- * use method 'lub'.
- */
- override def maxType(other: TypeKind) = other match {
- case REFERENCE(_) | ARRAY(_) => AnyRefReference
- case _ => uncomparable("REFERENCE", other)
- }
-
- /** Checks subtyping relationship. */
- def <:<(other: TypeKind) = isNothingType || (other match {
- case REFERENCE(cls2) => cls.tpe <:< cls2.tpe
- case ARRAY(_) => cls == NullClass
- case _ => false
- })
- override def isReferenceType = true
- }
-
- def ArrayN(elem: TypeKind, dims: Int): ARRAY = {
- assert(dims > 0)
- if (dims == 1) ARRAY(elem)
- else ARRAY(ArrayN(elem, dims - 1))
- }
-
- final case class ARRAY(elem: TypeKind) extends TypeKind {
- override def toString = "ARRAY[" + elem + "]"
- override def isArrayType = true
- override def dimensions = 1 + elem.dimensions
-
- /** The ultimate element type of this array. */
- def elementKind: TypeKind = elem match {
- case a @ ARRAY(_) => a.elementKind
- case k => k
- }
-
- /**
- * Approximate `lub`. The common type of two references is
- * always AnyRef. For 'real' least upper bound wrt to subclassing
- * use method 'lub'.
- */
- override def maxType(other: TypeKind) = other match {
- case ARRAY(elem2) if elem == elem2 => ARRAY(elem)
- case ARRAY(_) | REFERENCE(_) => AnyRefReference
- case _ => uncomparable("ARRAY", other)
- }
-
- /** Array subtyping is covariant, as in Java. Necessary for checking
- * code that interacts with Java. */
- def <:<(other: TypeKind) = other match {
- case ARRAY(elem2) => elem <:< elem2
- case REFERENCE(AnyRefClass | ObjectClass) => true // TODO: platform dependent!
- case _ => false
- }
- }
-
- /** A boxed value. */
- case class BOXED(kind: TypeKind) extends TypeKind {
- override def isBoxedType = true
-
- override def maxType(other: TypeKind) = other match {
- case BOXED(`kind`) => this
- case REFERENCE(_) | ARRAY(_) | BOXED(_) => AnyRefReference
- case _ => uncomparable("BOXED", other)
- }
-
- /** Checks subtyping relationship. */
- def <:<(other: TypeKind) = other match {
- case BOXED(`kind`) => true
- case REFERENCE(AnyRefClass | ObjectClass) => true // TODO: platform dependent!
- case _ => false
- }
- }
-
- /**
- * Dummy TypeKind to represent the ConcatClass in a platform-independent
- * way. For JVM it would have been a REFERENCE to 'StringBuffer'.
- */
- case object ConcatClass extends TypeKind {
- override def toString = "ConcatClass"
- def <:<(other: TypeKind): Boolean = this eq other
-
- /**
- * Approximate `lub`. The common type of two references is
- * always AnyRef. For 'real' least upper bound wrt to subclassing
- * use method 'lub'.
- */
- override def maxType(other: TypeKind) = other match {
- case REFERENCE(_) => AnyRefReference
- case _ => uncomparable(other)
- }
- }
-
- ////////////////// Conversions //////////////////////////////
-
- /** Return the TypeKind of the given type
- *
- * Call to dealiasWiden fixes #3003 (follow type aliases). Otherwise,
- * arrayOrClassType below would return ObjectReference.
- */
- def toTypeKind(t: Type): TypeKind = t.dealiasWiden match {
- case ThisType(ArrayClass) => ObjectReference
- case ThisType(sym) => REFERENCE(sym)
- case SingleType(_, sym) => primitiveOrRefType(sym)
- case ConstantType(_) => toTypeKind(t.underlying)
- case TypeRef(_, sym, args) => primitiveOrClassType(sym, args)
- case ClassInfoType(_, _, ArrayClass) => abort("ClassInfoType to ArrayClass!")
- case ClassInfoType(_, _, sym) => primitiveOrRefType(sym)
-
- // !!! Iulian says types which make no sense after erasure should not reach here,
- // which includes the ExistentialType, AnnotatedType, RefinedType. I don't know
- // if the first two cases exist because they do or as a defensive measure, but
- // at the time I added it, RefinedTypes were indeed reaching here.
- case ExistentialType(_, t) => toTypeKind(t)
- case AnnotatedType(_, t) => toTypeKind(t)
- case RefinedType(parents, _) => parents map toTypeKind reduceLeft lub
- // For sure WildcardTypes shouldn't reach here either, but when
- // debugging such situations this may come in handy.
- // case WildcardType => REFERENCE(ObjectClass)
- case norm => abort(
- "Unknown type: %s, %s [%s, %s] TypeRef? %s".format(
- t, norm, t.getClass, norm.getClass, t.isInstanceOf[TypeRef]
- )
- )
- }
-
- /** Return the type kind of a class, possibly an array type.
- */
- private def arrayOrClassType(sym: Symbol, targs: List[Type]) = sym match {
- case ArrayClass => ARRAY(toTypeKind(targs.head))
- case _ if sym.isClass => newReference(sym)
- case _ =>
- assert(sym.isType, sym) // it must be compiling Array[a]
- ObjectReference
- }
- /** Interfaces have to be handled delicately to avoid introducing
- * spurious errors, but if we treat them all as AnyRef we lose too
- * much information.
- */
- private def newReference(sym: Symbol): TypeKind = {
- // Can't call .toInterface (at this phase) or we trip an assertion.
- // See PackratParser#grow for a method which fails with an apparent mismatch
- // between "object PackratParsers$class" and "trait PackratParsers"
- if (sym.isImplClass) {
- // pos/spec-List.scala is the sole failure if we don't check for NoSymbol
- val traitSym = sym.owner.info.decl(tpnme.interfaceName(sym.name))
- if (traitSym != NoSymbol)
- return REFERENCE(traitSym)
- }
- REFERENCE(sym)
- }
-
- private def primitiveOrRefType(sym: Symbol) =
- primitiveTypeMap.getOrElse(sym, newReference(sym))
- private def primitiveOrClassType(sym: Symbol, targs: List[Type]) =
- primitiveTypeMap.getOrElse(sym, arrayOrClassType(sym, targs))
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/TypeStacks.scala b/src/compiler/scala/tools/nsc/backend/icode/TypeStacks.scala
deleted file mode 100644
index 57d51dad49..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/TypeStacks.scala
+++ /dev/null
@@ -1,82 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-/** This trait ...
- *
- * @author Iulian Dragos
- * @version 1.0
- */
-trait TypeStacks {
- self: ICodes =>
-
- /* This class simulates the type of the operand
- * stack of the ICode.
- */
- type Rep = List[TypeKind]
-
- class TypeStack(var types: Rep) {
- if (types.nonEmpty)
- checkerDebug("Created " + this)
-
- def this() = this(Nil)
- def this(that: TypeStack) = this(that.types)
-
- def length: Int = types.length
- def isEmpty = length == 0
- def nonEmpty = length != 0
-
- /** Push a type on the type stack. UNITs are ignored. */
- def push(t: TypeKind) = {
- if (t != UNIT)
- types = t :: types
- }
-
- def head: TypeKind = types.head
-
- /** Removes the value on top of the stack, and returns it. It assumes
- * the stack contains at least one element.
- */
- def pop: TypeKind = {
- val t = types.head
- types = types.tail
- t
- }
-
- /** Return the topmost two values on the stack. It assumes the stack
- * is large enough. Topmost element first.
- */
- def pop2: (TypeKind, TypeKind) = (pop, pop)
-
- /** Return the topmost three values on the stack. It assumes the stack
- * is large enough. Topmost element first.
- */
- def pop3: (TypeKind, TypeKind, TypeKind) = (pop, pop, pop)
-
- /** Drop the first n elements of the stack. */
- def pop(n: Int): List[TypeKind] = {
- val prefix = types.take(n)
- types = types.drop(n)
- prefix
- }
-
- def apply(n: Int): TypeKind = types(n)
-
- /* This method returns a String representation of the stack */
- override def toString() =
- if (types.isEmpty) "[]"
- else types.mkString("[", " ", "]")
-
- override def hashCode() = types.hashCode()
- override def equals(other: Any): Boolean = other match {
- case x: TypeStack => x.types == types
- case _ => false
- }
- }
-
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/CopyPropagation.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/CopyPropagation.scala
deleted file mode 100644
index 9d48d7a0d3..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/CopyPropagation.scala
+++ /dev/null
@@ -1,553 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala
-package tools.nsc
-package backend.icode.analysis
-
-import scala.collection.{ mutable, immutable }
-
-/** A modified copy-propagation like analysis. It
- * is augmented with a record-like value which is used
- * to represent closures.
- *
- * @author Iulian Dragos
- */
-abstract class CopyPropagation {
- val global: Global
- import global._
- import icodes._
-
- /** Locations can be local variables, this, and fields. */
- abstract sealed class Location
- case class LocalVar(l: Local) extends Location
- case class Field(r: Record, sym: Symbol) extends Location
- case object This extends Location
-
- /** Values that can be on the stack. */
- sealed abstract class Value { }
- case class Record(cls: Symbol, bindings: mutable.Map[Symbol, Value]) extends Value { }
- /** The value of some location in memory. */
- case class Deref(l: Location) extends Value
-
- /** The boxed value of some location. */
- case class Boxed(l: Location) extends Value
-
- /** The constant value c. */
- case class Const(c: Constant) extends Value
-
- /** Unknown. */
- case object Unknown extends Value
-
- /** The bottom record. */
- object AllRecords extends Record(NoSymbol, mutable.HashMap[Symbol, Value]())
-
- /** The lattice for this analysis. */
- object copyLattice extends SemiLattice {
- type Bindings = mutable.Map[Location, Value]
-
- def emptyBinding = mutable.HashMap[Location, Value]()
-
- class State(val bindings: Bindings, var stack: List[Value]) {
-
- override def hashCode = bindings.hashCode + stack.hashCode
- /* comparison with bottom is reference equality! */
- override def equals(that: Any): Boolean = that match {
- case x: State =>
- if ((this eq bottom) || (this eq top) || (x eq bottom) || (x eq top)) this eq x
- else bindings == x.bindings && stack == x.stack
- case _ =>
- false
- }
-
- /* Return an alias for the given local. It returns the last
- * local in the chain of aliased locals. Cycles are not allowed
- * to exist (by construction).
- */
- def getAlias(l: Local): Local = {
- var target = l
- var stop = false
-
- while (bindings.isDefinedAt(LocalVar(target)) && !stop) {
- bindings(LocalVar(target)) match {
- case Deref(LocalVar(t)) => target = t
- case _ => stop = true
- }
- }
- target
- }
-
- /* Return the value bound to the given local. */
- def getBinding(l: Local): Value = {
- def loop(lv: Local): Option[Value] = (bindings get LocalVar(lv)) match {
- case Some(Deref(LocalVar(t))) => loop(t)
- case x => x
- }
- loop(l) getOrElse Deref(LocalVar(l))
- }
-
- /** Return a local which contains the same value as this field, if any.
- * If the field holds a reference to a local, the returned value is the
- * binding of that local.
- */
- def getFieldValue(r: Record, f: Symbol): Option[Value] = r.bindings get f map {
- case Deref(LocalVar(l)) => getBinding(l)
- case target @ Deref(Field(r1, f1)) => getFieldValue(r1, f1) getOrElse target
- case target => target
- }
-
- /** The same as getFieldValue, but never returns Record/Field values. Use
- * this when you want to find a replacement for a field value (either a local,
- * or a constant/this value).
- */
- def getFieldNonRecordValue(r: Record, f: Symbol): Option[Value] = {
- assert(r.bindings contains f, "Record " + r + " does not contain a field " + f)
-
- r.bindings(f) match {
- case Deref(LocalVar(l)) =>
- val alias = getAlias(l)
- val derefAlias = Deref(LocalVar(alias))
-
- Some(getBinding(alias) match {
- case Record(_, _) => derefAlias
- case Deref(Field(r1, f1)) => getFieldNonRecordValue(r1, f1) getOrElse derefAlias
- case Boxed(_) => derefAlias
- case v => v
- })
- case Deref(Field(r1, f1)) => getFieldNonRecordValue(r1, f1)
- case target @ Deref(This) => Some(target)
- case target @ Const(k) => Some(target)
- case _ => None
- }
- }
-
- override def toString(): String =
- "\nBindings: " + bindings + "\nStack: " + stack
-
- def dup: State = {
- val b: Bindings = mutable.HashMap()
- b ++= bindings
- new State(b, stack)
- }
- }
-
- type Elem = State
-
- val top = new State(emptyBinding, Nil)
- val bottom = new State(emptyBinding, Nil)
-
- val exceptionHandlerStack = Unknown :: Nil
-
- def lub2(exceptional: Boolean)(a: Elem, b: Elem): Elem = {
- if (a eq bottom) b
- else if (b eq bottom) a
- else if (a == b) a
- else {
- //assert(!(a.stack eq exceptionHandlerStack) && !(b.stack eq exceptionHandlerStack))
- val resStack =
- if (exceptional) exceptionHandlerStack
- else {
-// if (a.stack.length != b.stack.length)
-// throw new LubException(a, b, "Invalid stacks in states: ");
- (a.stack, b.stack).zipped map { (v1, v2) =>
- if (v1 == v2) v1 else Unknown
- }
- }
-
-/* if (a.stack.length != b.stack.length)
- throw new LubException(a, b, "Invalid stacks in states: ");
- val resStack = List.map2(a.stack, b.stack) { (v1, v2) =>
- if (v1 == v2) v1 else Unknown
- }
- */
- val resBindings = mutable.HashMap[Location, Value]()
-
- for ((k, v) <- a.bindings if b.bindings.isDefinedAt(k) && v == b.bindings(k))
- resBindings += (k -> v)
- new State(resBindings, resStack)
- }
- }
- }
-
- final class CopyAnalysis extends DataFlowAnalysis[copyLattice.type] {
- type P = BasicBlock
- val lattice = copyLattice
-
- var method: IMethod = _
-
- def init(m: IMethod) {
- this.method = m
-
- init {
- worklist += m.startBlock
- worklist ++= (m.exh map (_.startBlock))
- m foreachBlock { b =>
- in(b) = lattice.bottom
- out(b) = lattice.bottom
- assert(out.contains(b), out)
- debuglog("CopyAnalysis added point: " + b)
- }
- m.exh foreach { e =>
- in(e.startBlock) = new copyLattice.State(copyLattice.emptyBinding, copyLattice.exceptionHandlerStack)
- }
-
- // first block is special: it's not bottom, but a precisely defined state with no bindings
- in(m.startBlock) = new lattice.State(lattice.emptyBinding, Nil)
- }
- }
-
- override def run() {
- forwardAnalysis(blockTransfer)
- if (settings.debug) {
- linearizer.linearize(method).foreach(b => if (b != method.startBlock)
- assert(in(b) != lattice.bottom,
- "Block " + b + " in " + this.method + " has input equal to bottom -- not visited?"))
- }
- }
-
- def blockTransfer(b: BasicBlock, in: lattice.Elem): lattice.Elem =
- b.iterator.foldLeft(in)(interpret)
-
- import opcodes._
-
- private def retain[A, B](map: mutable.Map[A, B])(p: (A, B) => Boolean) = {
- for ((k, v) <- map ; if !p(k, v)) map -= k
- map
- }
-
- /** Abstract interpretation for one instruction. */
- def interpret(in: copyLattice.Elem, i: Instruction): copyLattice.Elem = {
- var out = in.dup
- debuglog("- " + i + "\nin: " + in + "\n")
-
- i match {
- case THIS(_) =>
- out.stack = Deref(This) :: out.stack
-
- case CONSTANT(k) =>
- if (k.tag != UnitTag)
- out.stack = Const(k) :: out.stack
-
- case LOAD_ARRAY_ITEM(_) =>
- out.stack = (Unknown :: out.stack.drop(2))
-
- case LOAD_LOCAL(local) =>
- out.stack = Deref(LocalVar(local)) :: out.stack
-
- case LOAD_FIELD(field, isStatic) =>
- if (isStatic)
- out.stack = Unknown :: out.stack; /* ignore static fields */
- else {
- val v1 = in.stack match {
- case (r @ Record(cls, bindings)) :: xs =>
- Deref(Field(r, field))
-
- case Deref(LocalVar(l)) :: _ =>
- in.getBinding(l) match {
- case r @ Record(cls, bindings) => Deref(Field(r, field))
- case _ => Unknown
- }
-
- case Deref(Field(r, f)) :: _ =>
- val fld = in.getFieldValue(r, f)
- fld match {
- case Some(r @ Record(cls, bindings)) if bindings.isDefinedAt(f) =>
- in.getFieldValue(r, f).getOrElse(Unknown)
- case _ => Unknown
- }
-
- case _ => Unknown
- }
- out.stack = v1 :: out.stack.drop(1)
- }
-
- case LOAD_MODULE(module) =>
- out.stack = Unknown :: out.stack
-
- case STORE_ARRAY_ITEM(kind) =>
- out.stack = out.stack.drop(3)
-
- case STORE_LOCAL(local) =>
- cleanReferencesTo(out, LocalVar(local))
- in.stack match {
- case Unknown :: xs => ()
- case v :: vs =>
- v match {
- case Deref(LocalVar(other)) =>
- if (other != local)
- out.bindings += (LocalVar(local) -> v)
- case _ =>
- out.bindings += (LocalVar(local) -> v)
- }
- case Nil =>
- sys.error("Incorrect icode in " + method + ". Expecting something on the stack.")
- }
- out.stack = out.stack drop 1
-
- case STORE_THIS(_) =>
- cleanReferencesTo(out, This)
- out.stack = out.stack drop 1
-
- case STORE_FIELD(field, isStatic) =>
- if (isStatic)
- out.stack = out.stack.drop(1)
- else {
- out.stack = out.stack.drop(2)
- cleanReferencesTo(out, Field(AllRecords, field))
- in.stack match {
- case v :: Record(_, bindings) :: vs =>
- bindings += (field -> v)
- case _ => ()
- }
- }
-
- case CALL_PRIMITIVE(primitive) =>
- // TODO: model primitives
- out.stack = Unknown :: out.stack.drop(i.consumed)
-
- case CALL_METHOD(method, style) => style match {
- case Dynamic =>
- out = simulateCall(in, method, static = false)
-
- case Static(onInstance) =>
- if (onInstance) {
- val obj = out.stack.drop(method.info.paramTypes.length).head
-// if (method.isPrimaryConstructor) {
- if (method.isPrimaryConstructor) {
- obj match {
- case Record(_, bindings) =>
- for (v <- out.stack.take(method.info.paramTypes.length + 1)
- if v ne obj) {
- bindings ++= getBindingsForPrimaryCtor(in, method)
- }
- case _ => ()
- }
- // put the Record back on the stack and remove the 'returned' value
- out.stack = out.stack.drop(1 + method.info.paramTypes.length)
- } else
- out = simulateCall(in, method, static = false)
- } else
- out = simulateCall(in, method, static = true)
-
- case SuperCall(_) =>
- out = simulateCall(in, method, static = false)
- }
-
- case BOX(tpe) =>
- val top = out.stack.head match {
- case Deref(loc) => Boxed(loc)
- case _ => Unknown
- }
- out.stack = top :: out.stack.tail
-
- case UNBOX(tpe) =>
- val top = out.stack.head
- top match {
- case Boxed(loc) => Deref(loc) :: out.stack.tail
- case _ => out.stack = Unknown :: out.stack.drop(1)
- }
-
- case NEW(kind) =>
- val v1 = kind match {
- case REFERENCE(cls) => Record(cls, mutable.HashMap[Symbol, Value]())
- case _ => Unknown
- }
- out.stack = v1 :: out.stack
-
- case CREATE_ARRAY(elem, dims) =>
- out.stack = Unknown :: out.stack.drop(dims)
-
- case IS_INSTANCE(tpe) =>
- out.stack = Unknown :: out.stack.drop(1)
-
- case CHECK_CAST(tpe) =>
- out.stack = Unknown :: out.stack.drop(1)
-
- case SWITCH(tags, labels) =>
- out.stack = out.stack.drop(1)
-
- case JUMP(whereto) =>
- ()
-
- case CJUMP(success, failure, cond, kind) =>
- out.stack = out.stack.drop(2)
-
- case CZJUMP(success, failure, cond, kind) =>
- out.stack = out.stack.drop(1)
-
- case RETURN(kind) =>
- if (kind != UNIT)
- out.stack = out.stack.drop(1)
-
- case THROW(_) =>
- out.stack = out.stack.drop(1)
-
- case DROP(kind) =>
- out.stack = out.stack.drop(1)
-
- case DUP(kind) =>
- out.stack = out.stack.head :: out.stack
-
- case MONITOR_ENTER() =>
- out.stack = out.stack.drop(1)
-
- case MONITOR_EXIT() =>
- out.stack = out.stack.drop(1)
-
- case SCOPE_ENTER(_) | SCOPE_EXIT(_) =>
- ()
-
- case LOAD_EXCEPTION(_) =>
- out.stack = Unknown :: Nil
-
- case _ =>
- dumpClassesAndAbort("Unknown instruction: " + i)
- }
- out
- } /* def interpret */
-
- /** Remove all references to this local variable from both stack
- * and bindings. It is called when a new assignment destroys
- * previous copy-relations.
- */
- final def cleanReferencesTo(s: copyLattice.State, target: Location) {
- def cleanRecord(r: Record): Record = {
- retain(r.bindings) { (loc, value) =>
- (value match {
- case Deref(loc1) if (loc1 == target) => false
- case Boxed(loc1) if (loc1 == target) => false
- case _ => true
- }) && (target match {
- case Field(AllRecords, sym1) => !(loc == sym1)
- case _ => true
- })
- }
- r
- }
-
- s.stack = s.stack map { v => v match {
- case Record(_, bindings) =>
- cleanRecord(v.asInstanceOf[Record])
- case Boxed(loc1) if (loc1 == target) => Unknown
- case _ => v
- }}
-
- retain(s.bindings) { (loc, value) =>
- (value match {
- case Deref(loc1) if (loc1 == target) => false
- case Boxed(loc1) if (loc1 == target) => false
- case rec @ Record(_, _) =>
- cleanRecord(rec)
- true
- case _ => true
- }) &&
- (loc match {
- case l: Location if (l == target) => false
- case _ => true
- })
- }
- }
-
- /** Update the state `s` after the call to `method`.
- * The stack elements are dropped and replaced by the result of the call.
- * If the method is impure, all bindings to record fields are cleared.
- */
- final def simulateCall(state: copyLattice.State, method: Symbol, static: Boolean): copyLattice.State = {
- val out = new copyLattice.State(state.bindings, state.stack)
- out.stack = out.stack.drop(method.info.paramTypes.length + (if (static) 0 else 1))
- if (method.info.resultType != definitions.UnitTpe && !method.isConstructor)
- out.stack = Unknown :: out.stack
- if (!isPureMethod(method))
- invalidateRecords(out)
- out
- }
-
- /** Drop everything known about mutable record fields.
- *
- * A simple escape analysis would help here. Some of the records we
- * track never leak to other methods, therefore they can not be changed.
- * We should not drop their bindings in this case. A closure object
- * would be such an example. Some complications:
- *
- * - outer pointers. An closure escapes as an outer pointer to another
- * nested closure.
- */
- final def invalidateRecords(state: copyLattice.State) {
- def shouldRetain(sym: Symbol): Boolean = {
- if (sym.isMutable)
- log("dropping binding for " + sym.fullName)
- !sym.isMutable
- }
- state.stack = state.stack map { v => v match {
- case Record(cls, bindings) =>
- retain(bindings) { (sym, _) => shouldRetain(sym) }
- Record(cls, bindings)
- case _ => v
- }}
-
- retain(state.bindings) { (loc, value) =>
- value match {
- case Deref(Field(rec, sym)) => shouldRetain(sym)
- case Boxed(Field(rec, sym)) => shouldRetain(sym)
- case _ => true
- }
- }
- }
-
- /** Return bindings from an object fields to the values on the stack. This
- * method has to find the correct mapping from fields to the order in which
- * they are passed on the stack. It works for primary constructors.
- */
- private def getBindingsForPrimaryCtor(in: copyLattice.State, ctor: Symbol): mutable.Map[Symbol, Value] = {
- val paramAccessors = ctor.owner.constrParamAccessors
- var values = in.stack.take(1 + ctor.info.paramTypes.length).reverse.drop(1)
- val bindings = mutable.HashMap[Symbol, Value]()
-
- debuglog("getBindings for: " + ctor + " acc: " + paramAccessors)
-
- var paramTypes = ctor.tpe.paramTypes
- val diff = paramTypes.length - paramAccessors.length
- diff match {
- case 0 => ()
- case 1 if ctor.tpe.paramTypes.head == ctor.owner.rawowner.tpe =>
- // it's an unused outer
- debuglog("considering unused outer at position 0 in " + ctor.tpe.paramTypes)
- paramTypes = paramTypes.tail
- values = values.tail
- case _ =>
- debuglog("giving up on " + ctor + "(diff: " + diff + ")")
- return bindings
- }
-
- // this relies on having the same order in paramAccessors and
- // the arguments on the stack. It should be the same!
- for ((p, i) <- paramAccessors.zipWithIndex) {
-// assert(p.tpe == paramTypes(i), "In: " + ctor.fullName
-// + " having acc: " + (paramAccessors map (_.tpe))+ " vs. params" + paramTypes
-// + "\n\t failed at pos " + i + " with " + p.tpe + " == " + paramTypes(i))
- if (p.tpe == paramTypes(i))
- bindings += (p -> values.head)
- values = values.tail
- }
-
- debuglog("\t" + bindings)
- bindings
- }
-
- /** Is symbol `m` a pure method?
- */
- final def isPureMethod(m: Symbol): Boolean =
- m.isGetter // abstract getters are still pure, as we 'know'
-
- final override def toString() = (
- if (method eq null) List("<null>")
- else method.blocks map { b =>
- "\nIN(%s):\t Bindings: %s".format(b.label, in(b).bindings) +
- "\nIN(%s):\t Stack: %s".format(b.label, in(b).stack)
- }
- ).mkString
-
- } /* class CopyAnalysis */
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/DataFlowAnalysis.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/DataFlowAnalysis.scala
deleted file mode 100644
index a378998f8f..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/DataFlowAnalysis.scala
+++ /dev/null
@@ -1,92 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala
-package tools.nsc
-package backend.icode.analysis
-
-import scala.collection.{ mutable, immutable }
-
-/** A generic framework for data flow analysis.
- */
-trait DataFlowAnalysis[L <: SemiLattice] {
- /** A type for program points. */
- type P <: ProgramPoint[P]
- val lattice: L
-
- val worklist: mutable.Set[P] = new mutable.LinkedHashSet
- val in: mutable.Map[P, lattice.Elem] = new mutable.HashMap
- val out: mutable.Map[P, lattice.Elem] = new mutable.HashMap
- val visited: mutable.HashSet[P] = new mutable.HashSet
-
- /** collect statistics? */
- var stat = true
-
- /** the number of times we iterated before reaching a fixpoint. */
- var iterations = 0
-
- /* Implement this function to initialize the worklist. */
- def init(f: => Unit): Unit = {
- iterations = 0
- in.clear(); out.clear(); worklist.clear(); visited.clear()
- f
- }
-
- def run(): Unit
-
- /** Implements forward dataflow analysis: the transfer function is
- * applied when inputs to a Program point change, to obtain the new
- * output value.
- *
- * @param f the transfer function.
- */
- def forwardAnalysis(f: (P, lattice.Elem) => lattice.Elem): Unit = try {
- while (!worklist.isEmpty) {
- if (stat) iterations += 1
- //Console.println("worklist in: " + worklist);
- val point = worklist.iterator.next(); worklist -= point; visited += point
- //Console.println("taking out point: " + point + " worklist out: " + worklist);
- val output = f(point, in(point))
-
- if ((lattice.bottom == out(point)) || output != out(point)) {
- // Console.println("Output changed at " + point
- // + " from: " + out(point) + " to: " + output
- // + " for input: " + in(point) + " and they are different: " + (output != out(point)))
- out(point) = output
- val succs = point.successors
- succs foreach { p =>
- val updated = lattice.lub(in(p) :: (p.predecessors map out.apply), p.exceptionHandlerStart)
- if(updated != in(p)) {
- in(p) = updated
- if (!worklist(p)) { worklist += p; }
- }
- }
- }
- }
- } catch {
- case e: NoSuchElementException =>
- Console.println("in: " + in.mkString("", "\n", ""))
- Console.println("out: " + out.mkString("", "\n", ""))
- e.printStackTrace
- sys.error("Could not find element " + e.getMessage)
- }
-
- def backwardAnalysis(f: (P, lattice.Elem) => lattice.Elem): Unit =
- while (worklist.nonEmpty) {
- if (stat) iterations += 1
- val point = worklist.head
- worklist -= point
-
- out(point) = lattice.lub(point.successors map in.apply, exceptional = false) // TODO check for exception handlers
- val input = f(point, out(point))
-
- if ((lattice.bottom == in(point)) || input != in(point)) {
- in(point) = input
- worklist ++= point.predecessors
- }
- }
-
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/Liveness.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/Liveness.scala
deleted file mode 100644
index 939641c3eb..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/Liveness.scala
+++ /dev/null
@@ -1,102 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala.tools.nsc
-package backend.icode
-package analysis
-
-import scala.collection.{ mutable, immutable }
-import immutable.ListSet
-
-/**
- * Compute liveness information for local variables.
- *
- * @author Iulian Dragos
- */
-abstract class Liveness {
- val global: Global
- import global._
- import icodes._
-
- /** The lattice for this analysis. */
- object livenessLattice extends SemiLattice {
- type Elem = Set[Local]
-
- object top extends ListSet[Local] with ReferenceEquality
- object bottom extends ListSet[Local] with ReferenceEquality
-
- def lub2(exceptional: Boolean)(a: Elem, b: Elem): Elem = a ++ b
- }
-
- final class LivenessAnalysis extends DataFlowAnalysis[livenessLattice.type] {
- type P = BasicBlock
- val lattice = livenessLattice
- var method: IMethod = _
- val gen: mutable.Map[BasicBlock, Set[Local]] = perRunCaches.newMap()
- val kill: mutable.Map[BasicBlock, Set[Local]] = perRunCaches.newMap()
-
- def init(m: IMethod) {
- this.method = m
- gen.clear()
- kill.clear()
-
- m foreachBlock { b =>
- val (g, k) = genAndKill(b)
- gen += (b -> g)
- kill += (b -> k)
- }
-
- init {
- m foreachBlock { b =>
- worklist += b
- in(b) = lattice.bottom
- out(b) = lattice.bottom
- }
- }
- }
-
- import opcodes._
-
- /** Return the gen and kill sets for this block. */
- def genAndKill(b: BasicBlock): (Set[Local], Set[Local]) = {
- var genSet = new ListSet[Local]
- var killSet = new ListSet[Local]
- for (i <- b) i match {
- case LOAD_LOCAL(local) if (!killSet(local)) => genSet = genSet + local
- case STORE_LOCAL(local) if (!genSet(local)) => killSet = killSet + local
- case _ => ()
- }
- (genSet, killSet)
- }
-
- override def run() {
- backwardAnalysis(blockTransfer)
- if (settings.debug) {
- linearizer.linearize(method).foreach(b => if (b != method.startBlock)
- assert(lattice.bottom != in(b),
- "Block " + b + " in " + this.method + " has input equal to bottom -- not visited?"))
- }
- }
-
- def blockTransfer(b: BasicBlock, out: lattice.Elem): lattice.Elem =
- gen(b) ++ (out -- kill(b))
-
- /** Abstract interpretation for one instruction. Very important:
- * liveness is a backward DFA, so this method should be used to compute
- * liveness *before* the given instruction `i`.
- */
- def interpret(out: lattice.Elem, i: Instruction): lattice.Elem = {
- debuglog("- " + i + "\nout: " + out + "\n")
- i match {
- case LOAD_LOCAL(l) => out + l
- case STORE_LOCAL(l) => out - l
- case _ => out
- }
- }
- override def toString() =
- (method.blocks map (b => "\nlive-in(%s)=%s\nlive-out(%s)=%s".format(b, in(b), b, out(b)))).mkString
- } /* Liveness analysis */
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/LubException.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/LubException.scala
deleted file mode 100644
index e91bf7a044..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/LubException.scala
+++ /dev/null
@@ -1,12 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala.tools.nsc
-package backend.icode.analysis
-
-class LubException(a: Any, b: Any, msg: String) extends Exception {
- override def toString() = "Lub error: " + msg + a + b
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/ProgramPoint.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/ProgramPoint.scala
deleted file mode 100644
index 4e4026f526..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/ProgramPoint.scala
+++ /dev/null
@@ -1,18 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala.tools.nsc
-package backend.icode.analysis
-
-/** Program points are locations in the program where we want to
- * assert certain properties through data flow analysis, e.g.
- * basic blocks.
- */
-trait ProgramPoint[a <: ProgramPoint[a]] {
- def predecessors: List[a]
- def successors: List[a]
- def exceptionHandlerStart: Boolean
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/ReachingDefinitions.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/ReachingDefinitions.scala
deleted file mode 100644
index fecd48ed27..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/ReachingDefinitions.scala
+++ /dev/null
@@ -1,250 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala.tools.nsc
-package backend.icode
-package analysis
-
-import scala.collection.{ mutable, immutable }
-import immutable.ListSet
-
-/** Compute reaching definitions. We are only interested in reaching
- * definitions for local variables, since values on the stack
- * behave as-if in SSA form: the closest instruction which produces a value
- * on the stack is a reaching definition.
- */
-abstract class ReachingDefinitions {
- val global: Global
- import global._
- import icodes._
-
- /** The lattice for reaching definitions. Elements are
- * a triple (local variable, basic block, index of instruction of that basic block)
- */
- object rdefLattice extends SemiLattice {
- type Definition = (Local, BasicBlock, Int)
- type Elem = IState[ListSet[Definition], Stack]
- type StackPos = ListSet[(BasicBlock, Int)]
- type Stack = List[StackPos]
-
- private def referenceEqualSet(name: String) = new ListSet[Definition] with ReferenceEquality {
- override def toString = "<" + name + ">"
- }
-
- val top: Elem = IState(referenceEqualSet("top"), Nil)
- val bottom: Elem = IState(referenceEqualSet("bottom"), Nil)
-
- /** The least upper bound is set inclusion for locals, and pairwise set inclusion for stacks. */
- def lub2(exceptional: Boolean)(a: Elem, b: Elem): Elem = {
- if (bottom == a) b
- else if (bottom == b) a
- else IState(a.vars ++ b.vars,
- if (a.stack.isEmpty) b.stack
- else if (b.stack.isEmpty) a.stack
- else {
- // !!! These stacks are with some frequency not of the same size.
- // I can't reverse engineer the logic well enough to say whether this
- // indicates a problem. Even if it doesn't indicate a problem,
- // it'd be nice not to call zip with mismatched sequences because
- // it makes it harder to spot the real problems.
- val result = (a.stack, b.stack).zipped map (_ ++ _)
- if (settings.debug && (a.stack.length != b.stack.length))
- devWarning(s"Mismatched stacks in ReachingDefinitions#lub2: ${a.stack}, ${b.stack}, returning $result")
- result
- }
- )
- }
- }
-
- class ReachingDefinitionsAnalysis extends DataFlowAnalysis[rdefLattice.type] {
- type P = BasicBlock
- val lattice = rdefLattice
- import lattice.{ Definition, Stack, Elem, StackPos }
- var method: IMethod = _
-
- val gen = mutable.Map[BasicBlock, ListSet[Definition]]()
- val kill = mutable.Map[BasicBlock, ListSet[Local]]()
- val drops = mutable.Map[BasicBlock, Int]()
- val outStack = mutable.Map[BasicBlock, Stack]()
-
- def init(m: IMethod) {
- this.method = m
-
- gen.clear()
- kill.clear()
- drops.clear()
- outStack.clear()
-
- m foreachBlock { b =>
- val (g, k) = genAndKill(b)
- val (d, st) = dropsAndGen(b)
-
- gen += (b -> g)
- kill += (b -> k)
- drops += (b -> d)
- outStack += (b -> st)
- }
-
- init {
- m foreachBlock { b =>
- worklist += b
- in(b) = lattice.bottom
- out(b) = lattice.bottom
- }
- m.exh foreach { e =>
- in(e.startBlock) = lattice.IState(new ListSet[Definition], List(new StackPos))
- }
- }
- }
-
- import opcodes._
-
- def genAndKill(b: BasicBlock): (ListSet[Definition], ListSet[Local]) = {
- var genSet = ListSet[Definition]()
- var killSet = ListSet[Local]()
- for ((STORE_LOCAL(local), idx) <- b.toList.zipWithIndex) {
- killSet = killSet + local
- genSet = updateReachingDefinition(b, idx, genSet)
- }
- (genSet, killSet)
- }
-
- private def dropsAndGen(b: BasicBlock): (Int, Stack) = {
- var depth, drops = 0
- var stackOut: Stack = Nil
-
- for ((instr, idx) <- b.toList.zipWithIndex) {
- instr match {
- case LOAD_EXCEPTION(_) => ()
- case _ if instr.consumed > depth =>
- drops += (instr.consumed - depth)
- depth = 0
- stackOut = Nil
- case _ =>
- stackOut = stackOut.drop(instr.consumed)
- depth -= instr.consumed
- }
- var prod = instr.produced
- depth += prod
- while (prod > 0) {
- stackOut ::= ListSet((b, idx))
- prod -= 1
- }
- }
-// Console.println("drops(" + b + ") = " + drops)
-// Console.println("stackout(" + b + ") = " + stackOut)
- (drops, stackOut)
- }
-
- override def run() {
- forwardAnalysis(blockTransfer)
- if (settings.debug) {
- linearizer.linearize(method).foreach(b => if (b != method.startBlock)
- assert(lattice.bottom != in(b),
- "Block " + b + " in " + this.method + " has input equal to bottom -- not visited? " + in(b)
- + ": bot: " + lattice.bottom
- + "\nin(b) == bottom: " + (in(b) == lattice.bottom)
- + "\nbottom == in(b): " + (lattice.bottom == in(b))))
- }
- }
-
- import opcodes._
- import lattice.IState
- def updateReachingDefinition(b: BasicBlock, idx: Int, rd: ListSet[Definition]): ListSet[Definition] = {
- val STORE_LOCAL(local) = b(idx)
- val tmp = local
- (rd filter { case (l, _, _) => l != tmp }) + ((tmp, b, idx))
- }
-
- private def blockTransfer(b: BasicBlock, in: lattice.Elem): lattice.Elem = {
- var locals: ListSet[Definition] = (in.vars filter { case (l, _, _) => !kill(b)(l) }) ++ gen(b)
- if (locals eq lattice.bottom.vars) locals = new ListSet[Definition]
- IState(locals, outStack(b) ::: in.stack.drop(drops(b)))
- }
-
- /** Return the reaching definitions corresponding to the point after idx. */
- def interpret(b: BasicBlock, idx: Int, in: lattice.Elem): Elem = {
- var locals = in.vars
- var stack = in.stack
- val instr = b(idx)
-
- instr match {
- case STORE_LOCAL(l1) =>
- locals = updateReachingDefinition(b, idx, locals)
- stack = stack.drop(instr.consumed)
- case LOAD_EXCEPTION(_) =>
- stack = Nil
- case _ =>
- stack = stack.drop(instr.consumed)
- }
-
- var prod = instr.produced
- while (prod > 0) {
- stack ::= ListSet((b, idx))
- prod -= 1
- }
-
- IState(locals, stack)
- }
-
- /** Return the instructions that produced the 'm' elements on the stack, below given 'depth'.
- * for instance, findefs(bb, idx, 1, 1) returns the instructions that might have produced the
- * value found below the topmost element of the stack.
- */
- def findDefs(bb: BasicBlock, idx: Int, m: Int, depth: Int): List[(BasicBlock, Int)] = if (idx > 0) {
- assert(bb.closed, bb)
-
- val instrs = bb.getArray
- var res: List[(BasicBlock, Int)] = Nil
- var i = idx
- var n = m
- var d = depth
- // "I look for who produced the 'n' elements below the 'd' topmost slots of the stack"
- while (n > 0 && i > 0) {
- i -= 1
- val prod = instrs(i).produced
- if (prod > d) {
- res = (bb, i) :: res
- n = n - (prod - d)
- instrs(i) match {
- case LOAD_EXCEPTION(_) => ()
- case _ => d = instrs(i).consumed
- }
- } else {
- d -= prod
- d += instrs(i).consumed
- }
- }
-
- if (n > 0) {
- val stack = this.in(bb).stack
- assert(stack.length >= n, "entry stack is too small, expected: " + n + " found: " + stack)
- stack.drop(d).take(n) foreach { defs =>
- res = defs.toList ::: res
- }
- }
- res
- } else {
- val stack = this.in(bb).stack
- assert(stack.length >= m, "entry stack is too small, expected: " + m + " found: " + stack)
- stack.drop(depth).take(m) flatMap (_.toList)
- }
-
- /** Return the definitions that produced the topmost 'm' elements on the stack,
- * and that reach the instruction at index 'idx' in basic block 'bb'.
- */
- def findDefs(bb: BasicBlock, idx: Int, m: Int): List[(BasicBlock, Int)] =
- findDefs(bb, idx, m, 0)
-
- override def toString: String = {
- if (method eq null) "<null>"
- else method.code.blocks map { b =>
- " entry(%s) = %s\n".format(b, in(b)) +
- " exit(%s) = %s\n".format(b, out(b))
- } mkString ("ReachingDefinitions {\n", "\n", "\n}")
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/SemiLattice.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/SemiLattice.scala
deleted file mode 100644
index f718c705c2..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/SemiLattice.scala
+++ /dev/null
@@ -1,49 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend.icode
-package analysis
-
-/** A complete lattice.
- */
-trait SemiLattice {
- type Elem <: AnyRef
-
- /** Hold together local variable and stack state. The
- * equals method uses reference equality for top and bottom,
- * and structural equality for other values.
- */
- final case class IState[V, S](vars: V, stack: S) {
- override def hashCode = vars.hashCode + stack.hashCode
- override def equals(other: Any): Boolean = other match {
- case x: IState[_, _] =>
- if ((this eq bottom) || (this eq top) || (x eq bottom) || (x eq top)) this eq x
- else stack == x.stack && vars == x.vars
- case _ =>
- false
- }
- private def tstring(x: Any): String = x match {
- case xs: TraversableOnce[_] => xs map tstring mkString " "
- case _ => "" + x
- }
- override def toString = "IState(" + tstring(vars) + ", " + tstring(stack) + ")"
- }
-
- /** Return the least upper bound of a and b. */
- def lub2(exceptional: Boolean)(a: Elem, b: Elem): Elem
-
- /** Return the top element. */
- def top: Elem
-
- /** Return the bottom element. */
- def bottom: Elem
-
- /** Compute the least upper bound of a list of elements. */
- def lub(xs: List[Elem], exceptional: Boolean): Elem =
- if (xs.isEmpty) bottom
- else try xs reduceLeft lub2(exceptional)
- catch { case e: LubException => Console.println("Lub on blocks: " + xs) ; throw e }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala
deleted file mode 100644
index 64c9901a3e..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala
+++ /dev/null
@@ -1,725 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala
-package tools.nsc
-package backend.icode.analysis
-
-import scala.collection.{mutable, immutable}
-import java.util.concurrent.TimeUnit
-
-/** A data-flow analysis on types, that works on `ICode`.
- *
- * @author Iulian Dragos
- */
-abstract class TypeFlowAnalysis {
- val global: Global
- import global._
- import definitions.{ ObjectClass, NothingClass, AnyRefClass, StringClass, ThrowableClass }
-
- /** The lattice of ICode types.
- */
- object typeLattice extends SemiLattice {
- type Elem = icodes.TypeKind
-
- val top = icodes.REFERENCE(ObjectClass)
- val bottom = icodes.REFERENCE(NothingClass)
-
- def lub2(exceptional: Boolean)(a: Elem, b: Elem) =
- if (a eq bottom) b
- else if (b eq bottom) a
- else icodes.lub(a, b)
- }
-
- /** The lattice of type stacks. It is a straight forward extension of
- * the type lattice (lub is pairwise lub of the list elements).
- */
- object typeStackLattice extends SemiLattice {
- import icodes._
- type Elem = TypeStack
-
- val top = new TypeStack
- val bottom = new TypeStack
- val exceptionHandlerStack = new TypeStack(List(REFERENCE(AnyRefClass)))
-
- def lub2(exceptional: Boolean)(s1: TypeStack, s2: TypeStack) = {
- if (s1 eq bottom) s2
- else if (s2 eq bottom) s1
- else if ((s1 eq exceptionHandlerStack) || (s2 eq exceptionHandlerStack)) sys.error("merging with exhan stack")
- else {
-// if (s1.length != s2.length)
-// throw new CheckerException("Incompatible stacks: " + s1 + " and " + s2);
- new TypeStack((s1.types, s2.types).zipped map icodes.lub)
- }
- }
- }
-
- /** A map which returns the bottom type for unfound elements */
- class VarBinding extends mutable.HashMap[icodes.Local, icodes.TypeKind] {
- override def default(l: icodes.Local) = typeLattice.bottom
-
- def this(o: VarBinding) = {
- this()
- this ++= o
- }
- }
-
- /** The type flow lattice contains a binding from local variable
- * names to types and a type stack.
- */
- object typeFlowLattice extends SemiLattice {
- type Elem = IState[VarBinding, icodes.TypeStack]
-
- val top = new Elem(new VarBinding, typeStackLattice.top)
- val bottom = new Elem(new VarBinding, typeStackLattice.bottom)
-
- def lub2(exceptional: Boolean)(a: Elem, b: Elem) = {
- val IState(env1, _) = a
- val IState(env2, _) = b
-
- val resultingLocals = new VarBinding
- env1 foreach { case (k, v) =>
- resultingLocals += ((k, typeLattice.lub2(exceptional)(v, env2(k))))
- }
- env2 collect { case (k, v) if resultingLocals(k) eq typeLattice.bottom =>
- resultingLocals += ((k, typeLattice.lub2(exceptional)(v, env1(k))))
- }
- val stack =
- if (exceptional) typeStackLattice.exceptionHandlerStack
- else typeStackLattice.lub2(exceptional)(a.stack, b.stack)
-
- IState(resultingLocals, stack)
- }
- }
-
- val timer = new Timer
-
- class MethodTFA extends DataFlowAnalysis[typeFlowLattice.type] {
- import icodes._
- import icodes.opcodes._
-
- type P = BasicBlock
- val lattice = typeFlowLattice
-
- val STRING = icodes.REFERENCE(StringClass)
- var method: IMethod = _
-
- /** Initialize the in/out maps for the analysis of the given method. */
- def init(m: icodes.IMethod) {
- this.method = m
- //typeFlowLattice.lubs = 0
- init {
- worklist += m.startBlock
- worklist ++= (m.exh map (_.startBlock))
- m foreachBlock { b =>
- in(b) = typeFlowLattice.bottom
- out(b) = typeFlowLattice.bottom
- }
-
- // start block has var bindings for each of its parameters
- val entryBindings = new VarBinding ++= (m.params map (p => ((p, p.kind))))
- in(m.startBlock) = lattice.IState(entryBindings, typeStackLattice.bottom)
-
- m.exh foreach { e =>
- in(e.startBlock) = lattice.IState(in(e.startBlock).vars, typeStackLattice.exceptionHandlerStack)
- }
- }
- }
-
- def this(m: icodes.IMethod) {
- this()
- init(m)
- }
-
- def run() = {
- timer.start()
- // icodes.lubs0 = 0
- forwardAnalysis(blockTransfer)
- timer.stop
- if (settings.debug) {
- linearizer.linearize(method).foreach(b => if (b != method.startBlock)
- assert(visited.contains(b),
- "Block " + b + " in " + this.method + " has input equal to bottom -- not visited? .." + visited))
- }
- // log("" + method.symbol.fullName + " [" + method.code.blocks.size + " blocks] "
- // + "\n\t" + iterations + " iterations: " + t + " ms."
- // + "\n\tlubs: " + typeFlowLattice.lubs + " out of which " + icodes.lubs0 + " typer lubs")
- }
-
- def blockTransfer(b: BasicBlock, in: lattice.Elem): lattice.Elem = {
- var result = lattice.IState(new VarBinding(in.vars), new TypeStack(in.stack))
- var instrs = b.toList
- while(!instrs.isEmpty) {
- val i = instrs.head
- result = mutatingInterpret(result, i)
- instrs = instrs.tail
- }
- result
- }
-
- /** Abstract interpretation for one instruction. */
- def interpret(in: typeFlowLattice.Elem, i: Instruction): typeFlowLattice.Elem = {
- val out = lattice.IState(new VarBinding(in.vars), new TypeStack(in.stack))
- mutatingInterpret(out, i)
- }
-
- def mutatingInterpret(out: typeFlowLattice.Elem, i: Instruction): typeFlowLattice.Elem = {
- val bindings = out.vars
- val stack = out.stack
-
- if (settings.debug) {
- // Console.println("[before] Stack: " + stack);
- // Console.println(i);
- }
- i match {
-
- case THIS(clasz) => stack push toTypeKind(clasz.tpe)
- case CONSTANT(const) => stack push toTypeKind(const.tpe)
-
- case LOAD_ARRAY_ITEM(kind) =>
- stack.pop2 match {
- case (idxKind, ARRAY(elem)) =>
- assert(idxKind == INT || idxKind == CHAR || idxKind == SHORT || idxKind == BYTE)
- stack.push(elem)
- case (_, _) =>
- stack.push(kind)
- }
-
- case LOAD_LOCAL(local) =>
- val t = bindings(local)
- stack push (if (t == typeLattice.bottom) local.kind else t)
-
- case LOAD_FIELD(field, isStatic) =>
- if (!isStatic) { stack.pop }
- stack push toTypeKind(field.tpe)
-
- case LOAD_MODULE(module) => stack push toTypeKind(module.tpe)
- case STORE_ARRAY_ITEM(kind) => stack.pop3
- case STORE_LOCAL(local) => val t = stack.pop; bindings += (local -> t)
- case STORE_THIS(_) => stack.pop
-
- case STORE_FIELD(field, isStatic) => if (isStatic) stack.pop else stack.pop2
-
- case CALL_PRIMITIVE(primitive) =>
- primitive match {
- case Negation(kind) => stack.pop; stack.push(kind)
-
- case Test(_, kind, zero) =>
- stack.pop
- if (!zero) { stack.pop }
- stack push BOOL
-
- case Comparison(_, _) => stack.pop2; stack push INT
-
- case Arithmetic(op, kind) =>
- stack.pop
- if (op != NOT) { stack.pop }
- val k = kind match {
- case BYTE | SHORT | CHAR => INT
- case _ => kind
- }
- stack push k
-
- case Logical(op, kind) => stack.pop2; stack push kind
- case Shift(op, kind) => stack.pop2; stack push kind
- case Conversion(src, dst) => stack.pop; stack push dst
- case ArrayLength(kind) => stack.pop; stack push INT
- case StartConcat => stack.push(ConcatClass)
- case EndConcat => stack.pop; stack.push(STRING)
- case StringConcat(el) => stack.pop2; stack push ConcatClass
- }
-
- case cm @ CALL_METHOD(_, _) =>
- stack pop cm.consumed
- cm.producedTypes foreach (stack push _)
-
- case BOX(kind) => stack.pop; stack.push(BOXED(kind))
- case UNBOX(kind) => stack.pop; stack.push(kind)
-
- case NEW(kind) => stack.push(kind)
-
- case CREATE_ARRAY(elem, dims) => stack.pop(dims); stack.push(ARRAY(elem))
-
- case IS_INSTANCE(tpe) => stack.pop; stack.push(BOOL)
- case CHECK_CAST(tpe) => stack.pop; stack.push(tpe)
-
- case _: SWITCH => stack.pop
- case _: JUMP => ()
- case _: CJUMP => stack.pop2
- case _: CZJUMP => stack.pop
-
- case RETURN(kind) => if (kind != UNIT) { stack.pop }
- case THROW(_) => stack.pop
-
- case DROP(kind) => stack.pop
- case DUP(kind) => stack.push(stack.head)
-
- case MONITOR_ENTER() | MONITOR_EXIT() => stack.pop
-
- case SCOPE_ENTER(_) | SCOPE_EXIT(_) => ()
-
- case LOAD_EXCEPTION(clasz) =>
- stack.pop(stack.length)
- stack.push(toTypeKind(clasz.tpe))
-
- case _ =>
- dumpClassesAndAbort("Unknown instruction: " + i)
- }
- out
- } // interpret
-
- abstract class InferredType {
- /** Return the type kind pointed by this inferred type. */
- def getKind(in: lattice.Elem): icodes.TypeKind = this match {
- case Const(k) =>
- k
- case TypeOfVar(l: icodes.Local) =>
- if (in.vars.isDefinedAt(l)) in.vars(l) else l.kind
- case TypeOfStackPos(n: Int) =>
- assert(in.stack.length >= n)
- in.stack(n)
- }
- }
- /** A type that does not depend on input to the transfer function. */
- case class Const(t: icodes.TypeKind) extends InferredType
- /** The type of a given local variable. */
- case class TypeOfVar(l: icodes.Local) extends InferredType
- /** The type found at a stack position. */
- case class TypeOfStackPos(n: Int) extends InferredType
-
- abstract class Gen
- case class Bind(l: icodes.Local, t: InferredType) extends Gen
- case class Push(t: InferredType) extends Gen
-
- /** A flow transfer function of a basic block. */
- class TransferFunction(consumed: Int, gens: List[Gen]) extends (lattice.Elem => lattice.Elem) {
- def apply(in: lattice.Elem): lattice.Elem = {
- val out = lattice.IState(new VarBinding(in.vars), new TypeStack(in.stack))
- val stack = out.stack
-
- out.stack.pop(consumed)
- for (g <- gens) g match {
- case Bind(l, t) =>
- out.vars += (l -> t.getKind(in))
- case Push(t) =>
- stack.push(t.getKind(in))
- }
- out
- }
- }
- }
-
- case class CallsiteInfo(bb: icodes.BasicBlock, receiver: Symbol, stackLength: Int, concreteMethod: Symbol)
-
- /**
-
- A full type-flow analysis on a method computes in- and out-flows for each basic block (that's what MethodTFA does).
-
- For the purposes of Inliner, doing so guarantees that an abstract typestack-slot is available by the time an inlining candidate (a CALL_METHOD instruction) is visited.
- This subclass (MTFAGrowable) of MethodTFA also aims at performing such analysis on CALL_METHOD instructions, with some differences:
-
- (a) early screening is performed while the type-flow is being computed (in an override of `blockTransfer`) by testing a subset of the conditions that Inliner checks later.
- The reasoning here is: if the early check fails at some iteration, there's no chance a follow-up iteration (with a yet more lub-ed typestack-slot) will succeed.
- Failure is sufficient to remove that particular CALL_METHOD from the typeflow's `remainingCALLs`.
- A forward note: in case inlining occurs at some basic block B, all blocks reachable from B get their CALL_METHOD instructions considered again as candidates
- (because of the more precise types that -- perhaps -- can be computed).
-
- (b) in case the early check does not fail, no conclusive decision can be made, thus the CALL_METHOD stays `isOnwatchlist`.
-
- In other words, `remainingCALLs` tracks those callsites that still remain as candidates for inlining, so that Inliner can focus on those.
- `remainingCALLs` also caches info about the typestack just before the callsite, so as to spare computing them again at inlining time.
-
- Besides caching, a further optimization involves skipping those basic blocks whose in-flow and out-flow isn't needed anyway (as explained next).
- A basic block lacking a callsite in `remainingCALLs`, when visited by the standard algorithm, won't cause any inlining.
- But as we know from the way type-flows are computed, computing the in- and out-flow for a basic block relies in general on those of other basic blocks.
- In detail, we want to focus on that sub-graph of the CFG such that control flow may reach a remaining candidate callsite.
- Those basic blocks not in that subgraph can be skipped altogether. That's why:
- - `forwardAnalysis()` in `MTFAGrowable` now checks for inclusion of a basic block in `relevantBBs`
- - same check is performed before adding a block to the worklist, and as part of choosing successors.
- The bookkeeping supporting on-the-fly pruning of irrelevant blocks requires overriding most methods of the dataflow-analysis.
-
- The rest of the story takes place in Inliner, which does not visit all of the method's basic blocks but only on those represented in `remainingCALLs`.
-
- @author Miguel Garcia, http://lampwww.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/
-
- */
- class MTFAGrowable extends MethodTFA {
-
- import icodes._
-
- val remainingCALLs = mutable.Map.empty[opcodes.CALL_METHOD, CallsiteInfo]
-
- val preCandidates = mutable.Set.empty[BasicBlock]
-
- var callerLin: Traversable[BasicBlock] = null
-
- override def run {
-
- timer.start()
- forwardAnalysis(blockTransfer)
- timer.stop
-
- /* Now that `forwardAnalysis(blockTransfer)` has finished, all inlining candidates can be found in `remainingCALLs`,
- whose keys are callsites and whose values are pieces of information about the typestack just before the callsite in question.
- In order to keep `analyzeMethod()` simple, we collect in `preCandidates` those basic blocks containing at least one candidate. */
- preCandidates.clear()
- for(rc <- remainingCALLs) {
- preCandidates += rc._2.bb
- }
-
- if (settings.debug) {
- for(b <- callerLin; if (b != method.startBlock) && preCandidates(b)) {
- assert(visited.contains(b),
- "Block " + b + " in " + this.method + " has input equal to bottom -- not visited? .." + visited)
- }
- }
-
- }
-
- var shrinkedWatchlist = false
-
- /*
- This is the method where information cached elsewhere is put to use. References are given those other places that populate those caches.
-
- The goal is avoiding computing type-flows for blocks we don't need (ie blocks not tracked in `relevantBBs`). The method used to add to `relevantBBs` is `putOnRadar`.
-
- Moreover, it's often the case that the last CALL_METHOD of interest ("of interest" equates to "being tracked in `isOnWatchlist`) isn't the last instruction on the block.
- There are cases where the typeflows computed past this `lastInstruction` are needed, and cases when they aren't.
- The reasoning behind this decision is described in `populatePerimeter()`. All `blockTransfer()` needs to do (in order to know at which instruction it can stop)
- is querying `isOnPerimeter`.
-
- Upon visiting a CALL_METHOD that's an inlining candidate, the relevant pieces of information about the pre-instruction typestack are collected for future use.
- That is, unless the candidacy test fails. The reasoning here is: if such early check fails at some iteration, there's no chance a follow-up iteration
- (with a yet more lub-ed typestack-slot) will succeed. In case of failure we can safely remove the CALL_METHOD from both `isOnWatchlist` and `remainingCALLs`.
-
- */
- override def blockTransfer(b: BasicBlock, in: lattice.Elem): lattice.Elem = {
- var result = lattice.IState(new VarBinding(in.vars), new TypeStack(in.stack))
-
- val stopAt = if(isOnPerimeter(b)) lastInstruction(b) else null
- var isPastLast = false
-
- var instrs = b.toList
- while(!isPastLast && !instrs.isEmpty) {
- val i = instrs.head
-
- if(isOnWatchlist(i)) {
- val cm = i.asInstanceOf[opcodes.CALL_METHOD]
- val msym = cm.method
- val paramsLength = msym.info.paramTypes.size
- val receiver = result.stack.types.drop(paramsLength).head match {
- case REFERENCE(s) => s
- case _ => NoSymbol // e.g. the scrutinee is BOX(s) or ARRAY
- }
- val concreteMethod = inliner.lookupImplFor(msym, receiver)
- val isCandidate = {
- ( inliner.isClosureClass(receiver) || concreteMethod.isEffectivelyFinalOrNotOverridden || receiver.isEffectivelyFinalOrNotOverridden ) &&
- !blackballed(concreteMethod)
- }
- if(isCandidate) {
- remainingCALLs(cm) = CallsiteInfo(b, receiver, result.stack.length, concreteMethod)
- } else {
- remainingCALLs.remove(cm)
- isOnWatchlist.remove(cm)
- shrinkedWatchlist = true
- }
- }
-
- isPastLast = (i eq stopAt)
-
- if(!isPastLast) {
- result = mutatingInterpret(result, i)
- instrs = instrs.tail
- }
- }
-
- result
- } // end of method blockTransfer
-
- val isOnWatchlist = mutable.Set.empty[Instruction]
-
- val warnIfInlineFails = mutable.Set.empty[opcodes.CALL_METHOD] // cache for a given IMethod (ie cleared on Inliner.analyzeMethod).
-
- /* Each time CallerCalleeInfo.isSafeToInline determines a concrete callee is unsafe to inline in the current caller,
- the fact is recorded in this TFA instance for the purpose of avoiding devoting processing to that callsite next time.
- The condition of "being unsafe to inline in the current caller" sticks across inlinings and TFA re-inits
- because it depends on the instructions of the callee, which stay unchanged during the course of `analyzeInc(caller)`
- (with the caveat of the side-effecting `makePublic` in `helperIsSafeToInline`).*/
- val knownUnsafe = mutable.Set.empty[Symbol]
- val knownSafe = mutable.Set.empty[Symbol]
- val knownNever = mutable.Set.empty[Symbol] // `knownNever` needs be cleared only at the very end of the inlining phase (unlike `knownUnsafe` and `knownSafe`)
- final def blackballed(msym: Symbol): Boolean = { knownUnsafe(msym) || knownNever(msym) }
-
- val relevantBBs = mutable.Set.empty[BasicBlock]
-
- /*
- * Rationale to prevent some methods from ever being inlined:
- *
- * (1) inlining getters and setters results in exposing a private field,
- * which may itself prevent inlining of the caller (at best) or
- * lead to situations like SI-5442 ("IllegalAccessError when mixing optimized and unoptimized bytecode")
- *
- * (2) only invocations having a receiver object are considered (ie no static-methods are ever inlined).
- * This is taken care of by checking `isDynamic` (ie virtual method dispatch) and `Static(true)` (ie calls to private members)
- */
- private def isPreCandidate(cm: opcodes.CALL_METHOD): Boolean = {
- val msym = cm.method
- val style = cm.style
-
- !blackballed(msym) &&
- !msym.isConstructor &&
- (!msym.isAccessor || inliner.isClosureClass(msym.owner)) &&
- (style.isDynamic || (style.hasInstance && style.isStatic))
- }
-
- override def init(m: icodes.IMethod) {
- super.init(m)
- remainingCALLs.clear()
- knownUnsafe.clear()
- knownSafe.clear()
- // initially populate the watchlist with all callsites standing a chance of being inlined
- isOnWatchlist.clear()
- relevantBBs.clear()
- warnIfInlineFails.clear()
- /* TODO Do we want to perform inlining in non-finally exception handlers?
- * Seems counterproductive (the larger the method the less likely it will be JITed.
- * It's not that putting on radar only `linearizer linearizeAt (m, m.startBlock)` makes for much shorter inlining times (a minor speedup nonetheless)
- * but the effect on method size could be explored. */
- putOnRadar(m.linearizedBlocks(linearizer))
- populatePerimeter()
- // usually but not always true (counterexample in SI-6015) `(relevantBBs.isEmpty || relevantBBs.contains(m.startBlock))`
- }
-
- def conclusives(b: BasicBlock): List[opcodes.CALL_METHOD] = {
- knownBeforehand(b) filter { cm => inliner.isMonadicMethod(cm.method) || inliner.hasInline(cm.method) }
- }
-
- def knownBeforehand(b: BasicBlock): List[opcodes.CALL_METHOD] = {
- b.toList collect { case c : opcodes.CALL_METHOD => c } filter { cm => isPreCandidate(cm) && isReceiverKnown(cm) }
- }
-
- private def isReceiverKnown(cm: opcodes.CALL_METHOD): Boolean = {
- cm.method.isEffectivelyFinalOrNotOverridden && cm.method.owner.isEffectivelyFinalOrNotOverridden
- }
-
- private def putOnRadar(blocks: Traversable[BasicBlock]) {
- for(bb <- blocks) {
- val calls = bb.toList collect { case cm : opcodes.CALL_METHOD => cm }
- for(c <- calls; if(inliner.hasInline(c.method))) {
- warnIfInlineFails += c
- }
- val preCands = calls filter isPreCandidate
- isOnWatchlist ++= preCands
- }
- relevantBBs ++= blocks
- }
-
- /* those BBs in the argument are also included in the result */
- private def transitivePreds(starters: Traversable[BasicBlock]): Set[BasicBlock] = {
- val result = mutable.Set.empty[BasicBlock]
- var toVisit: List[BasicBlock] = starters.toList.distinct
- while(toVisit.nonEmpty) {
- val h = toVisit.head
- toVisit = toVisit.tail
- result += h
- for(p <- h.predecessors; if !result(p) && !toVisit.contains(p)) { toVisit = p :: toVisit }
- }
- result.toSet
- }
-
- /* A basic block B is "on the perimeter" of the current control-flow subgraph if none of its successors belongs to that subgraph.
- * In that case, for the purposes of inlining, we're interested in the typestack right before the last inline candidate in B, not in those afterwards.
- * In particular we can do without computing the outflow at B. */
- private def populatePerimeter() {
- isOnPerimeter.clear()
- var done = true
- do {
- val (frontier, toPrune) = (relevantBBs filter hasNoRelevantSuccs) partition isWatching
- isOnPerimeter ++= frontier
- relevantBBs --= toPrune
- done = toPrune.isEmpty
- } while(!done)
-
- lastInstruction.clear()
- for (b <- isOnPerimeter; lastIns = b.toList.reverse find isOnWatchlist) {
- lastInstruction += (b -> lastIns.get.asInstanceOf[opcodes.CALL_METHOD])
- }
-
- // assertion: "no relevant block can have a predecessor that is on perimeter"
- assert((for (b <- relevantBBs; if transitivePreds(b.predecessors) exists isOnPerimeter) yield b).isEmpty)
- }
-
- private val isOnPerimeter = mutable.Set.empty[BasicBlock]
- private val lastInstruction = mutable.Map.empty[BasicBlock, opcodes.CALL_METHOD]
-
- def hasNoRelevantSuccs(x: BasicBlock): Boolean = { !(x.successors exists relevantBBs) }
-
- def isWatching(x: BasicBlock): Boolean = (x.toList exists isOnWatchlist)
-
-
-
-
- /**
-
- This method is invoked after one or more inlinings have been performed in basic blocks whose in-flow is non-bottom (this makes a difference later).
- What we know about those inlinings is given by:
-
- - `staleOut`: These are the blocks where a callsite was inlined.
- For each callsite, all instructions in that block before the callsite were left in the block, and the rest moved to an `afterBlock`.
- The out-flow of these basic blocks is thus in general stale, that's why we'll add them to the TFA worklist.
-
- - `inlined` : These blocks were spliced into the method's CFG as part of inlining. Being new blocks, they haven't been visited yet by the typeflow analysis.
-
- - `staleIn` : These blocks are what `doInline()` calls `afterBlock`s, ie the new home for instructions that previously appeared
- after a callsite in a `staleOut` block.
-
- Based on the above information, we have to bring up-to-date the caches that `forwardAnalysis` and `blockTransfer` use to skip blocks and instructions.
- Those caches are `relevantBBs` and `isOnPerimeter` (for blocks) and `isOnWatchlist` and `lastInstruction` (for CALL_METHODs).
- Please notice that all `inlined` and `staleIn` blocks are reachable from `staleOut` blocks.
-
- The update takes place in two steps:
-
- (1) `staleOut foreach { so => putOnRadar(linearizer linearizeAt (m, so)) }`
- This results in initial populations for `relevantBBs` and `isOnWatchlist`.
- Because of the way `isPreCandidate` reuses previous decision-outcomes that are still valid,
- this already prunes some candidates standing no chance of being inlined.
-
- (2) `populatePerimeter()`
- Based on the CFG-subgraph determined in (1) as reflected in `relevantBBs`,
- this method detects some blocks whose typeflows aren't needed past a certain CALL_METHOD
- (not needed because none of its successors is relevant for the purposes of inlining, see `hasNoRelevantSuccs`).
- The blocks thus chosen are said to be "on the perimeter" of the CFG-subgraph.
- For each of them, its `lastInstruction` (after which no more typeflows are needed) is found.
-
- */
- def reinit(m: icodes.IMethod, staleOut: List[BasicBlock], inlined: scala.collection.Set[BasicBlock], staleIn: scala.collection.Set[BasicBlock]) {
- if (this.method == null || this.method.symbol != m.symbol) {
- init(m)
- return
- } else if(staleOut.isEmpty && inlined.isEmpty && staleIn.isEmpty) {
- // this promotes invoking reinit if in doubt, no performance degradation will ensue!
- return
- }
-
- worklist.clear() // calling reinit(f: => Unit) would also clear visited, thus forgetting about blocks visited before reinit.
-
- // asserts conveying an idea what CFG shapes arrive here:
- // staleIn foreach (p => assert( !in.isDefinedAt(p), p))
- // staleIn foreach (p => assert(!out.isDefinedAt(p), p))
- // inlined foreach (p => assert( !in.isDefinedAt(p), p))
- // inlined foreach (p => assert(!out.isDefinedAt(p), p))
- // inlined foreach (p => assert(!p.successors.isEmpty || p.lastInstruction.isInstanceOf[icodes.opcodes.THROW], p))
- // staleOut foreach (p => assert( in.isDefinedAt(p), p))
-
- // remainingCALLs.clear()
- isOnWatchlist.clear()
- relevantBBs.clear()
-
- // never rewrite in(m.startBlock)
- staleOut foreach { b =>
- enqueue(b)
- out(b) = typeFlowLattice.bottom
- }
- // nothing else is added to the worklist, bb's reachable via succs will be tfa'ed
- blankOut(inlined)
- blankOut(staleIn)
- // no need to add startBlocks from m.exh
-
- staleOut foreach { so => putOnRadar(linearizer linearizeAt (m, so)) }
- populatePerimeter()
-
- } // end of method reinit
-
- /* this is not a general purpose method to add to the worklist,
- * because the assert is expected to hold only when called from MTFAGrowable.reinit() */
- private def enqueue(b: BasicBlock) {
- assert(in(b) ne typeFlowLattice.bottom)
- if(!worklist.contains(b)) { worklist += b }
- }
-
- private def blankOut(blocks: scala.collection.Set[BasicBlock]) {
- blocks foreach { b =>
- in(b) = typeFlowLattice.bottom
- out(b) = typeFlowLattice.bottom
- }
- }
-
- /*
- This is basically the plain-old forward-analysis part of a dataflow algorithm,
- adapted to skip non-relevant blocks (as determined by `reinit()` via `populatePerimeter()`).
-
- The adaptations are:
-
- - only relevant blocks dequeued from the worklist move on to have the transfer function applied
-
- - `visited` now means the transfer function was applied to the block,
- but please notice that this does not imply anymore its out-flow to be different from bottom,
- because a block on the perimeter will have per-instruction typeflows computed only up to its `lastInstruction`.
- In case you need to know whether a visted block `v` has been "fully visited", evaluate `out(v) ne typeflowLattice.bottom`
-
- - given that the transfer function may remove callsite-candidates from the watchlist (thus, they are not candidates anymore)
- there's an opportunity to detect whether a previously relevant block has been left without candidates.
- That's what `shrinkedWatchlist` detects. Provided the block was on the perimeter, we know we can skip it from now now,
- and we can also constrain the CFG-subgraph by finding a new perimeter (thus the invocation to `populatePerimeter()`).
- */
- override def forwardAnalysis(f: (P, lattice.Elem) => lattice.Elem): Unit = {
- while (!worklist.isEmpty && relevantBBs.nonEmpty) {
- if (stat) iterations += 1
- val point = worklist.iterator.next(); worklist -= point
- if(relevantBBs(point)) {
- shrinkedWatchlist = false
- val output = f(point, in(point))
- visited += point
- if(isOnPerimeter(point)) {
- if(shrinkedWatchlist && !isWatching(point)) {
- relevantBBs -= point
- populatePerimeter()
- }
- } else {
- val propagate = ((lattice.bottom == out(point)) || output != out(point))
- if (propagate) {
- out(point) = output
- val succs = point.successors filter relevantBBs
- succs foreach { p =>
- assert((p.predecessors filter isOnPerimeter).isEmpty)
- val existing = in(p)
- // TODO move the following assertion to typeFlowLattice.lub2 for wider applicability (ie MethodTFA in addition to MTFAGrowable).
- assert(existing == lattice.bottom ||
- p.exceptionHandlerStart ||
- (output.stack.length == existing.stack.length),
- "Trying to merge non-bottom type-stacks with different stack heights. For a possible cause see SI-6157.")
- val updated = lattice.lub(List(output, existing), p.exceptionHandlerStart)
- if(updated != in(p)) {
- in(p) = updated
- enqueue(p)
- }
- }
- }
- }
- }
- }
- }
-
- }
-
- class Timer {
- var millis = 0L
-
- private var lastStart = 0L
-
- def start() {
- lastStart = System.nanoTime()
- }
-
- /** Stop the timer and return the number of milliseconds since the last
- * call to start. The 'millis' field is increased by the elapsed time.
- */
- def stop: Long = {
- val elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - lastStart)
- millis += elapsed
- elapsed
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
index cd7e0b83e8..402dc66a7f 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
@@ -5,11 +5,15 @@
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.{CheckClassAdapter, TraceClassVisitor, TraceMethodVisitor, Textifier}
-import scala.tools.asm.{ClassWriter, Attribute, ClassReader}
-import scala.collection.convert.decorateAsScala._
+import scala.tools.asm.tree.{AbstractInsnNode, ClassNode, FieldNode, InsnList, MethodNode}
+import java.io.{PrintWriter, StringWriter}
+import java.util
+
+import scala.tools.asm.util.{CheckClassAdapter, Textifier, TraceClassVisitor, TraceMethodVisitor}
+import scala.tools.asm.{Attribute, ClassReader, ClassWriter}
+import scala.collection.JavaConverters._
+import scala.concurrent.duration.Duration
+import scala.concurrent.{Await, Future}
import scala.tools.nsc.backend.jvm.analysis.InitialProducer
import scala.tools.nsc.backend.jvm.opt.InlineInfoAttributePrototype
@@ -29,7 +33,7 @@ object AsmUtils {
final val traceClassPattern = ""
/**
- * Print the bytedcode of classes as they are serialized by the ASM library. The serialization
+ * Print the bytecode of classes as they are serialized by the ASM library. The serialization
* performed by `asm.ClassWriter` can change the code generated by GenBCode. For example, it
* introduces stack map frames, it computes the maximal stack sizes, and it replaces dead
* code by NOPs (see also https://github.com/scala/scala/pull/3726#issuecomment-42861780).
@@ -55,6 +59,48 @@ object AsmUtils {
node
}
+ def readClass(filename: String): ClassNode = readClass(classBytes(filename))
+
+ def classBytes(file: String): Array[Byte] = {
+ val f = new java.io.RandomAccessFile(file, "r")
+ val bytes = new Array[Byte](f.length.toInt)
+ f.read(bytes)
+ bytes
+ }
+
+ def classFromBytes(bytes: Array[Byte]): ClassNode = {
+ val node = new ClassNode()
+ new ClassReader(bytes).accept(node, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
+
+ node
+ }
+
+// def main(args: Array[String]): Unit = println(textify(sortedClassRead(classBytes(args.head))))
+
+ def sortClassMembers(node: ClassNode): node.type = {
+ node.fields.sort(_.name compareTo _.name)
+ node.methods.sort(_.name compareTo _.name)
+ node
+ }
+
+ // drop ScalaSig annotation and class attributes
+ def zapScalaClassAttrs(node: ClassNode): node.type = {
+ if (node.visibleAnnotations != null)
+ node.visibleAnnotations = node.visibleAnnotations.asScala.filterNot(a => a == null || a.desc.contains("Lscala/reflect/ScalaSignature")).asJava
+
+ node.attrs = null
+ node
+ }
+
+ def main(args: Array[String]): Unit = args.par.foreach { classFileName =>
+ val node = zapScalaClassAttrs(sortClassMembers(classFromBytes(classBytes(classFileName))))
+
+ val pw = new PrintWriter(classFileName + ".asm")
+ val trace = new TraceClassVisitor(pw)
+ node.accept(trace)
+ pw.close()
+ }
+
/**
* Returns a human-readable representation of the cnode ClassNode.
*/
@@ -115,12 +161,12 @@ object AsmUtils {
* 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] = {
+ def checkClass(classNode: ClassNode, dumpNonErroneous: Boolean = false): 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)
+ CheckClassAdapter.verify(new ClassReader(cw.toByteArray), dumpNonErroneous, 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
deleted file mode 100644
index 93f5159f89..0000000000
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
+++ /dev/null
@@ -1,465 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2014 LAMP/EPFL
- * @author Martin Odersky
- */
-
-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
-import scala.tools.asm
-
-/**
- * This trait contains code shared between GenBCode and GenASM that depends on types defined in
- * the compiler cake (Global).
- */
-final class BCodeAsmCommon[G <: Global](val global: G) {
- import global._
- import definitions._
-
- val ExcludedForwarderFlags = {
- import scala.tools.nsc.symtab.Flags._
- // Should include DEFERRED but this breaks findMember.
- SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO
- }
-
- /**
- * 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
- * null.
- */
- def isAnonymousOrLocalClass(classSym: Symbol): Boolean = {
- assert(classSym.isClass, s"not a class: $classSym")
- 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
- }
-
- /**
- * Returns the enclosing method for non-member classes. In the following example
- *
- * class A {
- * def f = {
- * class B {
- * class C
- * }
- * }
- * }
- *
- * the method returns Some(f) for B, but None for C, because C is a member class. For non-member
- * classes that are not enclosed by a method, it returns None:
- *
- * class A {
- * { class B }
- * }
- *
- * In this case, for B, we return None.
- *
- * The EnclosingMethod attribute needs to be added to non-member classes (see doc in BTypes).
- * This is a source-level property, so we need to use the originalOwner chain to reconstruct it.
- */
- 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) {
- if (doesNotExist(sym)) None else Some(sym)
- }
- else enclosingMethod(nextEnclosing(sym))
- }
- enclosingMethod(nextEnclosing(classSym))
- }
-
- /**
- * The enclosing class for emitting the EnclosingMethod attribute. Since this is a source-level
- * property, this method looks at the originalOwner chain. See doc in BTypes.
- */
- private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = {
- assert(classSym.isClass, classSym)
- 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)
-
- /**
- * Data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not
- * an anonymous or local class). See doc in BTypes.
- *
- * The class is parametrized by two functions to obtain a bytecode class descriptor for a class
- * symbol, and to obtain a method signature descriptor fro a method symbol. These function depend
- * 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] = {
- // 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(enclosingClass),
- methodOpt.map(_.javaSimpleName.toString).orNull,
- methodOpt.map(methodDesc).orNull))
- } else {
- None
- }
- }
-
- /**
- * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain.
- *
- * The problem is that we are interested in a source-level property. Various phases changed the
- * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner.
- * Therefore, `sym.isStatic` is not what we want. For example, in
- * object T { def f { object U } }
- * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here.
- */
- def isOriginallyStaticOwner(sym: Symbol): Boolean = {
- sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner)
- }
-
- /**
- * Reconstruct the classfile flags from a Java defined class symbol.
- *
- * The implementation of this method is slightly different that `javaFlags` in BTypesFromSymbols.
- * The javaFlags method is primarily used to map Scala symbol flags to sensible classfile flags
- * that are used in the generated classfiles. For example, all classes emitted by the Scala
- * compiler have ACC_PUBLIC.
- *
- * When building a [[ClassBType]] from a Java class symbol, the flags in the type's `info` have
- * to correspond exactly to the flags in the classfile. For example, if the class is package
- * protected (i.e., it doesn't have the ACC_PUBLIC flag), this needs to be reflected in the
- * ClassBType. For example, the inliner needs the correct flags for access checks.
- *
- * Class flags are listed here:
- * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1-200-E.1
- */
- def javaClassfileFlags(classSym: Symbol): Int = {
- assert(classSym.isJava, s"Expected Java class symbol, got ${classSym.fullName}")
- import asm.Opcodes._
- def enumFlags = ACC_ENUM | {
- // Java enums have the `ACC_ABSTRACT` flag if they have a deferred method.
- // We cannot trust `hasAbstractFlag`: the ClassfileParser adds `ABSTRACT` and `SEALED` to all
- // Java enums for exhaustiveness checking.
- val hasAbstractMethod = classSym.info.decls.exists(s => s.isMethod && s.isDeferred)
- if (hasAbstractMethod) ACC_ABSTRACT else 0
- }
- GenBCode.mkFlags(
- // SI-9393: the classfile / java source parser make java annotation symbols look like classes.
- // here we recover the actual classfile flags.
- if (classSym.hasJavaAnnotationFlag) ACC_ANNOTATION | ACC_INTERFACE | ACC_ABSTRACT else 0,
- if (classSym.isPublic) ACC_PUBLIC else 0,
- if (classSym.isFinal) ACC_FINAL else 0,
- // see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces.
- if (classSym.isInterface) ACC_INTERFACE else ACC_SUPER,
- // for Java enums, we cannot trust `hasAbstractFlag` (see comment in enumFlags)
- if (!classSym.hasJavaEnumFlag && classSym.hasAbstractFlag) ACC_ABSTRACT else 0,
- if (classSym.isArtifact) ACC_SYNTHETIC else 0,
- if (classSym.hasJavaEnumFlag) enumFlags else 0
- )
- }
-
- /**
- * 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 memberClassesForInnerClassTable(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({
- case sym if sym.isClass && !considerAsTopLevelImplementationArtifact(sym) =>
- sym
- 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
- })(collection.breakOut)
-
- lazy val AnnotationRetentionPolicyModule = AnnotationRetentionPolicyAttr.companionModule
- lazy val AnnotationRetentionPolicySourceValue = AnnotationRetentionPolicyModule.tpe.member(TermName("SOURCE"))
- lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS"))
- lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME"))
-
- /** Whether an annotation should be emitted as a Java annotation
- * .initialize: if 'annot' is read from pickle, atp might be uninitialized
- */
- def shouldEmitAnnotation(annot: AnnotationInfo) = {
- annot.symbol.initialize.isJavaDefined &&
- annot.matches(ClassfileAnnotationClass) &&
- retentionPolicyOf(annot) != AnnotationRetentionPolicySourceValue &&
- annot.args.isEmpty
- }
-
- def isRuntimeVisible(annot: AnnotationInfo): Boolean = {
- annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr) match {
- case Some(retentionAnnot) =>
- retentionAnnot.assocs.contains(nme.value -> LiteralAnnotArg(Constant(AnnotationRetentionPolicyRuntimeValue)))
- case _ =>
- // SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the
- // annotation is emitted with visibility `RUNTIME`
- true
- }
- }
-
- private def retentionPolicyOf(annot: AnnotationInfo): Symbol =
- annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).flatMap(assoc =>
- assoc.collectFirst {
- case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value
- }).getOrElse(AnnotationRetentionPolicyClassValue)
-
- def implementedInterfaces(classSym: Symbol): List[Symbol] = {
- // Additional interface parents based on annotations and other cues
- def newParentForAnnotation(ann: AnnotationInfo): Option[Type] = ann.symbol match {
- case RemoteAttr => Some(RemoteInterfaceClass.tpe)
- case _ => None
- }
-
- // SI-9393: java annotations are interfaces, but the classfile / java source parsers make them look like classes.
- def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait || sym.hasJavaAnnotationFlag
-
- val classParents = {
- val parents = classSym.info.parents
- // SI-9393: the classfile / java source parsers add Annotation and ClassfileAnnotation to the
- // parents of a java annotations. undo this for the backend (where we need classfile-level information).
- if (classSym.hasJavaAnnotationFlag) parents.filterNot(c => c.typeSymbol == ClassfileAnnotationClass || c.typeSymbol == AnnotationClass)
- else parents
- }
-
- val allParents = classParents ++ classSym.annotations.flatMap(newParentForAnnotation)
-
- // We keep the superClass when computing minimizeParents to eliminate more interfaces.
- // Example: T can be eliminated from D
- // trait T
- // class C extends T
- // class D extends C with T
- val interfaces = erasure.minimizeParents(allParents) match {
- case superClass :: ifs if !isInterfaceOrTrait(superClass.typeSymbol) =>
- ifs
- case ifs =>
- // minimizeParents removes the superclass if it's redundant, for example:
- // trait A
- // class C extends Object with A // minimizeParents removes Object
- ifs
- }
- 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 traitSelfType = if (classSym.isTrait && !classSym.isImplClass) {
- // 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
- } 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(traitSelfType, isEffectivelyFinal, methodInlineInfos, warning)
- }
-}
-
-object BCodeAsmCommon {
- /**
- * Valid flags for InnerClass attribute entry.
- * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6
- */
- val INNER_CLASSES_FLAGS = {
- asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED |
- asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL | asm.Opcodes.ACC_INTERFACE |
- asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION |
- asm.Opcodes.ACC_ENUM
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
index 416628d5ba..37dea477c6 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
@@ -11,10 +11,13 @@ package jvm
import scala.annotation.switch
import scala.reflect.internal.Flags
-
import scala.tools.asm
import GenBCode._
import BackendReporting._
+import scala.collection.mutable
+import scala.tools.asm.Opcodes
+import scala.tools.asm.tree.{MethodInsnNode, MethodNode}
+import scala.tools.nsc.backend.jvm.BCodeHelpers.{InvokeStyle, TestOp}
/*
*
@@ -26,24 +29,12 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
import global._
import definitions._
import bTypes._
- import bCodeICodeCommon._
import coreBTypes._
/*
* Functionality to build the body of ASM MethodNode, except for `synchronized` and `try` expressions.
*/
abstract class PlainBodyBuilder(cunit: CompilationUnit) extends PlainSkelBuilder(cunit) {
- import icodes.TestOp
- import icodes.opcodes.InvokeStyle
-
- /* If the selector type has a member with the right name,
- * it is the host class; otherwise the symbol's owner.
- */
- def findHostClass(selector: Type, sym: Symbol) = selector member sym.name match {
- case NoSymbol => debuglog(s"Rejecting $selector as host class for $sym") ; sym.owner
- case _ => selector.typeSymbol
- }
-
/* ---------------- helper utils for generating methods and code ---------------- */
def emit(opc: Int) { mnode.visitInsn(opc) }
@@ -71,12 +62,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
def genStat(tree: Tree) {
lineNumber(tree)
tree match {
- case Assign(lhs @ Select(_, _), rhs) =>
+ case Assign(lhs @ Select(qual, _), rhs) =>
val isStatic = lhs.symbol.isStaticMember
if (!isStatic) { genLoadQualifier(lhs) }
genLoad(rhs, symInfoTK(lhs.symbol))
lineNumber(tree)
- fieldStore(lhs.symbol)
+ // receiverClass is used in the bytecode to access the field. using sym.owner may lead to IllegalAccessError, SI-4283
+ val receiverClass = qual.tpe.typeSymbol
+ fieldStore(lhs.symbol, receiverClass)
case Assign(lhs, rhs) =>
val s = lhs.symbol
@@ -94,12 +87,12 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val thrownKind = tpeTK(expr)
// `throw null` is valid although scala.Null (as defined in src/library-aux) isn't a subtype of Throwable.
// Similarly for scala.Nothing (again, as defined in src/library-aux).
- assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference).get)
+ assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(jlThrowableRef).get)
genLoad(expr, thrownKind)
lineNumber(expr)
emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level.
- RT_NOTHING // always returns the same, the invoker should know :)
+ srNothingRef // always returns the same, the invoker should know :)
}
/* Generate code for primitive arithmetic operations. */
@@ -119,21 +112,22 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
code match {
case POS => () // nothing
case NEG => bc.neg(resKind)
- case NOT => bc.genPrimitiveArithmetic(icodes.NOT, resKind)
+ case NOT => bc.genPrimitiveNot(resKind)
case _ => abort(s"Unknown unary operation: ${fun.symbol.fullName} code: $code")
}
// binary operation
case rarg :: Nil =>
- resKind = tpeTK(larg).maxType(tpeTK(rarg))
- if (scalaPrimitives.isShiftOp(code) || scalaPrimitives.isBitwiseOp(code)) {
+ val isShiftOp = scalaPrimitives.isShiftOp(code)
+ resKind = tpeTK(larg).maxType(if (isShiftOp) INT else tpeTK(rarg))
+
+ if (isShiftOp || scalaPrimitives.isBitwiseOp(code)) {
assert(resKind.isIntegralType || (resKind == BOOL),
s"$resKind incompatible with arithmetic modulo operation.")
}
genLoad(larg, resKind)
- genLoad(rarg, // check .NET size of shift arguments!
- if (scalaPrimitives.isShiftOp(code)) INT else resKind)
+ genLoad(rarg, if (isShiftOp) INT else resKind)
(code: @switch) match {
case ADD => bc add resKind
@@ -171,21 +165,13 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
genLoad(args.head, INT)
generatedType = k.asArrayBType.componentType
bc.aload(elementType)
- }
- else if (scalaPrimitives.isArraySet(code)) {
- args match {
- case a1 :: a2 :: Nil =>
- genLoad(a1, INT)
- genLoad(a2)
- // the following line should really be here, but because of bugs in erasure
- // we pretend we generate whatever type is expected from us.
- //generatedType = UNIT
- bc.astore(elementType)
- case _ =>
- abort(s"Too many arguments for array set operation: $tree")
- }
- }
- else {
+ } else if (scalaPrimitives.isArraySet(code)) {
+ val List(a1, a2) = args
+ genLoad(a1, INT)
+ genLoad(a2, elementType)
+ generatedType = UNIT
+ bc.astore(elementType)
+ } else {
generatedType = INT
emit(asm.Opcodes.ARRAYLENGTH)
}
@@ -203,14 +189,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val hasElse = !elsep.isEmpty
val postIf = if (hasElse) new asm.Label else failure
- genCond(condp, success, failure)
+ genCond(condp, success, failure, targetIfNoJump = success)
+ markProgramPoint(success)
val thenKind = tpeTK(thenp)
val elseKind = if (!hasElse) UNIT else tpeTK(elsep)
def hasUnitBranch = (thenKind == UNIT || elseKind == UNIT)
val resKind = if (hasUnitBranch) UNIT else tpeTK(tree)
- markProgramPoint(success)
genLoad(thenp, resKind)
if (hasElse) { bc goTo postIf }
markProgramPoint(failure)
@@ -235,14 +221,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
else if (isArrayOp(code)) genArrayOp(tree, code, expectedType)
else if (isLogicalOp(code) || isComparisonOp(code)) {
val success, failure, after = new asm.Label
- genCond(tree, success, failure)
+ genCond(tree, success, failure, targetIfNoJump = success)
// success block
- markProgramPoint(success)
- bc boolconst true
- bc goTo after
+ markProgramPoint(success)
+ bc boolconst true
+ bc goTo after
// failure block
- markProgramPoint(failure)
- bc boolconst false
+ markProgramPoint(failure)
+ bc boolconst false
// after
markProgramPoint(after)
@@ -311,6 +297,15 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
case app : Apply =>
generatedType = genApply(app, expectedType)
+ case app @ ApplyDynamic(qual, Literal(Constant(bootstrapMethodRef: Symbol)) :: staticAndDynamicArgs) =>
+ val numStaticArgs = bootstrapMethodRef.paramss.head.size - 3 /*JVM provided args*/
+ val (staticArgs, dynamicArgs) = staticAndDynamicArgs.splitAt(numStaticArgs)
+ val bootstrapDescriptor = staticHandleFromSymbol(bootstrapMethodRef)
+ val bootstrapArgs = staticArgs.map({case t @ Literal(c: Constant) => bootstrapMethodArg(c, t.pos)})
+ val descriptor = methodBTypeFromMethodType(qual.symbol.info, false)
+ genLoadArguments(dynamicArgs, qual.symbol.info.params.map(param => typeToBType(param.info)))
+ mnode.visitInvokeDynamicInsn(qual.symbol.name.encoded, descriptor.descriptor, bootstrapDescriptor, bootstrapArgs : _*)
+
case ApplyDynamic(qual, args) => sys.error("No invokedynamic support yet.")
case This(qual) =>
@@ -323,7 +318,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
else {
mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)
generatedType =
- if (tree.symbol == ArrayClass) ObjectReference
+ if (tree.symbol == ArrayClass) ObjectRef
else classBTypeFromSymbol(claszSymbol)
}
@@ -331,26 +326,22 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
assert(tree.symbol.isModule, s"Selection of non-module from empty package: $tree sym: ${tree.symbol} at: ${tree.pos}")
genLoadModule(tree)
- case Select(qualifier, selector) =>
+ case Select(qualifier, _) =>
val sym = tree.symbol
generatedType = symInfoTK(sym)
- val hostClass = findHostClass(qualifier.tpe, sym)
- debuglog(s"Host class of $sym with qual $qualifier (${qualifier.tpe}) is $hostClass")
val qualSafeToElide = treeInfo isQualifierSafeToElide qualifier
-
def genLoadQualUnlessElidable() { if (!qualSafeToElide) { genLoadQualifier(tree) } }
-
+ // receiverClass is used in the bytecode to access the field. using sym.owner may lead to IllegalAccessError, SI-4283
+ def receiverClass = qualifier.tpe.typeSymbol
if (sym.isModule) {
genLoadQualUnlessElidable()
genLoadModule(tree)
- }
- else if (sym.isStaticMember) {
+ } else if (sym.isStaticMember) {
genLoadQualUnlessElidable()
- fieldLoad(sym, hostClass)
- }
- else {
+ fieldLoad(sym, receiverClass)
+ } else {
genLoadQualifier(tree)
- fieldLoad(sym, hostClass)
+ fieldLoad(sym, receiverClass)
}
case Ident(name) =>
@@ -366,7 +357,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
if (value.tag != UnitTag) (value.tag, expectedType) match {
case (IntTag, LONG ) => bc.lconst(value.longValue); generatedType = LONG
case (FloatTag, DOUBLE) => bc.dconst(value.doubleValue); generatedType = DOUBLE
- case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = RT_NULL
+ case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = srNullRef
case _ => genConstant(value); generatedType = tpeTK(tree)
}
@@ -403,24 +394,18 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
/*
* must-single-thread
*/
- def fieldLoad( field: Symbol, hostClass: Symbol = null) {
- fieldOp(field, isLoad = true, hostClass)
- }
+ def fieldLoad(field: Symbol, hostClass: Symbol): Unit = fieldOp(field, isLoad = true, hostClass)
+
/*
* must-single-thread
*/
- def fieldStore(field: Symbol, hostClass: Symbol = null) {
- fieldOp(field, isLoad = false, hostClass)
- }
+ def fieldStore(field: Symbol, hostClass: Symbol): Unit = fieldOp(field, isLoad = false, hostClass)
/*
* must-single-thread
*/
- private def fieldOp(field: Symbol, isLoad: Boolean, hostClass: Symbol) {
- // LOAD_FIELD.hostClass , CALL_METHOD.hostClass , and #4283
- val owner =
- if (hostClass == null) internalName(field.owner)
- else internalName(hostClass)
+ private def fieldOp(field: Symbol, isLoad: Boolean, hostClass: Symbol): Unit = {
+ val owner = internalName(if (hostClass == null) field.owner else hostClass)
val fieldJName = field.javaSimpleName.toString
val fieldDescr = symInfoTK(field).descriptor
val isStatic = field.isStaticMember
@@ -428,7 +413,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
if (isLoad) { if (isStatic) asm.Opcodes.GETSTATIC else asm.Opcodes.GETFIELD }
else { if (isStatic) asm.Opcodes.PUTSTATIC else asm.Opcodes.PUTFIELD }
mnode.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
-
}
// ---------------- emitting constant values ----------------
@@ -461,19 +445,16 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
case NullTag => emit(asm.Opcodes.ACONST_NULL)
case ClazzTag =>
- val toPush: BType = {
- toTypeKind(const.typeValue) match {
- case kind: PrimitiveBType => boxedClassOfPrimitive(kind)
- case kind => kind
- }
- }
- mnode.visitLdcInsn(toPush.toASMType)
+ val tp = typeToBType(const.typeValue)
+ // classOf[Int] is transformed to Integer.TYPE by CleanUp
+ assert(!tp.isPrimitive, s"expected class type in classOf[T], found primitive type $tp")
+ mnode.visitLdcInsn(tp.toASMType)
case EnumTag =>
val sym = const.symbolValue
val ownerName = internalName(sym.owner)
val fieldName = sym.javaSimpleName.toString
- val fieldDesc = toTypeKind(sym.tpe.underlying).descriptor
+ val fieldDesc = typeToBType(sym.tpe.underlying).descriptor
mnode.visitFieldInsn(
asm.Opcodes.GETSTATIC,
ownerName,
@@ -507,16 +488,11 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
bc emitRETURN returnType
case nextCleanup :: rest =>
if (saveReturnValue) {
- if (insideCleanupBlock) {
- reporter.warning(r.pos, "Return statement found in finally-clause, discarding its return-value in favor of that of a more deeply nested return.")
- bc drop returnType
- } else {
- // regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted.
- if (earlyReturnVar == null) {
- earlyReturnVar = locals.makeLocal(returnType, "earlyReturnVar")
- }
- locals.store(earlyReturnVar)
+ // regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted.
+ if (earlyReturnVar == null) {
+ earlyReturnVar = locals.makeLocal(returnType, "earlyReturnVar")
}
+ locals.store(earlyReturnVar)
}
bc goTo nextCleanup
shouldEmitCleanup = true
@@ -527,6 +503,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
private def genApply(app: Apply, expectedType: BType): BType = {
var generatedType = expectedType
lineNumber(app)
+
app match {
case Apply(TypeApply(fun, targs), _) =>
@@ -551,8 +528,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
else if (l.isPrimitive) {
bc drop l
if (cast) {
- mnode.visitTypeInsn(asm.Opcodes.NEW, classCastExceptionReference.internalName)
- bc dup ObjectReference
+ mnode.visitTypeInsn(asm.Opcodes.NEW, jlClassCastExceptionRef.internalName)
+ bc dup ObjectRef
emit(asm.Opcodes.ATHROW)
} else {
bc boolconst false
@@ -574,19 +551,33 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
generatedType = genTypeApply()
- // 'super' call: Note: since constructors are supposed to
- // return an instance of what they construct, we have to take
- // special care. On JVM they are 'void', and Scala forbids (syntactically)
- // to call super constructors explicitly and/or use their 'returned' value.
- // therefore, we can ignore this fact, and generate code that leaves nothing
- // on the stack (contrary to what the type in the AST says).
- case Apply(fun @ Select(Super(_, mix), _), args) =>
- val invokeStyle = icodes.opcodes.SuperCall(mix)
- // if (fun.symbol.isConstructor) Static(true) else SuperCall(mix);
+ case Apply(fun @ Select(Super(_, _), _), args) =>
+ def initModule() {
+ // we initialize the MODULE$ field immediately after the super ctor
+ if (!isModuleInitialized &&
+ jMethodName == INSTANCE_CONSTRUCTOR_NAME &&
+ fun.symbol.javaSimpleName.toString == INSTANCE_CONSTRUCTOR_NAME &&
+ isStaticModuleClass(claszSymbol)) {
+ isModuleInitialized = true
+ mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)
+ mnode.visitFieldInsn(
+ asm.Opcodes.PUTSTATIC,
+ thisBType.internalName,
+ strMODULE_INSTANCE_FIELD,
+ thisBType.descriptor
+ )
+ }
+ }
+ // 'super' call: Note: since constructors are supposed to
+ // return an instance of what they construct, we have to take
+ // special care. On JVM they are 'void', and Scala forbids (syntactically)
+ // to call super constructors explicitly and/or use their 'returned' value.
+ // therefore, we can ignore this fact, and generate code that leaves nothing
+ // on the stack (contrary to what the type in the AST says).
mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)
genLoadArguments(args, paramTKs(app))
- genCallMethod(fun.symbol, invokeStyle, app.pos)
- generatedType = asmMethodType(fun.symbol).returnType
+ generatedType = genCallMethod(fun.symbol, InvokeStyle.Super, app.pos)
+ initModule()
// 'new' constructor call: Note: since constructors are
// thought to return an instance of what they construct,
@@ -617,8 +608,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
}
argsSize match {
case 1 => bc newarray elemKind
- case _ =>
- val descr = ('[' * argsSize) + elemKind.descriptor // denotes the same as: arrayN(elemKind, argsSize).descriptor
+ case _ => // this is currently dead code in Scalac, unlike in Dotty
+ val descr = ("[" * argsSize) + elemKind.descriptor // denotes the same as: arrayN(elemKind, argsSize).descriptor
mnode.visitMultiANewArrayInsn(descr, argsSize)
}
@@ -627,7 +618,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), app.pos)
+ genCallMethod(ctor, InvokeStyle.Special, app.pos)
case _ =>
abort(s"Cannot instantiate $tpt of kind: $generatedType")
@@ -635,85 +626,97 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
case Apply(fun, args) if app.hasAttachment[delambdafy.LambdaMetaFactoryCapable] =>
val attachment = app.attachments.get[delambdafy.LambdaMetaFactoryCapable].get
genLoadArguments(args, paramTKs(app))
- genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface)
- generatedType = asmMethodType(fun.symbol).returnType
+ genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface, attachment.sam, attachment.isSerializable, attachment.addScalaSerializableMarker)
+ generatedType = methodBTypeFromSymbol(fun.symbol).returnType
- case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) =>
- val nativeKind = tpeTK(expr)
+ case Apply(fun, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) =>
+ val nativeKind = typeToBType(fun.symbol.firstParam.info)
genLoad(expr, nativeKind)
- val MethodNameAndType(mname, methodType) = asmBoxTo(nativeKind)
- bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos)
- generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType)
+ val MethodNameAndType(mname, methodType) = srBoxesRuntimeBoxToMethods(nativeKind)
+ bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, itf = false, app.pos)
+ generatedType = boxResultType(fun.symbol)
- case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) =>
+ case Apply(fun, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) =>
genLoad(expr)
- val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe)
+ val boxType = unboxResultType(fun.symbol)
generatedType = boxType
- val MethodNameAndType(mname, methodType) = asmUnboxTo(boxType)
- bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos)
+ val MethodNameAndType(mname, methodType) = srBoxesRuntimeUnboxToMethods(boxType)
+ bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, itf = false, app.pos)
case app @ Apply(fun, args) =>
val sym = fun.symbol
- if (sym.isLabel) { // jump to a label
+ if (sym.isLabel) { // jump to a label
genLoadLabelArguments(args, labelDef(sym), app.pos)
bc goTo programPoint(sym)
} else if (isPrimitive(sym)) { // primitive method call
generatedType = genPrimitiveOp(app, expectedType)
- } else { // normal method call
-
- def genNormalMethodCall() {
-
- val invokeStyle =
- if (sym.isStaticMember) icodes.opcodes.Static(onInstance = false)
- else if (sym.isPrivate || sym.isClassConstructor) icodes.opcodes.Static(onInstance = true)
- else icodes.opcodes.Dynamic;
-
- if (invokeStyle.hasInstance) {
- genLoadQualifier(fun)
+ } else { // normal method call
+ def isTraitSuperAccessorBodyCall = app.hasAttachment[UseInvokeSpecial.type]
+ val invokeStyle =
+ if (sym.isStaticMember)
+ InvokeStyle.Static
+ else if (sym.isPrivate || sym.isClassConstructor) InvokeStyle.Special
+ else if (isTraitSuperAccessorBodyCall)
+ InvokeStyle.Special
+ else InvokeStyle.Virtual
+
+ if (invokeStyle.hasInstance) genLoadQualifier(fun)
+ genLoadArguments(args, paramTKs(app))
+
+ val Select(qual, _) = fun // fun is a Select, also checked in genLoadQualifier
+ if (sym == definitions.Array_clone) {
+ // Special-case Array.clone, introduced in 36ef60e. The goal is to generate this call
+ // as "[I.clone" instead of "java/lang/Object.clone". This is consistent with javac.
+ // Arrays have a public method `clone` (jls 10.7).
+ //
+ // The JVMS is not explicit about this, but that receiver type can be an array type
+ // descriptor (instead of a class internal name):
+ // invokevirtual #2; //Method "[I".clone:()Ljava/lang/Object
+ //
+ // Note that using `Object.clone()` would work as well, but only because the JVM
+ // relaxes protected access specifically if the receiver is an array:
+ // http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/interpreter/linkResolver.cpp#l439
+ // Example: `class C { override def clone(): Object = "hi" }`
+ // Emitting `def f(c: C) = c.clone()` as `Object.clone()` gives a VerifyError.
+ val target: String = tpeTK(qual).asRefBType.classOrArrayType
+ val methodBType = methodBTypeFromSymbol(sym)
+ bc.invokevirtual(target, sym.javaSimpleName.toString, methodBType.descriptor, app.pos)
+ generatedType = methodBType.returnType
+ } else {
+ val receiverClass = if (!invokeStyle.isVirtual) null else {
+ // receiverClass is used in the bytecode to as the method receiver. using sym.owner
+ // may lead to IllegalAccessErrors, see 9954eaf / aladdin bug 455.
+ val qualSym = qual.tpe.typeSymbol
+ if (qualSym == ArrayClass) {
+ // For invocations like `Array(1).hashCode` or `.wait()`, use Object as receiver
+ // in the bytecode. Using the array descriptor (like we do for clone above) seems
+ // to work as well, but it seems safer not to change this. Javac also uses Object.
+ // Note that array apply/update/length are handled by isPrimitive (above).
+ assert(sym.owner == ObjectClass, s"unexpected array call: ${show(app)}")
+ ObjectClass
+ } else qualSym
}
- genLoadArguments(args, paramTKs(app))
-
- // In "a couple cases", squirrel away a extra information (hostClass, targetTypeKind). TODO Document what "in a couple cases" refers to.
- var hostClass: Symbol = null
- var targetTypeKind: BType = null
- fun match {
- case Select(qual, _) =>
- val qualSym = findHostClass(qual.tpe, sym)
- if (qualSym == ArrayClass) {
- targetTypeKind = tpeTK(qual)
- log(s"Stored target type kind for ${sym.fullName} as $targetTypeKind")
- }
- else {
- hostClass = qualSym
- if (qual.tpe.typeSymbol != qualSym) {
- log(s"Precisified host class for $sym from ${qual.tpe.typeSymbol.fullName} to ${qualSym.fullName}")
- }
- }
-
- case _ =>
- }
- if ((targetTypeKind != null) && (sym == definitions.Array_clone) && invokeStyle.isDynamic) {
- // An invokevirtual points to a CONSTANT_Methodref_info which in turn points to a
- // CONSTANT_Class_info of the receiver type.
- // The JVMS is not explicit about this, but that receiver type may be an array type
- // 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;", app.pos)
- }
- else {
- genCallMethod(sym, invokeStyle, app.pos, hostClass)
+ generatedType = genCallMethod(sym, invokeStyle, app.pos, receiverClass)
+
+ // Check if the Apply tree has an InlineAnnotatedAttachment, added by the typer
+ // for callsites marked `f(): @inline/noinline`. For nullary calls, the attachment
+ // is on the Select node (not on the Apply node added by UnCurry).
+ def recordInlineAnnotated(t: Tree): Unit = {
+ if (t.hasAttachment[InlineAnnotatedAttachment]) lastInsn match {
+ case m: MethodInsnNode =>
+ if (app.hasAttachment[NoInlineCallsiteAttachment.type]) noInlineAnnotatedCallsites += m
+ else inlineAnnotatedCallsites += m
+ case _ =>
+ } else t match {
+ case Apply(fun, _) => recordInlineAnnotated(fun)
+ case _ =>
+ }
}
-
- } // end of genNormalMethodCall()
-
- genNormalMethodCall()
-
- generatedType = asmMethodType(sym).returnType
+ recordInlineAnnotated(app)
+ }
}
-
}
generatedType
@@ -767,7 +770,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
for (caze @ CaseDef(pat, guard, body) <- tree.cases) {
assert(guard == EmptyTree, guard)
val switchBlockPoint = new asm.Label
- switchBlocks ::= (switchBlockPoint, body)
+ switchBlocks ::= ((switchBlockPoint, body))
pat match {
case Literal(value) =>
flatKeys ::= value.intValue
@@ -843,8 +846,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
* loading another throwable first).
*
* New (http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1)
- * - Requires consistent stack map frames. GenBCode generates stack frames if -target:jvm-1.6
- * or higher.
+ * - Requires consistent stack map frames. GenBCode always generates stack frames.
* - In practice: the ASM library computes stack map frames for us (ClassWriter). Emitting
* correct frames after an ATHROW is probably complex, so ASM uses the following strategy:
* - Every time when generating an ATHROW, a new basic block is started.
@@ -866,10 +868,24 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
* emitted instruction was an ATHROW. As explained above, it is OK to emit a second ATHROW,
* the verifiers will be happy.
*/
- emit(asm.Opcodes.ATHROW)
+ if (lastInsn.getOpcode != asm.Opcodes.ATHROW)
+ emit(asm.Opcodes.ATHROW)
} else if (from.isNullType) {
- bc drop from
- emit(asm.Opcodes.ACONST_NULL)
+ /* After loading an expression of type `scala.runtime.Null$`, introduce POP; ACONST_NULL.
+ * This is required to pass the verifier: in Scala's type system, Null conforms to any
+ * reference type. In bytecode, the type Null is represented by scala.runtime.Null$, which
+ * is not a subtype of all reference types. Example:
+ *
+ * def nl: Null = null // in bytecode, nl has return type scala.runtime.Null$
+ * val a: String = nl // OK for Scala but not for the JVM, scala.runtime.Null$ does not conform to String
+ *
+ * In order to fix the above problem, the value returned by nl is dropped and ACONST_NULL is
+ * inserted instead - after all, an expression of type scala.runtime.Null$ can only be null.
+ */
+ if (lastInsn.getOpcode != asm.Opcodes.ACONST_NULL) {
+ bc drop from
+ emit(asm.Opcodes.ACONST_NULL)
+ }
}
else (from, to) match {
case (BYTE, LONG) | (SHORT, LONG) | (CHAR, LONG) | (INT, LONG) => bc.emitT2T(INT, LONG)
@@ -902,7 +918,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
(args zip params) filterNot isTrivial
}
- // first push *all* arguments. This makes sure muliple uses of the same labelDef-var will all denote the (previous) value.
+ // first push *all* arguments. This makes sure multiple uses of the same labelDef-var will all denote the (previous) value.
aps foreach { case (arg, param) => genLoad(arg, locals(param).tk) } // `locals` is known to contain `param` because `genDefDef()` visited `labelDefsAtOrUnder`
// second assign one by one to the LabelDef's variables.
@@ -922,7 +938,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
def genLoadModule(tree: Tree): BType = {
val module = (
if (!tree.symbol.isPackageClass) tree.symbol
- else tree.symbol.info.member(nme.PACKAGE) match {
+ else tree.symbol.info.packageObject match {
case NoSymbol => abort(s"SI-5604: Cannot use package as value: $tree")
case s => abort(s"SI-5604: found package class where package object expected: $tree")
}
@@ -942,7 +958,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
asm.Opcodes.GETSTATIC,
mbt.internalName /* + "$" */ ,
strMODULE_INSTANCE_FIELD,
- mbt.descriptor // for nostalgics: toTypeKind(module.tpe).descriptor
+ mbt.descriptor // for nostalgics: typeToBType(module.tpe).descriptor
)
}
}
@@ -978,92 +994,113 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
def genStringConcat(tree: Tree): BType = {
lineNumber(tree)
liftStringConcat(tree) match {
-
// Optimization for expressions of the form "" + x. We can avoid the StringBuilder.
case List(Literal(Constant("")), arg) =>
- genLoad(arg, ObjectReference)
- genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false), arg.pos)
+ genLoad(arg, ObjectRef)
+ genCallMethod(String_valueOf, InvokeStyle.Static, arg.pos)
case concatenations =>
bc.genStartConcat(tree.pos)
for (elem <- concatenations) {
- val kind = tpeTK(elem)
- genLoad(elem, kind)
- bc.genStringConcat(kind, elem.pos)
+ val loadedElem = elem match {
+ case Apply(boxOp, value :: Nil) if currentRun.runDefinitions.isBox(boxOp.symbol) =>
+ // Eliminate boxing of primitive values. Boxing is introduced by erasure because
+ // there's only a single synthetic `+` method "added" to the string class.
+ value
+
+ case _ => elem
+ }
+ val elemType = tpeTK(loadedElem)
+ genLoad(loadedElem, elemType)
+ bc.genConcat(elemType, loadedElem.pos)
}
bc.genEndConcat(tree.pos)
-
}
-
- StringReference
+ StringRef
}
- def genCallMethod(method: Symbol, style: InvokeStyle, pos: Position, hostClass0: Symbol = null) {
-
- val siteSymbol = claszSymbol
- val hostSymbol = if (hostClass0 == null) method.owner else hostClass0
+ /**
+ * Generate a method invocation. If `specificReceiver != null`, it is used as receiver in the
+ * invocation instruction, otherwise `method.owner`. A specific receiver class is needed to
+ * prevent an IllegalAccessError, (aladdin bug 455).
+ */
+ def genCallMethod(method: Symbol, style: InvokeStyle, pos: Position, specificReceiver: Symbol = null): BType = {
val methodOwner = method.owner
- // info calls so that types are up to date; erasure may add lateINTERFACE to traits
- hostSymbol.info ; methodOwner.info
-
- def needsInterfaceCall(sym: Symbol) = (
- sym.isInterface
- || sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass)
- )
-
- // whether to reference the type of the receiver or
- // the type of the method owner
- val useMethodOwner = (
- style != icodes.opcodes.Dynamic
- || hostSymbol.isBottomClass
- || methodOwner == definitions.ObjectClass
- )
- val receiver = if (useMethodOwner) methodOwner else hostSymbol
- val jowner = internalName(receiver)
- val jname = method.javaSimpleName.toString
- val bmType = asmMethodType(method)
- val mdescr = bmType.descriptor
-
- def initModule() {
- // we initialize the MODULE$ field immediately after the super ctor
- if (!isModuleInitialized &&
- jMethodName == INSTANCE_CONSTRUCTOR_NAME &&
- jname == INSTANCE_CONSTRUCTOR_NAME &&
- isStaticModuleClass(siteSymbol)) {
- isModuleInitialized = true
- mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)
- mnode.visitFieldInsn(
- asm.Opcodes.PUTSTATIC,
- thisName,
- strMODULE_INSTANCE_FIELD,
- "L" + thisName + ";"
- )
+ // the class used in the invocation's method descriptor in the classfile
+ val receiverClass = {
+ if (specificReceiver != null)
+ assert(style.isVirtual || specificReceiver == methodOwner, s"specificReceiver can only be specified for virtual calls. $method - $specificReceiver")
+
+ val useSpecificReceiver = specificReceiver != null && !specificReceiver.isBottomClass
+ val receiver = if (useSpecificReceiver) specificReceiver else methodOwner
+
+ // workaround for a JVM bug: https://bugs.openjdk.java.net/browse/JDK-8154587
+ // when an interface method overrides a member of Object (note that all interfaces implicitly
+ // have superclass Object), the receiver needs to be the interface declaring the override (and
+ // not a sub-interface that inherits it). example:
+ // trait T { override def clone(): Object = "" }
+ // trait U extends T
+ // class C extends U
+ // class D { def f(u: U) = u.clone() }
+ // The invocation `u.clone()` needs `T` as a receiver:
+ // - using Object is illegal, as Object.clone is protected
+ // - using U results in a `NoSuchMethodError: U.clone. This is the JVM bug.
+ // Note that a mixin forwarder is generated, so the correct method is executed in the end:
+ // class C { override def clone(): Object = super[T].clone() }
+ val isTraitMethodOverridingObjectMember = {
+ receiver != methodOwner && // fast path - the boolean is used to pick either of these two, if they are the same it does not matter
+ style.isVirtual &&
+ receiver.isTraitOrInterface &&
+ ObjectTpe.decl(method.name).exists && // fast path - compute overrideChain on the next line only if necessary
+ method.overrideChain.last.owner == ObjectClass
}
+ if (isTraitMethodOverridingObjectMember) methodOwner else receiver
}
- if (style.isStatic) {
- 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, pos) }
- else { bc.invokevirtual (jowner, jname, mdescr, pos) }
+ receiverClass.info // ensure types the type is up to date; erasure may add lateINTERFACE to traits
+ val receiverBType = classBTypeFromSymbol(receiverClass)
+ val receiverName = receiverBType.internalName
+
+ def needsInterfaceCall(sym: Symbol) = {
+ sym.isTraitOrInterface ||
+ sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass)
}
- else {
- assert(style.isSuper, s"An unknown InvokeStyle: $style")
- bc.invokespecial(jowner, jname, mdescr, pos)
- initModule()
+
+ val jname = method.javaSimpleName.toString
+ val bmType = methodBTypeFromSymbol(method)
+ val mdescr = bmType.descriptor
+
+ val isInterface = receiverBType.isInterface.get
+ import InvokeStyle._
+ if (style == Super) {
+ assert(receiverClass == methodOwner, s"for super call, expecting $receiverClass == $methodOwner")
+ if (receiverClass.isTrait && !receiverClass.isJavaDefined) {
+ val staticDesc = MethodBType(typeToBType(method.owner.info) :: bmType.argumentTypes, bmType.returnType).descriptor
+ val staticName = traitSuperAccessorName(method).toString
+ bc.invokestatic(receiverName, staticName, staticDesc, isInterface, pos)
+ } else {
+ if (receiverClass.isTraitOrInterface) {
+ // An earlier check in Mixin reports an error in this case, so it doesn't reach the backend
+ assert(cnode.interfaces.contains(receiverName), s"cannot invokespecial $receiverName.$jname, the interface is not a direct parent.")
+ }
+ bc.invokespecial(receiverName, jname, mdescr, isInterface, pos)
+ }
+ } else {
+ val opc = style match {
+ case Static => Opcodes.INVOKESTATIC
+ case Special => Opcodes.INVOKESPECIAL
+ case Virtual => if (isInterface) Opcodes.INVOKEINTERFACE else Opcodes.INVOKEVIRTUAL
+ }
+ bc.emitInvoke(opc, receiverName, jname, mdescr, isInterface, pos)
}
+ bmType.returnType
} // end of genCallMethod()
/* Generate the scala ## method. */
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), applyPos)
-
- INT
+ genLoad(tree, ObjectRef)
+ genCallMethod(hashMethodSym, InvokeStyle.Static, applyPos)
}
/*
@@ -1082,86 +1119,98 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
}
/* Emit code to compare the two top-most stack values using the 'op' operator. */
- private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType) {
- if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
- bc.emitIF_ICMP(op, success)
- } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_)
- bc.emitIF_ACMP(op, success)
- } else {
- (tk: @unchecked) match {
- case LONG => emit(asm.Opcodes.LCMP)
- case FLOAT =>
- if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.FCMPG)
- else emit(asm.Opcodes.FCMPL)
- case DOUBLE =>
- if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.DCMPG)
- else emit(asm.Opcodes.DCMPL)
+ private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label, negated: Boolean = false) {
+ if (targetIfNoJump == success) genCJUMP(failure, success, op.negate, tk, targetIfNoJump, negated = !negated)
+ else {
+ if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
+ bc.emitIF_ICMP(op, success)
+ } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_)
+ bc.emitIF_ACMP(op, success)
+ } else {
+ def useCmpG = if (negated) op == TestOp.GT || op == TestOp.GE else op == TestOp.LT || op == TestOp.LE
+ (tk: @unchecked) match {
+ case LONG => emit(asm.Opcodes.LCMP)
+ case FLOAT => emit(if (useCmpG) asm.Opcodes.FCMPG else asm.Opcodes.FCMPL)
+ case DOUBLE => emit(if (useCmpG) asm.Opcodes.DCMPG else asm.Opcodes.DCMPL)
+ }
+ bc.emitIF(op, success)
}
- bc.emitIF(op, success)
+ if (targetIfNoJump != failure) bc goTo failure
}
- bc goTo failure
}
/* Emits code to compare (and consume) stack-top and zero using the 'op' operator */
- private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType) {
- if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
- bc.emitIF(op, success)
- } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_)
- // @unchecked because references aren't compared with GT, GE, LT, LE.
- (op : @unchecked) match {
- case icodes.EQ => bc emitIFNULL success
- case icodes.NE => bc emitIFNONNULL success
- }
- } else {
- (tk: @unchecked) match {
- case LONG =>
- emit(asm.Opcodes.LCONST_0)
- emit(asm.Opcodes.LCMP)
- case FLOAT =>
- emit(asm.Opcodes.FCONST_0)
- if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.FCMPG)
- else emit(asm.Opcodes.FCMPL)
- case DOUBLE =>
- emit(asm.Opcodes.DCONST_0)
- if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.DCMPG)
- else emit(asm.Opcodes.DCMPL)
+ private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label, negated: Boolean = false) {
+ if (targetIfNoJump == success) genCZJUMP(failure, success, op.negate, tk, targetIfNoJump, negated = !negated)
+ else {
+ if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
+ bc.emitIF(op, success)
+ } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_)
+ op match { // references are only compared with EQ and NE
+ case TestOp.EQ => bc emitIFNULL success
+ case TestOp.NE => bc emitIFNONNULL success
+ }
+ } else {
+ def useCmpG = if (negated) op == TestOp.GT || op == TestOp.GE else op == TestOp.LT || op == TestOp.LE
+ (tk: @unchecked) match {
+ case LONG =>
+ emit(asm.Opcodes.LCONST_0)
+ emit(asm.Opcodes.LCMP)
+ case FLOAT =>
+ emit(asm.Opcodes.FCONST_0)
+ emit(if (useCmpG) asm.Opcodes.FCMPG else asm.Opcodes.FCMPL)
+ case DOUBLE =>
+ emit(asm.Opcodes.DCONST_0)
+ emit(if (useCmpG) asm.Opcodes.DCMPG else asm.Opcodes.DCMPL)
+ }
+ bc.emitIF(op, success)
}
- bc.emitIF(op, success)
+ if (targetIfNoJump != failure) bc goTo failure
}
- bc goTo failure
}
- val testOpForPrimitive: Array[TestOp] = Array(
- icodes.EQ, icodes.NE, icodes.EQ, icodes.NE, icodes.LT, icodes.LE, icodes.GE, icodes.GT
- )
+ def testOpForPrimitive(primitiveCode: Int) = (primitiveCode: @switch) match {
+ case scalaPrimitives.ID => TestOp.EQ
+ case scalaPrimitives.NI => TestOp.NE
+ case scalaPrimitives.EQ => TestOp.EQ
+ case scalaPrimitives.NE => TestOp.NE
+ case scalaPrimitives.LT => TestOp.LT
+ case scalaPrimitives.LE => TestOp.LE
+ case scalaPrimitives.GT => TestOp.GT
+ case scalaPrimitives.GE => TestOp.GE
+ }
+
+ /** Some useful equality helpers. */
+ def isNull(t: Tree) = PartialFunction.cond(t) { case Literal(Constant(null)) => true }
+ def isLiteral(t: Tree) = PartialFunction.cond(t) { case Literal(_) => true }
+ def isNonNullExpr(t: Tree) = isLiteral(t) || ((t.symbol ne null) && t.symbol.isModule)
+ /** If l or r is constant null, returns the other ; otherwise null */
+ def ifOneIsNull(l: Tree, r: Tree) = if (isNull(l)) r else if (isNull(r)) l else null
/*
* Generate code for conditional expressions.
* The jump targets success/failure of the test are `then-target` and `else-target` resp.
*/
- private def genCond(tree: Tree, success: asm.Label, failure: asm.Label) {
+ private def genCond(tree: Tree, success: asm.Label, failure: asm.Label, targetIfNoJump: asm.Label) {
def genComparisonOp(l: Tree, r: Tree, code: Int) {
- val op: TestOp = testOpForPrimitive(code - scalaPrimitives.ID)
- // special-case reference (in)equality test for null (null eq x, x eq null)
- var nonNullSide: Tree = null
- if (scalaPrimitives.isReferenceEqualityOp(code) &&
- { nonNullSide = ifOneIsNull(l, r); nonNullSide != null }
- ) {
- genLoad(nonNullSide, ObjectReference)
- genCZJUMP(success, failure, op, ObjectReference)
- }
- else {
+ val op = testOpForPrimitive(code)
+ val nonNullSide = if (scalaPrimitives.isReferenceEqualityOp(code)) ifOneIsNull(l, r) else null
+ if (nonNullSide != null) {
+ // special-case reference (in)equality test for null (null eq x, x eq null)
+ genLoad(nonNullSide, ObjectRef)
+ genCZJUMP(success, failure, op, ObjectRef, targetIfNoJump)
+ } else {
val tk = tpeTK(l).maxType(tpeTK(r))
genLoad(l, tk)
genLoad(r, tk)
- genCJUMP(success, failure, op, tk)
+ genCJUMP(success, failure, op, tk, targetIfNoJump)
}
}
- def default() = {
+ def loadAndTestBoolean() = {
genLoad(tree, BOOL)
- genCZJUMP(success, failure, icodes.NE, BOOL)
+ genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump)
}
lineNumber(tree)
@@ -1172,37 +1221,35 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
// lhs and rhs of test
lazy val Select(lhs, _) = fun
- val rhs = if (args.isEmpty) EmptyTree else args.head; // args.isEmpty only for ZNOT
+ val rhs = if (args.isEmpty) EmptyTree else args.head // args.isEmpty only for ZNOT
- def genZandOrZor(and: Boolean) { // TODO WRONG
+ def genZandOrZor(and: Boolean) {
// reaching "keepGoing" indicates the rhs should be evaluated too (ie not short-circuited).
val keepGoing = new asm.Label
- if (and) genCond(lhs, keepGoing, failure)
- else genCond(lhs, success, keepGoing)
+ if (and) genCond(lhs, keepGoing, failure, targetIfNoJump = keepGoing)
+ else genCond(lhs, success, keepGoing, targetIfNoJump = keepGoing)
markProgramPoint(keepGoing)
- genCond(rhs, success, failure)
+ genCond(rhs, success, failure, targetIfNoJump)
}
getPrimitive(fun.symbol) match {
- case ZNOT => genCond(lhs, failure, success)
+ case ZNOT => genCond(lhs, failure, success, targetIfNoJump)
case ZAND => genZandOrZor(and = true)
case ZOR => genZandOrZor(and = false)
case code =>
- // TODO !!!!!!!!!! isReferenceType, in the sense of TypeKind? (ie non-array, non-boxed, non-nothing, may be null)
if (scalaPrimitives.isUniversalEqualityOp(code) && tpeTK(lhs).isClass) {
- // `lhs` has reference type
- if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure, tree.pos)
- else genEqEqPrimitive(lhs, rhs, failure, success, tree.pos)
- }
- else if (scalaPrimitives.isComparisonOp(code))
+ // rewrite `==` to null tests and `equals`. not needed for arrays (`equals` is reference equality).
+ if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure, targetIfNoJump, tree.pos)
+ else genEqEqPrimitive(lhs, rhs, failure, success, targetIfNoJump, tree.pos)
+ } else if (scalaPrimitives.isComparisonOp(code)) {
genComparisonOp(lhs, rhs, code)
- else
- default
+ } else
+ loadAndTestBoolean()
}
- case _ => default
+ case _ => loadAndTestBoolean()
}
} // end of genCond()
@@ -1214,69 +1261,75 @@ 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, pos: Position) {
+ def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label, targetIfNoJump: 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
+ * comparator (scala.runtime.BoxesRunTime.equals). This is the case when either side of the
* comparison might have a run-time type subtype of java.lang.Number or java.lang.Character.
- * When it is statically known that both sides are equal and subtypes of Number of Character,
- * not using the rich equality is possible (their own equals method will do ok.)
+ *
+ * When it is statically known that both sides are equal and subtypes of Number or Character,
+ * not using the rich equality is possible (their own equals method will do ok), except for
+ * java.lang.Float and java.lang.Double: their `equals` have different behavior around `NaN`
+ * and `-0.0`, see Javadoc (scala-dev#329).
*/
val mustUseAnyComparator: Boolean = {
- val areSameFinals = l.tpe.isFinalType && r.tpe.isFinalType && (l.tpe =:= r.tpe)
-
- !areSameFinals && platform.isMaybeBoxed(l.tpe.typeSymbol) && platform.isMaybeBoxed(r.tpe.typeSymbol)
+ platform.isMaybeBoxed(l.tpe.typeSymbol) && platform.isMaybeBoxed(r.tpe.typeSymbol) && {
+ val areSameFinals = l.tpe.isFinalType && r.tpe.isFinalType && (l.tpe =:= r.tpe) && {
+ val sym = l.tpe.typeSymbol
+ sym != BoxedFloatClass && sym != BoxedDoubleClass
+ }
+ !areSameFinals
+ }
}
if (mustUseAnyComparator) {
val equalsMethod: Symbol = {
if (l.tpe <:< BoxedNumberClass.tpe) {
if (r.tpe <:< BoxedNumberClass.tpe) platform.externalEqualsNumNum
- else if (r.tpe <:< BoxedCharacterClass.tpe) platform.externalEqualsNumObject // will be externalEqualsNumChar in 2.12, SI-9030
+ else if (r.tpe <:< BoxedCharacterClass.tpe) platform.externalEqualsNumChar
else platform.externalEqualsNumObject
} else platform.externalEquals
}
- genLoad(l, ObjectReference)
- genLoad(r, ObjectReference)
- genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false), pos)
- genCZJUMP(success, failure, icodes.NE, BOOL)
- }
- else {
+ genLoad(l, ObjectRef)
+ genLoad(r, ObjectRef)
+ genCallMethod(equalsMethod, InvokeStyle.Static, pos)
+ genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump)
+ } else {
if (isNull(l)) {
// null == expr -> expr eq null
- genLoad(r, ObjectReference)
- genCZJUMP(success, failure, icodes.EQ, ObjectReference)
+ genLoad(r, ObjectRef)
+ genCZJUMP(success, failure, TestOp.EQ, ObjectRef, targetIfNoJump)
} else if (isNull(r)) {
// expr == null -> expr eq null
- genLoad(l, ObjectReference)
- genCZJUMP(success, failure, icodes.EQ, ObjectReference)
+ genLoad(l, ObjectRef)
+ genCZJUMP(success, failure, TestOp.EQ, ObjectRef, targetIfNoJump)
} else if (isNonNullExpr(l)) {
// SI-7852 Avoid null check if L is statically non-null.
- genLoad(l, ObjectReference)
- genLoad(r, ObjectReference)
- genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos)
- genCZJUMP(success, failure, icodes.NE, BOOL)
+ genLoad(l, ObjectRef)
+ genLoad(r, ObjectRef)
+ genCallMethod(Object_equals, InvokeStyle.Virtual, pos)
+ genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump)
} else {
// l == r -> if (l eq null) r eq null else l.equals(r)
- val eqEqTempLocal = locals.makeLocal(ObjectReference, nme.EQEQ_LOCAL_VAR.toString)
+ val eqEqTempLocal = locals.makeLocal(ObjectRef, nme.EQEQ_LOCAL_VAR.toString)
val lNull = new asm.Label
val lNonNull = new asm.Label
- genLoad(l, ObjectReference)
- genLoad(r, ObjectReference)
+ genLoad(l, ObjectRef)
+ genLoad(r, ObjectRef)
locals.store(eqEqTempLocal)
- bc dup ObjectReference
- genCZJUMP(lNull, lNonNull, icodes.EQ, ObjectReference)
+ bc dup ObjectRef
+ genCZJUMP(lNull, lNonNull, TestOp.EQ, ObjectRef, targetIfNoJump = lNull)
markProgramPoint(lNull)
- bc drop ObjectReference
+ bc drop ObjectRef
locals.load(eqEqTempLocal)
- genCZJUMP(success, failure, icodes.EQ, ObjectReference)
+ genCZJUMP(success, failure, TestOp.EQ, ObjectRef, targetIfNoJump = lNonNull)
markProgramPoint(lNonNull)
locals.load(eqEqTempLocal)
- genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos)
- genCZJUMP(success, failure, icodes.NE, BOOL)
+ genCallMethod(Object_equals, InvokeStyle.Virtual, pos)
+ genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump)
}
}
}
@@ -1285,44 +1338,37 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
def genSynchronized(tree: Apply, expectedType: BType): BType
def genLoadTry(tree: Try): BType
- def genInvokeDynamicLambda(lambdaTarget: Symbol, arity: Int, functionalInterface: Symbol) {
+ def genInvokeDynamicLambda(lambdaTarget: Symbol, arity: Int, functionalInterface: Symbol, sam: Symbol, isSerializable: Boolean, addScalaSerializableMarker: Boolean) {
val isStaticMethod = lambdaTarget.hasFlag(Flags.STATIC)
def asmType(sym: Symbol) = classBTypeFromSymbol(sym).toASMType
+ val isInterface = lambdaTarget.owner.isTrait
val implMethodHandle =
- new asm.Handle(if (lambdaTarget.hasFlag(Flags.STATIC)) asm.Opcodes.H_INVOKESTATIC else asm.Opcodes.H_INVOKEVIRTUAL,
+ new asm.Handle(if (lambdaTarget.hasFlag(Flags.STATIC)) asm.Opcodes.H_INVOKESTATIC else if (isInterface) asm.Opcodes.H_INVOKEINTERFACE else asm.Opcodes.H_INVOKEVIRTUAL,
classBTypeFromSymbol(lambdaTarget.owner).internalName,
lambdaTarget.name.toString,
- asmMethodType(lambdaTarget).descriptor)
+ methodBTypeFromSymbol(lambdaTarget).descriptor,
+ /* itf = */ isInterface)
val receiver = if (isStaticMethod) Nil else lambdaTarget.owner :: Nil
val (capturedParams, lambdaParams) = lambdaTarget.paramss.head.splitAt(lambdaTarget.paramss.head.length - arity)
- // Requires https://github.com/scala/scala-java8-compat on the runtime classpath
- val invokedType = asm.Type.getMethodDescriptor(asmType(functionalInterface), (receiver ::: capturedParams).map(sym => toTypeKind(sym.info).toASMType): _*)
-
- val constrainedType = new MethodBType(lambdaParams.map(p => toTypeKind(p.tpe)), toTypeKind(lambdaTarget.tpe.resultType)).toASMType
- val sam = functionalInterface.info.decls.find(_.isDeferred).getOrElse(functionalInterface.info.member(nme.apply))
- val samName = sam.name.toString
- val samMethodType = asmMethodType(sam).toASMType
-
- val flags = 3 // TODO 2.12.x Replace with LambdaMetafactory.FLAG_SERIALIZABLE | LambdaMetafactory.FLAG_MARKERS
-
- val ScalaSerializable = classBTypeFromSymbol(definitions.SerializableClass).toASMType
- bc.jmethod.visitInvokeDynamicInsn(samName, invokedType, lambdaMetaFactoryBootstrapHandle,
- /* samMethodType = */ samMethodType,
- /* implMethod = */ implMethodHandle,
- /* instantiatedMethodType = */ constrainedType,
- /* flags = */ flags.asInstanceOf[AnyRef],
- /* markerInterfaceCount = */ 1.asInstanceOf[AnyRef],
- /* markerInterfaces[0] = */ ScalaSerializable,
- /* bridgeCount = */ 0.asInstanceOf[AnyRef]
- )
- indyLambdaHosts += this.claszSymbol
+ val invokedType = asm.Type.getMethodDescriptor(asmType(functionalInterface), (receiver ::: capturedParams).map(sym => typeToBType(sym.info).toASMType): _*)
+ val constrainedType = new MethodBType(lambdaParams.map(p => typeToBType(p.tpe)), typeToBType(lambdaTarget.tpe.resultType)).toASMType
+ val samMethodType = methodBTypeFromSymbol(sam).toASMType
+ val markers = if (addScalaSerializableMarker) classBTypeFromSymbol(definitions.SerializableClass).toASMType :: Nil else Nil
+ visitInvokeDynamicInsnLMF(bc.jmethod, sam.name.toString, invokedType, samMethodType, implMethodHandle, constrainedType, isSerializable, markers)
+ if (isSerializable)
+ addIndyLambdaImplMethod(cnode.name, implMethodHandle :: Nil)
}
}
- lazy val lambdaMetaFactoryBootstrapHandle =
- new asm.Handle(asm.Opcodes.H_INVOKESTATIC,
- definitions.LambdaMetaFactory.fullName('/'), sn.AltMetafactory.toString,
- "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;")
+ private def visitInvokeDynamicInsnLMF(jmethod: MethodNode, samName: String, invokedType: String, samMethodType: asm.Type,
+ implMethodHandle: asm.Handle, instantiatedMethodType: asm.Type,
+ serializable: Boolean, markerInterfaces: Seq[asm.Type]) = {
+ import java.lang.invoke.LambdaMetafactory.{FLAG_MARKERS, FLAG_SERIALIZABLE}
+ def flagIf(b: Boolean, flag: Int): Int = if (b) flag else 0
+ val flags = FLAG_MARKERS | flagIf(serializable, FLAG_SERIALIZABLE)
+ val bsmArgs = Seq(samMethodType, implMethodHandle, instantiatedMethodType, Int.box(flags), Int.box(markerInterfaces.length)) ++ markerInterfaces
+ jmethod.visitInvokeDynamicInsn(samName, invokedType, lambdaMetaFactoryAltMetafactoryHandle, bsmArgs: _*)
+ }
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
index 1b97681743..a74c70a684 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
@@ -8,10 +8,10 @@ package tools.nsc
package backend.jvm
import scala.tools.asm
-import scala.collection.mutable
import scala.tools.nsc.io.AbstractFile
import GenBCode._
import BackendReporting._
+import scala.reflect.internal.Flags
/*
* Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes.
@@ -22,8 +22,223 @@ import BackendReporting._
*/
abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
import global._
+ import definitions._
import bTypes._
import coreBTypes._
+ import BTypes.{InternalName, InlineInfo, MethodInlineInfo}
+
+ /**
+ * 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.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
+ }
+ }
+
+ def needsStaticImplMethod(sym: Symbol) = sym.hasAttachment[global.mixer.NeedStaticImpl.type]
+
+ final def traitSuperAccessorName(sym: Symbol): Name = {
+ val name = sym.javaSimpleName
+ if (sym.isMixinConstructor) name
+ else name.append(nme.NAME_JOIN_STRING)
+ }
+
+ /**
+ * 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
+ * null.
+ */
+ def isAnonymousOrLocalClass(classSym: Symbol): Boolean = {
+ assert(classSym.isClass, s"not a class: $classSym")
+ val r = exitingPickler(classSym.isAnonymousClass) || !classSym.originalOwner.isClass
+ if (r) {
+ // lambda lift renames symbols and may accidentally introduce `$lambda` into a class name, making `isDelambdafyFunction` true.
+ // 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() && exitingPickler(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
+
+ /**
+ * Returns the enclosing method for non-member classes. In the following example
+ *
+ * class A {
+ * def f = {
+ * class B {
+ * class C
+ * }
+ * }
+ * }
+ *
+ * the method returns Some(f) for B, but None for C, because C is a member class. For non-member
+ * classes that are not enclosed by a method, it returns None:
+ *
+ * class A {
+ * { class B }
+ * }
+ *
+ * In this case, for B, we return None.
+ *
+ * The EnclosingMethod attribute needs to be added to non-member classes (see doc in BTypes).
+ * This is a source-level property, so we need to use the originalOwner chain to reconstruct it.
+ */
+ private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = {
+ assert(classSym.isClass, classSym)
+
+ def doesNotExist(method: Symbol) = {
+ // 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).
+ val enclCls = nextEnclosingClass(method)
+ exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls
+ }
+
+ def enclosingMethod(sym: Symbol): Option[Symbol] = {
+ if (sym.isClass || sym == NoSymbol) None
+ else if (sym.isMethod && !sym.isGetter) {
+ if (doesNotExist(sym)) None else Some(sym)
+ }
+ else enclosingMethod(nextEnclosing(sym))
+ }
+ enclosingMethod(nextEnclosing(classSym))
+ }
+
+ /**
+ * The enclosing class for emitting the EnclosingMethod attribute. Since this is a source-level
+ * property, this method looks at the originalOwner chain. See doc in BTypes.
+ */
+ private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = {
+ assert(classSym.isClass, classSym)
+ val r = nextEnclosingClass(nextEnclosing(classSym))
+ r
+ }
+
+ final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String)
+
+ /**
+ * Data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not
+ * an anonymous or local class). See doc in BTypes.
+ *
+ * The class is parameterized by two functions to obtain a bytecode class descriptor for a class
+ * symbol, and to obtain a method signature descriptor fro a method symbol. These function depend
+ * 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] = {
+ // specialized classes are always top-level, see comment in BTypes
+ if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) {
+ val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym)
+ val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym)
+ for (m <- methodOpt) assert(m.owner == enclosingClass, s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass")
+ Some(EnclosingMethodEntry(
+ classDesc(enclosingClass),
+ methodOpt.map(_.javaSimpleName.toString).orNull,
+ methodOpt.map(methodDesc).orNull))
+ } else {
+ None
+ }
+ }
+
+ /**
+ * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain.
+ *
+ * The problem is that we are interested in a source-level property. Various phases changed the
+ * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner.
+ * Therefore, `sym.isStatic` is not what we want. For example, in
+ * object T { def f { object U } }
+ * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here.
+ */
+ def isOriginallyStaticOwner(sym: Symbol): Boolean =
+ sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner)
+
+ /**
+ * 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
+ }
+
/*
* must-single-thread
@@ -117,7 +332,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)")
else if (companion.isTrait)
failNoForwarder("companion is a trait")
- // Now either succeeed, or issue some additional warnings for things which look like
+ // Now either succeed, or issue some additional warnings for things which look like
// attempts to be java main methods.
else (possibles exists definitions.isJavaMainMethod) || {
possibles exists { m =>
@@ -191,20 +406,18 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
}
/*
- * Populates the InnerClasses JVM attribute with `refedInnerClasses`.
- * In addition to inner classes mentioned somewhere in `jclass` (where `jclass` is a class file being emitted)
- * `refedInnerClasses` should contain those inner classes defined as direct member classes of `jclass`
- * but otherwise not mentioned in `jclass`.
+ * Populates the InnerClasses JVM attribute with `refedInnerClasses`. See also the doc on inner
+ * classes in BTypes.scala.
*
- * `refedInnerClasses` may contain duplicates,
- * need not contain the enclosing inner classes of each inner class it lists (those are looked up for consistency).
+ * `refedInnerClasses` may contain duplicates, need not contain the enclosing inner classes of
+ * each inner class it lists (those are looked up and included).
*
- * This method serializes in the InnerClasses JVM attribute in an appropriate order,
- * not necessarily that given by `refedInnerClasses`.
+ * This method serializes in the InnerClasses JVM attribute in an appropriate order, not
+ * necessarily that given by `refedInnerClasses`.
*
* can-multi-thread
*/
- final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) {
+ final def addInnerClasses(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) {
val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain.get).distinct
// sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler
@@ -310,85 +523,114 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
final val emitLines = debugLevel >= 2
final val emitVars = debugLevel >= 3
- /*
- * Contains class-symbols that:
- * (a) are known to denote inner classes
- * (b) are mentioned somewhere in the class being generated.
- *
- * In other words, the lifetime of `innerClassBufferASM` is associated to "the class being generated".
- */
- final val innerClassBufferASM = mutable.Set.empty[ClassBType]
-
/**
- * The class internal name for a given class symbol. If the symbol describes a nested class, the
- * ClassBType is added to the innerClassBufferASM.
+ * The class internal name for a given class symbol.
*/
- final def internalName(sym: Symbol): String = {
- // For each java class, the scala compiler creates a class and a module (thus a module class).
- // If the `sym` is a java module class, we use the java class instead. This ensures that we
- // register the class (instead of the module class) in innerClassBufferASM.
- // The two symbols have the same name, so the resulting internalName is the same.
- // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting)
- val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym
- getClassBTypeAndRegisterInnerClass(classSym).internalName
- }
+ final def internalName(sym: Symbol): String = classBTypeFromSymbol(sym).internalName
+ } // end of trait BCInnerClassGen
+
+ trait BCAnnotGen extends BCInnerClassGen {
+ private lazy val AnnotationRetentionPolicyModule = AnnotationRetentionPolicyAttr.companionModule
+ private lazy val AnnotationRetentionPolicySourceValue = AnnotationRetentionPolicyModule.tpe.member(TermName("SOURCE"))
+ private lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS"))
+ private lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME"))
/**
- * The ClassBType for a class symbol. If the class is nested, the ClassBType is added to the
- * innerClassBufferASM.
+ * Annotations are not processed by the compilation pipeline like ordinary trees. Instead, the
+ * typer extracts them into [[AnnotationInfo]] objects which are attached to the corresponding
+ * symbol (sym.annotations) or type (as an AnnotatedType, eliminated by erasure).
*
- * TODO: clean up the way we track referenced inner classes.
- * doing it during code generation is not correct when the optimizer changes the code.
+ * For Scala annotations this is OK: they are stored in the pickle and ignored by the backend.
+ * Java annotations on the other hand are additionally emitted to the classfile in Java's format.
+ *
+ * This means that [[Type]] instances within an AnnotationInfo reach the backend non-erased. Examples:
+ * - @(javax.annotation.Resource @annotation.meta.getter) val x = 0
+ * Here, annotationInfo.atp is an AnnotatedType.
+ * - @SomeAnnotation[T] val x = 0
+ * In principle, the annotationInfo.atp is a non-erased type ref. However, this cannot
+ * actually happen because Java annotations cannot be generic.
+ * - @javax.annotation.Resource(`type` = classOf[List[_]]) val x = 0
+ * The annotationInfo.assocs contains a LiteralAnnotArg(Constant(tp)) where tp is the
+ * non-erased existential type.
*/
- final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = {
- val r = classBTypeFromSymbol(sym)
- if (r.isNestedClass.get) innerClassBufferASM += r
- r
+ def erasedType(tp: Type): Type = enteringErasure {
+ // make sure we don't erase value class references to the type that the value class boxes
+ // this is basically the same logic as in erasure's preTransform, case Literal(classTag).
+ tp.dealiasWiden match {
+ case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => erasure.scalaErasure.eraseNormalClassRef(tr)
+ case tpe => erasure.erasure(tpe.typeSymbol)(tpe)
+ }
}
- /**
- * 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 = typeToBType(t) match {
- case c: ClassBType if c.isNestedClass.get =>
- innerClassBufferASM += c
- c
- case r => r
+ def descriptorForErasedType(tp: Type): String = typeToBType(erasedType(tp)).descriptor
+
+ /** Whether an annotation should be emitted as a Java annotation
+ * .initialize: if 'annot' is read from pickle, atp might be uninitialized
+ */
+ private def shouldEmitAnnotation(annot: AnnotationInfo) = {
+ annot.symbol.initialize.isJavaDefined &&
+ annot.matches(ClassfileAnnotationClass) &&
+ retentionPolicyOf(annot) != AnnotationRetentionPolicySourceValue &&
+ annot.args.isEmpty
}
- /**
- * 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 = {
- val r = methodBTypeFromSymbol(msym)
- (r.returnType :: r.argumentTypes) foreach {
- case c: ClassBType if c.isNestedClass.get => innerClassBufferASM += c
+ private def isRuntimeVisible(annot: AnnotationInfo): Boolean = {
+ annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr) match {
+ case Some(retentionAnnot) =>
+ retentionAnnot.assocs.contains(nme.value -> LiteralAnnotArg(Constant(AnnotationRetentionPolicyRuntimeValue)))
case _ =>
+ // SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the
+ // annotation is emitted with visibility `RUNTIME`
+ true
}
- r
}
- /**
- * The jvm descriptor of a type. If `t` references a nested class, its ClassBType is added to
- * the innerClassBufferASM.
- */
- final def descriptor(t: Type): String = { toTypeKind(t).descriptor }
-
- /**
- * The jvm descriptor for a symbol. If `sym` represents a nested class, its ClassBType is added
- * to the innerClassBufferASM.
- */
- final def descriptor(sym: Symbol): String = { getClassBTypeAndRegisterInnerClass(sym).descriptor }
-
- } // end of trait BCInnerClassGen
-
- trait BCAnnotGen extends BCInnerClassGen {
+ private def retentionPolicyOf(annot: AnnotationInfo): Symbol =
+ annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).flatMap(assoc =>
+ assoc.collectFirst {
+ case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value
+ }).getOrElse(AnnotationRetentionPolicyClassValue)
+
+ def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = {
+ val ca = new Array[Char](bytes.length)
+ var idx = 0
+ while(idx < bytes.length) {
+ val b: Byte = bytes(idx)
+ assert((b & ~0x7f) == 0)
+ ca(idx) = b.asInstanceOf[Char]
+ idx += 1
+ }
+ ca
+ }
- import genASM.{ubytesToCharArray, arrEncode}
- import bCodeAsmCommon.{shouldEmitAnnotation, isRuntimeVisible}
+ final def arrEncode(sb: ScalaSigBytes): Array[String] = {
+ var strs: List[String] = Nil
+ val bSeven: Array[Byte] = sb.sevenBitsMayBeZero
+ // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure)
+ var prevOffset = 0
+ var offset = 0
+ var encLength = 0
+ while(offset < bSeven.length) {
+ val deltaEncLength = (if(bSeven(offset) == 0) 2 else 1)
+ val newEncLength = encLength.toLong + deltaEncLength
+ if(newEncLength >= 65535) {
+ val ba = bSeven.slice(prevOffset, offset)
+ strs ::= new java.lang.String(ubytesToCharArray(ba))
+ encLength = 0
+ prevOffset = offset
+ } else {
+ encLength += deltaEncLength
+ offset += 1
+ }
+ }
+ if(prevOffset < offset) {
+ assert(offset == bSeven.length)
+ val ba = bSeven.slice(prevOffset, offset)
+ strs ::= new java.lang.String(ubytesToCharArray(ba))
+ }
+ assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict?
+ strs.reverse.toArray
+ }
/*
* can-multi-thread
@@ -420,9 +662,10 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
case StringTag =>
assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant`
av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag
- case ClazzTag => av.visit(name, toTypeKind(const.typeValue).toASMType)
+ case ClazzTag =>
+ av.visit(name, typeToBType(erasedType(const.typeValue)).toASMType)
case EnumTag =>
- val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class.
+ val edesc = descriptorForErasedType(const.tpe) // the class descriptor of the enumeration class.
val evalue = const.symbolValue.name.toString // value the actual enumeration value.
av.visitEnum(name, edesc, evalue)
}
@@ -435,7 +678,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
av.visit(name, strEncode(sb))
} else {
val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name)
- for(arg <- genASM.arrEncode(sb)) { arrAnnotV.visit(name, arg) }
+ for(arg <- arrEncode(sb)) { arrAnnotV.visit(name, arg) }
arrAnnotV.visitEnd()
} // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape.
@@ -447,7 +690,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
case NestedAnnotArg(annInfo) =>
val AnnotationInfo(typ, args, assocs) = annInfo
assert(args.isEmpty, args)
- val desc = descriptor(typ) // the class descriptor of the nested annotation class
+ val desc = descriptorForErasedType(typ) // the class descriptor of the nested annotation class
val nestedVisitor = av.visitAnnotation(name, desc)
emitAssocs(nestedVisitor, assocs)
}
@@ -472,7 +715,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
- val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
+ val av = cw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
emitAssocs(av, assocs)
}
}
@@ -484,7 +727,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
- val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
+ val av = mw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
emitAssocs(av, assocs)
}
}
@@ -496,7 +739,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
- val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
+ val av = fw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
emitAssocs(av, assocs)
}
}
@@ -511,16 +754,40 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
annot <- annots) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
- val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot))
+ val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptorForErasedType(typ), isRuntimeVisible(annot))
emitAssocs(pannVisitor, assocs)
}
}
+ /*
+ * must-single-thread
+ */
+ def emitParamNames(jmethod: asm.MethodVisitor, params: List[Symbol]) = {
+ for (param <- params) {
+ var access = asm.Opcodes.ACC_FINAL
+ if (param.isArtifact)
+ access |= asm.Opcodes.ACC_SYNTHETIC
+ jmethod.visitParameter(param.name.decoded, access)
+ }
+ }
} // end of trait BCAnnotGen
trait BCJGenSigGen {
- def getCurrentCUnit(): CompilationUnit
+ // @M don't generate java generics sigs for (members of) implementation
+ // classes, as they are monomorphic (TODO: ok?)
+ private def needsGenericSignature(sym: Symbol) = !(
+ // PP: This condition used to include sym.hasExpandedName, but this leads
+ // to the total loss of generic information if a private member is
+ // accessed from a closure: both the field and the accessor were generated
+ // without it. This is particularly bad because the availability of
+ // generic information could disappear as a consequence of a seemingly
+ // unrelated change.
+ settings.Ynogenericsig
+ || sym.isArtifact
+ || sym.isLiftedMethod
+ || sym.isBridge
+ )
/* @return
* - `null` if no Java signature is to be added (`null` is what ASM expects in these cases).
@@ -528,7 +795,63 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
*
* must-single-thread
*/
- def getGenericSignature(sym: Symbol, owner: Symbol): String = genASM.getGenericSignature(sym, owner, getCurrentCUnit())
+ def getGenericSignature(sym: Symbol, owner: Symbol): String = {
+ val memberTpe = enteringErasure(owner.thisType.memberInfo(sym))
+ getGenericSignature(sym, owner, memberTpe)
+ }
+
+ def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type): String = {
+ if (!needsGenericSignature(sym)) { return null }
+
+ val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe)
+ if (jsOpt.isEmpty) { return null }
+
+ val sig = jsOpt.get
+ log(sig) // This seems useful enough in the general case.
+
+ def wrap(op: => Unit) = {
+ try { op; true }
+ catch { case _: Throwable => false }
+ }
+
+ if (settings.Xverify) {
+ // Run the signature parser to catch bogus signatures.
+ val isValidSignature = wrap {
+ // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser)
+ import scala.tools.asm.util.CheckClassAdapter
+ if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } // requires asm-util.jar
+ else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig }
+ else { CheckClassAdapter checkClassSignature sig }
+ }
+
+ if(!isValidSignature) {
+ reporter.warning(sym.pos,
+ sm"""|compiler bug: created invalid generic signature for $sym in ${sym.owner.skipPackageObject.fullName}
+ |signature: $sig
+ |if this is reproducible, please report bug at https://issues.scala-lang.org/
+ """.trim)
+ return null
+ }
+ }
+
+ if ((settings.check containsName phaseName)) {
+ val normalizedTpe = enteringErasure(erasure.prepareSigMap(memberTpe))
+ val bytecodeTpe = owner.thisType.memberInfo(sym)
+ if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) {
+ reporter.warning(sym.pos,
+ sm"""|compiler bug: created generic signature for $sym in ${sym.owner.skipPackageObject.fullName} that does not conform to its erasure
+ |signature: $sig
+ |original type: $memberTpe
+ |normalized type: $normalizedTpe
+ |erasure type: $bytecodeTpe
+ |if this is reproducible, please report bug at http://issues.scala-lang.org/
+ """.trim)
+ return null
+ }
+ }
+
+ sig
+ }
} // end of trait BCJGenSigGen
@@ -541,11 +864,15 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
* must-single-thread
*/
def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) {
- val needsAnnotation = (
- ( isRemoteClass ||
- isRemote(meth) && isJMethodPublic
- ) && !(meth.throwsAnnotations contains definitions.RemoteExceptionClass)
- )
+ def hasThrowsRemoteException = meth.annotations.exists {
+ case ThrownException(exc) => exc.typeSymbol == definitions.RemoteExceptionClass
+ case _ => false
+ }
+ val needsAnnotation = {
+ (isRemoteClass ||
+ isRemote(meth) && isJMethodPublic
+ ) && !hasThrowsRemoteException
+ }
if (needsAnnotation) {
val c = Constant(definitions.RemoteExceptionClass.tpe)
val arg = Literal(c) setType c.tpe
@@ -557,10 +884,23 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
*
* must-single-thread
*/
- private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) {
- val moduleName = internalName(module)
- val methodInfo = module.thisType.memberInfo(m)
- val paramJavaTypes: List[BType] = methodInfo.paramTypes map toTypeKind
+ private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, moduleClass: Symbol, m: Symbol): Unit = {
+ def staticForwarderGenericSignature: String = {
+ // SI-3452 Static forwarder generation uses the same erased signature as the method if forwards to.
+ // By rights, it should use the signature as-seen-from the module class, and add suitable
+ // primitive and value-class boxing/unboxing.
+ // But for now, just like we did in mixin, we just avoid writing a wrong generic signature
+ // (one that doesn't erase to the actual signature). See run/t3452b for a test case.
+ val memberTpe = enteringErasure(moduleClass.thisType.memberInfo(m))
+ val erasedMemberType = erasure.erasure(m)(memberTpe)
+ if (erasedMemberType =:= m.info)
+ getGenericSignature(m, moduleClass, memberTpe)
+ else null
+ }
+
+ val moduleName = internalName(moduleClass)
+ val methodInfo = moduleClass.thisType.memberInfo(m)
+ val paramJavaTypes: List[BType] = methodInfo.paramTypes map typeToBType
// val paramNames = 0 until paramJavaTypes.length map ("x_" + _)
/* Forwarders must not be marked final,
@@ -574,12 +914,12 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
)
// TODO needed? for(ann <- m.annotations) { ann.symbol.initialize }
- val jgensig = genASM.staticForwarderGenericSignature(m, module, getCurrentCUnit())
+ val jgensig = staticForwarderGenericSignature
addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m)
val (throws, others) = m.annotations partition (_.symbol == definitions.ThrowsClass)
val thrownExceptions: List[String] = getExceptions(throws)
- val jReturnType = toTypeKind(methodInfo.resultType)
+ val jReturnType = typeToBType(methodInfo.resultType)
val mdesc = MethodBType(paramJavaTypes, jReturnType).descriptor
val mirrorMethodName = m.javaSimpleName.toString
val mirrorMethod: asm.MethodVisitor = jclass.visitMethod(
@@ -595,7 +935,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
mirrorMethod.visitCode()
- mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module))
+ mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, classBTypeFromSymbol(moduleClass).descriptor)
var index = 0
for(jparamType <- paramJavaTypes) {
@@ -604,7 +944,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
index += jparamType.size
}
- mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, asmMethodType(m).descriptor, false)
+ mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, methodBTypeFromSymbol(m).descriptor, false)
mirrorMethod.visitInsn(jReturnType.typedOpcode(asm.Opcodes.IRETURN))
mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
@@ -629,9 +969,9 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
}
debuglog(s"Potentially conflicting names for forwarders: $conflictingNames")
- for (m <- moduleClass.info.membersBasedOnFlags(bCodeAsmCommon.ExcludedForwarderFlags, symtab.Flags.METHOD)) {
+ for (m <- moduleClass.info.membersBasedOnFlags(BCodeHelpers.ExcludedForwarderFlags, symtab.Flags.METHOD)) {
if (m.isType || m.isDeferred || (m.owner eq definitions.ObjectClass) || m.isConstructor)
- debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'")
+ debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass': ${m.isType} || ${m.isDeferred} || ${m.owner eq definitions.ObjectClass} || ${m.isConstructor}")
else if (conflictingNames(m.name))
log(s"No forwarder for $m due to conflict with ${linkedClass.info.member(m.name)}")
else if (m.hasAccessBoundary)
@@ -654,8 +994,11 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
* must-single-thread
*/
def getExceptions(excs: List[AnnotationInfo]): List[String] = {
- for (ThrownException(exc) <- excs.distinct)
- yield internalName(exc)
+ for (ThrownException(tp) <- excs.distinct)
+ yield {
+ val erased = erasedType(tp)
+ internalName(erased.typeSymbol)
+ }
}
} // end of trait BCForwardersGen
@@ -682,60 +1025,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
new java.lang.Long(id)
).visitEnd()
}
-
- /**
- * Add:
- * private static java.util.Map $deserializeLambdaCache$ = null
- * private static Object $deserializeLambda$(SerializedLambda l) {
- * var cache = $deserializeLambdaCache$
- * if (cache eq null) {
- * cache = new java.util.HashMap()
- * $deserializeLambdaCache$ = cache
- * }
- * return scala.compat.java8.runtime.LambdaDeserializer.deserializeLambda(MethodHandles.lookup(), cache, l);
- * }
- */
- def addLambdaDeserialize(clazz: Symbol, jclass: asm.ClassVisitor): Unit = {
- val cw = jclass
- import scala.tools.asm.Opcodes._
-
- // Need to force creation of BTypes for these as `getCommonSuperClass` is called on
- // automatically computing the max stack size (`visitMaxs`) during method writing.
- javaUtilHashMapReference
- javaUtilMapReference
-
- cw.visitInnerClass("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup", ACC_PUBLIC + ACC_FINAL + ACC_STATIC)
-
- {
- val fv = cw.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambdaCache$", "Ljava/util/Map;", null, null)
- fv.visitEnd()
- }
-
- {
- val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", "(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", null, null)
- mv.visitCode()
- // javaBinaryName returns the internal name of a class. Also used in BTypesFromsymbols.classBTypeFromSymbol.
- mv.visitFieldInsn(GETSTATIC, clazz.javaBinaryName.toString, "$deserializeLambdaCache$", "Ljava/util/Map;")
- mv.visitVarInsn(ASTORE, 1)
- mv.visitVarInsn(ALOAD, 1)
- val l0 = new asm.Label()
- mv.visitJumpInsn(IFNONNULL, l0)
- mv.visitTypeInsn(NEW, "java/util/HashMap")
- mv.visitInsn(DUP)
- mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "<init>", "()V", false)
- mv.visitVarInsn(ASTORE, 1)
- mv.visitVarInsn(ALOAD, 1)
- mv.visitFieldInsn(PUTSTATIC, clazz.javaBinaryName.toString, "$deserializeLambdaCache$", "Ljava/util/Map;")
- mv.visitLabel(l0)
- mv.visitFieldInsn(GETSTATIC, "scala/compat/java8/runtime/LambdaDeserializer$", "MODULE$", "Lscala/compat/java8/runtime/LambdaDeserializer$;")
- mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false)
- mv.visitVarInsn(ALOAD, 1)
- mv.visitVarInsn(ALOAD, 0)
- mv.visitMethodInsn(INVOKEVIRTUAL, "scala/compat/java8/runtime/LambdaDeserializer$", "deserializeLambda", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/util/Map;Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", false)
- mv.visitInsn(ARETURN)
- mv.visitEnd()
- }
- }
} // end of trait BCClassGen
/* functionality for building plain and mirror classes */
@@ -748,9 +1037,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
/* builder of mirror classes */
class JMirrorBuilder extends JCommonBuilder {
- private var cunit: CompilationUnit = _
- def getCurrentCUnit(): CompilationUnit = cunit;
-
/* Generate a mirror class for a top-level module. A mirror class is a class
* containing only static methods that forward to the corresponding method
* on the MODULE instance of the given Scala object. It will only be
@@ -762,8 +1048,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
def genMirrorClass(moduleClass: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = {
assert(moduleClass.isModuleClass)
assert(moduleClass.companionClass == NoSymbol, moduleClass)
- innerClassBufferASM.clear()
- this.cunit = cunit
val bType = mirrorClassClassBType(moduleClass)
val mirrorClass = new asm.tree.ClassNode
@@ -772,7 +1056,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
bType.info.get.flags,
bType.internalName,
null /* no java-generic-signature */,
- ObjectReference.internalName,
+ ObjectRef.internalName,
EMPTY_STRING_ARRAY
)
@@ -785,9 +1069,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass)
- innerClassBufferASM ++= bType.info.get.nestedClasses
- addInnerClassesASM(mirrorClass, innerClassBufferASM.toList)
-
mirrorClass.visitEnd()
("" + moduleClass.name) // this side-effect is necessary, really.
@@ -811,18 +1092,15 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
def javaSimpleName(s: Symbol): String = { s.javaSimpleName.toString }
- innerClassBufferASM.clear()
-
- val flags = javaFlags(cls)
+ val beanInfoType = beanInfoClassClassBType(cls)
- val beanInfoName = (internalName(cls) + "BeanInfo")
val beanInfoClass = new asm.tree.ClassNode
beanInfoClass.visit(
classfileVersion,
- flags,
- beanInfoName,
+ beanInfoType.info.get.flags,
+ beanInfoType.internalName,
null, // no java-generic-signature
- "scala/beans/ScalaBeanInfo",
+ sbScalaBeanInfoRef.internalName,
EMPTY_STRING_ARRAY
)
@@ -859,7 +1137,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
EMPTY_STRING_ARRAY // no throwable exceptions
)
- val stringArrayJType: BType = ArrayBType(StringReference)
+ val stringArrayJType: BType = ArrayBType(StringRef)
val conJType: BType = MethodBType(
classBTypeFromSymbol(definitions.ClassClass) :: stringArrayJType :: stringArrayJType :: Nil,
UNIT
@@ -872,7 +1150,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
constructor.visitLdcInsn(new java.lang.Integer(fi))
if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) }
else { constructor.visitLdcInsn(f) }
- constructor.visitInsn(StringReference.typedOpcode(asm.Opcodes.IASTORE))
+ constructor.visitInsn(StringRef.typedOpcode(asm.Opcodes.IASTORE))
fi += 1
}
}
@@ -885,12 +1163,12 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
// push the string array of field information
constructor.visitLdcInsn(new java.lang.Integer(fieldList.length))
- constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringReference.internalName)
+ constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringRef.internalName)
push(fieldList)
// push the string array of method information
constructor.visitLdcInsn(new java.lang.Integer(methodList.length))
- constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringReference.internalName)
+ constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringRef.internalName)
push(methodList)
// invoke the superclass constructor, which will do the
@@ -901,9 +1179,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments
constructor.visitEnd()
- innerClassBufferASM ++= classBTypeFromSymbol(cls).info.get.nestedClasses
- addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList)
-
beanInfoClass.visitEnd()
beanInfoClass
@@ -932,8 +1207,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
* must-single-thread
*/
def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String) {
- // this tracks the inner class in innerClassBufferASM, if needed.
- val androidCreatorType = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass)
+ val androidCreatorType = classBTypeFromSymbol(AndroidCreatorClass)
val tdesc_creator = androidCreatorType.descriptor
cnode.visitField(
@@ -975,3 +1249,63 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
} // end of trait JAndroidBuilder
}
+
+object BCodeHelpers {
+ val ExcludedForwarderFlags = {
+ import scala.tools.nsc.symtab.Flags._
+ // Should include DEFERRED but this breaks findMember.
+ SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO
+ }
+
+ /**
+ * Valid flags for InnerClass attribute entry.
+ * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6
+ */
+ val INNER_CLASSES_FLAGS = {
+ asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED |
+ asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL | asm.Opcodes.ACC_INTERFACE |
+ asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION |
+ asm.Opcodes.ACC_ENUM
+ }
+
+ class TestOp(val op: Int) extends AnyVal {
+ import TestOp._
+ def negate = this match {
+ case EQ => NE
+ case NE => EQ
+ case LT => GE
+ case GE => LT
+ case GT => LE
+ case LE => GT
+ }
+ def opcodeIF = asm.Opcodes.IFEQ + op
+ def opcodeIFICMP = asm.Opcodes.IF_ICMPEQ + op
+ }
+
+ object TestOp {
+ // the order here / op numbers are important to get the correct result when calling opcodeIF
+ val EQ = new TestOp(0)
+ val NE = new TestOp(1)
+ val LT = new TestOp(2)
+ val GE = new TestOp(3)
+ val GT = new TestOp(4)
+ val LE = new TestOp(5)
+ }
+
+ class InvokeStyle(val style: Int) extends AnyVal {
+ import InvokeStyle._
+ def isVirtual: Boolean = this == Virtual
+ def isStatic : Boolean = this == Static
+ def isSpecial: Boolean = this == Special
+ def isSuper : Boolean = this == Super
+
+ def hasInstance = this != Static
+ }
+
+ object InvokeStyle {
+ val Virtual = new InvokeStyle(0) // InvokeVirtual or InvokeInterface
+ val Static = new InvokeStyle(1) // InvokeStatic
+ val Special = new InvokeStyle(2) // InvokeSpecial (private methods, constructors)
+ val Super = new InvokeStyle(3) // InvokeSpecial (super calls)
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeICodeCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeICodeCommon.scala
deleted file mode 100644
index 50d20921d5..0000000000
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeICodeCommon.scala
+++ /dev/null
@@ -1,25 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2014 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc.backend.jvm
-
-import scala.tools.nsc.Global
-import PartialFunction._
-
-/**
- * This trait contains code shared between GenBCode and GenICode that depends on types defined in
- * the compiler cake (Global).
- */
-final class BCodeICodeCommon[G <: Global](val global: G) {
- import global._
-
- /** Some useful equality helpers. */
- def isNull(t: Tree) = cond(t) { case Literal(Constant(null)) => true }
- def isLiteral(t: Tree) = cond(t) { case Literal(_) => true }
- def isNonNullExpr(t: Tree) = isLiteral(t) || ((t.symbol ne null) && t.symbol.isModule)
-
- /** If l or r is constant null, returns the other ; otherwise null */
- def ifOneIsNull(l: Tree, r: Tree) = if (isNull(l)) r else if (isNull(r)) l else null
-}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
index eb0da7caef..e3d45a9b3e 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
@@ -12,6 +12,7 @@ import scala.annotation.switch
import scala.collection.mutable
import GenBCode._
import scala.tools.asm.tree.MethodInsnNode
+import scala.tools.nsc.backend.jvm.BCodeHelpers.TestOp
/*
* A high-level facade to the ASM API for bytecode generation.
@@ -28,9 +29,6 @@ abstract class BCodeIdiomatic extends SubComponent {
import coreBTypes._
val classfileVersion: Int = settings.target.value match {
- case "jvm-1.5" => asm.Opcodes.V1_5
- case "jvm-1.6" => asm.Opcodes.V1_6
- case "jvm-1.7" => asm.Opcodes.V1_7
case "jvm-1.8" => asm.Opcodes.V1_8
}
@@ -42,7 +40,7 @@ abstract class BCodeIdiomatic extends SubComponent {
if (emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0
)
- val StringBuilderClassName = "scala/collection/mutable/StringBuilder"
+ lazy val JavaStringBuilderClassName = jlStringBuilderRef.internalName
val EMPTY_STRING_ARRAY = Array.empty[String]
val EMPTY_INT_ARRAY = Array.empty[Int]
@@ -109,41 +107,20 @@ abstract class BCodeIdiomatic extends SubComponent {
def jmethod: asm.tree.MethodNode
import asm.Opcodes;
- import icodes.opcodes.{ Static, Dynamic, SuperCall }
final def emit(opc: Int) { jmethod.visitInsn(opc) }
- /*
- * can-multi-thread
- */
- final def genPrimitiveArithmetic(op: icodes.ArithmeticOp, kind: BType) {
-
- import icodes.{ ADD, SUB, MUL, DIV, REM, NOT }
-
- op match {
-
- case ADD => add(kind)
- case SUB => sub(kind)
- case MUL => mul(kind)
- case DIV => div(kind)
- case REM => rem(kind)
-
- case NOT =>
- if (kind.isIntSizedType) {
- emit(Opcodes.ICONST_M1)
- emit(Opcodes.IXOR)
- } else if (kind == LONG) {
- jmethod.visitLdcInsn(new java.lang.Long(-1))
- jmethod.visitInsn(Opcodes.LXOR)
- } else {
- abort(s"Impossible to negate an $kind")
- }
-
- case _ =>
- abort(s"Unknown arithmetic primitive $op")
+ final def genPrimitiveNot(bType: BType): Unit = {
+ if (bType.isIntSizedType) {
+ emit(Opcodes.ICONST_M1)
+ emit(Opcodes.IXOR)
+ } else if (bType == LONG) {
+ jmethod.visitLdcInsn(new java.lang.Long(-1))
+ jmethod.visitInsn(Opcodes.LXOR)
+ } else {
+ abort(s"Impossible to negate a $bType")
}
-
- } // end of method genPrimitiveArithmetic()
+ }
/*
* can-multi-thread
@@ -207,12 +184,13 @@ abstract class BCodeIdiomatic extends SubComponent {
* can-multi-thread
*/
final def genStartConcat(pos: Position): Unit = {
- jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName)
+ jmethod.visitTypeInsn(Opcodes.NEW, JavaStringBuilderClassName)
jmethod.visitInsn(Opcodes.DUP)
invokespecial(
- StringBuilderClassName,
+ JavaStringBuilderClassName,
INSTANCE_CONSTRUCTOR_NAME,
"()V",
+ itf = false,
pos
)
}
@@ -220,22 +198,27 @@ abstract class BCodeIdiomatic extends SubComponent {
/*
* can-multi-thread
*/
- final def genStringConcat(el: BType, pos: Position): Unit = {
-
- val jtype =
- if (el.isArray || el.isClass) ObjectReference
- else el
-
- val bt = MethodBType(List(jtype), StringBuilderReference)
-
- invokevirtual(StringBuilderClassName, "append", bt.descriptor, pos)
+ def genConcat(elemType: BType, pos: Position): Unit = {
+ val paramType = elemType match {
+ case ct: ClassBType if ct.isSubtypeOf(StringRef).get => StringRef
+ case ct: ClassBType if ct.isSubtypeOf(jlStringBufferRef).get => jlStringBufferRef
+ case ct: ClassBType if ct.isSubtypeOf(jlCharSequenceRef).get => jlCharSequenceRef
+ // Don't match for `ArrayBType(CHAR)`, even though StringBuilder has such an overload:
+ // `"a" + Array('b')` should NOT be "ab", but "a[C@...".
+ case _: RefBType => ObjectRef
+ // jlStringBuilder does not have overloads for byte and short, but we can just use the int version
+ case BYTE | SHORT => INT
+ case pt: PrimitiveBType => pt
+ }
+ val bt = MethodBType(List(paramType), jlStringBuilderRef)
+ invokevirtual(JavaStringBuilderClassName, "append", bt.descriptor, pos)
}
/*
* can-multi-thread
*/
final def genEndConcat(pos: Position): Unit = {
- invokevirtual(StringBuilderClassName, "toString", "()Ljava/lang/String;", pos)
+ invokevirtual(JavaStringBuilderClassName, "toString", "()Ljava/lang/String;", pos)
}
/*
@@ -391,41 +374,38 @@ 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, pos: Position) {
- addInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, false, pos)
+ final def invokespecial(owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, itf, pos)
}
// can-multi-thread
- final def invokestatic(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKESTATIC, owner, name, desc, false, pos)
+ final def invokestatic(owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKESTATIC, owner, name, desc, itf, pos)
}
// can-multi-thread
- final def invokeinterface(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, true, pos)
+ final def invokeinterface(owner: String, name: String, desc: String, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, itf = true, pos)
}
// can-multi-thread
- final def invokevirtual(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, false, pos)
+ final def invokevirtual(owner: String, name: String, desc: String, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, itf = false, pos)
}
- private def addInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position) = {
+ def emitInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
val node = new MethodInsnNode(opcode, owner, name, desc, itf)
jmethod.instructions.add(node)
- if (settings.YoptInlinerEnabled) callsitePositions(node) = pos
- }
- final def invokedynamic(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKEDYNAMIC, owner, name, desc)
+ if (settings.optInlinerEnabled) callsitePositions(node) = pos
}
// can-multi-thread
final def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) }
// can-multi-thread
- final def emitIF(cond: icodes.TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF, label) }
+ final def emitIF(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF, label) }
// can-multi-thread
- final def emitIF_ICMP(cond: icodes.TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP, label) }
+ final def emitIF_ICMP(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP, label) }
// can-multi-thread
- final def emitIF_ACMP(cond: icodes.TestOp, label: asm.Label) {
- assert((cond == icodes.EQ) || (cond == icodes.NE), cond)
- val opc = (if (cond == icodes.EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE)
+ final def emitIF_ACMP(cond: TestOp, label: asm.Label) {
+ assert((cond == TestOp.EQ) || (cond == TestOp.NE), cond)
+ val opc = (if (cond == TestOp.EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE)
jmethod.visitJumpInsn(opc, label)
}
// 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 a9b6a312e9..03df1c76fa 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
@@ -8,13 +8,12 @@ package scala.tools.nsc
package backend
package jvm
-import scala.collection.{ mutable, immutable }
-import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository
+import scala.collection.{immutable, mutable}
import scala.tools.nsc.symtab._
-
import scala.tools.asm
import GenBCode._
import BackendReporting._
+import scala.tools.nsc.backend.jvm.BCodeHelpers.InvokeStyle
/*
*
@@ -26,7 +25,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
import global._
import bTypes._
import coreBTypes._
- import bCodeAsmCommon._
/*
* There's a dedicated PlainClassBuilder for each CompilationUnit,
@@ -61,48 +59,39 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
// current class
var cnode: asm.tree.ClassNode = null
- var thisName: String = null // the internal name of the class being emitted
+ var thisBType: ClassBType = null
var claszSymbol: Symbol = null
var isCZParcelable = false
var isCZStaticModule = false
var isCZRemote = false
- protected val indyLambdaHosts = collection.mutable.Set[Symbol]()
-
/* ---------------- idiomatic way to ask questions to typer ---------------- */
def paramTKs(app: Apply): List[BType] = {
val Apply(fun, _) = app
val funSym = fun.symbol
- (funSym.info.paramTypes map toTypeKind) // this tracks mentioned inner classes (in innerClassBufferASM)
+ funSym.info.paramTypes map typeToBType
}
- def symInfoTK(sym: Symbol): BType = {
- toTypeKind(sym.info) // this tracks mentioned inner classes (in innerClassBufferASM)
- }
+ def symInfoTK(sym: Symbol): BType = typeToBType(sym.info)
- def tpeTK(tree: Tree): BType = { toTypeKind(tree.tpe) }
+ def tpeTK(tree: Tree): BType = typeToBType(tree.tpe)
def log(msg: => AnyRef) {
global synchronized { global.log(msg) }
}
- override def getCurrentCUnit(): CompilationUnit = { cunit }
-
/* ---------------- helper utils for generating classes and fields ---------------- */
def genPlainClass(cd: ClassDef) {
assert(cnode == null, "GenBCode detected nested methods.")
- innerClassBufferASM.clear()
claszSymbol = cd.symbol
isCZParcelable = isAndroidParcelableClass(claszSymbol)
isCZStaticModule = isStaticModuleClass(claszSymbol)
isCZRemote = isRemote(claszSymbol)
- thisName = internalName(claszSymbol)
-
- val classBType = classBTypeFromSymbol(claszSymbol)
+ thisBType = classBTypeFromSymbol(claszSymbol)
cnode = new asm.tree.ClassNode()
@@ -121,30 +110,13 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
addClassFields()
- innerClassBufferASM ++= classBType.info.get.nestedClasses
gen(cd.impl)
-
- val shouldAddLambdaDeserialize = (
- settings.target.value == "jvm-1.8"
- && settings.Ydelambdafy.value == "method"
- && indyLambdaHosts.contains(claszSymbol))
-
- if (shouldAddLambdaDeserialize)
- addLambdaDeserialize(claszSymbol, cnode)
-
- addInnerClassesASM(cnode, innerClassBufferASM.toList)
-
- cnode.visitAttribute(classBType.inlineInfoAttribute.get)
+ cnode.visitAttribute(thisBType.inlineInfoAttribute.get)
if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern))
AsmUtils.traceClass(cnode)
- if (settings.YoptAddToBytecodeRepository) {
- // 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()
@@ -154,31 +126,27 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
private def initJClass(jclass: asm.ClassVisitor) {
val bType = classBTypeFromSymbol(claszSymbol)
- val superClass = bType.info.get.superClass.getOrElse(ObjectReference).internalName
- val interfaceNames = bType.info.get.interfaces map {
- case classBType =>
- if (classBType.isNestedClass.get) { innerClassBufferASM += classBType }
- classBType.internalName
- }
+ val superClass = bType.info.get.superClass.getOrElse(ObjectRef).internalName
+ val interfaceNames = bType.info.get.interfaces.map(_.internalName)
val flags = javaFlags(claszSymbol)
val thisSignature = getGenericSignature(claszSymbol, claszSymbol.owner)
cnode.visit(classfileVersion, flags,
- thisName, thisSignature,
+ thisBType.internalName, thisSignature,
superClass, interfaceNames.toArray)
if (emitSource) {
cnode.visitSource(cunit.source.toString, null /* SourceDebugExtension */)
}
- enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match {
+ enclosingMethodAttribute(claszSymbol, internalName, methodBTypeFromSymbol(_).descriptor) match {
case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) =>
cnode.visitOuterClass(className, methodName, methodDescriptor)
case _ => ()
}
- val ssa = getAnnotPickle(thisName, claszSymbol)
+ val ssa = getAnnotPickle(thisBType.internalName, claszSymbol)
cnode.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
emitAnnotations(cnode, claszSymbol.annotations ++ ssa)
@@ -188,18 +156,17 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
} else {
- val skipStaticForwarders = (claszSymbol.isInterface || settings.noForwarders)
- if (!skipStaticForwarders) {
+ if (!settings.noForwarders) {
val lmoc = claszSymbol.companionModule
// add static forwarders if there are no name conflicts; see bugs #363 and #1735
if (lmoc != NoSymbol) {
// it must be a top level class (name contains no $s)
val isCandidateForForwarders = {
- exitingPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass }
+ exitingPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isNestedClass }
}
if (isCandidateForForwarders) {
log(s"Adding static forwarders from '$claszSymbol' to implementations in '$lmoc'")
- addForwarders(isRemote(claszSymbol), cnode, thisName, lmoc.moduleClass)
+ addForwarders(isRemote(claszSymbol), cnode, thisBType.internalName, lmoc.moduleClass)
}
}
}
@@ -214,10 +181,17 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
* can-multi-thread
*/
private def addModuleInstanceField() {
+ // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
+ // SD-194 This can't be FINAL on JVM 1.9+ because we assign it from within the
+ // instance constructor, not from <clinit> directly. Assignment from <clinit>,
+ // after the constructor has completely finished, seems like the principled
+ // thing to do, but it would change behaviour when "benign" cyclic references
+ // between modules exist.
+ val mods = GenBCode.PublicStatic
val fv =
- cnode.visitField(GenBCode.PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
+ cnode.visitField(mods,
strMODULE_INSTANCE_FIELD,
- "L" + thisName + ";",
+ thisBType.descriptor,
null, // no java-generic-signature
null // no initial value
)
@@ -241,11 +215,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
/* "legacy static initialization" */
if (isCZStaticModule) {
- clinit.visitTypeInsn(asm.Opcodes.NEW, thisName)
+ clinit.visitTypeInsn(asm.Opcodes.NEW, thisBType.internalName)
clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL,
- thisName, INSTANCE_CONSTRUCTOR_NAME, "()V", false)
+ thisBType.internalName, INSTANCE_CONSTRUCTOR_NAME, "()V", false)
}
- if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisName) }
+ if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisBType.internalName) }
clinit.visitInsn(asm.Opcodes.RETURN)
clinit.visitMaxs(0, 0) // just to follow protocol, dummy arguments
@@ -253,13 +227,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
}
def addClassFields() {
- /* Non-method term members are fields, except for module members. Module
- * members can only happen on .NET (no flatten) for inner traits. There,
- * a module symbol is generated (transformInfo in mixin) which is used
- * as owner for the members of the implementation class (so that the
- * backend emits them as static).
- * No code is needed for this module symbol.
- */
for (f <- fieldSymbols(claszSymbol)) {
val javagensig = getGenericSignature(f, claszSymbol)
val flags = javaFieldFlags(f)
@@ -288,7 +255,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
// used by genLoadTry() and genSynchronized()
var earlyReturnVar: Symbol = null
var shouldEmitCleanup = false
- var insideCleanupBlock = false
// line numbers
var lastEmittedLineNr = -1
@@ -458,9 +424,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
var varsInScope: List[Tuple2[Symbol, asm.Label]] = null // (local-var-sym -> start-of-scope)
// helpers around program-points.
- def lastInsn: asm.tree.AbstractInsnNode = {
- mnode.instructions.getLast
- }
+ def lastInsn: asm.tree.AbstractInsnNode = mnode.instructions.getLast
def currProgramPoint(): asm.Label = {
lastInsn match {
case labnode: asm.tree.LabelNode => labnode.getLabel
@@ -522,7 +486,27 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
case ValDef(mods, name, tpt, rhs) => () // fields are added in `genPlainClass()`, via `addClassFields()`
- case dd : DefDef => genDefDef(dd)
+ case dd : DefDef =>
+ val sym = dd.symbol
+ if (needsStaticImplMethod(sym)) {
+ if (sym.isMixinConstructor) {
+ val statified = global.gen.mkStatic(dd, sym.name, _.cloneSymbol)
+ genDefDef(statified)
+ } else {
+ val forwarderDefDef = {
+ val dd1 = global.gen.mkStatic(deriveDefDef(dd)(_ => EmptyTree), traitSuperAccessorName(sym), _.cloneSymbol.withoutAnnotations)
+ dd1.symbol.setFlag(Flags.ARTIFACT).resetFlag(Flags.OVERRIDE)
+ val selfParam :: realParams = dd1.vparamss.head.map(_.symbol)
+ deriveDefDef(dd1)(_ =>
+ atPos(dd1.pos)(
+ Apply(Select(global.gen.mkAttributedIdent(selfParam).setType(sym.owner.typeConstructor), dd.symbol),
+ realParams.map(global.gen.mkAttributedIdent)).updateAttachment(UseInvokeSpecial))
+ )
+ }
+ genDefDef(forwarderDefDef)
+ genDefDef(dd)
+ }
+ } else genDefDef(dd)
case Template(_, _, body) => body foreach gen
@@ -533,7 +517,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
/*
* must-single-thread
*/
- def initJMethod(flags: Int, paramAnnotations: List[List[AnnotationInfo]]) {
+ def initJMethod(flags: Int, params: List[Symbol]) {
val jgensig = getGenericSignature(methSymbol, claszSymbol)
addRemoteExceptionAnnot(isCZRemote, hasPublicBitSet(flags), methSymbol)
@@ -544,7 +528,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
if (isMethSymStaticCtor) CLASS_CONSTRUCTOR_NAME
else jMethodName
- val mdesc = asmMethodType(methSymbol).descriptor
+ val mdesc = methodBTypeFromSymbol(methSymbol).descriptor
mnode = cnode.visitMethod(
flags,
bytecodeName,
@@ -553,10 +537,9 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
mkArray(thrownExceptions)
).asInstanceOf[asm.tree.MethodNode]
- // TODO param names: (m.params map (p => javaName(p.sym)))
-
+ emitParamNames(mnode, params)
emitAnnotations(mnode, others)
- emitParamAnnotations(mnode, paramAnnotations)
+ emitParamAnnotations(mnode, params.map(_.annotations))
} // end of method initJMethod
@@ -568,7 +551,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
methSymbol = dd.symbol
jMethodName = methSymbol.javaSimpleName.toString
- returnType = asmMethodType(dd.symbol).returnType
+ returnType = methodBTypeFromSymbol(dd.symbol).returnType
isMethSymStaticCtor = methSymbol.isStaticConstructor
resetMethodBookkeeping(dd)
@@ -587,16 +570,15 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
}
val isNative = methSymbol.hasAnnotation(definitions.NativeAttr)
- val isAbstractMethod = (methSymbol.isDeferred || methSymbol.owner.isInterface)
+ val isAbstractMethod = rhs == EmptyTree
val flags = GenBCode.mkFlags(
javaFlags(methSymbol),
- if (claszSymbol.isInterface) asm.Opcodes.ACC_ABSTRACT else 0,
+ if (isAbstractMethod) asm.Opcodes.ACC_ABSTRACT else 0,
if (methSymbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0,
if (isNative) asm.Opcodes.ACC_NATIVE else 0 // native methods of objects are generated in mirror classes
)
- // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize }
- initJMethod(flags, params.map(p => p.symbol.annotations))
+ initJMethod(flags, params.map(_.symbol))
/* Add method-local vars for LabelDef-params.
*
@@ -621,13 +603,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
genLoad(rhs, returnType)
rhs match {
- case Block(_, Return(_)) => ()
- case Return(_) => ()
+ case Return(_) | Block(_, Return(_)) | Throw(_) | Block(_, Throw(_)) => ()
case EmptyTree =>
globalError("Concrete method has no definition: " + dd + (
if (settings.debug) "(found: " + methSymbol.owner.info.decls.toList.mkString(", ") + ")"
- else "")
- )
+ else ""))
case _ =>
bc emitRETURN returnType
}
@@ -638,7 +618,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
if (!hasStaticBitSet) {
mnode.visitLocalVariable(
"this",
- "L" + thisName + ";",
+ thisBType.descriptor,
null,
veryFirstProgramPoint,
onePastLastProgramPoint,
@@ -697,7 +677,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
val callee = methSymbol.enclClass.primaryConstructor
val jname = callee.javaSimpleName.toString
val jowner = internalName(callee.owner)
- val jtype = asmMethodType(callee).descriptor
+ val jtype = methodBTypeFromSymbol(callee).descriptor
insnModB = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESPECIAL, jowner, jname, jtype, false)
}
@@ -706,7 +686,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
// android creator code
if (isCZParcelable) {
// add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator
- val andrFieldDescr = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass).descriptor
+ val andrFieldDescr = classBTypeFromSymbol(AndroidCreatorClass).descriptor
cnode.visitField(
asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL,
"CREATOR",
@@ -718,10 +698,10 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
val callee = definitions.getMember(claszSymbol.companionModule, androidFieldName)
val jowner = internalName(callee.owner)
val jname = callee.javaSimpleName.toString
- val jtype = asmMethodType(callee).descriptor
+ val jtype = methodBTypeFromSymbol(callee).descriptor
insnParcA = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESTATIC, jowner, jname, jtype, false)
- // PUTSTATIC `thisName`.CREATOR;
- insnParcB = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisName, "CREATOR", andrFieldDescr)
+ // PUTSTATIC `thisBType.internalName`.CREATOR;
+ insnParcB = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisBType.internalName, "CREATOR", andrFieldDescr)
}
// insert a few instructions for initialization before each return instruction
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala
index b94208c1a5..add2c5ffe6 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala
@@ -30,17 +30,17 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
def genSynchronized(tree: Apply, expectedType: BType): BType = {
val Apply(fun, args) = tree
- val monitor = locals.makeLocal(ObjectReference, "monitor")
+ val monitor = locals.makeLocal(ObjectRef, "monitor")
val monCleanup = new asm.Label
// if the synchronized block returns a result, store it in a local variable.
// Just leaving it on the stack is not valid in MSIL (stack is cleaned when leaving try-blocks).
val hasResult = (expectedType != UNIT)
- val monitorResult: Symbol = if (hasResult) locals.makeLocal(tpeTK(args.head), "monitorResult") else null;
+ val monitorResult: Symbol = if (hasResult) locals.makeLocal(tpeTK(args.head), "monitorResult") else null
/* ------ (1) pushing and entering the monitor, also keeping a reference to it in a local var. ------ */
genLoadQualifier(fun)
- bc dup ObjectReference
+ bc dup ObjectRef
locals.store(monitor)
emit(asm.Opcodes.MONITORENTER)
@@ -73,9 +73,11 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
/* ------ (4) exception-handler version of monitor-exit code.
* Reached upon abrupt termination of (2).
* Protected by whatever protects the whole synchronized expression.
+ * null => "any" exception in bytecode, like we emit for finally.
+ * Important not to use j/l/Throwable which dooms the method to a life of interpretation! (SD-233)
* ------
*/
- protect(startProtected, endProtected, currProgramPoint(), ThrowableReference)
+ protect(startProtected, endProtected, currProgramPoint(), null)
locals.load(monitor)
emit(asm.Opcodes.MONITOREXIT)
emit(asm.Opcodes.ATHROW)
@@ -184,7 +186,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
for (CaseDef(pat, _, caseBody) <- catches) yield {
pat match {
case Typed(Ident(nme.WILDCARD), tpt) => NamelessEH(tpeTK(tpt).asClassBType, caseBody)
- case Ident(nme.WILDCARD) => NamelessEH(ThrowableReference, caseBody)
+ case Ident(nme.WILDCARD) => NamelessEH(jlThrowableRef, caseBody)
case Bind(_, _) => BoundEH (pat.symbol, caseBody)
}
}
@@ -213,7 +215,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
* please notice `tmp` has type tree.tpe, while `earlyReturnVar` has the method return type.
* Because those two types can be different, dedicated vars are needed.
*/
- val tmp = if (guardResult) locals.makeLocal(tpeTK(tree), "tmp") else null;
+ val tmp = if (guardResult) locals.makeLocal(tpeTK(tree), "tmp") else null
/*
* upon early return from the try-body or one of its EHs (but not the EH-version of the finally-clause)
@@ -236,6 +238,34 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
val endTryBody = currProgramPoint()
bc goTo postHandlers
+ /**
+ * A return within a `try` or `catch` block where a `finally` is present ("early return")
+ * emits a store of the result to a local, jump to a "cleanup" version of the `finally` block,
+ * and sets `shouldEmitCleanup = true` (see [[PlainBodyBuilder.genReturn]]).
+ *
+ * If the try-catch is nested, outer `finally` blocks need to be emitted in a cleanup version
+ * as well, so the `shouldEmitCleanup` variable remains `true` until the outermost `finally`.
+ * Nested cleanup `finally` blocks jump to the next enclosing one. For the outermost, we emit
+ * a read of the local variable, a return, and we set `shouldEmitCleanup = false` (see
+ * [[pendingCleanups]]).
+ *
+ * Now, assume we have
+ *
+ * try { return 1 } finally {
+ * try { println() } finally { println() }
+ * }
+ *
+ * Here, the outer `finally` needs a cleanup version, but the inner one does not. The method
+ * here makes sure that `shouldEmitCleanup` is only propagated outwards, not inwards to
+ * nested `finally` blocks.
+ */
+ def withFreshCleanupScope(body: => Unit) = {
+ val savedShouldEmitCleanup = shouldEmitCleanup
+ shouldEmitCleanup = false
+ body
+ shouldEmitCleanup = savedShouldEmitCleanup || shouldEmitCleanup
+ }
+
/* ------ (2) One EH for each case-clause (this does not include the EH-version of the finally-clause)
* An EH in (2) is reached upon abrupt termination of (1).
* An EH in (2) is protected by:
@@ -244,8 +274,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
* ------
*/
- for (ch <- caseHandlers) {
-
+ for (ch <- caseHandlers) withFreshCleanupScope {
// (2.a) emit case clause proper
val startHandler = currProgramPoint()
var endHandler: asm.Label = null
@@ -275,9 +304,13 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
protect(startTryBody, endTryBody, startHandler, excType)
// (2.c) emit jump to the program point where the finally-clause-for-normal-exit starts, or in effect `after` if no finally-clause was given.
bc goTo postHandlers
-
}
+ // Need to save the state of `shouldEmitCleanup` at this point: while emitting the first
+ // version of the `finally` block below, the variable may become true. But this does not mean
+ // that we need a cleanup version for the current block, only for the enclosing ones.
+ val currentFinallyBlockNeedsCleanup = shouldEmitCleanup
+
/* ------ (3.A) The exception-handler-version of the finally-clause.
* Reached upon abrupt termination of (1) or one of the EHs in (2).
* Protected only by whatever protects the whole try-catch-finally expression.
@@ -286,11 +319,11 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
// a note on terminology: this is not "postHandlers", despite appearances.
// "postHandlers" as in the source-code view. And from that perspective, both (3.A) and (3.B) are invisible implementation artifacts.
- if (hasFinally) {
+ if (hasFinally) withFreshCleanupScope {
nopIfNeeded(startTryBody)
val finalHandler = currProgramPoint() // version of the finally-clause reached via unhandled exception.
protect(startTryBody, finalHandler, finalHandler, null)
- val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(ThrowableReference, "exc"))
+ val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(jlThrowableRef, "exc"))
bc.store(eIdx, eTK)
emitFinalizer(finalizer, null, isDuplicate = true)
bc.load(eIdx, eTK)
@@ -314,14 +347,11 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
// this is not "postHandlers" either.
// `shouldEmitCleanup` can be set, and at the same time this try expression may lack a finally-clause.
// In other words, all combinations of (hasFinally, shouldEmitCleanup) are valid.
- if (hasFinally && shouldEmitCleanup) {
- val savedInsideCleanup = insideCleanupBlock
- insideCleanupBlock = true
+ if (hasFinally && currentFinallyBlockNeedsCleanup) {
markProgramPoint(finCleanup)
// regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted.
emitFinalizer(finalizer, null, isDuplicate = true)
pendingCleanups()
- insideCleanupBlock = savedInsideCleanup
}
/* ------ (4) finally-clause-for-normal-nonEarlyReturn-exit
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
index 0c26e01322..3e3229d2c3 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
@@ -7,15 +7,18 @@ package scala.tools.nsc
package backend.jvm
import scala.annotation.switch
+import scala.collection.{concurrent, mutable}
import scala.collection.concurrent.TrieMap
import scala.reflect.internal.util.Position
import scala.tools.asm
import asm.Opcodes
-import scala.tools.asm.tree.{MethodNode, MethodInsnNode, InnerClassNode, ClassNode}
+import scala.tools.asm.tree._
import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo}
import scala.tools.nsc.backend.jvm.BackendReporting._
+import scala.tools.nsc.backend.jvm.analysis.BackendUtils
import scala.tools.nsc.backend.jvm.opt._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
+import scala.collection.mutable.ListBuffer
import scala.tools.nsc.settings.ScalaSettings
/**
@@ -29,6 +32,8 @@ import scala.tools.nsc.settings.ScalaSettings
abstract class BTypes {
import BTypes.InternalName
+ val backendUtils: BackendUtils[this.type]
+
// Some core BTypes are required here, in class BType, where no Global instance is available.
// The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual
// implementation (CoreBTypesProxy) here because it has members that refer to global.Symbol.
@@ -38,12 +43,14 @@ abstract class BTypes {
/**
* Tools for parsing classfiles, used by the inliner.
*/
- val byteCodeRepository: ByteCodeRepository
+ val byteCodeRepository: ByteCodeRepository[this.type]
val localOpt: LocalOpt[this.type]
val inliner: Inliner[this.type]
+ val inlinerHeuristics: InlinerHeuristics[this.type]
+
val closureOptimizer: ClosureOptimizer[this.type]
val callGraph: CallGraph[this.type]
@@ -56,7 +63,6 @@ abstract class BTypes {
// Allows access to the compiler settings for backend components that don't have a global in scope
def compilerSettings: ScalaSettings
-
/**
* A map from internal names to ClassBTypes. Every ClassBType is added to this map on its
* construction.
@@ -68,19 +74,27 @@ 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(TrieMap.empty)
+ val classBTypeFromInternalName: concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty)
/**
* 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)
+ val callsitePositions: concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty)
+
+ /**
+ * Stores callsite instructions of invocations annotated `f(): @inline/noinline`.
+ * Instructions are added during code generation (BCodeBodyBuilder). The maps are then queried
+ * when building the CallGraph, every Callsite object has an annotated(No)Inline field.
+ */
+ val inlineAnnotatedCallsites: mutable.Set[MethodInsnNode] = recordPerRunCache(mutable.Set.empty)
+ val noInlineAnnotatedCallsites: mutable.Set[MethodInsnNode] = recordPerRunCache(mutable.Set.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)
+ val javaDefinedClasses: mutable.Set[InternalName] = recordPerRunCache(mutable.Set.empty)
/**
* Cache, contains methods whose unreachable instructions are eliminated.
@@ -92,12 +106,47 @@ abstract class BTypes {
* This cache allows running dead code elimination whenever an analyzer is used. If the method
* is already optimized, DCE can return early.
*/
- val unreachableCodeEliminated: collection.mutable.Set[MethodNode] = recordPerRunCache(collection.mutable.Set.empty)
+ val unreachableCodeEliminated: mutable.Set[MethodNode] = recordPerRunCache(mutable.Set.empty)
+
+ /**
+ * Cache of methods which have correct `maxLocals` / `maxStack` values assigned. This allows
+ * invoking `computeMaxLocalsMaxStack` whenever running an analyzer but performing the actual
+ * computation only when necessary.
+ */
+ val maxLocalsMaxStackComputed: mutable.Set[MethodNode] = recordPerRunCache(mutable.Set.empty)
+
+ /**
+ * Classes with indyLambda closure instantiations where the SAM type is serializable (e.g. Scala's
+ * FunctionN) need a `$deserializeLambda$` method. This map contains classes for which such a
+ * method has been generated. It is used during ordinary code generation, as well as during
+ * inlining: when inlining an indyLambda instruction into a class, we need to make sure the class
+ * has the method.
+ */
+ val indyLambdaImplMethods: mutable.AnyRefMap[InternalName, mutable.LinkedHashSet[asm.Handle]] = recordPerRunCache(mutable.AnyRefMap())
+ def addIndyLambdaImplMethod(hostClass: InternalName, handle: Seq[asm.Handle]): Seq[asm.Handle] = {
+ if (handle.isEmpty) Nil else {
+ val set = indyLambdaImplMethods.getOrElseUpdate(hostClass, mutable.LinkedHashSet())
+ val added = handle.filterNot(set)
+ set ++= handle
+ added
+ }
+ }
+ def removeIndyLambdaImplMethod(hostClass: InternalName, handle: Seq[asm.Handle]): Unit = {
+ if (handle.nonEmpty)
+ indyLambdaImplMethods.getOrElseUpdate(hostClass, mutable.LinkedHashSet()) --= handle
+ }
+
+ def getIndyLambdaImplMethods(hostClass: InternalName): Iterable[asm.Handle] = {
+ indyLambdaImplMethods.getOrNull(hostClass) match {
+ case null => Nil
+ case xs => xs
+ }
+ }
/**
* 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)
@@ -128,7 +177,7 @@ abstract class BTypes {
val res = ClassBType(internalName)
byteCodeRepository.classNode(internalName) match {
case Left(msg) => res.info = Left(NoClassBTypeInfoMissingBytecode(msg)); res
- case Right(c) => setClassInfoFromParsedClassfile(c, res)
+ case Right(c) => setClassInfoFromClassNode(c, res)
}
})
}
@@ -138,21 +187,19 @@ abstract class BTypes {
*/
def classBTypeFromClassNode(classNode: ClassNode): ClassBType = {
classBTypeFromInternalName.getOrElse(classNode.name, {
- setClassInfoFromParsedClassfile(classNode, ClassBType(classNode.name))
+ setClassInfoFromClassNode(classNode, ClassBType(classNode.name))
})
}
- private def setClassInfoFromParsedClassfile(classNode: ClassNode, classBType: ClassBType): ClassBType = {
+ private def setClassInfoFromClassNode(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}")
+ assert(classNode.name == ObjectRef.internalName, s"class with missing super type: ${classNode.name}")
None
case superName =>
Some(classBTypeFromParsedClassfile(superName))
}
- val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut)
-
val flags = classNode.access
/**
@@ -197,6 +244,8 @@ abstract class BTypes {
val inlineInfo = inlineInfoFromClassfile(classNode)
+ val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut)
+
classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo))
classBType
}
@@ -226,22 +275,21 @@ abstract class BTypes {
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),
+ sam = inlinerHeuristics.javaSam(classNode.name),
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
+ // being compiled. For those classes, the info is only needed if the inliner is enabled, otherwise
// we can save the memory.
- if (!compilerSettings.YoptInlinerEnabled) BTypes.EmptyInlineInfo
+ if (!compilerSettings.optInlinerEnabled) BTypes.EmptyInlineInfo
else fromClassfileAttribute getOrElse fromClassfileWithoutAttribute
}
@@ -291,8 +339,8 @@ abstract class BTypes {
final def isNonVoidPrimitiveType = isPrimitive && this != UNIT
- final def isNullType = this == RT_NULL
- final def isNothingType = this == RT_NOTHING
+ final def isNullType = this == srNullRef
+ final def isNothingType = this == srNothingRef
final def isBoxed = this.isClass && boxedClasses(this.asClassBType)
@@ -315,7 +363,7 @@ abstract class BTypes {
this match {
case ArrayBType(component) =>
- if (other == ObjectReference || other == jlCloneableReference || other == jioSerializableReference) true
+ if (other == ObjectRef || other == jlCloneableRef || other == jiSerializableRef) true
else other match {
case ArrayBType(otherComponent) => component.conformsTo(otherComponent).orThrow
case _ => false
@@ -324,7 +372,7 @@ abstract class BTypes {
case classType: ClassBType =>
if (isBoxed) {
if (other.isBoxed) this == other
- else if (other == ObjectReference) true
+ else if (other == ObjectRef) true
else other match {
case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow // e.g., java/lang/Double conforms to java/lang/Number
case _ => false
@@ -367,7 +415,7 @@ abstract class BTypes {
assert(other.isRef, s"Cannot compute maxType: $this, $other")
// Approximate `lub`. The common type of two references is always ObjectReference.
- ObjectReference
+ ObjectRef
case _: MethodBType =>
assertionError(s"unexpected method type when computing maxType: $this")
@@ -554,6 +602,8 @@ abstract class BTypes {
* Terminology
* -----------
*
+ * Diagram here: https://blogs.oracle.com/darcy/entry/nested_inner_member_and_top
+ *
* - Nested class (JLS 8): class whose declaration occurs within the body of another class
*
* - Top-level class (JLS 8): non-nested class
@@ -603,7 +653,7 @@ abstract class BTypes {
* Fields in the InnerClass entries:
* - inner class: the (nested) class C we are talking about
* - outer class: the class of which C is a member. Has to be null for non-members, i.e. for
- * local and anonymous classes. NOTE: this co-incides with the presence of an
+ * local and anonymous classes. NOTE: this coincides with the presence of an
* EnclosingMethod attribute (see below)
* - inner name: A string with the simple name of the inner class. Null for anonymous classes.
* - flags: access property flags, details in JVMS, table in 4.7.6. Static flag: see
@@ -652,7 +702,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 an EnclosingMethod attribute is requried (local and anonymous classes), the "outer"
+ * NOTE: When an EnclosingMethod attribute is required (local and anonymous classes), the "outer"
* field in the InnerClass table must be null.
*
* Fields:
@@ -760,26 +810,17 @@ 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
- *
+ * Specialized Classes, Delambdafy:method closure classes
+ * ------------------------------------------------------
*
- * 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.
+ * Specialized classes are always considered top-level, as the InnerClass / EnclosingMethod
+ * attributes describe a source-level properties.
*
* 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.
*
+ * See also BCodeHelpers.considerAsTopLevelImplementationArtifact.
+ *
*
* Mirror Classes
* --------------
@@ -837,7 +878,7 @@ abstract class BTypes {
// 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
+ def isJLO(t: ClassBType) = t.internalName == ObjectRef.internalName
assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this")
@@ -900,7 +941,7 @@ abstract class BTypes {
// the static flag in the InnerClass table has a special meaning, see InnerClass comment
i.flags & ~Opcodes.ACC_STATIC,
if (isStaticNestedClass) Opcodes.ACC_STATIC else 0
- ) & BCodeAsmCommon.INNER_CLASSES_FLAGS
+ ) & BCodeHelpers.INNER_CLASSES_FLAGS
)
})
@@ -917,7 +958,7 @@ abstract class BTypes {
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 == ObjectRef) 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 {
@@ -950,13 +991,13 @@ abstract class BTypes {
// exercised by test/files/run/t4761.scala
if (other.isSubtypeOf(this).orThrow) this
else if (this.isSubtypeOf(other).orThrow) other
- else ObjectReference
+ else ObjectRef
case (true, false) =>
- if (other.isSubtypeOf(this).orThrow) this else ObjectReference
+ if (other.isSubtypeOf(this).orThrow) this else ObjectRef
case (false, true) =>
- if (this.isSubtypeOf(other).orThrow) other else ObjectReference
+ if (this.isSubtypeOf(other).orThrow) other else ObjectRef
case _ =>
// TODO @lry I don't really understand the reasoning here.
@@ -1081,7 +1122,7 @@ abstract class BTypes {
*/
/**
- * Just a named pair, used in CoreBTypes.asmBoxTo/asmUnboxTo.
+ * Just a named pair, used in CoreBTypes.srBoxesRuntimeBoxToMethods/srBoxesRuntimeUnboxToMethods.
*/
final case class MethodNameAndType(name: String, methodType: MethodBType)
@@ -1103,24 +1144,14 @@ object BTypes {
/**
* 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.
+ * More information may be added in the future to enable more elaborate inline heuristics.
+ * Note that this class should contain information that can only be obtained from the ClassSymbol.
+ * Information that can be computed from the ClassNode should be added to the call graph instead.
*
* @param isEffectivelyFinal True if the class cannot have subclasses: final classes, module
- * classes, trait impl classes.
+ * classes.
+ *
+ * @param sam If this class is a SAM type, the SAM's "$name$descriptor".
*
* @param methodInfos The [[MethodInlineInfo]]s for the methods declared in this class.
* The map is indexed by the string s"$name$descriptor" (to
@@ -1130,29 +1161,28 @@ object BTypes {
* 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,
+ final case class InlineInfo(isEffectivelyFinal: Boolean,
+ sam: Option[String],
methodInfos: Map[String, MethodInlineInfo],
warning: Option[ClassInlineInfoWarning])
- val EmptyInlineInfo = InlineInfo(None, false, Map.empty, None)
+ val EmptyInlineInfo = InlineInfo(false, None, 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
+
+ // when inlining, local variable names of the callee are prefixed with the name of the callee method
+ val InlinedLocalVariablePrefixMaxLenght = 128
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
index 45d9cc3ff3..f7ee36c1ba 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
@@ -7,10 +7,12 @@ package scala.tools.nsc
package backend.jvm
import scala.tools.asm
+import scala.tools.nsc.backend.jvm.analysis.BackendUtils
import scala.tools.nsc.backend.jvm.opt._
-import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo, InternalName}
+import scala.tools.nsc.backend.jvm.BTypes._
import BackendReporting._
import scala.tools.nsc.settings.ScalaSettings
+import scala.reflect.internal.Flags.{DEFERRED, SYNTHESIZE_IMPL_IN_SUBCLASS}
/**
* This class mainly contains the method classBTypeFromSymbol, which extracts the necessary
@@ -27,21 +29,22 @@ import scala.tools.nsc.settings.ScalaSettings
class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
import global._
import definitions._
+ import genBCode._
- val bCodeICodeCommon: BCodeICodeCommon[global.type] = new BCodeICodeCommon(global)
- val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global)
- import bCodeAsmCommon._
+ val backendUtils: BackendUtils[this.type] = new BackendUtils(this)
// Why the proxy, see documentation of class [[CoreBTypes]].
val coreBTypes = new CoreBTypesProxy[this.type](this)
import coreBTypes._
- val byteCodeRepository = new ByteCodeRepository(global.classPath, javaDefinedClasses, recordPerRunCache(collection.concurrent.TrieMap.empty))
+ val byteCodeRepository: ByteCodeRepository[this.type] = new ByteCodeRepository(global.optimizerClassPath(global.classPath), this)
val localOpt: LocalOpt[this.type] = new LocalOpt(this)
val inliner: Inliner[this.type] = new Inliner(this)
+ val inlinerHeuristics: InlinerHeuristics[this.type] = new InlinerHeuristics(this)
+
val closureOptimizer: ClosureOptimizer[this.type] = new ClosureOptimizer(this)
val callGraph: CallGraph[this.type] = new CallGraph(this)
@@ -94,21 +97,24 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
* 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 = {
+ final def classBTypeFromSymbol(sym: Symbol): ClassBType = {
+ // For each java class, the scala compiler creates a class and a module (thus a module class).
+ // If the `sym` is a java module class, we use the java class instead. This ensures that the
+ // ClassBType is created from the main class (instead of the module class).
+ // The two symbols have the same name, so the resulting internalName is the same.
+ // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting)
+ val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym
+
assert(classSym != NoSymbol, "Cannot create ClassBType from NoSymbol")
assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym")
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
+ assert(!primitiveTypeToBType.contains(classSym) || isCompilingPrimitive, s"Cannot create ClassBType for primitive class symbol $classSym")
+
+ if (classSym == NothingClass) srNothingRef
+ else if (classSym == NullClass) srNullRef
else {
- val internalName = classSym.javaBinaryName.toString
+ val internalName = classSym.javaBinaryNameString
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.
@@ -128,17 +134,36 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
*/
final def methodBTypeFromSymbol(methodSymbol: Symbol): MethodBType = {
assert(methodSymbol.isMethod, s"not a method-symbol: $methodSymbol")
+ methodBTypeFromMethodType(methodSymbol.info, methodSymbol.isClassConstructor || methodSymbol.isConstructor)
+ }
+
+ /**
+ * Builds a [[MethodBType]] for a method type.
+ */
+ final def methodBTypeFromMethodType(tpe: Type, isConstructor: Boolean): MethodBType = {
val resultType: BType =
- if (methodSymbol.isClassConstructor || methodSymbol.isConstructor) UNIT
- else typeToBType(methodSymbol.tpe.resultType)
- MethodBType(methodSymbol.tpe.paramTypes map typeToBType, resultType)
+ if (isConstructor) UNIT
+ else typeToBType(tpe.resultType)
+ MethodBType(tpe.paramTypes map typeToBType, resultType)
+ }
+
+ def bootstrapMethodArg(t: Constant, pos: Position): AnyRef = t match {
+ case Constant(mt: Type) => methodBTypeFromMethodType(transformedType(mt), isConstructor = false).toASMType
+ case c @ Constant(sym: Symbol) => staticHandleFromSymbol(sym)
+ case c @ Constant(value: String) => value
+ case c @ Constant(value) if c.isNonUnitAnyVal => c.value.asInstanceOf[AnyRef]
+ case _ => reporter.error(pos, "Unable to convert static argument of ApplyDynamic into a classfile constant: " + t); null
+ }
+
+ def staticHandleFromSymbol(sym: Symbol): asm.Handle = {
+ val owner = if (sym.owner.isModuleClass) sym.owner.linkedClassOfClass else sym.owner
+ val descriptor = methodBTypeFromMethodType(sym.info, isConstructor = false).descriptor
+ val ownerBType = classBTypeFromSymbol(owner)
+ new asm.Handle(asm.Opcodes.H_INVOKESTATIC, ownerBType.internalName, sym.name.encoded, descriptor, /* itf = */ ownerBType.isInterface.get)
}
/**
* 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
@@ -149,17 +174,16 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
*/
def primitiveOrClassToBType(sym: Symbol): BType = {
assertClassNotArray(sym)
- assert(!sym.isImplClass, sym)
- primitiveTypeMap.getOrElse(sym, classBTypeFromSymbol(sym))
+ primitiveTypeToBType.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.
+ * signatures, e.g. `def apply(i: Int): T`. A TypeRef for T is replaced by ObjectRef.
*/
def nonClassTypeRefToBType(sym: Symbol): ClassBType = {
assert(sym.isType && isCompilingArray, sym)
- ObjectReference
+ ObjectRef
}
t.dealiasWiden match {
@@ -168,39 +192,24 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
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,
+ 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)
+ case ThisType(ArrayClass) => ObjectRef // 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)
+ case AnnotatedType(_, t) => typeToBType(t)
+ case ExistentialType(_, t) => typeToBType(t)
}
}
}
@@ -212,15 +221,109 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
def assertClassNotArrayNotPrimitive(sym: Symbol): Unit = {
assertClassNotArray(sym)
- assert(!primitiveTypeMap.contains(sym) || isCompilingPrimitive, sym)
+ assert(!primitiveTypeToBType.contains(sym) || isCompilingPrimitive, sym)
}
+ def implementedInterfaces(classSym: Symbol): List[Symbol] = {
+ // Additional interface parents based on annotations and other cues
+ def newParentForAnnotation(ann: AnnotationInfo): Option[Type] = ann.symbol match {
+ case RemoteAttr => Some(RemoteInterfaceClass.tpe)
+ case _ => None
+ }
+
+ // SI-9393: java annotations are interfaces, but the classfile / java source parsers make them look like classes.
+ def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait || sym.hasJavaAnnotationFlag
+
+ val classParents = {
+ val parents = classSym.info.parents
+ // SI-9393: the classfile / java source parsers add Annotation and ClassfileAnnotation to the
+ // parents of a java annotations. undo this for the backend (where we need classfile-level information).
+ if (classSym.hasJavaAnnotationFlag) parents.filterNot(c => c.typeSymbol == ClassfileAnnotationClass || c.typeSymbol == AnnotationClass)
+ else parents
+ }
+
+ val allParents = classParents ++ classSym.annotations.flatMap(newParentForAnnotation)
+
+ val minimizedParents = if (classSym.isJavaDefined) allParents else erasure.minimizeParents(allParents)
+ // We keep the superClass when computing minimizeParents to eliminate more interfaces.
+ // Example: T can be eliminated from D
+ // trait T
+ // class C extends T
+ // class D extends C with T
+ val interfaces = minimizedParents match {
+ case superClass :: ifs if !isInterfaceOrTrait(superClass.typeSymbol) =>
+ ifs
+ case ifs =>
+ // minimizeParents removes the superclass if it's redundant, for example:
+ // trait A
+ // class C extends Object with A // minimizeParents removes Object
+ ifs
+ }
+ interfaces.map(_.typeSymbol)
+ }
+
+ /**
+ * 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.
+ *
+ * Specialized classes are always considered top-level, see comment in BTypes.
+ */
+ private def memberClassesForInnerClassTable(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({
+ case sym if sym.isClass && !considerAsTopLevelImplementationArtifact(sym) =>
+ sym
+ case sym if sym.isModule && !considerAsTopLevelImplementationArtifact(sym) =>
+ val r = exitingPickler(sym.moduleClass)
+ assert(r != NoSymbol, sym.fullLocationString)
+ r
+ })(collection.breakOut)
+
private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = {
- // Check for isImplClass: trait implementation classes have NoSymbol as superClass
+ /**
+ * Reconstruct the classfile flags from a Java defined class symbol.
+ *
+ * The implementation of this method is slightly different from `javaFlags` in BTypesFromSymbols.
+ * The javaFlags method is primarily used to map Scala symbol flags to sensible classfile flags
+ * that are used in the generated classfiles. For example, all classes emitted by the Scala
+ * compiler have ACC_PUBLIC.
+ *
+ * When building a [[ClassBType]] from a Java class symbol, the flags in the type's `info` have
+ * to correspond exactly to the flags in the classfile. For example, if the class is package
+ * protected (i.e., it doesn't have the ACC_PUBLIC flag), this needs to be reflected in the
+ * ClassBType. For example, the inliner needs the correct flags for access checks.
+ *
+ * Class flags are listed here:
+ * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1-200-E.1
+ */
+ def javaClassfileFlags(classSym: Symbol): Int = {
+ assert(classSym.isJava, s"Expected Java class symbol, got ${classSym.fullName}")
+ import asm.Opcodes._
+ def enumFlags = ACC_ENUM | {
+ // Java enums have the `ACC_ABSTRACT` flag if they have a deferred method.
+ // We cannot trust `hasAbstractFlag`: the ClassfileParser adds `ABSTRACT` and `SEALED` to all
+ // Java enums for exhaustiveness checking.
+ val hasAbstractMethod = classSym.info.decls.exists(s => s.isMethod && s.isDeferred)
+ if (hasAbstractMethod) ACC_ABSTRACT else 0
+ }
+ GenBCode.mkFlags(
+ // SI-9393: the classfile / java source parser make java annotation symbols look like classes.
+ // here we recover the actual classfile flags.
+ if (classSym.hasJavaAnnotationFlag) ACC_ANNOTATION | ACC_INTERFACE | ACC_ABSTRACT else 0,
+ if (classSym.isPublic) ACC_PUBLIC else 0,
+ if (classSym.isFinal) ACC_FINAL else 0,
+ // see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces.
+ if (classSym.isInterface) ACC_INTERFACE else ACC_SUPER,
+ // for Java enums, we cannot trust `hasAbstractFlag` (see comment in enumFlags)
+ if (!classSym.hasJavaEnumFlag && classSym.hasAbstractFlag) ACC_ABSTRACT else 0,
+ if (classSym.isArtifact) ACC_SYNTHETIC else 0,
+ if (classSym.hasJavaEnumFlag) enumFlags else 0
+ )
+ }
+
// Check for hasAnnotationFlag for SI-9393: the classfile / java source parsers add
// scala.annotation.Annotation as superclass to java annotations. In reality, java
// annotation classfiles have superclass Object (like any interface classfile).
- val superClassSym = if (classSym.isImplClass || classSym.hasJavaAnnotationFlag) ObjectClass else {
+ val superClassSym = if (classSym.hasJavaAnnotationFlag) ObjectClass else {
val sc = classSym.superClass
// SI-9393: Java annotation classes don't have the ABSTRACT/INTERFACE flag, so they appear
// (wrongly) as superclasses. Fix this for BTypes: the java annotation will appear as interface
@@ -235,7 +338,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
superClassSym == ObjectClass
else
// A ClassBType for a primitive class (scala.Boolean et al) is only created when compiling these classes.
- ((superClassSym != NoSymbol) && !superClassSym.isInterface) || (isCompilingPrimitive && primitiveTypeMap.contains(classSym)),
+ ((superClassSym != NoSymbol) && !superClassSym.isInterface) || (isCompilingPrimitive && primitiveTypeToBType.contains(classSym)),
s"Bad superClass for $classSym: $superClassSym"
)
val superClass = if (superClassSym == NoSymbol) None
@@ -251,13 +354,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
/* The InnerClass table of a class C must contain all nested classes of C, even if they are only
* declared but not otherwise referenced in C (from the bytecode or a method / field signature).
* We collect them here.
- *
- * Nested classes that are also referenced in C will be added to the innerClassBufferASM during
- * code generation, but those duplicates will be eliminated when emitting the InnerClass
- * attribute.
- *
- * Why do we need to collect classes into innerClassBufferASM at all? To collect references to
- * 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
@@ -286,8 +382,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
}
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:
+ // 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
@@ -298,11 +394,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
// (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
@@ -360,7 +451,7 @@ 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
- // impl classes are considered top-level, see comment in BTypes
+ // specialized classes are considered top-level, see comment in BTypes
if (isTopLevel || considerAsTopLevelImplementationArtifact(innerClassSym)) None
else if (innerClassSym.rawowner.isTerm) {
// This case should never be reached: the lambdalift phase mutates the rawowner field of all
@@ -428,13 +519,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
* classfile attribute.
*/
private def buildInlineInfo(classSym: Symbol, internalName: InternalName): InlineInfo = {
- def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym, classBTypeFromSymbol(_).internalName, methodBTypeFromSymbol(_).descriptor)
+ def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym)
// 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 (!compilerSettings.YoptInlinerEnabled) BTypes.EmptyInlineInfo // For other classes, we need the InlineInfo only inf the inliner is enabled.
+ else if (!compilerSettings.optInlinerEnabled) 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
@@ -444,30 +535,123 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
case Right(classNode) =>
inlineInfoFromClassfile(classNode)
case Left(missingClass) =>
- InlineInfo(None, false, Map.empty, Some(ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass)))
+ EmptyInlineInfo.copy(warning = Some(ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass)))
+ }
+ }
+ }
+
+ /**
+ * Build the [[InlineInfo]] for a class symbol.
+ */
+ def buildInlineInfoFromClassSymbol(classSym: Symbol): InlineInfo = {
+ val isEffectivelyFinal = classSym.isEffectivelyFinal
+
+ val sam = {
+ if (classSym.isEffectivelyFinal) None
+ else {
+ // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an
+ // empty parameter list in uncurry and would therefore be picked as SAM.
+ // Similarly, the fields phases adds abstract trait setters, which should not be considered
+ // abstract for SAMs (they do disqualify the SAM from LMF treatment,
+ // but an anonymous subclasss can be spun up by scalac after making just the single abstract method concrete)
+ val samSym = exitingPickler(definitions.samOf(classSym.tpe))
+ if (samSym == NoSymbol) None
+ else Some(samSym.javaSimpleName.toString + methodBTypeFromSymbol(samSym).descriptor)
}
}
+
+ var warning = Option.empty[ClassSymbolInfoFailureSI9111]
+
+ def keepMember(sym: Symbol) = sym.isMethod && !scalaPrimitives.isPrimitive(sym)
+ val classMethods = classSym.info.decls.iterator.filter(keepMember)
+ val methods = if (!classSym.isJavaDefined) classMethods else {
+ val staticMethods = classSym.companionModule.info.decls.iterator.filter(m => !m.isConstructor && keepMember(m))
+ staticMethods ++ classMethods
+ }
+
+ // 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 = methods.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))
+ Nil
+ } else {
+ val name = methodSym.javaSimpleName.toString // same as in genDefDef
+ val signature = name + methodBTypeFromSymbol(methodSym).descriptor
+
+ // In `trait T { object O }`, `oSym.isEffectivelyFinalOrNotOverridden` is true, but the
+ // method is abstract in bytecode, `defDef.rhs.isEmpty`. Abstract methods are excluded
+ // so they are not marked final in the InlineInfo attribute.
+ //
+ // However, due to https://github.com/scala/scala-dev/issues/126, this currently does not
+ // work, the abstract accessor for O will be marked effectivelyFinal.
+ val effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden && !(methodSym hasFlag DEFERRED | SYNTHESIZE_IMPL_IN_SUBCLASS)
+
+ val info = MethodInlineInfo(
+ effectivelyFinal = effectivelyFinal,
+ annotatedInline = methodSym.hasAnnotation(ScalaInlineClass),
+ annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass))
+
+ if (needsStaticImplMethod(methodSym)) {
+ val staticName = traitSuperAccessorName(methodSym).toString
+ val selfParam = methodSym.newSyntheticValueParam(methodSym.owner.typeConstructor, nme.SELF)
+ val staticMethodType = methodSym.info match {
+ case mt @ MethodType(params, res) => copyMethodType(mt, selfParam :: params, res)
+ }
+ val staticMethodSignature = staticName + methodBTypeFromMethodType(staticMethodType, isConstructor = false)
+ val staticMethodInfo = MethodInlineInfo(
+ effectivelyFinal = true,
+ annotatedInline = info.annotatedInline,
+ annotatedNoInline = info.annotatedNoInline)
+ if (methodSym.isMixinConstructor)
+ List((staticMethodSignature, staticMethodInfo))
+ else
+ List((signature, info), (staticMethodSignature, staticMethodInfo))
+ } else
+ List((signature, info))
+ }
+ }).toMap
+
+ InlineInfo(isEffectivelyFinal, sam, methodInlineInfos, warning)
}
/**
- * For top-level objects without a companion class, the compilere generates a mirror class with
+ * For top-level objects without a companion class, the compiler 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).
*/
def mirrorClassClassBType(moduleClassSym: Symbol): ClassBType = {
assert(isTopLevelModuleClass(moduleClassSym), s"not a top-level module class: $moduleClassSym")
- val internalName = moduleClassSym.javaBinaryName.dropModule.toString
+ val internalName = moduleClassSym.javaBinaryNameString.stripSuffix(nme.MODULE_SUFFIX_STRING)
classBTypeFromInternalName.getOrElse(internalName, {
val c = ClassBType(internalName)
// class info consistent with BCodeHelpers.genMirrorClass
val nested = exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol
c.info = Right(ClassInfo(
- superClass = Some(ObjectReference),
+ superClass = Some(ObjectRef),
interfaces = Nil,
flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL,
nestedClasses = nested,
nestedInfo = None,
- InlineInfo(None, true, Map.empty, None))) // no InlineInfo needed, scala never invokes methods on the mirror class
+ inlineInfo = EmptyInlineInfo.copy(isEffectivelyFinal = true))) // no method inline infos needed, scala never invokes methods on the mirror class
+ c
+ })
+ }
+
+ def beanInfoClassClassBType(mainClass: Symbol): ClassBType = {
+ val internalName = mainClass.javaBinaryNameString + "BeanInfo"
+ classBTypeFromInternalName.getOrElse(internalName, {
+ val c = ClassBType(internalName)
+ c.info = Right(ClassInfo(
+ superClass = Some(sbScalaBeanInfoRef),
+ interfaces = Nil,
+ flags = javaFlags(mainClass),
+ nestedClasses = Nil,
+ nestedInfo = None,
+ inlineInfo = EmptyInlineInfo))
c
})
}
@@ -478,26 +662,16 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
*/
final def isTopLevelModuleClass(sym: Symbol): Boolean = exitingPickler {
// phase travel to pickler required for isNestedClass (looks at owner)
- val r = sym.isModuleClass && !sym.isNestedClass
- // The mixin phase adds the `lateMODULE` flag to trait implementation classes. Since the flag
- // is late, it should not be visible here inside the time travel. We check this.
- if (r) assert(!sym.isImplClass, s"isModuleClass should be false for impl class $sym")
- r
+ sym.isModuleClass && !sym.isNestedClass
}
/**
* True for module classes of modules that are top-level or owned only by objects. Module classes
- * for such objects will get a MODULE$ flag and a corresponding static initializer.
+ * for such objects will get a MODULE$ field and a corresponding static initializer.
*/
final def isStaticModuleClass(sym: Symbol): Boolean = {
- /* (1) Phase travel to to pickler is required to exclude implementation classes; they have the
- * lateMODULEs after mixin, so isModuleClass would be true.
- * (2) isStaticModuleClass is a source-level property. See comment on isOriginallyStaticOwner.
- */
- exitingPickler { // (1)
- sym.isModuleClass &&
- isOriginallyStaticOwner(sym.originalOwner) // (2)
- }
+ sym.isModuleClass &&
+ isOriginallyStaticOwner(sym.originalOwner) // isStaticModuleClass is a source-level property, see comment on isOriginallyStaticOwner
}
// legacy, to be removed when the @remote annotation gets removed
@@ -550,34 +724,28 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
// scala compiler. The word final is heavily overloaded unfortunately;
// for us it means "not overridable". At present you can't override
// vars regardless; this may change.
- //
- // The logic does not check .isFinal (which checks flags for the FINAL flag,
- // and includes symbols marked lateFINAL) instead inspecting rawflags so
- // we can exclude lateFINAL. Such symbols are eligible for inlining, but to
- // avoid breaking proxy software which depends on subclassing, we do not
- // emit ACC_FINAL.
val finalFlag = (
- (((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModuleClass(sym))
- && !sym.enclClass.isInterface
+ (sym.isFinal || isTopLevelModuleClass(sym))
+ && !sym.enclClass.isTrait
&& !sym.isClassConstructor
- && !sym.isMutable // lazy vals and vars both
+ && (!sym.isMutable || nme.isTraitSetterName(sym.name)) // lazy vals and vars and their setters cannot be final, but trait setters are
)
// Primitives are "abstract final" to prohibit instantiation
// without having to provide any implementations, but that is an
// illegal combination of modifiers at the bytecode level so
- // suppress final if abstract if present.
+ // suppress final if abstract is present.
import asm.Opcodes._
GenBCode.mkFlags(
if (privateFlag) ACC_PRIVATE else ACC_PUBLIC,
- if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0,
- if (sym.isInterface) ACC_INTERFACE else 0,
+ if ((sym.isDeferred && !sym.hasFlag(symtab.Flags.JAVA_DEFAULTMETHOD))|| sym.hasAbstractFlag) ACC_ABSTRACT else 0,
+ if (sym.isTraitOrInterface) ACC_INTERFACE else 0,
if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0,
if (sym.isStaticMember) ACC_STATIC else 0,
if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0,
if (sym.isArtifact) ACC_SYNTHETIC else 0,
- if (sym.isClass && !sym.isInterface) ACC_SUPER else 0,
+ if (sym.isClass && !sym.isTraitOrInterface) ACC_SUPER else 0,
if (sym.hasJavaEnumFlag) ACC_ENUM else 0,
if (sym.isVarargsMethod) ACC_VARARGS else 0,
if (sym.hasFlag(symtab.Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0,
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
index b41d0de92f..e6ae073a2a 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
@@ -1,7 +1,7 @@
package scala.tools.nsc
package backend.jvm
-import scala.tools.asm.tree.{InvokeDynamicInsnNode, AbstractInsnNode, MethodNode}
+import scala.tools.asm.tree.{AbstractInsnNode, MethodNode}
import scala.tools.nsc.backend.jvm.BTypes.InternalName
import scala.reflect.internal.util.Position
import scala.tools.nsc.settings.ScalaSettings
@@ -26,9 +26,7 @@ final class BackendReportingImpl(val global: Global) extends BackendReporting {
/**
* 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).
+ * Defines some utility methods to make error reporting with Either easier.
*/
object BackendReporting {
def methodSignature(classInternalName: InternalName, name: String, desc: String) = {
@@ -42,19 +40,12 @@ object BackendReporting {
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 {
+ def withFilter(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.
- */
+ /** Get the value, fail with an assertion if this is an error. */
def get: B = {
assert(v.isRight, v.left.get)
v.right.get
@@ -86,8 +77,8 @@ object BackendReporting {
def emitWarning(settings: ScalaSettings): 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
+ // Method withFilter in RightBiasedEither requires an implicit empty value. Taking the value here
+ // in scope allows for-comprehensions that desugar into withFilter calls (for example when using a
// tuple de-constructor).
implicit object emptyOptimizerWarning extends OptimizerWarning {
def emitWarning(settings: ScalaSettings): Boolean = false
@@ -101,11 +92,14 @@ object BackendReporting {
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 MethodNotFound(name, descriptor, ownerInternalName, missingClass) =>
+ val missingClassWarning = missingClass match {
+ case None => ""
+ case Some(c) =>
+ if (c.definedInJavaSource) s"\nNote that class ${c.internalName} is defined in a Java source (mixed compilation), no bytecode is available."
+ else s"\nNote that class ${c.internalName} could not be found on the classpath."
+ }
+ s"The method $name$descriptor could not be found in the class $ownerInternalName or any of its parents." + missingClassWarning
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." +
@@ -114,20 +108,20 @@ object BackendReporting {
def emitWarning(settings: ScalaSettings): Boolean = this match {
case ClassNotFound(_, javaDefined) =>
- if (javaDefined) settings.YoptWarningNoInlineMixed
- else settings.YoptWarningNoInlineMissingBytecode
+ if (javaDefined) settings.optWarningNoInlineMixed
+ else settings.optWarningNoInlineMissingBytecode
case m @ MethodNotFound(_, _, _, missing) =>
if (m.isArrayMethod) false
- else settings.YoptWarningNoInlineMissingBytecode || missing.exists(_.emitWarning(settings))
+ else settings.optWarningNoInlineMissingBytecode || missing.exists(_.emitWarning(settings))
case FieldNotFound(_, _, _, missing) =>
- settings.YoptWarningNoInlineMissingBytecode || missing.exists(_.emitWarning(settings))
+ settings.optWarningNoInlineMissingBytecode || 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 {
+ case class MethodNotFound(name: String, descriptor: String, ownerInternalNameOrArrayDescriptor: InternalName, missingClass: Option[ClassNotFound]) extends MissingBytecodeWarning {
def isArrayMethod = ownerInternalNameOrArrayDescriptor.charAt(0) == '['
}
case class FieldNotFound(name: String, descriptor: String, ownerInternalName: InternalName, missingClass: Option[ClassNotFound]) extends MissingBytecodeWarning
@@ -143,7 +137,7 @@ object BackendReporting {
def emitWarning(settings: ScalaSettings): Boolean = this match {
case NoClassBTypeInfoMissingBytecode(cause) => cause.emitWarning(settings)
- case NoClassBTypeInfoClassSymbolInfoFailedSI9111(_) => settings.YoptWarningNoInlineMissingBytecode
+ case NoClassBTypeInfoClassSymbolInfoFailedSI9111(_) => settings.optWarningNoInlineMissingBytecode
}
}
@@ -170,85 +164,89 @@ object BackendReporting {
case MethodInlineInfoError(_, _, _, cause) =>
s"Error while computing the inline information for method $warningMessageSignature:\n" + cause
-
- case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) =>
- cause.toString
}
def emitWarning(settings: ScalaSettings): Boolean = this match {
case MethodInlineInfoIncomplete(_, _, _, cause) => cause.emitWarning(settings)
case MethodInlineInfoMissing(_, _, _, Some(cause)) => cause.emitWarning(settings)
- case MethodInlineInfoMissing(_, _, _, None) => settings.YoptWarningNoInlineMissingBytecode
+ case MethodInlineInfoMissing(_, _, _, None) => settings.optWarningNoInlineMissingBytecode
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)
+ /** Either the callee or the callsite is annotated @inline */
+ def annotatedInline: Boolean
- 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 calleeMethodSig = BackendReporting.methodSignature(calleeDeclarationClass, name, descriptor)
- case StrictfpMismatch(_, _, _, callsiteClass, callsiteName, callsiteDesc) =>
- s"""The callsite method ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)}
- |does not have the same strictfp mode as the callee $calleeMethodSig.
+ override def toString = {
+ val annotWarn = if (annotatedInline) " is annotated @inline but" else ""
+ val warning = s"$calleeMethodSig$annotWarn could not be inlined:\n"
+ val reason = this match {
+ case CalleeNotFinal(_, _, _, _) =>
+ s"The method is not final and may be overridden."
+ 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."
+
+ case StrictfpMismatch(_, _, _, _, callsiteClass, callsiteName, callsiteDesc) =>
+ s"""The callsite method ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)}
+ |does not have the same strictfp mode as the callee $calleeMethodSig.
""".stripMargin
- case ResultingMethodTooLarge(_, _, _, callsiteClass, callsiteName, callsiteDesc) =>
- s"""The size of the callsite method ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)}
- |would exceed the JVM method size limit after inlining $calleeMethodSig.
+ case ResultingMethodTooLarge(_, _, _, _, callsiteClass, callsiteName, callsiteDesc) =>
+ s"""The size of the callsite method ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)}
+ |would exceed the JVM method size limit after inlining $calleeMethodSig.
""".stripMargin
+ }
+ warning + reason
}
- def emitWarning(settings: ScalaSettings): Boolean = this match {
- case _: IllegalAccessInstruction | _: MethodWithHandlerCalledOnNonEmptyStack | _: SynchronizedMethod | _: StrictfpMismatch | _: ResultingMethodTooLarge =>
- settings.YoptWarningEmitAtInlineFailed
-
- case IllegalAccessCheckFailed(_, _, _, _, _, cause) =>
- cause.emitWarning(settings)
+ def emitWarning(settings: ScalaSettings): Boolean = {
+ settings.optWarnings.contains(settings.optWarningsChoices.anyInlineFailed) ||
+ annotatedInline && settings.optWarningEmitAtInlineFailed
}
}
- case class IllegalAccessInstruction(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ case class CalleeNotFinal(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean) extends CannotInlineWarning
+ case class IllegalAccessInstruction(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean,
callsiteClass: InternalName, instruction: AbstractInsnNode) extends CannotInlineWarning
- case class IllegalAccessCheckFailed(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ case class IllegalAccessCheckFailed(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean,
callsiteClass: InternalName, instruction: AbstractInsnNode, cause: OptimizerWarning) extends CannotInlineWarning
- case class MethodWithHandlerCalledOnNonEmptyStack(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ case class MethodWithHandlerCalledOnNonEmptyStack(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean,
callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning
- case class SynchronizedMethod(calleeDeclarationClass: InternalName, name: String, descriptor: String) extends CannotInlineWarning
- case class StrictfpMismatch(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ case class SynchronizedMethod(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean) extends CannotInlineWarning
+ case class StrictfpMismatch(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean,
callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning
- case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean,
callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning
+ // TODO: this should be a subtype of CannotInlineWarning
+ // but at the place where it's created (in findIllegalAccess) we don't have the necessary data (calleeName, calleeDescriptor).
case object UnknownInvokeDynamicInstruction extends OptimizerWarning {
override def toString = "The callee contains an InvokeDynamic instruction with an unknown bootstrap method (not a LambdaMetaFactory)."
- def emitWarning(settings: ScalaSettings): Boolean = settings.YoptWarningEmitAtInlineFailed
+ def emitWarning(settings: ScalaSettings): Boolean = settings.optWarnings.contains(settings.optWarningsChoices.anyInlineFailed)
}
/**
@@ -260,7 +258,7 @@ object BackendReporting {
override def emitWarning(settings: ScalaSettings): Boolean = this match {
case RewriteClosureAccessCheckFailed(_, cause) => cause.emitWarning(settings)
- case RewriteClosureIllegalAccess(_, _) => settings.YoptWarningEmitAtInlineFailed
+ case RewriteClosureIllegalAccess(_, _) => settings.optWarnings.contains(settings.optWarningsChoices.anyInlineFailed)
}
override def toString: String = this match {
@@ -285,17 +283,17 @@ object BackendReporting {
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."
+ 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: ScalaSettings): Boolean = this match {
- case NoInlineInfoAttribute(_) => settings.YoptWarningNoInlineMissingScalaInlineInfoAttr
+ case NoInlineInfoAttribute(_) => settings.optWarningNoInlineMissingScalaInlineInfoAttr
case ClassNotFoundWhenBuildingInlineInfoFromSymbol(cause) => cause.emitWarning(settings)
- case ClassSymbolInfoFailureSI9111(_) => settings.YoptWarningNoInlineMissingBytecode
- case UnknownScalaInlineInfoVersion(_, _) => settings.YoptWarningNoInlineMissingScalaInlineInfoAttr
+ case ClassSymbolInfoFailureSI9111(_) => settings.optWarningNoInlineMissingBytecode
+ case UnknownScalaInlineInfoVersion(_, _) => settings.optWarningNoInlineMissingScalaInlineInfoAttr
}
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala
index 03306f30aa..8d0547b607 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala
@@ -8,6 +8,7 @@ package backend.jvm
import scala.reflect.internal.util.Statistics
+// Enable with `-Ystatistics:jvm`
object BackendStats {
import Statistics.{newTimer, newSubTimer}
val bcodeTimer = newTimer("time in backend", "jvm")
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala
index 1d29fdee10..2cf5cfcb8d 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala
@@ -6,7 +6,7 @@
package scala.tools.nsc
package backend.jvm
-import java.io.{ DataOutputStream, FileOutputStream, IOException, OutputStream, File => JFile }
+import java.io.{ DataOutputStream, FileOutputStream, IOException, File => JFile }
import scala.tools.nsc.io._
import java.util.jar.Attributes.Name
import scala.language.postfixOps
@@ -78,7 +78,7 @@ trait BytecodeWriters {
}
/*
- * The ASM textual representation for bytecode overcomes disadvantages of javap ouput in three areas:
+ * The ASM textual representation for bytecode overcomes disadvantages of javap output in three areas:
* (a) pickle dingbats undecipherable to the naked eye;
* (b) two constant pools, while having identical contents, are displayed differently due to physical layout.
* (c) stack maps (classfile version 50 and up) are displayed in encoded form by javap,
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala
index 00ca096e59..acb950929f 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala
@@ -1,7 +1,8 @@
package scala.tools.nsc
package backend.jvm
-import scala.annotation.switch
+import scala.tools.asm
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
/**
* Core BTypes and some other definitions. The initialization of these definitions requires access
@@ -9,7 +10,7 @@ import scala.annotation.switch
*
* The symbols used to initialize the ClassBTypes may change from one compiler run to the next. To
* make sure the definitions are consistent with the symbols in the current run, the
- * `intializeCoreBTypes` method in BTypesFromSymbols creates a new instance of CoreBTypes in each
+ * `initializeCoreBTypes` method in BTypesFromSymbols creates a new instance of CoreBTypes in each
* compiler run.
*
* The class BTypesFromSymbols does not directly reference CoreBTypes, but CoreBTypesProxy. The
@@ -29,14 +30,14 @@ import scala.annotation.switch
class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
import bTypes._
import global._
- import rootMirror.{requiredClass, getClassIfDefined}
+ import rootMirror.{requiredClass, getRequiredClass, getClassIfDefined}
import definitions._
/**
* Maps primitive types to their corresponding PrimitiveBType. The map is defined lexically above
* the first use of `classBTypeFromSymbol` because that method looks at the map.
*/
- lazy val primitiveTypeMap: Map[Symbol, PrimitiveBType] = Map(
+ lazy val primitiveTypeToBType: Map[Symbol, PrimitiveBType] = Map(
UnitClass -> UNIT,
BooleanClass -> BOOL,
CharClass -> CHAR,
@@ -45,34 +46,22 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
IntClass -> INT,
LongClass -> LONG,
FloatClass -> FLOAT,
- DoubleClass -> DOUBLE
- )
-
- lazy val BOXED_UNIT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Void])
- lazy val BOXED_BOOLEAN : ClassBType = classBTypeFromSymbol(BoxedBooleanClass)
- lazy val BOXED_BYTE : ClassBType = classBTypeFromSymbol(BoxedByteClass)
- lazy val BOXED_SHORT : ClassBType = classBTypeFromSymbol(BoxedShortClass)
- lazy val BOXED_CHAR : ClassBType = classBTypeFromSymbol(BoxedCharacterClass)
- lazy val BOXED_INT : ClassBType = classBTypeFromSymbol(BoxedIntClass)
- lazy val BOXED_LONG : ClassBType = classBTypeFromSymbol(BoxedLongClass)
- lazy val BOXED_FLOAT : ClassBType = classBTypeFromSymbol(BoxedFloatClass)
- lazy val BOXED_DOUBLE : ClassBType = classBTypeFromSymbol(BoxedDoubleClass)
+ DoubleClass -> DOUBLE)
/**
* Map from primitive types to their boxed class type. Useful when pushing class literals onto the
* operand stack (ldc instruction taking a class literal), see genConstant.
*/
lazy val boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = Map(
- UNIT -> BOXED_UNIT,
- BOOL -> BOXED_BOOLEAN,
- BYTE -> BOXED_BYTE,
- SHORT -> BOXED_SHORT,
- CHAR -> BOXED_CHAR,
- INT -> BOXED_INT,
- LONG -> BOXED_LONG,
- FLOAT -> BOXED_FLOAT,
- DOUBLE -> BOXED_DOUBLE
- )
+ UNIT -> classBTypeFromSymbol(requiredClass[java.lang.Void]),
+ BOOL -> classBTypeFromSymbol(BoxedBooleanClass),
+ BYTE -> classBTypeFromSymbol(BoxedByteClass),
+ SHORT -> classBTypeFromSymbol(BoxedShortClass),
+ CHAR -> classBTypeFromSymbol(BoxedCharacterClass),
+ INT -> classBTypeFromSymbol(BoxedIntClass),
+ LONG -> classBTypeFromSymbol(BoxedLongClass),
+ FLOAT -> classBTypeFromSymbol(BoxedFloatClass),
+ DOUBLE -> classBTypeFromSymbol(BoxedDoubleClass))
lazy val boxedClasses: Set[ClassBType] = boxedClassOfPrimitive.values.toSet
@@ -82,7 +71,7 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
*/
lazy val boxResultType: Map[Symbol, ClassBType] = {
for ((valueClassSym, boxMethodSym) <- currentRun.runDefinitions.boxMethod)
- yield boxMethodSym -> boxedClassOfPrimitive(primitiveTypeMap(valueClassSym))
+ yield boxMethodSym -> boxedClassOfPrimitive(primitiveTypeToBType(valueClassSym))
}
/**
@@ -90,96 +79,148 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
* For example, the method symbol for `Byte.unbox()`) is mapped to the PrimitiveBType BYTE. */
lazy val unboxResultType: Map[Symbol, PrimitiveBType] = {
for ((valueClassSym, unboxMethodSym) <- currentRun.runDefinitions.unboxMethod)
- yield unboxMethodSym -> primitiveTypeMap(valueClassSym)
+ yield unboxMethodSym -> primitiveTypeToBType(valueClassSym)
}
/*
* RT_NOTHING and RT_NULL exist at run-time only. They are the bytecode-level manifestation (in
- * method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs.
+ * method signatures only) of what shows up as NothingClass (scala.Nothing) resp. NullClass
+ * (scala.Null) in Scala ASTs.
*
* Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal
* names of NothingClass and NullClass can't be emitted as-is.
*/
- 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)
-
- lazy val StringReference : ClassBType = classBTypeFromSymbol(StringClass)
- lazy val StringBuilderReference : ClassBType = classBTypeFromSymbol(StringBuilderClass)
- lazy val ThrowableReference : ClassBType = classBTypeFromSymbol(ThrowableClass)
- lazy val jlCloneableReference : ClassBType = classBTypeFromSymbol(JavaCloneableClass) // java/lang/Cloneable
- lazy val jlNPEReference : ClassBType = classBTypeFromSymbol(NullPointerExceptionClass) // java/lang/NullPointerException
- lazy val jioSerializableReference : ClassBType = classBTypeFromSymbol(JavaSerializableClass) // java/io/Serializable
- lazy val scalaSerializableReference : ClassBType = classBTypeFromSymbol(SerializableClass) // scala/Serializable
- lazy val classCastExceptionReference : ClassBType = classBTypeFromSymbol(ClassCastExceptionClass) // java/lang/ClassCastException
- lazy val javaUtilMapReference : ClassBType = classBTypeFromSymbol(JavaUtilMap) // java/util/Map
- lazy val javaUtilHashMapReference : ClassBType = classBTypeFromSymbol(JavaUtilHashMap) // java/util/HashMap
-
- lazy val srBooleanRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BooleanRef])
- lazy val srByteRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.ByteRef])
- lazy val srCharRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.CharRef])
- lazy val srIntRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.IntRef])
- lazy val srLongRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.LongRef])
- lazy val srFloatRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.FloatRef])
- lazy val srDoubleRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.DoubleRef])
-
- lazy val hashMethodSym: Symbol = getMember(ScalaRunTimeModule, nme.hash_)
+ lazy val srNothingRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Nothing$])
+ lazy val srNullRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Null$])
+
+ lazy val ObjectRef : ClassBType = classBTypeFromSymbol(ObjectClass)
+ lazy val StringRef : ClassBType = classBTypeFromSymbol(StringClass)
+ lazy val PredefRef : ClassBType = classBTypeFromSymbol(PredefModule.moduleClass)
+ lazy val jlStringBuilderRef : ClassBType = classBTypeFromSymbol(JavaStringBuilderClass)
+ lazy val jlStringBufferRef : ClassBType = classBTypeFromSymbol(JavaStringBufferClass)
+ lazy val jlCharSequenceRef : ClassBType = classBTypeFromSymbol(JavaCharSequenceClass)
+ lazy val jlThrowableRef : ClassBType = classBTypeFromSymbol(ThrowableClass)
+ lazy val jlCloneableRef : ClassBType = classBTypeFromSymbol(JavaCloneableClass) // java/lang/Cloneable
+ lazy val jiSerializableRef : ClassBType = classBTypeFromSymbol(JavaSerializableClass) // java/io/Serializable
+ lazy val jlClassCastExceptionRef : ClassBType = classBTypeFromSymbol(ClassCastExceptionClass) // java/lang/ClassCastException
+ lazy val juMapRef : ClassBType = classBTypeFromSymbol(JavaUtilMap) // java/util/Map
+ lazy val juHashMapRef : ClassBType = classBTypeFromSymbol(JavaUtilHashMap) // java/util/HashMap
+ lazy val sbScalaBeanInfoRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.beans.ScalaBeanInfo])
+ lazy val jliSerializedLambdaRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.SerializedLambda])
+ lazy val jliMethodHandleRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodHandle])
+ lazy val jliMethodHandlesRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodHandles])
+ lazy val jliMethodHandlesLookupRef : ClassBType = classBTypeFromSymbol(exitingPickler(getRequiredClass("java.lang.invoke.MethodHandles.Lookup"))) // didn't find a reliable non-stringly-typed way that works for inner classes in the backend
+ lazy val jliMethodTypeRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodType])
+ lazy val jliCallSiteRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.CallSite])
+ lazy val jliLambdaMetafactoryRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.LambdaMetafactory])
+ lazy val srBoxesRunTimeRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime])
+ lazy val srSymbolLiteral : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.SymbolLiteral])
+ lazy val srStructuralCallSite : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.StructuralCallSite])
+ lazy val srLambdaDeserialize : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.LambdaDeserialize])
+ lazy val srBoxedUnitRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxedUnit])
+
+ private def methodNameAndType(cls: Symbol, name: Name, static: Boolean = false, filterOverload: Symbol => Boolean = _ => true): MethodNameAndType = {
+ val holder = if (static) cls.companionModule.moduleClass else cls
+ val method = holder.info.member(name).suchThat(filterOverload)
+ assert(!method.isOverloaded, method)
+ MethodNameAndType(name.toString, methodBTypeFromSymbol(method))
+ }
- // TODO @lry avoiding going through through missingHook for every line in the REPL: https://github.com/scala/scala/commit/8d962ed4ddd310cc784121c426a2e3f56a112540
- lazy val AndroidParcelableInterface : Symbol = getClassIfDefined("android.os.Parcelable")
- lazy val AndroidCreatorClass : Symbol = getClassIfDefined("android.os.Parcelable$Creator")
+ private def srBoxesRuntimeMethods(getName: (String, String) => String): Map[BType, MethodNameAndType] = {
+ ScalaValueClassesNoUnit.map(primitive => {
+ val bType = primitiveTypeToBType(primitive)
+ val name = newTermName(getName(primitive.name.toString, boxedClass(primitive).name.toString))
+ (bType, methodNameAndType(BoxesRunTimeClass, name))
+ })(collection.breakOut)
+ }
- lazy val BeanInfoAttr: Symbol = requiredClass[scala.beans.BeanInfo]
+ // Z -> MethodNameAndType(boxToBoolean,(Z)Ljava/lang/Boolean;)
+ lazy val srBoxesRuntimeBoxToMethods: Map[BType, MethodNameAndType] = srBoxesRuntimeMethods((primitive, boxed) => "boxTo" + boxed)
- /* The Object => String overload. */
- lazy val String_valueOf: Symbol = {
- getMember(StringModule, nme.valueOf) filter (sym => sym.info.paramTypes match {
- case List(pt) => pt.typeSymbol == ObjectClass
- case _ => false
- })
+ // Z -> MethodNameAndType(unboxToBoolean,(Ljava/lang/Object;)Z)
+ lazy val srBoxesRuntimeUnboxToMethods: Map[BType, MethodNameAndType] = srBoxesRuntimeMethods((primitive, boxed) => "unboxTo" + primitive)
+
+ def singleParamOfClass(cls: Symbol) = (s: Symbol) => s.paramss match {
+ case List(List(param)) => param.info.typeSymbol == cls
+ case _ => false
}
- // scala.FunctionX and scala.runtim.AbstractFunctionX
- lazy val FunctionReference : Vector[ClassBType] = (0 to MaxFunctionArity).map(i => classBTypeFromSymbol(FunctionClass(i)))(collection.breakOut)
- lazy val AbstractFunctionReference : Vector[ClassBType] = (0 to MaxFunctionArity).map(i => classBTypeFromSymbol(AbstractFunctionClass(i)))(collection.breakOut)
- lazy val AbstractFunctionArityMap : Map[ClassBType, Int] = AbstractFunctionReference.zipWithIndex.toMap
+ // java/lang/Boolean -> MethodNameAndType(valueOf,(Z)Ljava/lang/Boolean;)
+ lazy val javaBoxMethods: Map[InternalName, MethodNameAndType] = {
+ ScalaValueClassesNoUnit.map(primitive => {
+ val boxed = boxedClass(primitive)
+ val method = methodNameAndType(boxed, newTermName("valueOf"), static = true, filterOverload = singleParamOfClass(primitive))
+ (classBTypeFromSymbol(boxed).internalName, method)
+ })(collection.breakOut)
+ }
- lazy val PartialFunctionReference : ClassBType = classBTypeFromSymbol(PartialFunctionClass)
- lazy val AbstractPartialFunctionReference : ClassBType = classBTypeFromSymbol(AbstractPartialFunctionClass)
+ // java/lang/Boolean -> MethodNameAndType(booleanValue,()Z)
+ lazy val javaUnboxMethods: Map[InternalName, MethodNameAndType] = {
+ ScalaValueClassesNoUnit.map(primitive => {
+ val boxed = boxedClass(primitive)
+ val name = primitive.name.toString.toLowerCase + "Value"
+ (classBTypeFromSymbol(boxed).internalName, methodNameAndType(boxed, newTermName(name)))
+ })(collection.breakOut)
+ }
- lazy val BoxesRunTime: ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime])
+ private def predefBoxingMethods(getName: (String, String) => String): Map[String, MethodBType] = {
+ ScalaValueClassesNoUnit.map(primitive => {
+ val boxed = boxedClass(primitive)
+ val name = getName(primitive.name.toString, boxed.name.toString)
+ (name, methodNameAndType(PredefModule.moduleClass, newTermName(name)).methodType)
+ })(collection.breakOut)
+ }
- /**
- * Methods in scala.runtime.BoxesRuntime
- */
- lazy val asmBoxTo : Map[BType, MethodNameAndType] = Map(
- BOOL -> MethodNameAndType("boxToBoolean", MethodBType(List(BOOL), BOXED_BOOLEAN)),
- BYTE -> MethodNameAndType("boxToByte", MethodBType(List(BYTE), BOXED_BYTE)),
- CHAR -> MethodNameAndType("boxToCharacter", MethodBType(List(CHAR), BOXED_CHAR)),
- SHORT -> MethodNameAndType("boxToShort", MethodBType(List(SHORT), BOXED_SHORT)),
- INT -> MethodNameAndType("boxToInteger", MethodBType(List(INT), BOXED_INT)),
- LONG -> MethodNameAndType("boxToLong", MethodBType(List(LONG), BOXED_LONG)),
- FLOAT -> MethodNameAndType("boxToFloat", MethodBType(List(FLOAT), BOXED_FLOAT)),
- DOUBLE -> MethodNameAndType("boxToDouble", MethodBType(List(DOUBLE), BOXED_DOUBLE))
- )
-
- lazy val asmUnboxTo: Map[BType, MethodNameAndType] = Map(
- BOOL -> MethodNameAndType("unboxToBoolean", MethodBType(List(ObjectReference), BOOL)),
- BYTE -> MethodNameAndType("unboxToByte", MethodBType(List(ObjectReference), BYTE)),
- CHAR -> MethodNameAndType("unboxToChar", MethodBType(List(ObjectReference), CHAR)),
- SHORT -> MethodNameAndType("unboxToShort", MethodBType(List(ObjectReference), SHORT)),
- INT -> MethodNameAndType("unboxToInt", MethodBType(List(ObjectReference), INT)),
- LONG -> MethodNameAndType("unboxToLong", MethodBType(List(ObjectReference), LONG)),
- FLOAT -> MethodNameAndType("unboxToFloat", MethodBType(List(ObjectReference), FLOAT)),
- DOUBLE -> MethodNameAndType("unboxToDouble", MethodBType(List(ObjectReference), DOUBLE))
- )
+ // boolean2Boolean -> (Z)Ljava/lang/Boolean;
+ lazy val predefAutoBoxMethods: Map[String, MethodBType] = predefBoxingMethods((primitive, boxed) => primitive.toLowerCase + "2" + boxed)
+
+ // Boolean2boolean -> (Ljava/lang/Boolean;)Z
+ lazy val predefAutoUnboxMethods: Map[String, MethodBType] = predefBoxingMethods((primitive, boxed) => boxed + "2" + primitive.toLowerCase)
+
+ private def staticRefMethods(name: Name): Map[InternalName, MethodNameAndType] = {
+ allRefClasses.map(refClass =>
+ (classBTypeFromSymbol(refClass).internalName, methodNameAndType(refClass, name, static = true)))(collection.breakOut)
+ }
+
+ // scala/runtime/BooleanRef -> MethodNameAndType(create,(Z)Lscala/runtime/BooleanRef;)
+ lazy val srRefCreateMethods: Map[InternalName, MethodNameAndType] = staticRefMethods(nme.create)
+
+ // scala/runtime/BooleanRef -> MethodNameAndType(zero,()Lscala/runtime/BooleanRef;)
+ lazy val srRefZeroMethods: Map[InternalName, MethodNameAndType] = staticRefMethods(nme.zero)
+
+ // java/lang/Boolean -> MethodNameAndType(<init>,(Z)V)
+ lazy val primitiveBoxConstructors: Map[InternalName, MethodNameAndType] = {
+ ScalaValueClassesNoUnit.map(primitive => {
+ val boxed = boxedClass(primitive)
+ (classBTypeFromSymbol(boxed).internalName, methodNameAndType(boxed, nme.CONSTRUCTOR, filterOverload = singleParamOfClass(primitive)))
+ })(collection.breakOut)
+ }
+
+ private def nonOverloadedConstructors(classes: Iterable[Symbol]): Map[InternalName, MethodNameAndType] = {
+ classes.map(cls => (classBTypeFromSymbol(cls).internalName, methodNameAndType(cls, nme.CONSTRUCTOR)))(collection.breakOut)
+ }
+
+ // scala/runtime/BooleanRef -> MethodNameAndType(<init>,(Z)V)
+ lazy val srRefConstructors: Map[InternalName, MethodNameAndType] = nonOverloadedConstructors(allRefClasses)
+
+ private def specializedSubclasses(cls: Symbol): List[Symbol] = {
+ exitingSpecialize(cls.info) // the `transformInfo` method of specialization adds specialized subclasses to the `specializedClass` map
+ specializeTypes.specializedClass.collect({
+ case ((`cls`, _), specCls) => specCls
+ }).toList
+ }
+
+ // scala/Tuple3 -> MethodNameAndType(<init>,(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V)
+ // scala/Tuple2$mcZC$sp -> MethodNameAndType(<init>,(ZC)V)
+ lazy val tupleClassConstructors: Map[InternalName, MethodNameAndType] = {
+ val tupleClassSymbols = TupleClass.seq ++ specializedSubclasses(TupleClass(1)) ++ specializedSubclasses(TupleClass(2))
+ nonOverloadedConstructors(tupleClassSymbols)
+ }
lazy val typeOfArrayOp: Map[Int, BType] = {
import scalaPrimitives._
Map(
- (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++
+ (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++
(List(BARRAY_LENGTH, BARRAY_GET, BARRAY_SET) map (_ -> BYTE)) ++
(List(SARRAY_LENGTH, SARRAY_GET, SARRAY_SET) map (_ -> SHORT)) ++
(List(CARRAY_LENGTH, CARRAY_GET, CARRAY_SET) map (_ -> CHAR)) ++
@@ -187,9 +228,67 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
(List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++
(List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++
(List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++
- (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectReference)) : _*
+ (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectRef)) : _*
)
}
+
+ lazy val hashMethodSym: Symbol = getMember(RuntimeStaticsModule, nme.anyHash)
+
+ // TODO @lry avoiding going through through missingHook for every line in the REPL: https://github.com/scala/scala/commit/8d962ed4ddd310cc784121c426a2e3f56a112540
+ lazy val AndroidParcelableInterface : Symbol = getClassIfDefined("android.os.Parcelable")
+ lazy val AndroidCreatorClass : Symbol = getClassIfDefined("android.os.Parcelable$Creator")
+
+ lazy val BeanInfoAttr: Symbol = requiredClass[scala.beans.BeanInfo]
+
+ /* The Object => String overload. */
+ lazy val String_valueOf: Symbol = {
+ getMember(StringModule, nme.valueOf) filter (sym => sym.info.paramTypes match {
+ case List(pt) => pt.typeSymbol == ObjectClass
+ case _ => false
+ })
+ }
+
+ lazy val lambdaMetaFactoryMetafactoryHandle =
+ new asm.Handle(asm.Opcodes.H_INVOKESTATIC,
+ coreBTypes.jliLambdaMetafactoryRef.internalName, sn.Metafactory.toString,
+ MethodBType(
+ List(
+ coreBTypes.jliMethodHandlesLookupRef,
+ coreBTypes.StringRef,
+ coreBTypes.jliMethodTypeRef,
+ coreBTypes.jliMethodTypeRef,
+ coreBTypes.jliMethodHandleRef,
+ coreBTypes.jliMethodTypeRef),
+ coreBTypes.jliCallSiteRef
+ ).descriptor,
+ /* itf = */ coreBTypes.jliLambdaMetafactoryRef.isInterface.get)
+
+ lazy val lambdaMetaFactoryAltMetafactoryHandle =
+ new asm.Handle(asm.Opcodes.H_INVOKESTATIC,
+ coreBTypes.jliLambdaMetafactoryRef.internalName, sn.AltMetafactory.toString,
+ MethodBType(
+ List(
+ coreBTypes.jliMethodHandlesLookupRef,
+ coreBTypes.StringRef,
+ coreBTypes.jliMethodTypeRef,
+ ArrayBType(ObjectRef)),
+ coreBTypes.jliCallSiteRef
+ ).descriptor,
+ /* itf = */ coreBTypes.jliLambdaMetafactoryRef.isInterface.get)
+
+ lazy val lambdaDeserializeBootstrapHandle =
+ new scala.tools.asm.Handle(scala.tools.asm.Opcodes.H_INVOKESTATIC,
+ coreBTypes.srLambdaDeserialize.internalName, sn.Bootstrap.toString,
+ MethodBType(
+ List(
+ coreBTypes.jliMethodHandlesLookupRef,
+ coreBTypes.StringRef,
+ coreBTypes.jliMethodTypeRef,
+ ArrayBType(jliMethodHandleRef)
+ ),
+ coreBTypes.jliCallSiteRef
+ ).descriptor,
+ /* itf = */ coreBTypes.srLambdaDeserialize.isInterface.get)
}
/**
@@ -205,13 +304,46 @@ trait CoreBTypesProxyGlobalIndependent[BTS <: BTypes] {
import bTypes._
def boxedClasses: Set[ClassBType]
-
- def RT_NOTHING : ClassBType
- def RT_NULL : ClassBType
-
- def ObjectReference : ClassBType
- def jlCloneableReference : ClassBType
- def jioSerializableReference : ClassBType
+ def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType]
+
+ def srNothingRef : ClassBType
+ def srNullRef : ClassBType
+
+ def ObjectRef : ClassBType
+ def StringRef : ClassBType
+ def PredefRef : ClassBType
+ def jlCloneableRef : ClassBType
+ def jiSerializableRef : ClassBType
+ def juHashMapRef : ClassBType
+ def juMapRef : ClassBType
+ def jliCallSiteRef : ClassBType
+ def jliLambdaMetafactoryRef : ClassBType
+ def jliMethodTypeRef : ClassBType
+ def jliSerializedLambdaRef : ClassBType
+ def jliMethodHandleRef : ClassBType
+ def jliMethodHandlesLookupRef : ClassBType
+ def srBoxesRunTimeRef : ClassBType
+ def srBoxedUnitRef : ClassBType
+
+ def srBoxesRuntimeBoxToMethods : Map[BType, MethodNameAndType]
+ def srBoxesRuntimeUnboxToMethods : Map[BType, MethodNameAndType]
+
+ def javaBoxMethods : Map[InternalName, MethodNameAndType]
+ def javaUnboxMethods : Map[InternalName, MethodNameAndType]
+
+ def predefAutoBoxMethods : Map[String, MethodBType]
+ def predefAutoUnboxMethods : Map[String, MethodBType]
+
+ def srRefCreateMethods : Map[InternalName, MethodNameAndType]
+ def srRefZeroMethods : Map[InternalName, MethodNameAndType]
+
+ def primitiveBoxConstructors : Map[InternalName, MethodNameAndType]
+ def srRefConstructors : Map[InternalName, MethodNameAndType]
+ def tupleClassConstructors : Map[InternalName, MethodNameAndType]
+
+ def lambdaMetaFactoryMetafactoryHandle : asm.Handle
+ def lambdaMetaFactoryAltMetafactoryHandle : asm.Handle
+ def lambdaDeserializeBootstrapHandle : asm.Handle
}
/**
@@ -226,50 +358,63 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes:
_coreBTypes = coreBTypes.asInstanceOf[CoreBTypes[bTypes.type]]
}
- def primitiveTypeMap: Map[Symbol, PrimitiveBType] = _coreBTypes.primitiveTypeMap
-
- def BOXED_UNIT : ClassBType = _coreBTypes.BOXED_UNIT
- def BOXED_BOOLEAN : ClassBType = _coreBTypes.BOXED_BOOLEAN
- def BOXED_BYTE : ClassBType = _coreBTypes.BOXED_BYTE
- def BOXED_SHORT : ClassBType = _coreBTypes.BOXED_SHORT
- def BOXED_CHAR : ClassBType = _coreBTypes.BOXED_CHAR
- def BOXED_INT : ClassBType = _coreBTypes.BOXED_INT
- def BOXED_LONG : ClassBType = _coreBTypes.BOXED_LONG
- def BOXED_FLOAT : ClassBType = _coreBTypes.BOXED_FLOAT
- def BOXED_DOUBLE : ClassBType = _coreBTypes.BOXED_DOUBLE
+ def primitiveTypeToBType: Map[Symbol, PrimitiveBType] = _coreBTypes.primitiveTypeToBType
def boxedClasses: Set[ClassBType] = _coreBTypes.boxedClasses
-
def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = _coreBTypes.boxedClassOfPrimitive
def boxResultType: Map[Symbol, ClassBType] = _coreBTypes.boxResultType
-
def unboxResultType: Map[Symbol, PrimitiveBType] = _coreBTypes.unboxResultType
- def RT_NOTHING : ClassBType = _coreBTypes.RT_NOTHING
- def RT_NULL : ClassBType = _coreBTypes.RT_NULL
-
- def ObjectReference : ClassBType = _coreBTypes.ObjectReference
- def objArrayReference : ArrayBType = _coreBTypes.objArrayReference
-
- def StringReference : ClassBType = _coreBTypes.StringReference
- def StringBuilderReference : ClassBType = _coreBTypes.StringBuilderReference
- def ThrowableReference : ClassBType = _coreBTypes.ThrowableReference
- def jlCloneableReference : ClassBType = _coreBTypes.jlCloneableReference
- def jlNPEReference : ClassBType = _coreBTypes.jlNPEReference
- def jioSerializableReference : ClassBType = _coreBTypes.jioSerializableReference
- def scalaSerializableReference : ClassBType = _coreBTypes.scalaSerializableReference
- def classCastExceptionReference : ClassBType = _coreBTypes.classCastExceptionReference
- def javaUtilMapReference : ClassBType = _coreBTypes.javaUtilMapReference
- def javaUtilHashMapReference : ClassBType = _coreBTypes.javaUtilHashMapReference
-
- def srBooleanRef : ClassBType = _coreBTypes.srBooleanRef
- def srByteRef : ClassBType = _coreBTypes.srByteRef
- def srCharRef : ClassBType = _coreBTypes.srCharRef
- def srIntRef : ClassBType = _coreBTypes.srIntRef
- def srLongRef : ClassBType = _coreBTypes.srLongRef
- def srFloatRef : ClassBType = _coreBTypes.srFloatRef
- def srDoubleRef : ClassBType = _coreBTypes.srDoubleRef
+ def srNothingRef : ClassBType = _coreBTypes.srNothingRef
+ def srNullRef : ClassBType = _coreBTypes.srNullRef
+
+ def ObjectRef : ClassBType = _coreBTypes.ObjectRef
+ def StringRef : ClassBType = _coreBTypes.StringRef
+ def PredefRef : ClassBType = _coreBTypes.PredefRef
+ def jlStringBuilderRef : ClassBType = _coreBTypes.jlStringBuilderRef
+ def jlStringBufferRef : ClassBType = _coreBTypes.jlStringBufferRef
+ def jlCharSequenceRef : ClassBType = _coreBTypes.jlCharSequenceRef
+ def jlThrowableRef : ClassBType = _coreBTypes.jlThrowableRef
+ def jlCloneableRef : ClassBType = _coreBTypes.jlCloneableRef
+ def jiSerializableRef : ClassBType = _coreBTypes.jiSerializableRef
+ def jlClassCastExceptionRef : ClassBType = _coreBTypes.jlClassCastExceptionRef
+ def juMapRef : ClassBType = _coreBTypes.juMapRef
+ def juHashMapRef : ClassBType = _coreBTypes.juHashMapRef
+ def sbScalaBeanInfoRef : ClassBType = _coreBTypes.sbScalaBeanInfoRef
+ def jliSerializedLambdaRef : ClassBType = _coreBTypes.jliSerializedLambdaRef
+ def jliMethodHandleRef : ClassBType = _coreBTypes.jliMethodHandleRef
+ def jliMethodHandlesRef : ClassBType = _coreBTypes.jliMethodHandlesRef
+ def jliMethodHandlesLookupRef : ClassBType = _coreBTypes.jliMethodHandlesLookupRef
+ def jliMethodTypeRef : ClassBType = _coreBTypes.jliMethodTypeRef
+ def jliCallSiteRef : ClassBType = _coreBTypes.jliCallSiteRef
+ def jliLambdaMetafactoryRef : ClassBType = _coreBTypes.jliLambdaMetafactoryRef
+ def srBoxesRunTimeRef : ClassBType = _coreBTypes.srBoxesRunTimeRef
+ def srBoxedUnitRef : ClassBType = _coreBTypes.srBoxedUnitRef
+
+ def srBoxesRuntimeBoxToMethods : Map[BType, MethodNameAndType] = _coreBTypes.srBoxesRuntimeBoxToMethods
+ def srBoxesRuntimeUnboxToMethods : Map[BType, MethodNameAndType] = _coreBTypes.srBoxesRuntimeUnboxToMethods
+
+ def javaBoxMethods : Map[InternalName, MethodNameAndType] = _coreBTypes.javaBoxMethods
+ def javaUnboxMethods : Map[InternalName, MethodNameAndType] = _coreBTypes.javaUnboxMethods
+
+ def predefAutoBoxMethods : Map[String, MethodBType] = _coreBTypes.predefAutoBoxMethods
+ def predefAutoUnboxMethods : Map[String, MethodBType] = _coreBTypes.predefAutoUnboxMethods
+
+ def srRefCreateMethods : Map[InternalName, MethodNameAndType] = _coreBTypes.srRefCreateMethods
+ def srRefZeroMethods : Map[InternalName, MethodNameAndType] = _coreBTypes.srRefZeroMethods
+
+ def primitiveBoxConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.primitiveBoxConstructors
+ def srRefConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.srRefConstructors
+ def tupleClassConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.tupleClassConstructors
+
+ def srSymbolLiteral : ClassBType = _coreBTypes.srSymbolLiteral
+ def srStructuralCallSite : ClassBType = _coreBTypes.srStructuralCallSite
+ def srLambdaDeserialize : ClassBType = _coreBTypes.srLambdaDeserialize
+
+ def typeOfArrayOp: Map[Int, BType] = _coreBTypes.typeOfArrayOp
+
+ // Some symbols. These references should probably be moved to Definitions.
def hashMethodSym: Symbol = _coreBTypes.hashMethodSym
@@ -280,17 +425,7 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes:
def String_valueOf: Symbol = _coreBTypes.String_valueOf
- def FunctionReference : Vector[ClassBType] = _coreBTypes.FunctionReference
- def AbstractFunctionReference : Vector[ClassBType] = _coreBTypes.AbstractFunctionReference
- def AbstractFunctionArityMap : Map[ClassBType, Int] = _coreBTypes.AbstractFunctionArityMap
-
- def PartialFunctionReference : ClassBType = _coreBTypes.PartialFunctionReference
- def AbstractPartialFunctionReference : ClassBType = _coreBTypes.AbstractPartialFunctionReference
-
- def BoxesRunTime: ClassBType = _coreBTypes.BoxesRunTime
-
- def asmBoxTo : Map[BType, MethodNameAndType] = _coreBTypes.asmBoxTo
- def asmUnboxTo: Map[BType, MethodNameAndType] = _coreBTypes.asmUnboxTo
-
- def typeOfArrayOp: Map[Int, BType] = _coreBTypes.typeOfArrayOp
+ def lambdaMetaFactoryMetafactoryHandle : asm.Handle = _coreBTypes.lambdaMetaFactoryMetafactoryHandle
+ def lambdaMetaFactoryAltMetafactoryHandle : asm.Handle = _coreBTypes.lambdaMetaFactoryAltMetafactoryHandle
+ def lambdaDeserializeBootstrapHandle : asm.Handle = _coreBTypes.lambdaDeserializeBootstrapHandle
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
deleted file mode 100644
index 4768417c67..0000000000
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
+++ /dev/null
@@ -1,3350 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala
-package tools.nsc
-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
-import scala.annotation.tailrec
-
-/**
- * @author Iulian Dragos (version 1.0, FJBG-based implementation)
- * @author Miguel Garcia (version 2.0, ASM-based implementation)
- *
- * Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2012Q2/GenASM.pdf
- */
-abstract class GenASM extends SubComponent with BytecodeWriters { self =>
- import global._
- import icodes._
- import icodes.opcodes._
- import definitions._
-
- val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global)
- import bCodeAsmCommon._
-
- // Strangely I can't find this in the asm code
- // 255, but reserving 1 for "this"
- final val MaximumJvmParameters = 254
-
- val phaseName = "jvm"
-
- /** Create a new phase */
- override def newPhase(p: Phase): Phase = new AsmPhase(p)
-
- /** From the reference documentation of the Android SDK:
- * The `Parcelable` interface identifies classes whose instances can be written to and restored from a `Parcel`.
- * Classes implementing the `Parcelable` interface must also have a static field called `CREATOR`,
- * which is an object implementing the `Parcelable.Creator` interface.
- */
- private val androidFieldName = newTermName("CREATOR")
-
- private lazy val AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable")
- private lazy val AndroidCreatorClass = rootMirror.getClassIfDefined("android.os.Parcelable$Creator")
-
- /** JVM code generation phase
- */
- class AsmPhase(prev: Phase) extends ICodePhase(prev) {
- def name = phaseName
- override def erasedTypes = true
- def apply(cls: IClass) = sys.error("no implementation")
-
- // An AsmPhase starts and ends within a Run, thus the caches in question will get populated and cleared within a Run, too), SI-7422
- javaNameCache.clear()
- javaNameCache ++= List(
- NothingClass -> binarynme.RuntimeNothing,
- RuntimeNothingClass -> binarynme.RuntimeNothing,
- NullClass -> binarynme.RuntimeNull,
- RuntimeNullClass -> binarynme.RuntimeNull
- )
-
- // unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names.
- reverseJavaName.clear()
- reverseJavaName ++= List(
- binarynme.RuntimeNothing.toString() -> RuntimeNothingClass, // RuntimeNothingClass is the bytecode-level return type of Scala methods with Nothing return-type.
- binarynme.RuntimeNull.toString() -> RuntimeNullClass
- )
-
- // Lazy val; can't have eager vals in Phase constructors which may
- // cause cycles before Global has finished initialization.
- lazy val BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo")
-
- private def initBytecodeWriter(entryPoints: List[IClass]): BytecodeWriter = {
- settings.outputDirs.getSingleOutput match {
- case Some(f) if f hasExtension "jar" =>
- // If no main class was specified, see if there's only one
- // entry point among the classes going into the jar.
- if (settings.mainClass.isDefault) {
- entryPoints map (_.symbol fullName '.') match {
- case Nil =>
- log("No Main-Class designated or discovered.")
- case name :: Nil =>
- log("Unique entry point: setting Main-Class to " + name)
- settings.mainClass.value = name
- case names =>
- log("No Main-Class due to multiple entry points:\n " + names.mkString("\n "))
- }
- }
- else log("Main-Class was specified: " + settings.mainClass.value)
-
- new DirectToJarfileWriter(f.file)
-
- case _ => factoryNonJarBytecodeWriter()
- }
- }
-
- private def isJavaEntryPoint(icls: IClass) = {
- val sym = icls.symbol
- def fail(msg: String, pos: Position = sym.pos) = {
- reporter.warning(sym.pos,
- sym.name + " has a main method with parameter type Array[String], but " + sym.fullName('.') + " will not be a runnable program.\n" +
- " Reason: " + msg
- // TODO: make this next claim true, if possible
- // by generating valid main methods as static in module classes
- // not sure what the jvm allows here
- // + " You can still run the program by calling it as " + sym.javaSimpleName + " instead."
- )
- false
- }
- def failNoForwarder(msg: String) = {
- fail(msg + ", which means no static forwarder can be generated.\n")
- }
- val possibles = if (sym.hasModuleFlag) (sym.tpe nonPrivateMember nme.main).alternatives else Nil
- val hasApproximate = possibles exists { m =>
- m.info match {
- case MethodType(p :: Nil, _) => p.tpe.typeSymbol == ArrayClass
- case _ => false
- }
- }
- // At this point it's a module with a main-looking method, so either succeed or warn that it isn't.
- hasApproximate && {
- // Before erasure so we can identify generic mains.
- enteringErasure {
- val companion = sym.linkedClassOfClass
-
- if (hasJavaMainMethod(companion))
- failNoForwarder("companion contains its own main method")
- else if (companion.tpe.member(nme.main) != NoSymbol)
- // this is only because forwarders aren't smart enough yet
- failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)")
- else if (companion.isTrait)
- failNoForwarder("companion is a trait")
- // Now either succeeed, or issue some additional warnings for things which look like
- // attempts to be java main methods.
- else (possibles exists isJavaMainMethod) || {
- possibles exists { m =>
- m.info match {
- case PolyType(_, _) =>
- fail("main methods cannot be generic.")
- case MethodType(params, res) =>
- if (res.typeSymbol :: params exists (_.isAbstractType))
- fail("main methods cannot refer to type parameters or abstract types.", m.pos)
- else
- isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos)
- case tp =>
- fail("don't know what this is: " + tp, m.pos)
- }
- }
- }
- }
- }
- }
-
- override def run() {
-
- if (settings.debug)
- inform("[running phase " + name + " on icode]")
-
- if (settings.Xdce) {
- val classes = icodes.classes.keys.toList // copy to avoid mutating the map while iterating
- for (sym <- classes if inliner.isClosureClass(sym) && !deadCode.liveClosures(sym)) {
- log(s"Optimizer eliminated ${sym.fullNameString}")
- deadCode.elidedClosures += sym
- icodes.classes -= sym
- }
- }
-
- // For predictably ordered error messages.
- var sortedClasses = classes.values.toList sortBy (_.symbol.fullName)
-
- // Warn when classes will overwrite one another on case-insensitive systems.
- for ((_, v1 :: v2 :: _) <- sortedClasses groupBy (_.symbol.javaClassName.toString.toLowerCase)) {
- reporter.warning(v1.symbol.pos,
- s"Class ${v1.symbol.javaClassName} differs only in case from ${v2.symbol.javaClassName}. " +
- "Such classes will overwrite one another on case-insensitive filesystems.")
- }
-
- debuglog(s"Created new bytecode generator for ${classes.size} classes.")
- val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint)
- val needsOutfile = bytecodeWriter.isInstanceOf[ClassBytecodeWriter]
- val plainCodeGen = new JPlainBuilder( bytecodeWriter, needsOutfile)
- val mirrorCodeGen = new JMirrorBuilder( bytecodeWriter, needsOutfile)
- val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter, needsOutfile)
-
- def emitFor(c: IClass) {
- if (isStaticModule(c.symbol) && isTopLevelModule(c.symbol)) {
- if (c.symbol.companionClass == NoSymbol)
- mirrorCodeGen genMirrorClass (c.symbol, c.cunit)
- else
- log(s"No mirror class for module with linked class: ${c.symbol.fullName}")
- }
- plainCodeGen genClass c
- if (c.symbol hasAnnotation BeanInfoAttr) beanInfoCodeGen genBeanInfoClass c
- }
-
- while (!sortedClasses.isEmpty) {
- val c = sortedClasses.head
- try emitFor(c)
- catch {
- case e: FileConflictException =>
- reporter.error(c.symbol.pos, s"error writing ${c.symbol}: ${e.getMessage}")
- }
- sortedClasses = sortedClasses.tail
- classes -= c.symbol // GC opportunity
- }
-
- bytecodeWriter.close()
-
- /* don't javaNameCache.clear() because that causes the following tests to fail:
- * test/files/run/macro-repl-dontexpand.scala
- * test/files/jvm/interpreter.scala
- * TODO but why? what use could javaNameCache possibly see once GenASM is over?
- */
-
- /* TODO After emitting all class files (e.g., in a separate compiler phase) ASM can perform bytecode verification:
- *
- * (1) call the asm.util.CheckAdapter.verify() overload:
- * public static void verify(ClassReader cr, ClassLoader loader, boolean dump, PrintWriter pw)
- *
- * (2) passing a custom ClassLoader to verify inter-dependent classes.
- *
- * Alternatively, an offline-bytecode verifier could be used (e.g. Maxine brings one as separate tool).
- */
-
- } // end of AsmPhase.run()
-
- } // end of class AsmPhase
-
- var pickledBytes = 0 // statistics
-
- val javaNameCache = perRunCaches.newAnyRefMap[Symbol, Name]()
-
- // unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names.
- val reverseJavaName = perRunCaches.newAnyRefMap[String, Symbol]()
-
- private def mkFlags(args: Int*) = args.foldLeft(0)(_ | _)
- private def hasPublicBitSet(flags: Int) = (flags & asm.Opcodes.ACC_PUBLIC) != 0
- private def isRemote(s: Symbol) = s hasAnnotation RemoteAttr
-
- /**
- * Return the Java modifiers for the given symbol.
- * Java modifiers for classes:
- * - public, abstract, final, strictfp (not used)
- * for interfaces:
- * - the same as for classes, without 'final'
- * for fields:
- * - public, private (*)
- * - static, final
- * for methods:
- * - the same as for fields, plus:
- * - abstract, synchronized (not used), strictfp (not used), native (not used)
- *
- * (*) protected cannot be used, since inner classes 'see' protected members,
- * and they would fail verification after lifted.
- */
- def javaFlags(sym: Symbol): Int = {
- // constructors of module classes should be private
- // PP: why are they only being marked private at this stage and not earlier?
- val privateFlag =
- sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModule(sym.owner))
-
- // Final: the only fields which can receive ACC_FINAL are eager vals.
- // Neither vars nor lazy vals can, because:
- //
- // Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3
- // "Another problem is that the specification allows aggressive
- // optimization of final fields. Within a thread, it is permissible to
- // reorder reads of a final field with those modifications of a final
- // field that do not take place in the constructor."
- //
- // A var or lazy val which is marked final still has meaning to the
- // scala compiler. The word final is heavily overloaded unfortunately;
- // for us it means "not overridable". At present you can't override
- // vars regardless; this may change.
- //
- // The logic does not check .isFinal (which checks flags for the FINAL flag,
- // and includes symbols marked lateFINAL) instead inspecting rawflags so
- // we can exclude lateFINAL. Such symbols are eligible for inlining, but to
- // avoid breaking proxy software which depends on subclassing, we do not
- // emit ACC_FINAL.
- // Nested objects won't receive ACC_FINAL in order to allow for their overriding.
-
- val finalFlag = (
- (((sym.rawflags & Flags.FINAL) != 0) || isTopLevelModule(sym))
- && !sym.enclClass.isInterface
- && !sym.isClassConstructor
- && !sym.isMutable // lazy vals and vars both
- )
-
- // Primitives are "abstract final" to prohibit instantiation
- // without having to provide any implementations, but that is an
- // illegal combination of modifiers at the bytecode level so
- // suppress final if abstract if present.
- import asm.Opcodes._
- mkFlags(
- if (privateFlag) ACC_PRIVATE else ACC_PUBLIC,
- if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0,
- if (sym.isInterface) ACC_INTERFACE else 0,
- if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0,
- if (sym.isStaticMember) ACC_STATIC else 0,
- if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0,
- if (sym.isArtifact) ACC_SYNTHETIC else 0,
- if (sym.isClass && !sym.isInterface) ACC_SUPER else 0,
- if (sym.hasJavaEnumFlag) ACC_ENUM else 0,
- if (sym.isVarargsMethod) ACC_VARARGS else 0,
- if (sym.hasFlag(Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0
- )
- }
-
- def javaFieldFlags(sym: Symbol) = {
- javaFlags(sym) | mkFlags(
- if (sym hasAnnotation TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0,
- if (sym hasAnnotation VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0,
- if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL
- )
- }
-
- def isTopLevelModule(sym: Symbol): Boolean =
- exitingPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass }
-
- def isStaticModule(sym: Symbol): Boolean = {
- sym.isModuleClass && !sym.isImplClass && !sym.isLifted
- }
-
- // -----------------------------------------------------------------------------------------
- // finding the least upper bound in agreement with the bytecode verifier (given two internal names handed by ASM)
- // Background:
- // http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf
- // http://comments.gmane.org/gmane.comp.java.vm.languages/2293
- // https://issues.scala-lang.org/browse/SI-3872
- // -----------------------------------------------------------------------------------------
-
- /**
- * Given an internal name (eg "java/lang/Integer") returns the class symbol for it.
- *
- * Better not to need this method (an example where control flow arrives here is welcome).
- * This method is invoked only upon both (1) and (2) below happening:
- * (1) providing an asm.ClassWriter with an internal name by other means than javaName()
- * (2) forgetting to track the corresponding class-symbol in reverseJavaName.
- *
- * (The first item is already unlikely because we rely on javaName()
- * to do the bookkeeping for entries that should go in innerClassBuffer.)
- *
- * (We could do completely without this method at the expense of computing stack-map-frames ourselves and
- * invoking visitFrame(), but that would require another pass over all instructions.)
- *
- * Right now I can't think of any invocation of visitSomething() on MethodVisitor
- * where we hand an internal name not backed by a reverseJavaName.
- * However, I'm leaving this note just in case any such oversight is discovered.
- */
- def inameToSymbol(iname: String): Symbol = {
- val name = global.newTypeName(iname)
- val res0 =
- if (nme.isModuleName(name)) rootMirror.getModuleByName(name.dropModule)
- else rootMirror.getClassByName(name.replace('/', '.')) // TODO fails for inner classes (but this hasn't been tested).
- assert(res0 != NoSymbol)
- val res = jsymbol(res0)
- res
- }
-
- def jsymbol(sym: Symbol): Symbol = {
- if(sym.isJavaDefined && sym.isModuleClass) sym.linkedClassOfClass
- else if(sym.isModule) sym.moduleClass
- else sym // we track only module-classes and plain-classes
- }
-
- private def superClasses(s: Symbol): List[Symbol] = {
- assert(!s.isInterface)
- s.superClass match {
- case NoSymbol => List(s)
- case sc => s :: superClasses(sc)
- }
- }
-
- private def firstCommonSuffix(as: List[Symbol], bs: List[Symbol]): Symbol = {
- assert(!(as contains NoSymbol))
- assert(!(bs contains NoSymbol))
- var chainA = as
- var chainB = bs
- var fcs: Symbol = NoSymbol
- do {
- if (chainB contains chainA.head) fcs = chainA.head
- else if (chainA contains chainB.head) fcs = chainB.head
- else {
- chainA = chainA.tail
- chainB = chainB.tail
- }
- } while(fcs == NoSymbol)
- fcs
- }
-
- private def jvmWiseLUB(a: Symbol, b: Symbol): Symbol = {
- assert(a.isClass)
- assert(b.isClass)
-
- val res = (a.isInterface, b.isInterface) match {
- case (true, true) =>
- global.lub(List(a.tpe, b.tpe)).typeSymbol // TODO assert == firstCommonSuffix of resp. parents
- case (true, false) =>
- if(b isSubClass a) a else ObjectClass
- case (false, true) =>
- if(a isSubClass b) b else ObjectClass
- case _ =>
- firstCommonSuffix(superClasses(a), superClasses(b))
- }
- assert(res != NoSymbol)
- res
- }
-
- /* The internal name of the least common ancestor of the types given by inameA and inameB.
- It's what ASM needs to know in order to compute stack map frames, http://asm.ow2.org/doc/developer-guide.html#controlflow */
- def getCommonSuperClass(inameA: String, inameB: String): String = {
- val a = reverseJavaName.getOrElseUpdate(inameA, inameToSymbol(inameA))
- val b = reverseJavaName.getOrElseUpdate(inameB, inameToSymbol(inameB))
-
- // global.lub(List(a.tpe, b.tpe)).typeSymbol.javaBinaryName.toString()
- // icodes.lub(icodes.toTypeKind(a.tpe), icodes.toTypeKind(b.tpe)).toType
- val lcaSym = jvmWiseLUB(a, b)
- val lcaName = lcaSym.javaBinaryName.toString // don't call javaName because that side-effects innerClassBuffer.
- val oldsym = reverseJavaName.put(lcaName, lcaSym)
- assert(oldsym.isEmpty || (oldsym.get == lcaSym), "somehow we're not managing to compute common-super-class for ASM consumption")
- assert(lcaName != "scala/Any")
-
- lcaName // TODO ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Do some caching.
- }
-
- class CClassWriter(flags: Int) extends asm.ClassWriter(flags) {
- override def getCommonSuperClass(iname1: String, iname2: String): String = {
- GenASM.this.getCommonSuperClass(iname1, iname2)
- }
- }
-
- // -----------------------------------------------------------------------------------------
- // constants
- // -----------------------------------------------------------------------------------------
-
- private val classfileVersion: Int = settings.target.value match {
- case "jvm-1.5" => asm.Opcodes.V1_5
- case "jvm-1.6" => asm.Opcodes.V1_6
- case "jvm-1.7" => asm.Opcodes.V1_7
- case "jvm-1.8" => asm.Opcodes.V1_8
- }
-
- private val majorVersion: Int = (classfileVersion & 0xFF)
- private val emitStackMapFrame = (majorVersion >= 50)
-
- private val extraProc: Int = mkFlags(
- asm.ClassWriter.COMPUTE_MAXS,
- if(emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0
- )
-
- val JAVA_LANG_OBJECT = asm.Type.getObjectType("java/lang/Object")
- val JAVA_LANG_STRING = asm.Type.getObjectType("java/lang/String")
-
- /**
- * We call many Java varargs methods from ASM library that expect Arra[asm.Type] as argument so
- * we override default (compiler-generated) ClassTag so we can provide specialized newArray implementation.
- *
- * Examples of methods that should pick our definition are: JBuilder.javaType and JPlainBuilder.genMethod.
- */
- private implicit val asmTypeTag: scala.reflect.ClassTag[asm.Type] = new scala.reflect.ClassTag[asm.Type] {
- def runtimeClass: java.lang.Class[asm.Type] = classOf[asm.Type]
- final override def newArray(len: Int): Array[asm.Type] = new Array[asm.Type](len)
- }
-
- /** basic functionality for class file building */
- abstract class JBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) {
-
- val EMPTY_STRING_ARRAY = Array.empty[String]
-
- val mdesc_arglessvoid = "()V"
-
- val CLASS_CONSTRUCTOR_NAME = "<clinit>"
- val INSTANCE_CONSTRUCTOR_NAME = "<init>"
-
- // -----------------------------------------------------------------------------------------
- // factory methods
- // -----------------------------------------------------------------------------------------
-
- /**
- * Returns a new ClassWriter for the class given by arguments.
- *
- * @param access the class's access flags. This parameter also indicates if the class is deprecated.
- *
- * @param name the internal name of the class.
- *
- * @param signature the signature of this class. May be <tt>null</tt> if
- * the class is not a generic one, and does not extend or implement
- * generic classes or interfaces.
- *
- * @param superName the internal of name of the super class. For interfaces,
- * the super class is [[Object]]. May be <tt>null</tt>, but
- * only for the [[Object]] class.
- *
- * @param interfaces the internal names of the class's interfaces (see
- * {@link Type#getInternalName() getInternalName}). May be
- * <tt>null</tt>.
- */
- def createJClass(access: Int, name: String, signature: String, superName: String, interfaces: Array[String]): asm.ClassWriter = {
- val cw = new CClassWriter(extraProc)
- cw.visit(classfileVersion,
- access, name, signature,
- superName, interfaces)
-
- cw
- }
-
- def createJAttribute(name: String, b: Array[Byte], offset: Int, len: Int): asm.Attribute = {
- val dest = new Array[Byte](len)
- System.arraycopy(b, offset, dest, 0, len)
- new asm.CustomAttr(name, dest)
- }
-
- // -----------------------------------------------------------------------------------------
- // utilities useful when emitting plain, mirror, and beaninfo classes.
- // -----------------------------------------------------------------------------------------
-
- def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) {
- try {
- val arr = jclass.toByteArray()
- val outF: scala.tools.nsc.io.AbstractFile = {
- if(needsOutfile) getFile(sym, jclassName, ".class") else null
- }
- bytecodeWriter.writeClass(label, jclassName, arr, outF)
- } catch {
- 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}")
- case e: java.io.IOException if e.getMessage != null && (e.getMessage contains "File name too long") =>
- reporter.error(sym.pos, e.getMessage + "\n" +
- "This can happen on some encrypted or legacy file systems. Please see SI-3623 for more details.")
-
- }
- }
-
- /** Specialized array conversion to prevent calling
- * java.lang.reflect.Array.newInstance via TraversableOnce.toArray
- */
- def mkArray(xs: Traversable[String]): Array[String] = { val a = new Array[String](xs.size); xs.copyToArray(a); a }
-
- // -----------------------------------------------------------------------------------------
- // Getters for (JVMS 4.2) internal and unqualified names (represented as JType instances).
- // These getters track behind the scenes the inner classes referred to in the class being emitted,
- // so as to build the InnerClasses attribute (JVMS 4.7.6) via `addInnerClasses()`
- // (which also adds as member classes those inner classes that have been declared,
- // thus also covering the case of inner classes declared but otherwise not referred).
- // -----------------------------------------------------------------------------------------
-
- val innerClassBuffer = mutable.LinkedHashSet[Symbol]()
-
- /** For given symbol return a symbol corresponding to a class that should be declared as inner class.
- *
- * For example:
- * class A {
- * class B
- * object C
- * }
- *
- * then method will return:
- * NoSymbol for A,
- * the same symbol for A.B (corresponding to A$B class), and
- * A$C$ symbol for A.C.
- */
- def innerClassSymbolFor(s: Symbol): Symbol =
- if (s.isClass) s else if (s.isModule) s.moduleClass else NoSymbol
-
- /** Return the name of this symbol that can be used on the Java platform. It removes spaces from names.
- *
- * Special handling:
- * scala.Nothing erases to scala.runtime.Nothing$
- * scala.Null erases to scala.runtime.Null$
- *
- * This is needed because they are not real classes, and they mean
- * 'abrupt termination upon evaluation of that expression' or null respectively.
- * This handling is done already in GenICode, but here we need to remove
- * references from method signatures to these types, because such classes
- * cannot exist in the classpath: the type checker will be very confused.
- */
- def javaName(sym: Symbol): String = {
-
- /*
- * Checks if given symbol corresponds to inner class/object and add it to innerClassBuffer
- *
- * Note: This method is called recursively thus making sure that we add complete chain
- * of inner class all until root class.
- */
- def collectInnerClass(s: Symbol): Unit = {
- // TODO: some enteringFlatten { ... } which accounts for
- // being nested in parameterized classes (if we're going to selectively flatten.)
- val x = innerClassSymbolFor(s)
- if(x ne NoSymbol) {
- assert(x.isClass, "not an inner-class symbol")
- // impl classes are considered top-level, see comment in BTypes
- val isInner = !considerAsTopLevelImplementationArtifact(s) && !x.rawowner.isPackageClass
- if (isInner) {
- innerClassBuffer += x
- collectInnerClass(x.rawowner)
- }
- }
- }
-
- collectInnerClass(sym)
-
- val hasInternalName = sym.isClass || sym.isModuleNotMethod
- val cachedJN = javaNameCache.getOrElseUpdate(sym, {
- if (hasInternalName) { sym.javaBinaryName }
- else { sym.javaSimpleName }
- })
-
- if(emitStackMapFrame && hasInternalName) {
- val internalName = cachedJN.toString()
- val trackedSym = jsymbol(sym)
- reverseJavaName.get(internalName) match {
- case None =>
- reverseJavaName.put(internalName, trackedSym)
- case Some(oldsym) =>
- // TODO: `duplicateOk` seems pretty ad-hoc (a more aggressive version caused SI-9356 because it called oldSym.exists, which failed in the unpickler; see also SI-5031)
- def duplicateOk = oldsym == NoSymbol || trackedSym == NoSymbol || (syntheticCoreClasses contains oldsym) || (oldsym.isModuleClass && (oldsym.sourceModule == trackedSym.sourceModule))
- if (oldsym != trackedSym && !duplicateOk)
- devWarning(s"""|Different class symbols have the same bytecode-level internal name:
- | name: $internalName
- | oldsym: ${oldsym.fullNameString}
- | tracked: ${trackedSym.fullNameString}""".stripMargin)
- }
- }
-
- cachedJN.toString
- }
-
- def descriptor(t: Type): String = { javaType(t).getDescriptor }
- def descriptor(k: TypeKind): String = { javaType(k).getDescriptor }
- def descriptor(s: Symbol): String = { javaType(s).getDescriptor }
-
- def javaType(tk: TypeKind): asm.Type = {
- if(tk.isValueType) {
- if(tk.isIntSizedType) {
- (tk: @unchecked) match {
- case BOOL => asm.Type.BOOLEAN_TYPE
- case BYTE => asm.Type.BYTE_TYPE
- case SHORT => asm.Type.SHORT_TYPE
- case CHAR => asm.Type.CHAR_TYPE
- case INT => asm.Type.INT_TYPE
- }
- } else {
- (tk: @unchecked) match {
- case UNIT => asm.Type.VOID_TYPE
- case LONG => asm.Type.LONG_TYPE
- case FLOAT => asm.Type.FLOAT_TYPE
- case DOUBLE => asm.Type.DOUBLE_TYPE
- }
- }
- } else {
- assert(!tk.isBoxedType, tk) // documentation (BOXED matches none below anyway)
- (tk: @unchecked) match {
- case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls))
- case ARRAY(elem) => javaArrayType(javaType(elem))
- }
- }
- }
-
- def javaType(t: Type): asm.Type = javaType(toTypeKind(t))
-
- def javaType(s: Symbol): asm.Type = {
- if (s.isMethod) {
- val resT: asm.Type = if (s.isClassConstructor) asm.Type.VOID_TYPE else javaType(s.tpe.resultType)
- asm.Type.getMethodType( resT, (s.tpe.paramTypes map javaType): _*)
- } else { javaType(s.tpe) }
- }
-
- def javaArrayType(elem: asm.Type): asm.Type = { asm.Type.getObjectType("[" + elem.getDescriptor) }
-
- def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) }
-
- def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor, isMirror: Boolean = false) {
- /* The outer name for this inner class. Note that it returns null
- * when the inner class should not get an index in the constant pool.
- * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS.
- */
- def outerName(innerSym: Symbol): String = {
- if (isAnonymousOrLocalClass(innerSym))
- null
- else {
- val outerName = javaName(innerSym.rawowner)
- if (isTopLevelModule(innerSym.rawowner)) "" + TermName(outerName).dropModule
- else outerName
- }
- }
-
- 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(memberClassesForInnerClassTable(csym))
- // lambdalift makes all classes (also local, anonymous) members of their enclosing class
- 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)) 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))
- }
-
- // 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
-
- if (allInners.nonEmpty) {
- debuglog(csym.fullName('.') + " contains " + allInners.size + " inner classes.")
-
- // entries ready to be serialized into the classfile, used to detect duplicates.
- val entries = mutable.Map.empty[String, String]
-
- // sort them so inner classes succeed their enclosing class to satisfy the Eclipse Java compiler
- for (innerSym <- allInners sortBy (_.name.length)) { // TODO why not sortBy (_.name.toString()) ??
- val flagsWithFinal: Int = mkFlags(
- // See comment in BTypes, when is a class marked static in the InnerClass table.
- if (isOriginallyStaticOwner(innerSym.originalOwner)) asm.Opcodes.ACC_STATIC else 0,
- (if (innerSym.isJava) javaClassfileFlags(innerSym) else javaFlags(innerSym)) & ~asm.Opcodes.ACC_STATIC,
- if(isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag
- ) & (BCodeAsmCommon.INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED)
- val flags = if (innerSym.isModuleClass) flagsWithFinal & ~asm.Opcodes.ACC_FINAL else flagsWithFinal // For SI-5676, object overriding.
- val jname = javaName(innerSym) // never null
- val oname = outerName(innerSym) // null when method-enclosed
- val iname = innerName(innerSym) // null for anonymous inner class
-
- // Mimicking javap inner class output
- debuglog(
- if (oname == null || iname == null) "//class " + jname
- else "//%s=class %s of class %s".format(iname, jname, oname)
- )
-
- assert(jname != null, "javaName is broken.") // documentation
- val doAdd = entries.get(jname) match {
- // TODO is it ok for prevOName to be null? (Someone should really document the invariants of the InnerClasses bytecode attribute)
- case Some(prevOName) =>
- // this occurs e.g. when innerClassBuffer contains both class Thread$State, object Thread$State,
- // i.e. for them it must be the case that oname == java/lang/Thread
- assert(prevOName == oname, "duplicate")
- false
- case None => true
- }
-
- if(doAdd) {
- entries += (jname -> oname)
- jclass.visitInnerClass(jname, oname, iname, flags)
- }
-
- /*
- * TODO assert (JVMS 4.7.6 The InnerClasses attribute)
- * If a class file has a version number that is greater than or equal to 51.0, and
- * has an InnerClasses attribute in its attributes table, then for all entries in the
- * classes array of the InnerClasses attribute, the value of the
- * outer_class_info_index item must be zero if the value of the
- * inner_name_index item is zero.
- */
-
- }
- }
- }
-
- } // end of class JBuilder
-
-
- /** functionality for building plain and mirror classes */
- abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JBuilder(bytecodeWriter, needsOutfile) {
-
- def debugLevel = settings.debuginfo.indexOfChoice
-
- val emitSource = debugLevel >= 1
- val emitLines = debugLevel >= 2
- val emitVars = debugLevel >= 3
-
- // -----------------------------------------------------------------------------------------
- // more constants
- // -----------------------------------------------------------------------------------------
-
- val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC
- val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL
-
- val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString
-
- // -----------------------------------------------------------------------------------------
- // Custom attribute (JVMS 4.7.1) "ScalaSig" used as marker only
- // i.e., the pickle is contained in a custom annotation, see:
- // (1) `addAnnotations()`,
- // (2) SID # 10 (draft) - Storage of pickled Scala signatures in class files, http://www.scala-lang.org/sid/10
- // (3) SID # 5 - Internals of Scala Annotations, http://www.scala-lang.org/sid/5
- // That annotation in turn is not related to the "java-generic-signature" (JVMS 4.7.9)
- // other than both ending up encoded as attributes (JVMS 4.7)
- // (with the caveat that the "ScalaSig" attribute is associated to some classes,
- // while the "Signature" attribute can be associated to classes, methods, and fields.)
- // -----------------------------------------------------------------------------------------
-
- val versionPickle = {
- val vp = new PickleBuffer(new Array[Byte](16), -1, 0)
- assert(vp.writeIndex == 0, vp)
- vp writeNat PickleFormat.MajorVersion
- vp writeNat PickleFormat.MinorVersion
- vp writeNat 0
- vp
- }
-
- def pickleMarkerLocal = {
- createJAttribute(tpnme.ScalaSignatureATTR.toString, versionPickle.bytes, 0, versionPickle.writeIndex)
- }
-
- def pickleMarkerForeign = {
- createJAttribute(tpnme.ScalaATTR.toString, new Array[Byte](0), 0, 0)
- }
-
- /** Returns a ScalaSignature annotation if it must be added to this class, none otherwise.
- * This annotation must be added to the class' annotations list when generating them.
- *
- * Depending on whether the returned option is defined, it adds to `jclass` one of:
- * (a) the ScalaSig marker attribute
- * (indicating that a scala-signature-annotation aka pickle is present in this class); or
- * (b) the Scala marker attribute
- * (indicating that a scala-signature-annotation aka pickle is to be found in another file).
- *
- *
- * @param jclassName The class file that is being readied.
- * @param sym The symbol for which the signature has been entered in the symData map.
- * This is different than the symbol
- * that is being generated in the case of a mirror class.
- * @return An option that is:
- * - defined and contains an AnnotationInfo of the ScalaSignature type,
- * instantiated with the pickle signature for sym.
- * - empty if the jclass/sym pair must not contain a pickle.
- *
- */
- def getAnnotPickle(jclassName: String, sym: Symbol): Option[AnnotationInfo] = {
- currentRun.symData get sym match {
- case Some(pickle) if !nme.isModuleName(newTermName(jclassName)) =>
- val scalaAnnot = {
- val sigBytes = ScalaSigBytes(pickle.bytes.take(pickle.writeIndex))
- AnnotationInfo(sigBytes.sigAnnot, Nil, List((nme.bytes, sigBytes)))
- }
- pickledBytes += pickle.writeIndex
- currentRun.symData -= sym
- currentRun.symData -= sym.companionSymbol
- Some(scalaAnnot)
- case _ =>
- None
- }
- }
-
- /**
- * Quoting from JVMS 4.7.5 The Exceptions Attribute
- * "The Exceptions attribute indicates which checked exceptions a method may throw.
- * There may be at most one Exceptions attribute in each method_info structure."
- *
- * The contents of that attribute are determined by the `String[] exceptions` argument to ASM's ClassVisitor.visitMethod()
- * This method returns such list of internal names.
- */
- def getExceptions(excs: List[AnnotationInfo]): List[String] =
- for (ThrownException(exc) <- excs.distinct)
- yield javaName(exc)
-
- def getCurrentCUnit(): CompilationUnit
-
- def getGenericSignature(sym: Symbol, owner: Symbol) = self.getGenericSignature(sym, owner, getCurrentCUnit())
-
- def emitArgument(av: asm.AnnotationVisitor,
- name: String,
- arg: ClassfileAnnotArg) {
- (arg: @unchecked) match {
-
- case LiteralAnnotArg(const) =>
- if(const.isNonUnitAnyVal) { av.visit(name, const.value) }
- else {
- const.tag match {
- case StringTag =>
- assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant`
- av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag
- case ClazzTag => av.visit(name, javaType(const.typeValue))
- case EnumTag =>
- val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class.
- val evalue = const.symbolValue.name.toString // value the actual enumeration value.
- av.visitEnum(name, edesc, evalue)
- }
- }
-
- case sb@ScalaSigBytes(bytes) =>
- // see http://www.scala-lang.org/sid/10 (Storage of pickled Scala signatures in class files)
- // also JVMS Sec. 4.7.16.1 The element_value structure and JVMS Sec. 4.4.7 The CONSTANT_Utf8_info Structure.
- if (sb.fitsInOneString)
- av.visit(name, strEncode(sb))
- else {
- val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name)
- for(arg <- arrEncode(sb)) { arrAnnotV.visit(name, arg) }
- arrAnnotV.visitEnd()
- }
- // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape.
-
- case ArrayAnnotArg(args) =>
- val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name)
- for(arg <- args) { emitArgument(arrAnnotV, null, arg) }
- arrAnnotV.visitEnd()
-
- case NestedAnnotArg(annInfo) =>
- val AnnotationInfo(typ, args, assocs) = annInfo
- assert(args.isEmpty, args)
- val desc = descriptor(typ) // the class descriptor of the nested annotation class
- val nestedVisitor = av.visitAnnotation(name, desc)
- emitAssocs(nestedVisitor, assocs)
- }
- }
-
- def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, ClassfileAnnotArg)]) {
- for ((name, value) <- assocs) {
- emitArgument(av, name.toString(), value)
- }
- av.visitEnd()
- }
-
- def emitAnnotations(cw: asm.ClassVisitor, annotations: List[AnnotationInfo]) {
- for(annot <- annotations; if shouldEmitAnnotation(annot)) {
- val AnnotationInfo(typ, args, assocs) = annot
- assert(args.isEmpty, args)
- val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
- emitAssocs(av, assocs)
- }
- }
-
- def emitAnnotations(mw: asm.MethodVisitor, annotations: List[AnnotationInfo]) {
- for(annot <- annotations; if shouldEmitAnnotation(annot)) {
- val AnnotationInfo(typ, args, assocs) = annot
- assert(args.isEmpty, args)
- val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
- emitAssocs(av, assocs)
- }
- }
-
- def emitAnnotations(fw: asm.FieldVisitor, annotations: List[AnnotationInfo]) {
- for(annot <- annotations; if shouldEmitAnnotation(annot)) {
- val AnnotationInfo(typ, args, assocs) = annot
- assert(args.isEmpty, args)
- val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
- emitAssocs(av, assocs)
- }
- }
-
- def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[AnnotationInfo]]) {
- val annotationss = pannotss map (_ filter shouldEmitAnnotation)
- if (annotationss forall (_.isEmpty)) return
- for ((annots, idx) <- annotationss.zipWithIndex;
- annot <- annots) {
- val AnnotationInfo(typ, args, assocs) = annot
- assert(args.isEmpty, args)
- val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot))
- emitAssocs(pannVisitor, assocs)
- }
- }
-
- /** Adds a @remote annotation, actual use unknown.
- *
- * Invoked from genMethod() and addForwarder().
- */
- def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) {
- val needsAnnotation = (
- ( isRemoteClass ||
- isRemote(meth) && isJMethodPublic
- ) && !(meth.throwsAnnotations contains RemoteExceptionClass)
- )
- if (needsAnnotation) {
- val c = Constant(RemoteExceptionClass.tpe)
- val arg = Literal(c) setType c.tpe
- meth.addAnnotation(appliedType(ThrowsClass, c.tpe), arg)
- }
- }
-
- // -----------------------------------------------------------------------------------------
- // Static forwarders (related to mirror classes but also present in
- // a plain class lacking companion module, for details see `isCandidateForForwarders`).
- // -----------------------------------------------------------------------------------------
-
- /** Add a forwarder for method m. Used only from addForwarders(). */
- private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) {
- val moduleName = javaName(module)
- val methodInfo = module.thisType.memberInfo(m)
- val paramJavaTypes: List[asm.Type] = methodInfo.paramTypes map javaType
- // val paramNames = 0 until paramJavaTypes.length map ("x_" + _)
-
- /* Forwarders must not be marked final,
- * as the JVM will not allow redefinition of a final static method,
- * and we don't know what classes might be subclassing the companion class. See SI-4827.
- */
- // TODO: evaluate the other flags we might be dropping on the floor here.
- // TODO: ACC_SYNTHETIC ?
- val flags = PublicStatic | (
- if (m.isVarargsMethod) asm.Opcodes.ACC_VARARGS else 0
- )
-
- // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize }
- val jgensig = staticForwarderGenericSignature(m, module, getCurrentCUnit())
- addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m)
- val (throws, others) = m.annotations partition (_.symbol == ThrowsClass)
- val thrownExceptions: List[String] = getExceptions(throws)
-
- val jReturnType = javaType(methodInfo.resultType)
- val mdesc = asm.Type.getMethodDescriptor(jReturnType, paramJavaTypes: _*)
- val mirrorMethodName = javaName(m)
- val mirrorMethod: asm.MethodVisitor = jclass.visitMethod(
- flags,
- mirrorMethodName,
- mdesc,
- jgensig,
- mkArray(thrownExceptions)
- )
-
- // typestate: entering mode with valid call sequences:
- // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )*
-
- emitAnnotations(mirrorMethod, others)
- emitParamAnnotations(mirrorMethod, m.info.params.map(_.annotations))
-
- // typestate: entering mode with valid call sequences:
- // visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd
-
- mirrorMethod.visitCode()
-
- mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module))
-
- var index = 0
- for(jparamType <- paramJavaTypes) {
- mirrorMethod.visitVarInsn(jparamType.getOpcode(asm.Opcodes.ILOAD), index)
- assert(jparamType.getSort() != asm.Type.METHOD, jparamType)
- index += jparamType.getSize()
- }
-
- mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, javaType(m).getDescriptor, false)
- mirrorMethod.visitInsn(jReturnType.getOpcode(asm.Opcodes.IRETURN))
-
- mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
- mirrorMethod.visitEnd()
-
- }
-
- /** Add forwarders for all methods defined in `module` that don't conflict
- * with methods in the companion class of `module`. A conflict arises when
- * a method with the same name is defined both in a class and its companion object:
- * method signature is not taken into account.
- */
- def addForwarders(isRemoteClass: Boolean, jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol) {
- assert(moduleClass.isModuleClass, moduleClass)
- debuglog("Dumping mirror class for object: " + moduleClass)
-
- val linkedClass = moduleClass.companionClass
- lazy val conflictingNames: Set[Name] = {
- (linkedClass.info.members collect { case sym if sym.name.isTermName => sym.name }).toSet
- }
- debuglog("Potentially conflicting names for forwarders: " + conflictingNames)
-
- for (m <- moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, Flags.METHOD)) {
- if (m.isType || m.isDeferred || (m.owner eq ObjectClass) || m.isConstructor)
- debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'")
- else if (conflictingNames(m.name))
- log(s"No forwarder for $m due to conflict with " + linkedClass.info.member(m.name))
- else if (m.hasAccessBoundary)
- log(s"No forwarder for non-public member $m")
- else {
- debuglog(s"Adding static forwarder for '$m' from $jclassName to '$moduleClass'")
- addForwarder(isRemoteClass, jclass, moduleClass, m)
- }
- }
- }
-
- } // end of class JCommonBuilder
-
-
- trait JAndroidBuilder {
- self: JPlainBuilder =>
-
- def isAndroidParcelableClass(sym: Symbol) =
- (AndroidParcelableInterface != NoSymbol) &&
- (sym.parentSymbols contains AndroidParcelableInterface)
-
- /* Typestate: should be called before emitting fields (because it adds an IField to the current IClass). */
- def addCreatorCode(block: BasicBlock) {
- val fieldSymbol = (
- clasz.symbol.newValue(androidFieldName, NoPosition, Flags.STATIC | Flags.FINAL)
- setInfo AndroidCreatorClass.tpe
- )
- val methodSymbol = definitions.getMember(clasz.symbol.companionModule, androidFieldName)
- clasz addField new IField(fieldSymbol)
- block emit CALL_METHOD(methodSymbol, Static(onInstance = false))
- block emit STORE_FIELD(fieldSymbol, isStatic = true)
- }
-
- def legacyAddCreatorCode(clinit: asm.MethodVisitor) {
- val creatorType: asm.Type = javaType(AndroidCreatorClass)
- val tdesc_creator = creatorType.getDescriptor
-
- jclass.visitField(
- PublicStaticFinal,
- androidFieldName.toString,
- tdesc_creator,
- null, // no java-generic-signature
- null // no initial value
- ).visitEnd()
-
- val moduleName = javaName(clasz.symbol)+"$"
-
- // GETSTATIC `moduleName`.MODULE$ : `moduleName`;
- clinit.visitFieldInsn(
- asm.Opcodes.GETSTATIC,
- moduleName,
- strMODULE_INSTANCE_FIELD,
- asm.Type.getObjectType(moduleName).getDescriptor
- )
-
- // INVOKEVIRTUAL `moduleName`.CREATOR() : android.os.Parcelable$Creator;
- clinit.visitMethodInsn(
- asm.Opcodes.INVOKEVIRTUAL,
- moduleName,
- androidFieldName.toString,
- asm.Type.getMethodDescriptor(creatorType, Array.empty[asm.Type]: _*),
- false
- )
-
- // PUTSTATIC `thisName`.CREATOR;
- clinit.visitFieldInsn(
- asm.Opcodes.PUTSTATIC,
- thisName,
- androidFieldName.toString,
- tdesc_creator
- )
- }
-
- } // end of trait JAndroidBuilder
-
- /** Map from type kinds to the Java reference types.
- * It is used to push class literals onto the operand stack.
- * @see Predef.classOf
- * @see genConstant()
- */
- private val classLiteral = immutable.Map[TypeKind, asm.Type](
- UNIT -> asm.Type.getObjectType("java/lang/Void"),
- BOOL -> asm.Type.getObjectType("java/lang/Boolean"),
- BYTE -> asm.Type.getObjectType("java/lang/Byte"),
- SHORT -> asm.Type.getObjectType("java/lang/Short"),
- CHAR -> asm.Type.getObjectType("java/lang/Character"),
- INT -> asm.Type.getObjectType("java/lang/Integer"),
- LONG -> asm.Type.getObjectType("java/lang/Long"),
- FLOAT -> asm.Type.getObjectType("java/lang/Float"),
- DOUBLE -> asm.Type.getObjectType("java/lang/Double")
- )
-
- def isNonUnitValueTK(tk: TypeKind): Boolean = { tk.isValueType && tk != UNIT }
-
- case class MethodNameAndType(mname: String, mdesc: String)
-
- private val jBoxTo: Map[TypeKind, MethodNameAndType] = {
- Map(
- BOOL -> MethodNameAndType("boxToBoolean", "(Z)Ljava/lang/Boolean;" ) ,
- BYTE -> MethodNameAndType("boxToByte", "(B)Ljava/lang/Byte;" ) ,
- CHAR -> MethodNameAndType("boxToCharacter", "(C)Ljava/lang/Character;") ,
- SHORT -> MethodNameAndType("boxToShort", "(S)Ljava/lang/Short;" ) ,
- INT -> MethodNameAndType("boxToInteger", "(I)Ljava/lang/Integer;" ) ,
- LONG -> MethodNameAndType("boxToLong", "(J)Ljava/lang/Long;" ) ,
- FLOAT -> MethodNameAndType("boxToFloat", "(F)Ljava/lang/Float;" ) ,
- DOUBLE -> MethodNameAndType("boxToDouble", "(D)Ljava/lang/Double;" )
- )
- }
-
- private val jUnboxTo: Map[TypeKind, MethodNameAndType] = {
- Map(
- BOOL -> MethodNameAndType("unboxToBoolean", "(Ljava/lang/Object;)Z") ,
- BYTE -> MethodNameAndType("unboxToByte", "(Ljava/lang/Object;)B") ,
- CHAR -> MethodNameAndType("unboxToChar", "(Ljava/lang/Object;)C") ,
- SHORT -> MethodNameAndType("unboxToShort", "(Ljava/lang/Object;)S") ,
- INT -> MethodNameAndType("unboxToInt", "(Ljava/lang/Object;)I") ,
- LONG -> MethodNameAndType("unboxToLong", "(Ljava/lang/Object;)J") ,
- FLOAT -> MethodNameAndType("unboxToFloat", "(Ljava/lang/Object;)F") ,
- DOUBLE -> MethodNameAndType("unboxToDouble", "(Ljava/lang/Object;)D")
- )
- }
-
- case class BlockInteval(start: BasicBlock, end: BasicBlock)
-
- /** builder of plain classes */
- class JPlainBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean)
- extends JCommonBuilder(bytecodeWriter, needsOutfile)
- with JAndroidBuilder {
-
- val MIN_SWITCH_DENSITY = 0.7
-
- val StringBuilderClassName = javaName(definitions.StringBuilderClass)
- val BoxesRunTime = "scala/runtime/BoxesRunTime"
-
- val StringBuilderType = asm.Type.getObjectType(StringBuilderClassName)
- val mdesc_toString = "()Ljava/lang/String;"
- val mdesc_arrayClone = "()Ljava/lang/Object;"
-
- val tdesc_long = asm.Type.LONG_TYPE.getDescriptor // ie. "J"
-
- def isParcelableClass = isAndroidParcelableClass(clasz.symbol)
-
- def serialVUID: Option[Long] = genBCode.serialVUID(clasz.symbol)
-
- var clasz: IClass = _ // this var must be assigned only by genClass()
- var jclass: asm.ClassWriter = _ // the classfile being emitted
- var thisName: String = _ // the internal name of jclass
-
- def thisDescr: String = {
- assert(thisName != null, "thisDescr invoked too soon.")
- asm.Type.getObjectType(thisName).getDescriptor
- }
-
- def getCurrentCUnit(): CompilationUnit = { clasz.cunit }
-
- def genClass(c: IClass) {
- clasz = c
- innerClassBuffer.clear()
-
- thisName = javaName(c.symbol) // the internal name of the class being emitted
-
- val ps = c.symbol.info.parents
- val superClass: String = if(ps.isEmpty) JAVA_LANG_OBJECT.getInternalName else javaName(ps.head.typeSymbol)
-
- val ifaces: Array[String] = implementedInterfaces(c.symbol).map(javaName)(collection.breakOut)
-
- val thisSignature = getGenericSignature(c.symbol, c.symbol.owner)
- val flags = mkFlags(
- javaFlags(c.symbol),
- if(isDeprecated(c.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
- )
- jclass = createJClass(flags,
- thisName, thisSignature,
- superClass, ifaces)
-
- // typestate: entering mode with valid call sequences:
- // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )*
-
- if(emitSource) {
- jclass.visitSource(c.cunit.source.toString,
- null /* SourceDebugExtension */)
- }
-
- enclosingMethodAttribute(clasz.symbol, javaName, javaType(_).getDescriptor) match {
- case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) =>
- jclass.visitOuterClass(className, methodName, methodDescriptor)
- case _ => ()
- }
-
- // typestate: entering mode with valid call sequences:
- // ( visitAnnotation | visitAttribute )*
-
- val ssa = getAnnotPickle(thisName, c.symbol)
- 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
-
- if (isStaticModule(c.symbol) || isParcelableClass) {
-
- if (isStaticModule(c.symbol)) { addModuleInstanceField() }
- addStaticInit(c.lookupStaticCtor)
-
- } else {
-
- for (constructor <- c.lookupStaticCtor) {
- addStaticInit(Some(constructor))
- }
- val skipStaticForwarders = (c.symbol.isInterface || settings.noForwarders)
- if (!skipStaticForwarders) {
- val lmoc = c.symbol.companionModule
- // add static forwarders if there are no name conflicts; see bugs #363 and #1735
- if (lmoc != NoSymbol) {
- // it must be a top level class (name contains no $s)
- val isCandidateForForwarders = {
- exitingPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass }
- }
- if (isCandidateForForwarders) {
- log("Adding static forwarders from '%s' to implementations in '%s'".format(c.symbol, lmoc))
- addForwarders(isRemote(clasz.symbol), jclass, thisName, lmoc.moduleClass)
- }
- }
- }
-
- }
-
- // add static serialVersionUID field if `clasz` annotated with `@SerialVersionUID(uid: Long)`
- serialVUID foreach { value =>
- val fieldName = "serialVersionUID"
- jclass.visitField(
- PublicStaticFinal,
- fieldName,
- tdesc_long,
- null, // no java-generic-signature
- value
- ).visitEnd()
- }
-
- clasz.fields foreach genField
- clasz.methods foreach { im => genMethod(im, c.symbol.isInterface) }
-
- addInnerClasses(clasz.symbol, jclass)
- jclass.visitEnd()
- writeIfNotTooBig("" + c.symbol.name, thisName, jclass, c.symbol)
- }
-
- def genField(f: IField) {
- debuglog("Adding field: " + f.symbol.fullName)
-
- val javagensig = getGenericSignature(f.symbol, clasz.symbol)
-
- val flags = mkFlags(
- javaFieldFlags(f.symbol),
- if(isDeprecated(f.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
- )
-
- val jfield: asm.FieldVisitor = jclass.visitField(
- flags,
- javaName(f.symbol),
- javaType(f.symbol.tpe).getDescriptor(),
- javagensig,
- null // no initial value
- )
-
- emitAnnotations(jfield, f.symbol.annotations)
- jfield.visitEnd()
- }
-
- var method: IMethod = _
- var jmethod: asm.MethodVisitor = _
- var jMethodName: String = _
-
- final def emit(opc: Int) { jmethod.visitInsn(opc) }
-
- def genMethod(m: IMethod, isJInterface: Boolean) {
-
- def isClosureApply(sym: Symbol): Boolean = {
- (sym.name == nme.apply) &&
- sym.owner.isSynthetic &&
- sym.owner.tpe.parents.exists { t =>
- val TypeRef(_, sym, _) = t
- FunctionClass.seq contains sym
- }
- }
-
- if (m.symbol.isStaticConstructor || definitions.isGetClass(m.symbol)) return
-
- if (m.params.size > MaximumJvmParameters) {
- reporter.error(m.symbol.pos, s"Platform restriction: a parameter list's length cannot exceed $MaximumJvmParameters.")
- return
- }
-
- debuglog("Generating method " + m.symbol.fullName)
- method = m
- computeLocalVarsIndex(m)
-
- var resTpe: asm.Type = javaType(m.symbol.tpe.resultType)
- if (m.symbol.isClassConstructor)
- resTpe = asm.Type.VOID_TYPE
-
- val flags = mkFlags(
- javaFlags(m.symbol),
- if (isJInterface) asm.Opcodes.ACC_ABSTRACT else 0,
- if (m.symbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0,
- if (method.native) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes
- if(isDeprecated(m.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
- )
-
- // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize }
- val jgensig = getGenericSignature(m.symbol, clasz.symbol)
- addRemoteExceptionAnnot(isRemote(clasz.symbol), hasPublicBitSet(flags), m.symbol)
- val (excs, others) = m.symbol.annotations partition (_.symbol == ThrowsClass)
- val thrownExceptions: List[String] = getExceptions(excs)
-
- jMethodName = javaName(m.symbol)
- val mdesc = asm.Type.getMethodDescriptor(resTpe, (m.params map (p => javaType(p.kind))): _*)
- jmethod = jclass.visitMethod(
- flags,
- jMethodName,
- mdesc,
- jgensig,
- mkArray(thrownExceptions)
- )
-
- // TODO param names: (m.params map (p => javaName(p.sym)))
-
- // typestate: entering mode with valid call sequences: (see ASM Guide, 3.2.1)
- // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )*
-
- emitAnnotations(jmethod, others)
- emitParamAnnotations(jmethod, m.params.map(_.sym.annotations))
-
- // typestate: entering mode with valid call sequences:
- // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd
- // In addition, the visitXInsn and visitLabel methods must be called in the sequential order of the bytecode instructions of the visited code,
- // visitTryCatchBlock must be called before the labels passed as arguments have been visited, and
- // the visitLocalVariable and visitLineNumber methods must be called after the labels passed as arguments have been visited.
-
- val hasAbstractBitSet = ((flags & asm.Opcodes.ACC_ABSTRACT) != 0)
- val hasCodeAttribute = (!hasAbstractBitSet && !method.native)
- if (hasCodeAttribute) {
-
- jmethod.visitCode()
-
- if (emitVars && isClosureApply(method.symbol)) {
- // add a fake local for debugging purposes
- val outerField = clasz.symbol.info.decl(nme.OUTER_LOCAL)
- if (outerField != NoSymbol) {
- log("Adding fake local to represent outer 'this' for closure " + clasz)
- val _this =
- new Local(method.symbol.newVariable(nme.FAKE_LOCAL_THIS),
- toTypeKind(outerField.tpe),
- false)
- m.locals = m.locals ::: List(_this)
- computeLocalVarsIndex(m) // since we added a new local, we need to recompute indexes
- jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0)
- jmethod.visitFieldInsn(asm.Opcodes.GETFIELD,
- javaName(clasz.symbol), // field owner
- javaName(outerField), // field name
- descriptor(outerField) // field descriptor
- )
- assert(_this.kind.isReferenceType, _this.kind)
- jmethod.visitVarInsn(asm.Opcodes.ASTORE, indexOf(_this))
- }
- }
-
- assert( m.locals forall { local => (m.params contains local) == local.arg }, m.locals )
-
- val hasStaticBitSet = ((flags & asm.Opcodes.ACC_STATIC) != 0)
- genCode(m, emitVars, hasStaticBitSet)
-
- // visitMaxs needs to be called according to the protocol. The arguments will be ignored
- // since maximums (and stack map frames) are computed. See ASM Guide, Section 3.2.1,
- // section "ClassWriter options"
- jmethod.visitMaxs(0, 0)
- }
-
- jmethod.visitEnd()
-
- }
-
- def addModuleInstanceField() {
- val fv =
- jclass.visitField(PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
- strMODULE_INSTANCE_FIELD,
- thisDescr,
- null, // no java-generic-signature
- null // no initial value
- )
-
- // typestate: entering mode with valid call sequences:
- // ( visitAnnotation | visitAttribute )* visitEnd.
-
- fv.visitEnd()
- }
-
-
- /* Typestate: should be called before being done with emitting fields (because it invokes addCreatorCode() which adds an IField to the current IClass). */
- def addStaticInit(mopt: Option[IMethod]) {
-
- val clinitMethod: asm.MethodVisitor = jclass.visitMethod(
- PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
- CLASS_CONSTRUCTOR_NAME,
- mdesc_arglessvoid,
- null, // no java-generic-signature
- null // no throwable exceptions
- )
-
- mopt match {
-
- case Some(m) =>
-
- val oldLastBlock = m.lastBlock
- val lastBlock = m.newBlock()
- oldLastBlock.replaceInstruction(oldLastBlock.length - 1, JUMP(lastBlock))
-
- if (isStaticModule(clasz.symbol)) {
- // call object's private ctor from static ctor
- lastBlock emit NEW(REFERENCE(m.symbol.enclClass))
- lastBlock emit CALL_METHOD(m.symbol.enclClass.primaryConstructor, Static(onInstance = true))
- }
-
- if (isParcelableClass) { addCreatorCode(lastBlock) }
-
- lastBlock emit RETURN(UNIT)
- lastBlock.close()
-
- method = m
- jmethod = clinitMethod
- jMethodName = CLASS_CONSTRUCTOR_NAME
- jmethod.visitCode()
- computeLocalVarsIndex(m)
- genCode(m, emitVars = false, isStatic = true)
- jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
- jmethod.visitEnd()
-
- case None =>
- clinitMethod.visitCode()
- legacyStaticInitializer(clinitMethod)
- clinitMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
- clinitMethod.visitEnd()
-
- }
- }
-
- /* used only from addStaticInit() */
- private def legacyStaticInitializer(clinit: asm.MethodVisitor) {
- if (isStaticModule(clasz.symbol)) {
- clinit.visitTypeInsn(asm.Opcodes.NEW, thisName)
- clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL,
- thisName, INSTANCE_CONSTRUCTOR_NAME, mdesc_arglessvoid, false)
- }
-
- if (isParcelableClass) { legacyAddCreatorCode(clinit) }
-
- clinit.visitInsn(asm.Opcodes.RETURN)
- }
-
- // -----------------------------------------------------------------------------------------
- // Emitting bytecode instructions.
- // -----------------------------------------------------------------------------------------
-
- private def genConstant(mv: asm.MethodVisitor, const: Constant) {
- const.tag match {
-
- case BooleanTag => jcode.boolconst(const.booleanValue)
-
- case ByteTag => jcode.iconst(const.byteValue.toInt)
- case ShortTag => jcode.iconst(const.shortValue.toInt)
- case CharTag => jcode.iconst(const.charValue)
- case IntTag => jcode.iconst(const.intValue)
-
- case LongTag => jcode.lconst(const.longValue)
- case FloatTag => jcode.fconst(const.floatValue)
- case DoubleTag => jcode.dconst(const.doubleValue)
-
- case UnitTag => ()
-
- case StringTag =>
- assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant`
- mv.visitLdcInsn(const.stringValue) // `stringValue` special-cases null, but not for a const with StringTag
-
- case NullTag => mv.visitInsn(asm.Opcodes.ACONST_NULL)
-
- case ClazzTag =>
- val kind = toTypeKind(const.typeValue)
- val toPush: asm.Type =
- if (kind.isValueType) classLiteral(kind)
- else javaType(kind)
- mv.visitLdcInsn(toPush)
-
- case EnumTag =>
- val sym = const.symbolValue
- mv.visitFieldInsn(
- asm.Opcodes.GETSTATIC,
- javaName(sym.owner),
- javaName(sym),
- javaType(sym.tpe.underlying).getDescriptor()
- )
-
- case _ => abort("Unknown constant value: " + const)
- }
- }
-
- /** Just a namespace for utilities that encapsulate MethodVisitor idioms.
- * In the ASM world, org.objectweb.asm.commons.InstructionAdapter plays a similar role,
- * but the methods here allow choosing when to transition from ICode to ASM types
- * (including not at all, e.g. for performance).
- */
- object jcode {
-
- import asm.Opcodes
-
- final def boolconst(b: Boolean) { iconst(if(b) 1 else 0) }
-
- def iconst(cst: Char) { iconst(cst.toInt) }
- def iconst(cst: Int) {
- if (cst >= -1 && cst <= 5) {
- jmethod.visitInsn(Opcodes.ICONST_0 + cst)
- } else if (cst >= java.lang.Byte.MIN_VALUE && cst <= java.lang.Byte.MAX_VALUE) {
- jmethod.visitIntInsn(Opcodes.BIPUSH, cst)
- } else if (cst >= java.lang.Short.MIN_VALUE && cst <= java.lang.Short.MAX_VALUE) {
- jmethod.visitIntInsn(Opcodes.SIPUSH, cst)
- } else {
- jmethod.visitLdcInsn(new Integer(cst))
- }
- }
-
- def lconst(cst: Long) {
- if (cst == 0L || cst == 1L) {
- jmethod.visitInsn(Opcodes.LCONST_0 + cst.asInstanceOf[Int])
- } else {
- jmethod.visitLdcInsn(new java.lang.Long(cst))
- }
- }
-
- def fconst(cst: Float) {
- val bits: Int = java.lang.Float.floatToIntBits(cst)
- if (bits == 0L || bits == 0x3f800000 || bits == 0x40000000) { // 0..2
- jmethod.visitInsn(Opcodes.FCONST_0 + cst.asInstanceOf[Int])
- } else {
- jmethod.visitLdcInsn(new java.lang.Float(cst))
- }
- }
-
- def dconst(cst: Double) {
- val bits: Long = java.lang.Double.doubleToLongBits(cst)
- if (bits == 0L || bits == 0x3ff0000000000000L) { // +0.0d and 1.0d
- jmethod.visitInsn(Opcodes.DCONST_0 + cst.asInstanceOf[Int])
- } else {
- jmethod.visitLdcInsn(new java.lang.Double(cst))
- }
- }
-
- def newarray(elem: TypeKind) {
- if(elem.isRefOrArrayType) {
- jmethod.visitTypeInsn(Opcodes.ANEWARRAY, javaType(elem).getInternalName)
- } else {
- val rand = {
- if(elem.isIntSizedType) {
- (elem: @unchecked) match {
- case BOOL => Opcodes.T_BOOLEAN
- case BYTE => Opcodes.T_BYTE
- case SHORT => Opcodes.T_SHORT
- case CHAR => Opcodes.T_CHAR
- case INT => Opcodes.T_INT
- }
- } else {
- (elem: @unchecked) match {
- case LONG => Opcodes.T_LONG
- case FLOAT => Opcodes.T_FLOAT
- case DOUBLE => Opcodes.T_DOUBLE
- }
- }
- }
- jmethod.visitIntInsn(Opcodes.NEWARRAY, rand)
- }
- }
-
-
- def load( idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ILOAD, idx, tk) }
- def store(idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ISTORE, idx, tk) }
-
- def aload( tk: TypeKind) { emitTypeBased(aloadOpcodes, tk) }
- def astore(tk: TypeKind) { emitTypeBased(astoreOpcodes, tk) }
-
- def neg(tk: TypeKind) { emitPrimitive(negOpcodes, tk) }
- def add(tk: TypeKind) { emitPrimitive(addOpcodes, tk) }
- def sub(tk: TypeKind) { emitPrimitive(subOpcodes, tk) }
- def mul(tk: TypeKind) { emitPrimitive(mulOpcodes, tk) }
- def div(tk: TypeKind) { emitPrimitive(divOpcodes, tk) }
- def rem(tk: TypeKind) { emitPrimitive(remOpcodes, tk) }
-
- def invokespecial(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, false)
- }
- def invokestatic(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, false)
- }
- def invokeinterface(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc, true)
- }
- def invokevirtual(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false)
- }
-
- def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) }
- def emitIF(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF(), label) }
- def emitIF_ICMP(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP(), label) }
- def emitIF_ACMP(cond: TestOp, label: asm.Label) {
- assert((cond == EQ) || (cond == NE), cond)
- val opc = (if(cond == EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE)
- jmethod.visitJumpInsn(opc, label)
- }
- def emitIFNONNULL(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNONNULL, label) }
- def emitIFNULL (label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNULL, label) }
-
- def emitRETURN(tk: TypeKind) {
- if(tk == UNIT) { jmethod.visitInsn(Opcodes.RETURN) }
- else { emitTypeBased(returnOpcodes, tk) }
- }
-
- /** Emits one of tableswitch or lookoupswitch. */
- def emitSWITCH(keys: Array[Int], branches: Array[asm.Label], defaultBranch: asm.Label, minDensity: Double) {
- assert(keys.length == branches.length)
-
- // For empty keys, it makes sense emitting LOOKUPSWITCH with defaultBranch only.
- // Similar to what javac emits for a switch statement consisting only of a default case.
- if (keys.length == 0) {
- jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches)
- return
- }
-
- // sort `keys` by increasing key, keeping `branches` in sync. TODO FIXME use quicksort
- var i = 1
- while (i < keys.length) {
- var j = 1
- while (j <= keys.length - i) {
- if (keys(j) < keys(j - 1)) {
- val tmp = keys(j)
- keys(j) = keys(j - 1)
- keys(j - 1) = tmp
- val tmpL = branches(j)
- branches(j) = branches(j - 1)
- branches(j - 1) = tmpL
- }
- j += 1
- }
- i += 1
- }
-
- // check for duplicate keys to avoid "VerifyError: unsorted lookupswitch" (SI-6011)
- i = 1
- while (i < keys.length) {
- if(keys(i-1) == keys(i)) {
- abort("duplicate keys in SWITCH, can't pick arbitrarily one of them to evict, see SI-6011.")
- }
- i += 1
- }
-
- val keyMin = keys(0)
- val keyMax = keys(keys.length - 1)
-
- val isDenseEnough: Boolean = {
- /* Calculate in long to guard against overflow. TODO what overflow??? */
- val keyRangeD: Double = (keyMax.asInstanceOf[Long] - keyMin + 1).asInstanceOf[Double]
- val klenD: Double = keys.length.toDouble
- val kdensity: Double = (klenD / keyRangeD)
-
- kdensity >= minDensity
- }
-
- if (isDenseEnough) {
- // use a table in which holes are filled with defaultBranch.
- val keyRange = (keyMax - keyMin + 1)
- val newBranches = new Array[asm.Label](keyRange)
- var oldPos = 0
- var i = 0
- while(i < keyRange) {
- val key = keyMin + i
- if (keys(oldPos) == key) {
- newBranches(i) = branches(oldPos)
- oldPos += 1
- } else {
- newBranches(i) = defaultBranch
- }
- i += 1
- }
- assert(oldPos == keys.length, "emitSWITCH")
- jmethod.visitTableSwitchInsn(keyMin, keyMax, defaultBranch, newBranches: _*)
- } else {
- jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches)
- }
- }
-
- // internal helpers -- not part of the public API of `jcode`
- // don't make private otherwise inlining will suffer
-
- def emitVarInsn(opc: Int, idx: Int, tk: TypeKind) {
- assert((opc == Opcodes.ILOAD) || (opc == Opcodes.ISTORE), opc)
- jmethod.visitVarInsn(javaType(tk).getOpcode(opc), idx)
- }
-
- // ---------------- array load and store ----------------
-
- val aloadOpcodes = { import Opcodes._; Array(AALOAD, BALOAD, SALOAD, CALOAD, IALOAD, LALOAD, FALOAD, DALOAD) }
- val astoreOpcodes = { import Opcodes._; Array(AASTORE, BASTORE, SASTORE, CASTORE, IASTORE, LASTORE, FASTORE, DASTORE) }
-
- val returnOpcodes = { import Opcodes._; Array(ARETURN, IRETURN, IRETURN, IRETURN, IRETURN, LRETURN, FRETURN, DRETURN) }
-
- def emitTypeBased(opcs: Array[Int], tk: TypeKind) {
- assert(tk != UNIT, tk)
- val opc = {
- if(tk.isRefOrArrayType) { opcs(0) }
- else if(tk.isIntSizedType) {
- (tk: @unchecked) match {
- case BOOL | BYTE => opcs(1)
- case SHORT => opcs(2)
- case CHAR => opcs(3)
- case INT => opcs(4)
- }
- } else {
- (tk: @unchecked) match {
- case LONG => opcs(5)
- case FLOAT => opcs(6)
- case DOUBLE => opcs(7)
- }
- }
- }
- jmethod.visitInsn(opc)
- }
-
- // ---------------- primitive operations ----------------
-
- val negOpcodes: Array[Int] = { import Opcodes._; Array(INEG, LNEG, FNEG, DNEG) }
- val addOpcodes: Array[Int] = { import Opcodes._; Array(IADD, LADD, FADD, DADD) }
- val subOpcodes: Array[Int] = { import Opcodes._; Array(ISUB, LSUB, FSUB, DSUB) }
- val mulOpcodes: Array[Int] = { import Opcodes._; Array(IMUL, LMUL, FMUL, DMUL) }
- val divOpcodes: Array[Int] = { import Opcodes._; Array(IDIV, LDIV, FDIV, DDIV) }
- val remOpcodes: Array[Int] = { import Opcodes._; Array(IREM, LREM, FREM, DREM) }
-
- def emitPrimitive(opcs: Array[Int], tk: TypeKind) {
- val opc = {
- if(tk.isIntSizedType) { opcs(0) }
- else {
- (tk: @unchecked) match {
- case LONG => opcs(1)
- case FLOAT => opcs(2)
- case DOUBLE => opcs(3)
- }
- }
- }
- jmethod.visitInsn(opc)
- }
-
- }
-
- /** Invoked from genMethod() and addStaticInit() */
- def genCode(m: IMethod,
- emitVars: Boolean, // this param name hides the instance-level var
- isStatic: Boolean) {
-
-
- newNormal.normalize(m)
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 1 of genCode(): setting up one-to-one correspondence between ASM Labels and BasicBlocks `linearization`
- // ------------------------------------------------------------------------------------------------------------
-
- val linearization: List[BasicBlock] = linearizer.linearize(m)
- if(linearization.isEmpty) { return }
-
- var isModuleInitialized = false
-
- val labels: scala.collection.Map[BasicBlock, asm.Label] = mutable.HashMap(linearization map (_ -> new asm.Label()) : _*)
-
- val onePastLast = new asm.Label // token for the mythical instruction past the last instruction in the method being emitted
-
- // maps a BasicBlock b to the Label that corresponds to b's successor in the linearization. The last BasicBlock is mapped to the onePastLast label.
- val linNext: scala.collection.Map[BasicBlock, asm.Label] = {
- val result = mutable.HashMap.empty[BasicBlock, asm.Label]
- var rest = linearization
- var prev = rest.head
- rest = rest.tail
- while(!rest.isEmpty) {
- result += (prev -> labels(rest.head))
- prev = rest.head
- rest = rest.tail
- }
- assert(!result.contains(prev))
- result += (prev -> onePastLast)
-
- result
- }
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 2 of genCode(): demarcating exception handler boundaries (visitTryCatchBlock() must be invoked before visitLabel() in genBlock())
- // ------------------------------------------------------------------------------------------------------------
-
- /* Generate exception handlers for the current method.
- *
- * Quoting from the JVMS 4.7.3 The Code Attribute
- * The items of the Code_attribute structure are as follows:
- * . . .
- * exception_table[]
- * Each entry in the exception_table array describes one
- * exception handler in the code array. The order of the handlers in
- * the exception_table array is significant.
- * Each exception_table entry contains the following four items:
- * start_pc, end_pc:
- * ... The value of end_pc either must be a valid index into
- * the code array of the opcode of an instruction or must be equal to code_length,
- * the length of the code array.
- * handler_pc:
- * The value of the handler_pc item indicates the start of the exception handler
- * catch_type:
- * ... If the value of the catch_type item is zero,
- * this exception handler is called for all exceptions.
- * This is used to implement finally
- */
- def genExceptionHandlers() {
-
- /* Return a list of pairs of intervals where the handler is active.
- * Each interval is closed on both ends, ie. inclusive both in the left and right endpoints: [start, end].
- * Preconditions:
- * - e.covered non-empty
- * Postconditions for the result:
- * - always non-empty
- * - intervals are sorted as per `linearization`
- * - the argument's `covered` blocks have been grouped into maximally contiguous intervals,
- * ie. between any two intervals in the result there is a non-empty gap.
- * - each of the `covered` blocks in the argument is contained in some interval in the result
- */
- def intervals(e: ExceptionHandler): List[BlockInteval] = {
- assert(e.covered.nonEmpty, e)
- var result: List[BlockInteval] = Nil
- var rest = linearization
-
- // find intervals
- while(!rest.isEmpty) {
- // find interval start
- var start: BasicBlock = null
- while(!rest.isEmpty && (start eq null)) {
- if(e.covered(rest.head)) { start = rest.head }
- rest = rest.tail
- }
- if(start ne null) {
- // find interval end
- var end = start // for the time being
- while(!rest.isEmpty && (e.covered(rest.head))) {
- end = rest.head
- rest = rest.tail
- }
- result = BlockInteval(start, end) :: result
- }
- }
-
- assert(result.nonEmpty, e)
-
- result
- }
-
- /* TODO test/files/run/exceptions-2.scala displays an ExceptionHandler.covered that contains
- * blocks not in the linearization (dead-code?). Is that well-formed or not?
- * For now, we ignore those blocks (after all, that's what `genBlocks(linearization)` in effect does).
- */
- for (e <- this.method.exh) {
- val ignore: Set[BasicBlock] = (e.covered filterNot { b => linearization contains b } )
- // TODO someday assert(ignore.isEmpty, "an ExceptionHandler.covered contains blocks not in the linearization (dead-code?)")
- if(ignore.nonEmpty) {
- e.covered = e.covered filterNot ignore
- }
- }
-
- // an ExceptionHandler lacking covered blocks doesn't get an entry in the Exceptions table.
- // TODO in that case, ExceptionHandler.cls doesn't go through javaName(). What if cls is an inner class?
- for (e <- this.method.exh ; if e.covered.nonEmpty ; p <- intervals(e)) {
- debuglog("Adding exception handler " + e + "at block: " + e.startBlock + " for " + method +
- " from: " + p.start + " to: " + p.end + " catching: " + e.cls)
- val cls: String = if (e.cls == NoSymbol || e.cls == ThrowableClass) null
- else javaName(e.cls)
- jmethod.visitTryCatchBlock(labels(p.start), linNext(p.end), labels(e.startBlock), cls)
- }
- } // end of genCode()'s genExceptionHandlers()
-
- if (m.exh.nonEmpty) { genExceptionHandlers() }
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 3 of genCode(): "Infrastructure" to later emit debug info for local variables and method params (LocalVariablesTable bytecode attribute).
- // ------------------------------------------------------------------------------------------------------------
-
- case class LocVarEntry(local: Local, start: asm.Label, end: asm.Label) // start is inclusive while end exclusive.
-
- case class Interval(lstart: asm.Label, lend: asm.Label) {
- final def start = lstart.getOffset
- final def end = lend.getOffset
-
- def precedes(that: Interval): Boolean = { this.end < that.start }
-
- def overlaps(that: Interval): Boolean = { !(this.precedes(that) || that.precedes(this)) }
-
- def mergeWith(that: Interval): Interval = {
- val newStart = if(this.start <= that.start) this.lstart else that.lstart
- val newEnd = if(this.end <= that.end) that.lend else this.lend
- Interval(newStart, newEnd)
- }
-
- def repOK: Boolean = { start <= end }
-
- }
-
- /** Track those instruction ranges where certain locals are in scope. Used to later emit the LocalVariableTable attribute (JVMS 4.7.13) */
- object scoping {
-
- private val pending = mutable.Map.empty[Local, mutable.Stack[Label]]
- private var seen: List[LocVarEntry] = Nil
-
- private def fuse(ranges: List[Interval], added: Interval): List[Interval] = {
- assert(added.repOK, added)
- if(ranges.isEmpty) { return List(added) }
- // precond: ranges is sorted by increasing start
- var fused: List[Interval] = Nil
- var done = false
- var rest = ranges
- while(!done && rest.nonEmpty) {
- val current = rest.head
- assert(current.repOK, current)
- rest = rest.tail
- if(added precedes current) {
- fused = fused ::: ( added :: current :: rest )
- done = true
- } else if(current overlaps added) {
- fused = fused ::: ( added.mergeWith(current) :: rest )
- done = true
- }
- }
- if(!done) { fused = fused ::: List(added) }
- assert(repOK(fused), fused)
-
- fused
- }
-
- def pushScope(lv: Local, start: Label) {
- val st = pending.getOrElseUpdate(lv, mutable.Stack.empty[Label])
- st.push(start)
- }
- def popScope(lv: Local, end: Label, iPos: Position) {
- pending.get(lv) match {
- case Some(st) if st.nonEmpty =>
- val start = st.pop()
- seen ::= LocVarEntry(lv, start, end)
- case _ =>
- // TODO SI-6049 track down the cause for these.
- devWarning(s"$iPos: Visited SCOPE_EXIT before visiting corresponding SCOPE_ENTER. SI-6191")
- }
- }
-
- def getMerged(): scala.collection.Map[Local, List[Interval]] = {
- // TODO should but isn't: unbalanced start(s) of scope(s)
- val shouldBeEmpty = pending filter { p => val (_, st) = p; st.nonEmpty }
- val merged = mutable.Map[Local, List[Interval]]()
- def addToMerged(lv: Local, start: Label, end: Label) {
- val intv = Interval(start, end)
- merged(lv) = if (merged contains lv) fuse(merged(lv), intv) else intv :: Nil
- }
- for(LocVarEntry(lv, start, end) <- seen) { addToMerged(lv, start, end) }
-
- /* for each var with unbalanced start(s) of scope(s):
- (a) take the earliest start (among unbalanced and balanced starts)
- (b) take the latest end (onePastLast if none available)
- (c) merge the thus made-up interval
- */
- for((k, st) <- shouldBeEmpty) {
- var start = st.toList.sortBy(_.getOffset).head
- if(merged.isDefinedAt(k)) {
- val balancedStart = merged(k).head.lstart
- if(balancedStart.getOffset < start.getOffset) {
- start = balancedStart
- }
- }
- val endOpt: Option[Label] = for(ranges <- merged.get(k)) yield ranges.last.lend
- val end = endOpt.getOrElse(onePastLast)
- addToMerged(k, start, end)
- }
-
- merged
- }
-
- private def repOK(fused: List[Interval]): Boolean = {
- fused match {
- case Nil => true
- case h :: Nil => h.repOK
- case h :: n :: rest =>
- h.repOK && h.precedes(n) && !h.overlaps(n) && repOK(n :: rest)
- }
- }
-
- }
-
- def genLocalVariableTable() {
- // adding `this` and method params.
- if (!isStatic) {
- jmethod.visitLocalVariable("this", thisDescr, null, labels(m.startBlock), onePastLast, 0)
- }
- for(lv <- m.params) {
- jmethod.visitLocalVariable(javaName(lv.sym), descriptor(lv.kind), null, labels(m.startBlock), onePastLast, indexOf(lv))
- }
- // adding non-param locals
- var anonCounter = 0
- var fltnd: List[Tuple3[String, Local, Interval]] = Nil
- for((local, ranges) <- scoping.getMerged()) {
- var name = javaName(local.sym)
- if (name == null) {
- anonCounter += 1
- name = "<anon" + anonCounter + ">"
- }
- for(intrvl <- ranges) {
- fltnd ::= (name, local, intrvl)
- }
- }
- // quest for deterministic output that Map.toList doesn't provide (so that ant test.stability doesn't complain).
- val srtd = fltnd.sortBy { kr =>
- val (name: String, _, intrvl: Interval) = kr
-
- (intrvl.start, intrvl.end - intrvl.start, name) // ie sort by (start, length, name)
- }
-
- for((name, local, Interval(start, end)) <- srtd) {
- jmethod.visitLocalVariable(name, descriptor(local.kind), null, start, end, indexOf(local))
- }
- // "There may be no more than one LocalVariableTable attribute per local variable in the Code attribute"
- }
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 4 of genCode(): Bookkeeping (to later emit debug info) of association between line-number and instruction position.
- // ------------------------------------------------------------------------------------------------------------
-
- case class LineNumberEntry(line: Int, start: asm.Label)
- var lastLineNr: Int = -1
- var lnEntries: List[LineNumberEntry] = Nil
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 5 of genCode(): "Utilities" to emit code proper (most prominently: genBlock()).
- // ------------------------------------------------------------------------------------------------------------
-
- var nextBlock: BasicBlock = linearization.head
-
- def genBlocks(l: List[BasicBlock]): Unit = l match {
- case Nil => ()
- case x :: Nil => nextBlock = null; genBlock(x)
- case x :: y :: ys => nextBlock = y; genBlock(x); genBlocks(y :: ys)
- }
-
- def genCallMethod(call: CALL_METHOD) {
- val CALL_METHOD(method, style) = call
- val siteSymbol = clasz.symbol
- val hostSymbol = call.hostClass
- val methodOwner = method.owner
- // info calls so that types are up to date; erasure may add lateINTERFACE to traits
- hostSymbol.info ; methodOwner.info
-
- def needsInterfaceCall(sym: Symbol) = (
- sym.isInterface
- || sym.isJavaDefined && sym.isNonBottomSubClass(ClassfileAnnotationClass)
- )
- // whether to reference the type of the receiver or
- // the type of the method owner
- val useMethodOwner = (
- style != Dynamic
- || hostSymbol.isBottomClass
- || methodOwner == ObjectClass
- )
- val receiver = if (useMethodOwner) methodOwner else hostSymbol
- val jowner = javaName(receiver)
- val jname = javaName(method)
- val jtype = javaType(method).getDescriptor()
-
- def dbg(invoke: String) {
- debuglog("%s %s %s.%s:%s".format(invoke, receiver.accessString, jowner, jname, jtype))
- }
-
- def initModule() {
- // we initialize the MODULE$ field immediately after the super ctor
- if (isStaticModule(siteSymbol) && !isModuleInitialized &&
- jMethodName == INSTANCE_CONSTRUCTOR_NAME &&
- jname == INSTANCE_CONSTRUCTOR_NAME) {
- isModuleInitialized = true
- jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0)
- jmethod.visitFieldInsn(asm.Opcodes.PUTSTATIC, thisName, strMODULE_INSTANCE_FIELD, thisDescr)
- }
- }
-
- style match {
- case Static(true) => dbg("invokespecial"); jcode.invokespecial (jowner, jname, jtype)
- case Static(false) => dbg("invokestatic"); jcode.invokestatic (jowner, jname, jtype)
- case Dynamic if needsInterfaceCall(receiver) => dbg("invokinterface"); jcode.invokeinterface(jowner, jname, jtype)
- case Dynamic => dbg("invokevirtual"); jcode.invokevirtual (jowner, jname, jtype)
- case SuperCall(_) =>
- dbg("invokespecial")
- jcode.invokespecial(jowner, jname, jtype)
- initModule()
- }
- } // end of genCode()'s genCallMethod()
-
- def genBlock(b: BasicBlock) {
- jmethod.visitLabel(labels(b))
-
- debuglog("Generating code for block: " + b)
-
- // val lastInstr = b.lastInstruction
-
- for (instr <- b) {
-
- if(instr.pos.isDefined) {
- val iPos = instr.pos
- val currentLineNr = iPos.line
- val skip = (currentLineNr == lastLineNr) // if(iPos.isRange) iPos.sameRange(lastPos) else
- if(!skip) {
- lastLineNr = currentLineNr
- val lineLab = new asm.Label
- jmethod.visitLabel(lineLab)
- lnEntries ::= LineNumberEntry(iPos.finalPosition.line, lineLab)
- }
- }
-
- genInstr(instr, b)
-
- }
-
- }
-
- def genInstr(instr: Instruction, b: BasicBlock) {
- import asm.Opcodes
- (instr.category: @scala.annotation.switch) match {
-
-
- case icodes.localsCat =>
- def genLocalInstr() = (instr: @unchecked) match {
- case THIS(_) => jmethod.visitVarInsn(Opcodes.ALOAD, 0)
- case LOAD_LOCAL(local) => jcode.load(indexOf(local), local.kind)
- case STORE_LOCAL(local) => jcode.store(indexOf(local), local.kind)
- case STORE_THIS(_) =>
- // this only works for impl classes because the self parameter comes first
- // in the method signature. If that changes, this code has to be revisited.
- jmethod.visitVarInsn(Opcodes.ASTORE, 0)
-
- case SCOPE_ENTER(lv) =>
- // locals removed by closelim (via CopyPropagation) may have left behind SCOPE_ENTER, SCOPE_EXIT that are to be ignored
- val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv))
- if (relevant) { // TODO check: does GenICode emit SCOPE_ENTER, SCOPE_EXIT for synthetic vars?
- // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes)
- // similarly, these labels aren't tracked in the `labels` map.
- val start = new asm.Label
- jmethod.visitLabel(start)
- scoping.pushScope(lv, start)
- }
-
- case SCOPE_EXIT(lv) =>
- val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv))
- if (relevant) {
- // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes)
- // similarly, these labels aren't tracked in the `labels` map.
- val end = new asm.Label
- jmethod.visitLabel(end)
- scoping.popScope(lv, end, instr.pos)
- }
- }
- genLocalInstr()
-
- case icodes.stackCat =>
- def genStackInstr() = (instr: @unchecked) match {
-
- case LOAD_MODULE(module) =>
- // assert(module.isModule, "Expected module: " + module)
- debuglog("generating LOAD_MODULE for: " + module + " flags: " + module.flagString)
- def inStaticMethod = this.method != null && this.method.symbol.isStaticMember
- if (clasz.symbol == module.moduleClass && jMethodName != nme.readResolve.toString && !inStaticMethod) {
- jmethod.visitVarInsn(Opcodes.ALOAD, 0)
- } else {
- jmethod.visitFieldInsn(
- Opcodes.GETSTATIC,
- javaName(module) /* + "$" */ ,
- strMODULE_INSTANCE_FIELD,
- descriptor(module))
- }
-
- case DROP(kind) => emit(if (kind.isWideType) Opcodes.POP2 else Opcodes.POP)
-
- case DUP(kind) => emit(if (kind.isWideType) Opcodes.DUP2 else Opcodes.DUP)
-
- case LOAD_EXCEPTION(_) => ()
- }
- genStackInstr()
-
- case icodes.constCat => genConstant(jmethod, instr.asInstanceOf[CONSTANT].constant)
-
- case icodes.arilogCat => genPrimitive(instr.asInstanceOf[CALL_PRIMITIVE].primitive, instr.pos)
-
- case icodes.castsCat =>
- def genCastInstr() = (instr: @unchecked) match {
-
- case IS_INSTANCE(tpe) =>
- val jtyp: asm.Type =
- tpe match {
- case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls))
- case ARRAY(elem) => javaArrayType(javaType(elem))
- case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe)
- }
- jmethod.visitTypeInsn(Opcodes.INSTANCEOF, jtyp.getInternalName)
-
- case CHECK_CAST(tpe) =>
- tpe match {
-
- case REFERENCE(cls) =>
- if (cls != ObjectClass) { // No need to checkcast for Objects
- jmethod.visitTypeInsn(Opcodes.CHECKCAST, javaName(cls))
- }
-
- case ARRAY(elem) =>
- val iname = javaArrayType(javaType(elem)).getInternalName
- jmethod.visitTypeInsn(Opcodes.CHECKCAST, iname)
-
- case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe)
- }
-
- }
- genCastInstr()
-
- case icodes.objsCat =>
- def genObjsInstr() = (instr: @unchecked) match {
- case BOX(kind) =>
- val MethodNameAndType(mname, mdesc) = jBoxTo(kind)
- jcode.invokestatic(BoxesRunTime, mname, mdesc)
-
- case UNBOX(kind) =>
- val MethodNameAndType(mname, mdesc) = jUnboxTo(kind)
- jcode.invokestatic(BoxesRunTime, mname, mdesc)
-
- case NEW(REFERENCE(cls)) =>
- val className = javaName(cls)
- jmethod.visitTypeInsn(Opcodes.NEW, className)
-
- case MONITOR_ENTER() => emit(Opcodes.MONITORENTER)
- case MONITOR_EXIT() => emit(Opcodes.MONITOREXIT)
- }
- genObjsInstr()
-
- case icodes.fldsCat =>
- def genFldsInstr() = (instr: @unchecked) match {
-
- case lf @ LOAD_FIELD(field, isStatic) =>
- val owner = javaName(lf.hostClass)
- debuglog("LOAD_FIELD with owner: " + owner + " flags: " + field.owner.flagString)
- val fieldJName = javaName(field)
- val fieldDescr = descriptor(field)
- val opc = if (isStatic) Opcodes.GETSTATIC else Opcodes.GETFIELD
- jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
-
- case STORE_FIELD(field, isStatic) =>
- val owner = javaName(field.owner)
- val fieldJName = javaName(field)
- val fieldDescr = descriptor(field)
- val opc = if (isStatic) Opcodes.PUTSTATIC else Opcodes.PUTFIELD
- jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
-
- }
- genFldsInstr()
-
- case icodes.mthdsCat =>
- def genMethodsInstr() = (instr: @unchecked) match {
-
- /* Special handling to access native Array.clone() */
- case call @ CALL_METHOD(definitions.Array_clone, Dynamic) =>
- val target: String = javaType(call.targetTypeKind).getInternalName
- jcode.invokevirtual(target, "clone", mdesc_arrayClone)
-
- case call @ CALL_METHOD(method, style) => genCallMethod(call)
-
- }
- genMethodsInstr()
-
- case icodes.arraysCat =>
- def genArraysInstr() = (instr: @unchecked) match {
- case LOAD_ARRAY_ITEM(kind) => jcode.aload(kind)
- case STORE_ARRAY_ITEM(kind) => jcode.astore(kind)
- case CREATE_ARRAY(elem, 1) => jcode newarray elem
- case CREATE_ARRAY(elem, dims) => jmethod.visitMultiANewArrayInsn(descriptor(ArrayN(elem, dims)), dims)
- }
- genArraysInstr()
-
- case icodes.jumpsCat =>
- def genJumpInstr() = (instr: @unchecked) match {
-
- case sw @ SWITCH(tagss, branches) =>
- assert(branches.length == tagss.length + 1, sw)
- val flatSize = sw.flatTagsCount
- val flatKeys = new Array[Int](flatSize)
- val flatBranches = new Array[asm.Label](flatSize)
-
- var restTagss = tagss
- var restBranches = branches
- var k = 0 // ranges over flatKeys and flatBranches
- while (restTagss.nonEmpty) {
- val currLabel = labels(restBranches.head)
- for (cTag <- restTagss.head) {
- flatKeys(k) = cTag
- flatBranches(k) = currLabel
- k += 1
- }
- restTagss = restTagss.tail
- restBranches = restBranches.tail
- }
- val defaultLabel = labels(restBranches.head)
- assert(restBranches.tail.isEmpty)
- debuglog("Emitting SWITCH:\ntags: " + tagss + "\nbranches: " + branches)
- jcode.emitSWITCH(flatKeys, flatBranches, defaultLabel, MIN_SWITCH_DENSITY)
-
- case JUMP(whereto) =>
- if (nextBlock != whereto)
- jcode goTo labels(whereto)
- // 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))) {
- devWarning("Had a jump only block that wasn't collapsed")
- emit(asm.Opcodes.NOP)
- }
-
- case CJUMP(success, failure, cond, kind) =>
- if (kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
- if (nextBlock == success) {
- jcode.emitIF_ICMP(cond.negate(), labels(failure))
- // .. and fall through to success label
- } else {
- jcode.emitIF_ICMP(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- } else if (kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_)
- if (nextBlock == success) {
- jcode.emitIF_ACMP(cond.negate(), labels(failure))
- // .. and fall through to success label
- } else {
- jcode.emitIF_ACMP(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- } else {
- (kind: @unchecked) match {
- case LONG => emit(Opcodes.LCMP)
- case FLOAT =>
- if (cond == LT || cond == LE) emit(Opcodes.FCMPG)
- else emit(Opcodes.FCMPL)
- case DOUBLE =>
- if (cond == LT || cond == LE) emit(Opcodes.DCMPG)
- else emit(Opcodes.DCMPL)
- }
- if (nextBlock == success) {
- jcode.emitIF(cond.negate(), labels(failure))
- // .. and fall through to success label
- } else {
- jcode.emitIF(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- }
-
- case CZJUMP(success, failure, cond, kind) =>
- if (kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
- if (nextBlock == success) {
- jcode.emitIF(cond.negate(), labels(failure))
- } else {
- jcode.emitIF(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- } else if (kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_)
- val Success = success
- val Failure = failure
- // @unchecked because references aren't compared with GT, GE, LT, LE.
- ((cond, nextBlock): @unchecked) match {
- case (EQ, Success) => jcode emitIFNONNULL labels(failure)
- case (NE, Failure) => jcode emitIFNONNULL labels(success)
- case (EQ, Failure) => jcode emitIFNULL labels(success)
- case (NE, Success) => jcode emitIFNULL labels(failure)
- case (EQ, _) =>
- jcode emitIFNULL labels(success)
- jcode goTo labels(failure)
- case (NE, _) =>
- jcode emitIFNONNULL labels(success)
- jcode goTo labels(failure)
- }
- } else {
- (kind: @unchecked) match {
- case LONG =>
- emit(Opcodes.LCONST_0)
- emit(Opcodes.LCMP)
- case FLOAT =>
- emit(Opcodes.FCONST_0)
- if (cond == LT || cond == LE) emit(Opcodes.FCMPG)
- else emit(Opcodes.FCMPL)
- case DOUBLE =>
- emit(Opcodes.DCONST_0)
- if (cond == LT || cond == LE) emit(Opcodes.DCMPG)
- else emit(Opcodes.DCMPL)
- }
- if (nextBlock == success) {
- jcode.emitIF(cond.negate(), labels(failure))
- } else {
- jcode.emitIF(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- }
-
- }
- genJumpInstr()
-
- case icodes.retCat =>
- def genRetInstr() = (instr: @unchecked) match {
- case RETURN(kind) => jcode emitRETURN kind
- case THROW(_) => emit(Opcodes.ATHROW)
- }
- genRetInstr()
- }
- }
-
- /*
- * Emits one or more conversion instructions based on the types given as arguments.
- *
- * @param from The type of the value to be converted into another type.
- * @param to The type the value will be converted into.
- */
- def emitT2T(from: TypeKind, to: TypeKind) {
- assert(isNonUnitValueTK(from) && isNonUnitValueTK(to), s"Cannot emit primitive conversion from $from to $to")
-
- def pickOne(opcs: Array[Int]) {
- val chosen = (to: @unchecked) match {
- case BYTE => opcs(0)
- case SHORT => opcs(1)
- case CHAR => opcs(2)
- case INT => opcs(3)
- case LONG => opcs(4)
- case FLOAT => opcs(5)
- case DOUBLE => opcs(6)
- }
- if(chosen != -1) { emit(chosen) }
- }
-
- if(from == to) { return }
- // the only conversion involving BOOL that is allowed is (BOOL -> BOOL)
- assert(from != BOOL && to != BOOL, s"inconvertible types : $from -> $to")
-
- if(from.isIntSizedType) { // BYTE, CHAR, SHORT, and INT. (we're done with BOOL already)
-
- val fromByte = { import asm.Opcodes._; Array( -1, -1, I2C, -1, I2L, I2F, I2D) } // do nothing for (BYTE -> SHORT) and for (BYTE -> INT)
- val fromChar = { import asm.Opcodes._; Array(I2B, I2S, -1, -1, I2L, I2F, I2D) } // for (CHAR -> INT) do nothing
- val fromShort = { import asm.Opcodes._; Array(I2B, -1, I2C, -1, I2L, I2F, I2D) } // for (SHORT -> INT) do nothing
- val fromInt = { import asm.Opcodes._; Array(I2B, I2S, I2C, -1, I2L, I2F, I2D) }
-
- (from: @unchecked) match {
- case BYTE => pickOne(fromByte)
- case SHORT => pickOne(fromShort)
- case CHAR => pickOne(fromChar)
- case INT => pickOne(fromInt)
- }
-
- } else { // FLOAT, LONG, DOUBLE
-
- (from: @unchecked) match {
- case FLOAT =>
- import asm.Opcodes.{ F2L, F2D, F2I }
- (to: @unchecked) match {
- case LONG => emit(F2L)
- case DOUBLE => emit(F2D)
- case _ => emit(F2I); emitT2T(INT, to)
- }
-
- case LONG =>
- import asm.Opcodes.{ L2F, L2D, L2I }
- (to: @unchecked) match {
- case FLOAT => emit(L2F)
- case DOUBLE => emit(L2D)
- case _ => emit(L2I); emitT2T(INT, to)
- }
-
- case DOUBLE =>
- import asm.Opcodes.{ D2L, D2F, D2I }
- (to: @unchecked) match {
- case FLOAT => emit(D2F)
- case LONG => emit(D2L)
- case _ => emit(D2I); emitT2T(INT, to)
- }
- }
- }
- } // end of genCode()'s emitT2T()
-
- def genPrimitive(primitive: Primitive, pos: Position) {
-
- import asm.Opcodes
-
- primitive match {
-
- case Negation(kind) => jcode.neg(kind)
-
- case Arithmetic(op, kind) =>
- def genArith() = {
- op match {
-
- case ADD => jcode.add(kind)
- case SUB => jcode.sub(kind)
- case MUL => jcode.mul(kind)
- case DIV => jcode.div(kind)
- case REM => jcode.rem(kind)
-
- case NOT =>
- if(kind.isIntSizedType) {
- emit(Opcodes.ICONST_M1)
- emit(Opcodes.IXOR)
- } else if(kind == LONG) {
- jmethod.visitLdcInsn(new java.lang.Long(-1))
- jmethod.visitInsn(Opcodes.LXOR)
- } else {
- abort("Impossible to negate an " + kind)
- }
-
- case _ =>
- abort("Unknown arithmetic primitive " + primitive)
- }
- }
- genArith()
-
- // TODO Logical's 2nd elem should be declared ValueTypeKind, to better approximate its allowed values (isIntSized, its comments appears to convey)
- // TODO GenICode uses `toTypeKind` to define that elem, `toValueTypeKind` would be needed instead.
- // TODO How about adding some asserts to Logical and similar ones to capture the remaining constraint (UNIT not allowed).
- case Logical(op, kind) =>
- def genLogical() = op match {
- case AND =>
- kind match {
- case LONG => emit(Opcodes.LAND)
- case INT => emit(Opcodes.IAND)
- case _ =>
- emit(Opcodes.IAND)
- if (kind != BOOL) { emitT2T(INT, kind) }
- }
- case OR =>
- kind match {
- case LONG => emit(Opcodes.LOR)
- case INT => emit(Opcodes.IOR)
- case _ =>
- emit(Opcodes.IOR)
- if (kind != BOOL) { emitT2T(INT, kind) }
- }
- case XOR =>
- kind match {
- case LONG => emit(Opcodes.LXOR)
- case INT => emit(Opcodes.IXOR)
- case _ =>
- emit(Opcodes.IXOR)
- if (kind != BOOL) { emitT2T(INT, kind) }
- }
- }
- genLogical()
-
- case Shift(op, kind) =>
- def genShift() = op match {
- case LSL =>
- kind match {
- case LONG => emit(Opcodes.LSHL)
- case INT => emit(Opcodes.ISHL)
- case _ =>
- emit(Opcodes.ISHL)
- emitT2T(INT, kind)
- }
- case ASR =>
- kind match {
- case LONG => emit(Opcodes.LSHR)
- case INT => emit(Opcodes.ISHR)
- case _ =>
- emit(Opcodes.ISHR)
- emitT2T(INT, kind)
- }
- case LSR =>
- kind match {
- case LONG => emit(Opcodes.LUSHR)
- case INT => emit(Opcodes.IUSHR)
- case _ =>
- emit(Opcodes.IUSHR)
- emitT2T(INT, kind)
- }
- }
- genShift()
-
- case Comparison(op, kind) =>
- def genCompare() = op match {
- case CMP =>
- (kind: @unchecked) match {
- case LONG => emit(Opcodes.LCMP)
- }
- case CMPL =>
- (kind: @unchecked) match {
- case FLOAT => emit(Opcodes.FCMPL)
- case DOUBLE => emit(Opcodes.DCMPL)
- }
- case CMPG =>
- (kind: @unchecked) match {
- case FLOAT => emit(Opcodes.FCMPG)
- case DOUBLE => emit(Opcodes.DCMPL) // TODO bug? why not DCMPG? http://docs.oracle.com/javase/specs/jvms/se6/html/Instructions2.doc3.html
-
- }
- }
- genCompare()
-
- case Conversion(src, dst) =>
- debuglog("Converting from: " + src + " to: " + dst)
- emitT2T(src, dst)
-
- case ArrayLength(_) => emit(Opcodes.ARRAYLENGTH)
-
- case StartConcat =>
- jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName)
- jmethod.visitInsn(Opcodes.DUP)
- jcode.invokespecial(
- StringBuilderClassName,
- INSTANCE_CONSTRUCTOR_NAME,
- mdesc_arglessvoid
- )
-
- case StringConcat(el) =>
- val jtype = el match {
- case REFERENCE(_) | ARRAY(_) => JAVA_LANG_OBJECT
- case _ => javaType(el)
- }
- jcode.invokevirtual(
- StringBuilderClassName,
- "append",
- asm.Type.getMethodDescriptor(StringBuilderType, Array(jtype): _*)
- )
-
- case EndConcat =>
- jcode.invokevirtual(StringBuilderClassName, "toString", mdesc_toString)
-
- case _ => abort("Unimplemented primitive " + primitive)
- }
- } // end of genCode()'s genPrimitive()
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 6 of genCode(): the executable part of genCode() starts here.
- // ------------------------------------------------------------------------------------------------------------
-
- genBlocks(linearization)
-
- jmethod.visitLabel(onePastLast)
-
- if(emitLines) {
- for(LineNumberEntry(line, start) <- lnEntries.sortBy(_.start.getOffset)) { jmethod.visitLineNumber(line, start) }
- }
- if(emitVars) { genLocalVariableTable() }
-
- } // end of BytecodeGenerator.genCode()
-
-
- ////////////////////// local vars ///////////////////////
-
- def sizeOf(k: TypeKind): Int = if(k.isWideType) 2 else 1
-
- final def indexOf(local: Local): Int = {
- assert(local.index >= 0, "Invalid index for: " + local + "{" + local.## + "}: ")
- local.index
- }
-
- /**
- * Compute the indexes of each local variable of the given method.
- * *Does not assume the parameters come first!*
- */
- def computeLocalVarsIndex(m: IMethod) {
- var idx = if (m.symbol.isStaticMember) 0 else 1
-
- for (l <- m.params) {
- debuglog("Index value for " + l + "{" + l.## + "}: " + idx)
- l.index = idx
- idx += sizeOf(l.kind)
- }
-
- for (l <- m.locals if !l.arg) {
- debuglog("Index value for " + l + "{" + l.## + "}: " + idx)
- l.index = idx
- idx += sizeOf(l.kind)
- }
- }
-
- } // end of class JPlainBuilder
-
-
- /** builder of mirror classes */
- class JMirrorBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JCommonBuilder(bytecodeWriter, needsOutfile) {
-
- private var cunit: CompilationUnit = _
- def getCurrentCUnit(): CompilationUnit = cunit
-
- /** Generate a mirror class for a top-level module. A mirror class is a class
- * containing only static methods that forward to the corresponding method
- * on the MODULE instance of the given Scala object. It will only be
- * generated if there is no companion class: if there is, an attempt will
- * instead be made to add the forwarder methods to the companion class.
- */
- def genMirrorClass(modsym: Symbol, cunit: CompilationUnit) {
- assert(modsym.companionClass == NoSymbol, modsym)
- innerClassBuffer.clear()
- this.cunit = cunit
- val moduleName = javaName(modsym) // + "$"
- val mirrorName = moduleName.substring(0, moduleName.length() - 1)
-
- val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL)
- val mirrorClass = createJClass(flags,
- mirrorName,
- null /* no java-generic-signature */,
- JAVA_LANG_OBJECT.getInternalName,
- EMPTY_STRING_ARRAY)
-
- log(s"Dumping mirror class for '$mirrorName'")
-
- // typestate: entering mode with valid call sequences:
- // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )*
-
- if(emitSource) {
- mirrorClass.visitSource("" + cunit.source,
- null /* SourceDebugExtension */)
- }
-
- val ssa = getAnnotPickle(mirrorName, modsym.companionSymbol)
- mirrorClass.visitAttribute(if(ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
- emitAnnotations(mirrorClass, modsym.annotations ++ ssa)
-
- // typestate: entering mode with valid call sequences:
- // ( visitInnerClass | visitField | visitMethod )* visitEnd
-
- addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym)
-
- addInnerClasses(modsym, mirrorClass, isMirror = true)
- mirrorClass.visitEnd()
- writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym)
- }
- } // end of class JMirrorBuilder
-
-
- /** builder of bean info classes */
- class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JBuilder(bytecodeWriter, needsOutfile) {
-
- /**
- * Generate a bean info class that describes the given class.
- *
- * @author Ross Judson (ross.judson@soletta.com)
- */
- def genBeanInfoClass(clasz: IClass) {
-
- // val BeanInfoSkipAttr = definitions.getRequiredClass("scala.beans.BeanInfoSkip")
- // val BeanDisplayNameAttr = definitions.getRequiredClass("scala.beans.BeanDisplayName")
- // val BeanDescriptionAttr = definitions.getRequiredClass("scala.beans.BeanDescription")
- // val description = c.symbol getAnnotation BeanDescriptionAttr
- // informProgress(description.toString)
- innerClassBuffer.clear()
-
- val flags = mkFlags(
- javaFlags(clasz.symbol),
- if(isDeprecated(clasz.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
- )
-
- val beanInfoName = (javaName(clasz.symbol) + "BeanInfo")
- val beanInfoClass = createJClass(
- flags,
- beanInfoName,
- null, // no java-generic-signature
- "scala/beans/ScalaBeanInfo",
- EMPTY_STRING_ARRAY
- )
-
- // beanInfoClass typestate: entering mode with valid call sequences:
- // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )*
-
- beanInfoClass.visitSource(
- clasz.cunit.source.toString,
- null /* SourceDebugExtension */
- )
-
- var fieldList = List[String]()
-
- for (f <- clasz.fields if f.symbol.hasGetter;
- g = f.symbol.getterIn(clasz.symbol);
- s = f.symbol.setterIn(clasz.symbol)
- if g.isPublic && !(f.symbol.name startsWith "$")
- ) {
- // inserting $outer breaks the bean
- fieldList = javaName(f.symbol) :: javaName(g) :: (if (s != NoSymbol) javaName(s) else null) :: fieldList
- }
-
- val methodList: List[String] =
- for (m <- clasz.methods
- if !m.symbol.isConstructor &&
- m.symbol.isPublic &&
- !(m.symbol.name startsWith "$") &&
- !m.symbol.isGetter &&
- !m.symbol.isSetter)
- yield javaName(m.symbol)
-
- // beanInfoClass typestate: entering mode with valid call sequences:
- // ( visitInnerClass | visitField | visitMethod )* visitEnd
-
- val constructor = beanInfoClass.visitMethod(
- asm.Opcodes.ACC_PUBLIC,
- INSTANCE_CONSTRUCTOR_NAME,
- mdesc_arglessvoid,
- null, // no java-generic-signature
- EMPTY_STRING_ARRAY // no throwable exceptions
- )
-
- // constructor typestate: entering mode with valid call sequences:
- // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )*
-
- val stringArrayJType: asm.Type = javaArrayType(JAVA_LANG_STRING)
- val conJType: asm.Type =
- asm.Type.getMethodType(
- asm.Type.VOID_TYPE,
- Array(javaType(ClassClass), stringArrayJType, stringArrayJType): _*
- )
-
- def push(lst: List[String]) {
- var fi = 0
- for (f <- lst) {
- constructor.visitInsn(asm.Opcodes.DUP)
- constructor.visitLdcInsn(new java.lang.Integer(fi))
- if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) }
- else { constructor.visitLdcInsn(f) }
- constructor.visitInsn(JAVA_LANG_STRING.getOpcode(asm.Opcodes.IASTORE))
- fi += 1
- }
- }
-
- // constructor typestate: entering mode with valid call sequences:
- // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd
-
- constructor.visitCode()
-
- constructor.visitVarInsn(asm.Opcodes.ALOAD, 0)
- // push the class
- constructor.visitLdcInsn(javaType(clasz.symbol))
-
- // push the string array of field information
- constructor.visitLdcInsn(new java.lang.Integer(fieldList.length))
- constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName)
- push(fieldList)
-
- // push the string array of method information
- constructor.visitLdcInsn(new java.lang.Integer(methodList.length))
- constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName)
- push(methodList)
-
- // invoke the superclass constructor, which will do the
- // necessary java reflection and create Method objects.
- constructor.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, "scala/beans/ScalaBeanInfo", INSTANCE_CONSTRUCTOR_NAME, conJType.getDescriptor, false)
- constructor.visitInsn(asm.Opcodes.RETURN)
-
- constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments
- constructor.visitEnd()
-
- addInnerClasses(clasz.symbol, beanInfoClass)
- beanInfoClass.visitEnd()
-
- writeIfNotTooBig("BeanInfo ", beanInfoName, beanInfoClass, clasz.symbol)
- }
-
- } // end of class JBeanInfoBuilder
-
- /** A namespace for utilities to normalize the code of an IMethod, over and beyond what IMethod.normalize() strives for.
- * In particular, IMethod.normalize() doesn't collapseJumpChains().
- *
- * TODO Eventually, these utilities should be moved to IMethod and reused from normalize() (there's nothing JVM-specific about them).
- */
- object newNormal {
- /**
- * True if a block is "jump only" which is defined
- * as being a block that consists only of 0 or more instructions that
- * won't make it to the JVM followed by a JUMP.
- */
- def isJumpOnly(b: BasicBlock): Boolean = {
- val nonICode = firstNonIcodeOnlyInstructions(b)
- // by definition a block has to have a jump, conditional jump, return, or throw
- assert(nonICode.hasNext, "empty block")
- nonICode.next.isInstanceOf[JUMP]
- }
-
- /**
- * Returns the list of instructions in a block that follow all ICode only instructions,
- * where an ICode only instruction is one that won't make it to the JVM
- */
- private def firstNonIcodeOnlyInstructions(b: BasicBlock): Iterator[Instruction] = {
- def isICodeOnlyInstruction(i: Instruction) = i match {
- case LOAD_EXCEPTION(_) | SCOPE_ENTER(_) | SCOPE_EXIT(_) => true
- case _ => false
- }
- b.iterator dropWhile isICodeOnlyInstruction
- }
-
- /**
- * Returns the target of a block that is "jump only" which is defined
- * as being a block that consists only of 0 or more instructions that
- * won't make it to the JVM followed by a JUMP.
- *
- * @param b The basic block to examine
- * @return Some(target) if b is a "jump only" block or None if it's not
- */
- private def getJumpOnlyTarget(b: BasicBlock): Option[BasicBlock] = {
- val nonICode = firstNonIcodeOnlyInstructions(b)
- // by definition a block has to have a jump, conditional jump, return, or throw
- assert(nonICode.nonEmpty, "empty block")
- nonICode.next match {
- case JUMP(whereto) =>
- assert(!nonICode.hasNext, "A block contains instructions after JUMP (looks like enterIgnoreMode() was itself ignored.)")
- Some(whereto)
- case _ => None
- }
- }
-
- /**
- * Collapse a chain of "jump-only" blocks such as:
- *
- * JUMP b1;
- * b1: JUMP b2;
- * b2: JUMP ... etc.
- *
- * by re-wiring predecessors to target directly the "final destination".
- * Even if covered by an exception handler, a "non-self-loop jump-only block" can always be removed.
-
- * Returns true if any replacement was made, false otherwise.
- *
- * In more detail:
- * Starting at each of the entry points (m.startBlock, the start block of each exception handler)
- * rephrase those control-flow instructions targeting a jump-only block (which jumps to a final destination D) to target D.
- * The blocks thus skipped become eligible to removed by the reachability analyzer
- *
- * Rationale for this normalization:
- * test/files/run/private-inline.scala after -optimize is chock full of
- * BasicBlocks containing just JUMP(whereto), where no exception handler straddles them.
- * They should be collapsed by IMethod.normalize() but aren't.
- * That was fine in FJBG times when by the time the exception table was emitted,
- * it already contained "anchored" labels (ie instruction offsets were known)
- * and thus ranges with identical (start, end) (i.e, identical after GenJVM omitted the JUMPs in question)
- * could be weeded out to avoid "java.lang.ClassFormatError: Illegal exception table range"
- * Now that visitTryCatchBlock() must be called before Labels are resolved,
- * renders the BasicBlocks described above (to recap, consisting of just a JUMP) unreachable.
- */
- private def collapseJumpOnlyBlocks(m: IMethod) {
- assert(m.hasCode, "code-less method")
-
- def rephraseGotos(detour: mutable.Map[BasicBlock, BasicBlock]) {
- def lookup(b: BasicBlock) = detour.getOrElse(b, b)
-
- m.code.startBlock = lookup(m.code.startBlock)
-
- for(eh <- m.exh)
- eh.setStartBlock(lookup(eh.startBlock))
-
- for (b <- m.blocks) {
- def replaceLastInstruction(i: Instruction) = {
- if (b.lastInstruction != i) {
- val idxLast = b.size - 1
- debuglog(s"In block $b, replacing last instruction ${b.lastInstruction} with ${i}")
- b.replaceInstruction(idxLast, i)
- }
- }
-
- b.lastInstruction match {
- case JUMP(whereto) =>
- replaceLastInstruction(JUMP(lookup(whereto)))
- case CJUMP(succ, fail, cond, kind) =>
- replaceLastInstruction(CJUMP(lookup(succ), lookup(fail), cond, kind))
- case CZJUMP(succ, fail, cond, kind) =>
- replaceLastInstruction(CZJUMP(lookup(succ), lookup(fail), cond, kind))
- case SWITCH(tags, labels) =>
- val newLabels = (labels map lookup)
- replaceLastInstruction(SWITCH(tags, newLabels))
- case _ => ()
- }
- }
- }
-
- /*
- * Computes a mapping from jump only block to its
- * final destination which is either a non-jump-only
- * block or, if it's in a jump-only block cycle, is
- * itself
- */
- def computeDetour: mutable.Map[BasicBlock, BasicBlock] = {
- // fetch the jump only blocks and their immediate destinations
- val pairs = for {
- block <- m.blocks.toIterator
- target <- getJumpOnlyTarget(block)
- } yield(block, target)
-
- // mapping from a jump-only block to our current knowledge of its
- // final destination. Initially it's just jump block to immediate jump
- // target
- val detour = mutable.Map[BasicBlock, BasicBlock](pairs.toSeq:_*)
-
- // for each jump-only block find its final destination
- // taking advantage of the destinations we found for previous
- // blocks
- for (key <- detour.keySet) {
- // we use the Robert Floyd's classic Tortoise and Hare algorithm
- @tailrec
- def findDestination(tortoise: BasicBlock, hare: BasicBlock): BasicBlock = {
- if (tortoise == hare)
- // cycle detected, map key to key
- key
- else if (detour contains hare) {
- // advance hare once
- val hare1 = detour(hare)
- // make sure we can advance hare a second time
- if (detour contains hare1)
- // advance tortoise once and hare a second time
- findDestination(detour(tortoise), detour(hare1))
- else
- // hare1 is not in the map so it's not a jump-only block, it's the destination
- hare1
- } else
- // hare is not in the map so it's not a jump-only block, it's the destination
- hare
- }
- // update the mapping for key based on its final destination
- detour(key) = findDestination(key, detour(key))
- }
- detour
- }
-
- val detour = computeDetour
- rephraseGotos(detour)
-
- if (settings.debug) {
- 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) 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) 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){}'?")
- }
- }
- }
-
- /**
- * Removes all blocks that are unreachable in a method using a standard reachability analysis.
- */
- def elimUnreachableBlocks(m: IMethod) {
- assert(m.hasCode, "code-less method")
-
- // assume nothing is reachable until we prove it can be reached
- val reachable = mutable.Set[BasicBlock]()
-
- // the set of blocks that we know are reachable but have
- // yet to be marked reachable, initially only the start block
- val worklist = mutable.Set(m.startBlock)
-
- while (worklist.nonEmpty) {
- val block = worklist.head
- worklist remove block
- // we know that one is reachable
- reachable add block
- // so are its successors, so go back around and add the ones we still
- // think are unreachable
- worklist ++= (block.successors filterNot reachable)
- }
-
- // exception handlers need to be told not to cover unreachable blocks
- // and exception handlers that no longer cover any blocks need to be
- // removed entirely
- val unusedExceptionHandlers = mutable.Set[ExceptionHandler]()
- for (exh <- m.exh) {
- exh.covered = exh.covered filter reachable
- if (exh.covered.isEmpty) {
- unusedExceptionHandlers += exh
- }
- }
-
- // remove the unused exception handler references
- if (settings.debug)
- for (exh <- unusedExceptionHandlers) debuglog(s"eliding exception handler $exh because it does not cover any reachable blocks")
- m.exh = m.exh filterNot unusedExceptionHandlers
-
- // everything not in the reachable set is unreachable, unused, and unloved. buh bye
- for (b <- m.blocks filterNot reachable) {
- debuglog(s"eliding block $b because it is unreachable")
- m.code removeBlock b
- }
- }
-
- def normalize(m: IMethod) {
- if(!m.hasCode) { return }
- collapseJumpOnlyBlocks(m)
- if (settings.optimise)
- elimUnreachableBlocks(m)
- icodes checkValid m
- }
-
- }
-
- // @M don't generate java generics sigs for (members of) implementation
- // classes, as they are monomorphic (TODO: ok?)
- private def needsGenericSignature(sym: Symbol) = !(
- // PP: This condition used to include sym.hasExpandedName, but this leads
- // to the total loss of generic information if a private member is
- // accessed from a closure: both the field and the accessor were generated
- // without it. This is particularly bad because the availability of
- // generic information could disappear as a consequence of a seemingly
- // unrelated change.
- settings.Ynogenericsig
- || sym.isArtifact
- || sym.isLiftedMethod
- || sym.isBridge
- || (sym.ownerChain exists (_.isImplClass))
- )
-
- final def staticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol, unit: CompilationUnit): String = {
- if (sym.isDeferred) null // only add generic signature if method concrete; bug #1745
- else {
- // SI-3452 Static forwarder generation uses the same erased signature as the method if forwards to.
- // By rights, it should use the signature as-seen-from the module class, and add suitable
- // primitive and value-class boxing/unboxing.
- // But for now, just like we did in mixin, we just avoid writing a wrong generic signature
- // (one that doesn't erase to the actual signature). See run/t3452b for a test case.
- val memberTpe = enteringErasure(moduleClass.thisType.memberInfo(sym))
- val erasedMemberType = erasure.erasure(sym)(memberTpe)
- if (erasedMemberType =:= sym.info)
- getGenericSignature(sym, moduleClass, memberTpe, unit)
- else null
- }
- }
-
- /** @return
- * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases).
- * - otherwise the signature in question
- */
- def getGenericSignature(sym: Symbol, owner: Symbol, unit: CompilationUnit): String = {
- val memberTpe = enteringErasure(owner.thisType.memberInfo(sym))
- getGenericSignature(sym, owner, memberTpe, unit)
- }
- def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type, unit: CompilationUnit): String = {
- if (!needsGenericSignature(sym)) { return null }
-
- val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe)
- if (jsOpt.isEmpty) { return null }
-
- val sig = jsOpt.get
- log(sig) // This seems useful enough in the general case.
-
- def wrap(op: => Unit) = {
- try { op; true }
- catch { case _: Throwable => false }
- }
-
- if (settings.Xverify) {
- // Run the signature parser to catch bogus signatures.
- val isValidSignature = wrap {
- // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser)
- import scala.tools.asm.util.CheckClassAdapter
- if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } // requires asm-util.jar
- else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig }
- else { CheckClassAdapter checkClassSignature sig }
- }
-
- if(!isValidSignature) {
- reporter.warning(sym.pos,
- """|compiler bug: created invalid generic signature for %s in %s
- |signature: %s
- |if this is reproducible, please report bug at https://issues.scala-lang.org/
- """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig))
- return null
- }
- }
-
- if ((settings.check containsName phaseName)) {
- val normalizedTpe = enteringErasure(erasure.prepareSigMap(memberTpe))
- val bytecodeTpe = owner.thisType.memberInfo(sym)
- if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) {
- reporter.warning(sym.pos,
- """|compiler bug: created generic signature for %s in %s that does not conform to its erasure
- |signature: %s
- |original type: %s
- |normalized type: %s
- |erasure type: %s
- |if this is reproducible, please report bug at http://issues.scala-lang.org/
- """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig, memberTpe, normalizedTpe, bytecodeTpe))
- return null
- }
- }
-
- sig
- }
-
- def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = {
- val ca = new Array[Char](bytes.length)
- var idx = 0
- while(idx < bytes.length) {
- val b: Byte = bytes(idx)
- assert((b & ~0x7f) == 0)
- ca(idx) = b.asInstanceOf[Char]
- idx += 1
- }
-
- ca
- }
-
- final def arrEncode(sb: ScalaSigBytes): Array[String] = {
- var strs: List[String] = Nil
- val bSeven: Array[Byte] = sb.sevenBitsMayBeZero
- // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure)
- var prevOffset = 0
- var offset = 0
- var encLength = 0
- while(offset < bSeven.length) {
- val deltaEncLength = (if(bSeven(offset) == 0) 2 else 1)
- val newEncLength = encLength.toLong + deltaEncLength
- if(newEncLength >= 65535) {
- val ba = bSeven.slice(prevOffset, offset)
- strs ::= new java.lang.String(ubytesToCharArray(ba))
- encLength = 0
- prevOffset = offset
- } else {
- encLength += deltaEncLength
- offset += 1
- }
- }
- if(prevOffset < offset) {
- assert(offset == bSeven.length)
- val ba = bSeven.slice(prevOffset, offset)
- strs ::= new java.lang.String(ubytesToCharArray(ba))
- }
- assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict?
- strs.reverse.toArray
- }
-
- private def strEncode(sb: ScalaSigBytes): String = {
- val ca = ubytesToCharArray(sb.sevenBitsMayBeZero)
- new java.lang.String(ca)
- // debug val bvA = new asm.ByteVector; bvA.putUTF8(s)
- // debug val enc: Array[Byte] = scala.reflect.internal.pickling.ByteCodecs.encode(bytes)
- // debug assert(enc(idx) == bvA.getByte(idx + 2))
- // debug assert(bvA.getLength == enc.size + 2)
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
index af962c4ce0..6593d4b725 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
@@ -14,6 +14,7 @@ import scala.reflect.internal.util.Statistics
import scala.tools.asm
import scala.tools.asm.tree.ClassNode
+import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository
/*
* Prepare in-memory representations of classfiles using the ASM Tree API, and serialize them to disk.
@@ -76,15 +77,16 @@ abstract class GenBCode extends BCodeSyncAndTry {
/* ---------------- q2 ---------------- */
- case class Item2(arrivalPos: Int,
- mirror: asm.tree.ClassNode,
- plain: asm.tree.ClassNode,
- bean: asm.tree.ClassNode,
- outFolder: scala.tools.nsc.io.AbstractFile) {
+ case class Item2(arrivalPos: Int,
+ mirror: asm.tree.ClassNode,
+ plain: asm.tree.ClassNode,
+ bean: asm.tree.ClassNode,
+ sourceFilePath: String,
+ outFolder: scala.tools.nsc.io.AbstractFile) {
def isPoison = { arrivalPos == Int.MaxValue }
}
- private val poison2 = Item2(Int.MaxValue, null, null, null, null)
+ private val poison2 = Item2(Int.MaxValue, null, null, null, null, null)
private val q2 = new _root_.java.util.LinkedList[Item2]
/* ---------------- q3 ---------------- */
@@ -134,7 +136,7 @@ abstract class GenBCode extends BCodeSyncAndTry {
return
}
else {
- try { withCurrentUnit(item.cunit)(visit(item)) }
+ try { withCurrentUnitNoLog(item.cunit)(visit(item)) }
catch {
case ex: Throwable =>
ex.printStackTrace()
@@ -186,7 +188,7 @@ abstract class GenBCode extends BCodeSyncAndTry {
// -------------- "plain" class --------------
val pcb = new PlainClassBuilder(cunit)
pcb.genPlainClass(cd)
- val outF = if (needsOutFolder) getOutFolder(claszSymbol, pcb.thisName, cunit) else null;
+ val outF = if (needsOutFolder) getOutFolder(claszSymbol, pcb.thisBType.internalName, cunit) else null
val plainC = pcb.cnode
// -------------- bean info class, if needed --------------
@@ -204,6 +206,7 @@ abstract class GenBCode extends BCodeSyncAndTry {
val item2 =
Item2(arrivalPos,
mirrorC, plainC, beanC,
+ cunit.source.file.canonicalPath,
outF)
q2 add item2 // at the very end of this method so that no Worker2 thread starts mutating before we're done.
@@ -220,17 +223,24 @@ abstract class GenBCode extends BCodeSyncAndTry {
*/
class Worker2 {
def runGlobalOptimizations(): Unit = {
- import scala.collection.convert.decorateAsScala._
- if (settings.YoptBuildCallGraph) {
- 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) callGraph.addClass(plain)
- }
+ import scala.collection.JavaConverters._
+
+ // add classes to the bytecode repo before building the call graph: the latter needs to
+ // look up classes and methods in the code repo.
+ if (settings.optAddToBytecodeRepository) q2.asScala foreach {
+ case Item2(_, mirror, plain, bean, sourceFilePath, _) =>
+ val someSourceFilePath = Some(sourceFilePath)
+ if (mirror != null) byteCodeRepository.add(mirror, someSourceFilePath)
+ if (plain != null) byteCodeRepository.add(plain, someSourceFilePath)
+ if (bean != null) byteCodeRepository.add(bean, someSourceFilePath)
+ }
+ if (settings.optBuildCallGraph) q2.asScala foreach { item =>
+ // skip call graph for mirror / bean: wd don't inline into tem, and they are not used in the plain class
+ if (item.plain != null) callGraph.addClass(item.plain)
}
- if (settings.YoptInlinerEnabled)
+ if (settings.optInlinerEnabled)
bTypes.inliner.runInliner()
- if (settings.YoptClosureElimination)
+ if (settings.optClosureInvocations)
closureOptimizer.rewriteClosureApplyInvocations()
}
@@ -238,6 +248,11 @@ abstract class GenBCode extends BCodeSyncAndTry {
BackendStats.timed(BackendStats.methodOptTimer)(localOpt.methodOptimizations(classNode))
}
+ def setInnerClasses(classNode: ClassNode): Unit = if (classNode != null) {
+ classNode.innerClasses.clear()
+ addInnerClasses(classNode, bTypes.backendUtils.collectNestedClasses(classNode))
+ }
+
def run() {
runGlobalOptimizations()
@@ -250,8 +265,17 @@ abstract class GenBCode extends BCodeSyncAndTry {
else {
try {
localOptimizations(item.plain)
+ setInnerClasses(item.plain)
+ val lambdaImplMethods = getIndyLambdaImplMethods(item.plain.name)
+ if (lambdaImplMethods.nonEmpty)
+ backendUtils.addLambdaDeserialize(item.plain, lambdaImplMethods)
+ setInnerClasses(item.mirror)
+ setInnerClasses(item.bean)
addToQ3(item)
} catch {
+ case e: java.lang.RuntimeException if e.getMessage != null && (e.getMessage contains "too large!") =>
+ reporter.error(NoPosition,
+ s"Could not write class ${item.plain.name} because it exceeds JVM code size limits. ${e.getMessage}")
case ex: Throwable =>
ex.printStackTrace()
error(s"Error while emitting ${item.plain.name}\n${ex.getMessage}")
@@ -268,7 +292,7 @@ abstract class GenBCode extends BCodeSyncAndTry {
cw.toByteArray
}
- val Item2(arrivalPos, mirror, plain, bean, outFolder) = item
+ val Item2(arrivalPos, mirror, plain, bean, _, outFolder) = item
val mirrorC = if (mirror == null) null else SubItem3(mirror.name, getByteArray(mirror))
val plainC = SubItem3(plain.name, getByteArray(plain))
@@ -313,7 +337,7 @@ abstract class GenBCode extends BCodeSyncAndTry {
bTypes.initializeCoreBTypes()
bTypes.javaDefinedClasses.clear()
bTypes.javaDefinedClasses ++= currentRun.symSource collect {
- case (sym, _) if sym.isJavaDefined => sym.javaBinaryName.toString
+ case (sym, _) if sym.isJavaDefined => sym.javaBinaryNameString
}
Statistics.stopTimer(BackendStats.bcodeInitTimer, initStart)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala
index 7bbe1e2a49..086946e4e3 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala
@@ -3,17 +3,22 @@ package backend.jvm
package analysis
import scala.annotation.switch
-import scala.collection.{mutable, immutable}
+import scala.collection.mutable
import scala.tools.asm.Opcodes
import scala.tools.asm.tree._
import scala.tools.asm.tree.analysis.{Analyzer, Value, Frame, Interpreter}
import opt.BytecodeUtils._
+import AliasSet.SmallBitSet
-object AliasingFrame {
- private var _idCounter: Long = 0l
- private def nextId = { _idCounter += 1; _idCounter }
-}
-
+/**
+ * A subclass of Frame that tracks aliasing of values stored in local variables and on the stack.
+ *
+ * Note: an analysis tracking aliases is roughly 5x slower than a usual analysis (assuming a simple
+ * value domain with a fast merge function). For example, nullness analysis is roughly 5x slower
+ * than a BasicValue analysis.
+ *
+ * See the doc of package object `analysis` for some notes on the performance of alias analysis.
+ */
class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLocals, nStack) {
import Opcodes._
@@ -23,63 +28,80 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc
init(src)
}
- /**
- * For each slot (entry in the `values` array of the frame), an id that uniquely represents
- * the object stored in it. If two values have the same id, they are aliases of the same
- * object.
- */
- private val aliasIds: Array[Long] = Array.fill(nLocals + nStack)(AliasingFrame.nextId)
+ override def toString: String = super.toString + " - " + aliases.toList.filter(s => s != null && s.size > 1).map(_.toString).distinct.mkString(",")
/**
- * The object alias id of for a value index.
- */
- def aliasId(entry: Int) = aliasIds(entry)
-
- /**
- * Returns the indices of the values array which are aliases of the object `id`.
+ * For every value the set of values that are aliases of it.
+ *
+ * Invariants:
+ * - If `aliases(i) == null` then i has no aliases. This is equivalent to having
+ * `aliases(i) == SingletonSet(i)`.
+ * - If `aliases(i) != null` then `aliases(i) contains i`.
+ * - If `aliases(i) contains j` then `aliases(i) eq aliases(j)`, i.e., they are references to the
+ * same (mutable) AliasSet.
*/
- def valuesWithAliasId(id: Long): Set[Int] = immutable.BitSet.empty ++ aliasIds.indices.iterator.filter(i => aliasId(i) == id)
+ val aliases: Array[AliasSet] = new Array[AliasSet](getLocals + getMaxStackSize)
/**
* The set of aliased values for a given entry in the `values` array.
*/
- def aliasesOf(entry: Int): Set[Int] = valuesWithAliasId(aliasIds(entry))
+ def aliasesOf(entry: Int): AliasSet = {
+ if (aliases(entry) != null) aliases(entry)
+ else {
+ val init = new AliasSet(new AliasSet.SmallBitSet(entry, -1, -1, -1), 1)
+ aliases(entry) = init
+ init
+ }
+ }
/**
- * Define a new alias. For example, given
- * var a = this // this, a have the same aliasId
- * then an assignment
+ * Define a new alias. For example, an assignment
* b = a
- * will set the same the aliasId for `b`.
+ * adds b to the set of aliases of a.
*/
private def newAlias(assignee: Int, source: Int): Unit = {
- aliasIds(assignee) = aliasIds(source)
+ removeAlias(assignee)
+ val sourceAliases = aliasesOf(source)
+ sourceAliases += assignee
+ aliases(assignee) = sourceAliases
}
/**
- * An assignment
+ * Remove an alias. For example, an assignment
* a = someUnknownValue()
- * sets a fresh alias id for `a`.
- * A stack value is also removed from its alias set when being consumed.
+ * removes a from its former alias set.
+ * As another example, stack values are removed from their alias sets when being consumed.
*/
private def removeAlias(assignee: Int): Unit = {
- aliasIds(assignee) = AliasingFrame.nextId
+ if (aliases(assignee) != null) {
+ aliases(assignee) -= assignee
+ aliases(assignee) = null
+ }
+ }
+
+ /**
+ * Define the alias set for a given value.
+ */
+ private def setAliasSet(assignee: Int, set: AliasSet): Unit = {
+ if (aliases(assignee) != null) {
+ aliases(assignee) -= assignee
+ }
+ aliases(assignee) = set
}
override def execute(insn: AbstractInsnNode, interpreter: Interpreter[V]): Unit = {
- // Make the extendsion methods easier to use (otherwise we have to repeat `this`.stackTop)
+ // Make the extension methods easier to use (otherwise we have to repeat `this`.stackTop)
def stackTop: Int = this.stackTop
def peekStack(n: Int): V = this.peekStack(n)
- // the val pattern `val (p, c) = f` still allocates a tuple (https://github.com/scala-opt/scala/issues/28)
- val prodCons = InstructionStackEffect(insn, this) // needs to be called before super.execute, see its doc
- val consumed = prodCons._1
- val produced = prodCons._2
+ val prodCons = InstructionStackEffect.forAsmAnalysis(insn, this) // needs to be called before super.execute, see its doc
+ val consumed = InstructionStackEffect.cons(prodCons)
+ val produced = InstructionStackEffect.prod(prodCons)
super.execute(insn, interpreter)
(insn.getOpcode: @switch) match {
- case ALOAD =>
+ case ILOAD | LLOAD | FLOAD | DLOAD | ALOAD =>
newAlias(assignee = stackTop, source = insn.asInstanceOf[VarInsnNode].`var`)
case DUP =>
@@ -166,31 +188,54 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc
}
case SWAP =>
+ // could be written more elegantly with higher-order combinators, but thinking of performance
val top = stackTop
- val idTop = aliasIds(top)
- aliasIds(top) = aliasIds(top - 1)
- aliasIds(top - 1) = idTop
- case opcode =>
- if (opcode == ASTORE) {
- // Not a separate case because we need to remove the consumed stack value from alias sets after.
- val stackTopBefore = stackTop - produced + consumed
- val local = insn.asInstanceOf[VarInsnNode].`var`
- newAlias(assignee = local, source = stackTopBefore)
- // if the value written is size 2, it overwrites the subsequent slot, which is then no
- // longer an alias of anything. see the corresponding case in `Frame.execute`.
- if (getLocal(local).getSize == 2)
- removeAlias(local + 1)
-
- // if the value at the preceding index is size 2, it is no longer valid, so we remove its
- // aliasing. see corresponding case in `Frame.execute`
- if (local > 0) {
- val precedingValue = getLocal(local - 1)
- if (precedingValue != null && precedingValue.getSize == 2)
- removeAlias(local - 1)
+ def moveNextToTop(): Unit = {
+ val nextAliases = aliases(top - 1)
+ aliases(top) = nextAliases
+ nextAliases -= (top - 1)
+ nextAliases += top
+ }
+
+ if (aliases(top) != null) {
+ val topAliases = aliases(top)
+ if (aliases(top - 1) != null) moveNextToTop()
+ else aliases(top) = null
+ // move top to next
+ aliases(top - 1) = topAliases
+ topAliases -= top
+ topAliases += (top - 1)
+ } else {
+ if (aliases(top - 1) != null) {
+ moveNextToTop()
+ aliases(top - 1) = null
}
}
+ case opcode =>
+ (opcode: @switch) match {
+ case ISTORE | LSTORE | FSTORE | DSTORE | ASTORE =>
+ // not a separate case: we re-use the code below that removes the consumed stack value from alias sets
+ val stackTopBefore = stackTop - produced + consumed
+ val local = insn.asInstanceOf[VarInsnNode].`var`
+ newAlias(assignee = local, source = stackTopBefore)
+ // if the value written is size 2, it overwrites the subsequent slot, which is then no
+ // longer an alias of anything. see the corresponding case in `Frame.execute`.
+ if (getLocal(local).getSize == 2)
+ removeAlias(local + 1)
+
+ // if the value at the preceding index is size 2, it is no longer valid, so we remove its
+ // aliasing. see corresponding case in `Frame.execute`
+ if (local > 0) {
+ val precedingValue = getLocal(local - 1)
+ if (precedingValue != null && precedingValue.getSize == 2)
+ removeAlias(local - 1)
+ }
+
+ case _ =>
+ }
+
// Remove consumed stack values from aliasing sets.
// Example: iadd
// - before: local1, local2, stack1, consumed1, consumed2
@@ -198,10 +243,22 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc
val firstConsumed = stackTop - produced + 1 // firstConsumed = 3
for (i <- 0 until consumed)
removeAlias(firstConsumed + i) // remove aliases for 3 and 4
+ }
+ }
- // We don't need to set the aliases ids for the produced values: the aliasIds array already
- // contains fresh ids for non-used stack values (ensured by removeAlias).
+ /**
+ * When entering an exception handler, all values are dropped from the stack (and the exception
+ * value is pushed). The ASM analyzer invokes `firstHandlerInstructionFrame.clearStack()`. To
+ * ensure consistent aliasing sets, we need to remove the dropped values from aliasing sets.
+ */
+ override def clearStack(): Unit = {
+ var i = getLocals
+ val end = i + getStackSize
+ while (i < end) {
+ removeAlias(i)
+ i += 1
}
+ super.clearStack()
}
/**
@@ -217,30 +274,131 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc
* x = a
* y = b // (x, a) and (y, b)
* }
- * [...] // (x, a)
+ * [...] // (x, a) -- merge of ((x, y, a)) and ((x, a), (y, b))
*/
override def merge(other: Frame[_ <: V], interpreter: Interpreter[V]): Boolean = {
+ // merge is the main performance hot spot of a data flow analysis.
+
+ // in nullness analysis, super.merge (which actually merges the nullness values) takes 20% of
+ // the overall analysis time.
val valuesChanged = super.merge(other, interpreter)
+
+ // in nullness analysis, merging the alias sets takes ~55% of the analysis time. therefore, this
+ // code has been heavily optimized. most of the time is spent in the `hasNext` method of the
+ // andNotIterator, see its comment.
+
var aliasesChanged = false
val aliasingOther = other.asInstanceOf[AliasingFrame[_]]
- for (i <- aliasIds.indices) {
- val thisAliases = aliasesOf(i)
- val thisNotOther = thisAliases diff (thisAliases intersect aliasingOther.aliasesOf(i))
- if (thisNotOther.nonEmpty) {
- aliasesChanged = true
- thisNotOther foreach removeAlias
+
+ val numValues = getLocals + getStackSize
+ // assume (a, b) are aliases both in this frame, and the other frame. when merging the alias set
+ // for a, we already see that a and b will be aliases in the final result. so we can skip over
+ // merging the alias set for b. in this case, while merging the sets for a, knownOk(b) will be
+ // set to `true`.
+ val knownOk = new Array[Boolean](numValues)
+ var i = 0
+ while (i < numValues) {
+ if (!knownOk(i)) {
+ val thisAliases = this.aliases(i)
+ val otherAliases = aliasingOther.aliases(i)
+ if (thisAliases != null) {
+ if (otherAliases == null) {
+ if (thisAliases.size > 1) {
+ aliasesChanged = true
+ removeAlias(i)
+ }
+ } else {
+ // The iterator yields elements that are in `thisAliases` but not in `otherAliases`.
+ // As a side-effect, for every index `i` that is in both alias sets, the iterator sets
+ // `knownOk(i) = true`: the alias sets for these values don't need to be merged again.
+ val thisNotOtherIt = AliasSet.andNotIterator(thisAliases, otherAliases, knownOk)
+ if (thisNotOtherIt.hasNext) {
+ aliasesChanged = true
+ val newSet = AliasSet.empty
+ while (thisNotOtherIt.hasNext) {
+ val next = thisNotOtherIt.next()
+ newSet += next
+ setAliasSet(next, newSet)
+ }
+ }
+ }
+ }
}
+ i += 1
}
+
valuesChanged || aliasesChanged
}
+ private def min(s: SmallBitSet) = {
+ var r = s.a
+ if ( s.b < r) r = s.b
+ if (s.c != -1 && s.c < r) r = s.c
+ if (s.d != -1 && s.d < r) r = s.d
+ r
+ }
+
override def init(src: Frame[_ <: V]): Frame[V] = {
- super.init(src)
- compat.Platform.arraycopy(src.asInstanceOf[AliasingFrame[_]].aliasIds, 0, aliasIds, 0, aliasIds.length)
+ super.init(src) // very quick (just an arraycopy)
+ System.arraycopy(src.asInstanceOf[AliasingFrame[_]].aliases, 0, aliases, 0, aliases.length) // also quick
+
+ val newSets = mutable.HashMap.empty[AliasSet, AliasSet]
+
+ // the rest of this method (cloning alias sets) is the second performance˙hotspot (next to
+ // AliasingFrame.merge). for nullness, it takes ~20% of the analysis time.
+ // the difficulty here is that we have to clone the alias sets correctly. if two values a, b are
+ // aliases, then aliases(a) eq aliases(b). we need to make sure to use the same clone for the
+ // two values.
+
+ var i = 0
+ while (i < aliases.length) {
+ val set = aliases(i)
+ if (set != null) {
+ // size cannot be 0 - alias sets are always at least singletons.
+ // for sets of size 1-4, don't use the `newSets` map - lookup / update is slow
+ if (set.size == 1) {
+ aliases(i) = null
+ } else if (set.size <= 4) {
+ val small = set.set.asInstanceOf[AliasSet.SmallBitSet]
+ val firstOfSet = i == min(small)
+ if (firstOfSet) {
+ val newSet = set.clone()
+ aliases(small.a) = newSet
+ aliases(small.b) = newSet
+ if (small.c != -1) aliases(small.c) = newSet
+ if (small.d != -1) aliases(small.d) = newSet
+ }
+ } else {
+ // the actual hot spot is the hash map operations here: this is where almost all of the 20%
+ // mentioned above is spent.
+ // i also benchmarked an alternative implementation: keep an array of booleans for indexes
+ // that already contain the cloned set. iterate through all elements of the cloned set and
+ // assign the cloned set. this approach is 50% slower than using a hash map.
+ if (newSets contains set) aliases(i) = newSets(set)
+ else {
+ val newSet = set.clone()
+ newSets(set) = newSet
+ aliases(i) = newSet
+ }
+ }
+ }
+ i += 1
+ }
this
}
}
+object AliasingFrame {
+// val start1 = AliasingFrame.timer1.start()
+// AliasingFrame.timer1.stop(start1)
+ import scala.reflect.internal.util.Statistics._
+ val timer1 = newTimer("t1", "jvm")
+ val timer2 = newTimer("t2", "jvm")
+ val timer3 = newTimer("t3", "jvm")
+ val timers = List(timer1, timer2, timer3)
+ def reset(): Unit = for (t <- timers) { t.nanos = 0; t.timings = 0 }
+}
+
/**
* An analyzer that uses AliasingFrames instead of bare Frames. This can be used when an analysis
* needs to track aliases, but doesn't require a more specific Frame subclass.
@@ -249,3 +407,269 @@ class AliasingAnalyzer[V <: Value](interpreter: Interpreter[V]) extends Analyzer
override def newFrame(nLocals: Int, nStack: Int): AliasingFrame[V] = new AliasingFrame(nLocals, nStack)
override def newFrame(src: Frame[_ <: V]): AliasingFrame[V] = new AliasingFrame(src)
}
+
+/**
+ * An iterator over Int (required to prevent boxing the result of next).
+ */
+abstract class IntIterator extends Iterator[Int] {
+ def hasNext: Boolean
+ def next(): Int
+}
+
+/**
+ * An efficient mutable bit set.
+ *
+ * @param set Either a SmallBitSet or an Array[Long]
+ * @param size The size of the set, useful for performance of certain operations
+ */
+class AliasSet(var set: Object /*SmallBitSet | Array[Long]*/, var size: Int) {
+ import AliasSet._
+
+ override def toString: String = iterator.toSet.mkString("<", ",", ">")
+
+ /**
+ * An iterator for the elements of this bit set. Note that only one iterator can be used at a
+ * time. Also make sure not to change the underlying AliasSet during iteration.
+ */
+ def iterator: IntIterator = andNotIterator(this, empty, null)
+
+ def +=(value: Int): Unit = this.set match {
+ case s: SmallBitSet => (size: @switch) match {
+ case 0 => s.a = value; size = 1
+ case 1 => if (value != s.a) { s.b = value; size = 2 }
+ case 2 => if (value != s.a && value != s.b) { s.c = value; size = 3 }
+ case 3 => if (value != s.a && value != s.b && value != s.c) { s.d = value; size = 4 }
+ case 4 =>
+ if (value != s.a && value != s.b && value != s.c && value != s.d) {
+ this.set = bsEmpty
+ this.size = 0
+ bsAdd(this, s.a)
+ bsAdd(this, s.b)
+ bsAdd(this, s.c)
+ bsAdd(this, s.d)
+ bsAdd(this, value)
+ }
+ }
+ case bits: Array[Long] =>
+ bsAdd(this, value)
+ }
+
+ def -=(value: Int): Unit = this.set match {
+ case s: SmallBitSet => (size: @switch) match {
+ case 0 =>
+ case 1 =>
+ if (value == s.a) { s.a = -1; size = 0 }
+ case 2 =>
+ if (value == s.a) { s.a = s.b; s.b = -1; size = 1 }
+ else if (value == s.b) { s.b = -1; size = 1 }
+ case 3 =>
+ if (value == s.a) { s.a = s.b; s.b = s.c; s.c = -1; size = 2 }
+ else if (value == s.b) { s.b = s.c; s.c = -1; size = 2 }
+ else if (value == s.c) { s.c = -1; size = 2 }
+ case 4 =>
+ if (value == s.a) { s.a = s.b; s.b = s.c; s.c = s.d; s.d = -1; size = 3 }
+ else if (value == s.b) { s.b = s.c; s.c = s.d; s.d = -1; size = 3 }
+ else if (value == s.c) { s.c = s.d; s.d = -1; size = 3 }
+ else if (value == s.d) { s.d = -1; size = 3 }
+ }
+ case bits: Array[Long] =>
+ bsRemove(this, value)
+ if (this.size == 4)
+ this.set = bsToSmall(this.set.asInstanceOf[Array[Long]])
+ }
+
+ override def clone(): AliasSet = {
+ val resSet = this.set match {
+ case s: SmallBitSet => new SmallBitSet(s.a, s.b, s.c, s.d)
+ case bits: Array[Long] => bits.clone()
+ }
+ new AliasSet(resSet, this.size)
+ }
+}
+
+object AliasSet {
+ def empty = new AliasSet(new SmallBitSet(-1, -1, -1, -1), 0)
+
+ final class SmallBitSet(var a: Int, var b: Int, var c: Int, var d: Int) {
+ override def toString = s"($a, $b, $c, $d)"
+ }
+
+ def bsEmpty: Array[Long] = new Array[Long](1)
+
+ private def bsEnsureCapacity(set: Array[Long], index: Int): Array[Long] = {
+ if (index < set.length) set
+ else {
+ var newLength = set.length
+ while (index >= newLength) newLength *= 2
+ val newSet = new Array[Long](newLength)
+ Array.copy(set, 0, newSet, 0, set.length)
+ newSet
+ }
+ }
+
+ def bsAdd(set: AliasSet, bit: Int): Unit = {
+ val bits = set.set.asInstanceOf[Array[Long]]
+ val index = bit >> 6
+ val resSet = bsEnsureCapacity(bits, index)
+ val before = resSet(index)
+ val result = before | (1l << bit)
+ if (result != before) {
+ resSet(index) = result
+ set.set = resSet
+ set.size += 1
+ }
+ }
+
+ def bsRemove(set: AliasSet, bit: Int): Unit = {
+ val bits = set.set.asInstanceOf[Array[Long]]
+ val index = bit >> 6
+ if (index < bits.length) {
+ val before = bits(index)
+ val result = before & ~(1l << bit)
+ if (result != before) {
+ bits(index) = result
+ set.size -= 1
+ }
+ }
+ }
+
+ def bsContains(set: Array[Long], bit: Int): Boolean = {
+ val index = bit >> 6
+ bit >= 0 && index < set.length && (set(index) & (1L << bit)) != 0L
+ }
+
+// var sizesHist: Array[Int] = new Array[Int](1000)
+
+ /**
+ * Convert a bit array to a SmallBitSet. Requires the bit array to contain exactly four bits.
+ */
+ def bsToSmall(bits: Array[Long]): SmallBitSet = {
+ var a = -1
+ var b = -1
+ var c = -1
+ var i = 0
+ val end = bits.length * 64
+ while (i < end) {
+ if (bsContains(bits, i)) {
+ if (a == -1) a = i
+ else if (b == -1) b = i
+ else if (c == -1) c = i
+ else return new SmallBitSet(a, b, c, i)
+ }
+ i += 1
+ }
+ null
+ }
+
+ /**
+ * An iterator that yields the elements that are in one bit set and not in another (&~).
+ */
+ private class AndNotIt(setA: AliasSet, setB: AliasSet, thisAndOther: Array[Boolean]) extends IntIterator {
+ // values in the first bit set
+ private var a, b, c, d = -1
+ private var xs: Array[Long] = null
+
+ // values in the second bit set
+ private var notA, notB, notC, notD = -1
+ private var notXs: Array[Long] = null
+
+ // holds the next value of `x`, `y` or `z` that should be returned. assigned in hasNext
+ private var abcdNext = -1
+
+ // counts through elements in the `xs` bit set
+ private var i = 0
+ // true if the current value of `i` should be returned by this iterator
+ private var iValid = false
+
+ setA.set match {
+ case s: SmallBitSet => a = s.a; b = s.b; c = s.c; d = s.d
+ case bits: Array[Long] => xs = bits
+ }
+
+ setB.set match {
+ case s: SmallBitSet => notA = s.a; notB = s.b; notC = s.c; notD = s.d
+ case bits: Array[Long] => notXs = bits
+ }
+
+ // for each value that exists both in this AND (&) the other bit, `thisAndOther` is set to true.
+ // hacky side-effect, used for performance of AliasingFrame.merge.
+ private def setThisAndOther(x: Int) = if (thisAndOther != null) thisAndOther(x) = true
+
+ private def checkABCD(x: Int, num: Int): Boolean = {
+ // assert(x == a && num == 1 || x == b && num == 2 || ...)
+ x != -1 && {
+ val otherHasA = x == notA || x == notB || x == notC || x == notD || (notXs != null && bsContains(notXs, x))
+ if (otherHasA) setThisAndOther(x)
+ else abcdNext = x
+ (num: @switch) match {
+ case 1 => a = -1
+ case 2 => b = -1
+ case 3 => c = -1
+ case 4 => d = -1
+ }
+ !otherHasA
+ }
+ }
+
+ // main performance hot spot
+ private def checkXs = {
+ (xs != null) && {
+ val end = xs.length * 64
+
+ while (i < end && {
+ val index = i >> 6
+ if (xs(index) == 0l) { // boom. for nullness, this saves 35% of the overall analysis time.
+ i = ((index + 1) << 6) - 1 // -1 required because i is incremented in the loop body
+ true
+ } else {
+ val mask = 1l << i
+ // if (mask > xs(index)) we could also advance i to the next value, but that didn't pay off in benchmarks
+ val thisHasI = (xs(index) & mask) != 0l
+ !thisHasI || {
+ val otherHasI = i == notA || i == notB || i == notC || i == notD || (notXs != null && index < notXs.length && (notXs(index) & mask) != 0l)
+ if (otherHasI) setThisAndOther(i)
+ otherHasI
+ }
+ }
+ }) i += 1
+
+ iValid = i < end
+ iValid
+ }
+ }
+
+ // this is the main hot spot of alias analysis. for nullness, 38% of the overall analysis time
+ // is spent here. within hasNext, almost the entire time is spent in `checkXs`.
+ //
+ def hasNext: Boolean = iValid || abcdNext != -1 || checkABCD(a, 1) || checkABCD(b, 2) || checkABCD(c, 3) || checkABCD(d, 4) || checkXs
+
+ def next(): Int = {
+ if (hasNext) {
+ if (abcdNext != -1) {
+ val r = abcdNext; abcdNext = -1; r
+ } else {
+ val r = i; i += 1; iValid = false; r
+ }
+ } else Iterator.empty.next()
+ }
+ }
+
+// The number of bits in a bit array. Useful for debugging.
+// def bsSize(bits: Array[Long]) = {
+// var r = 0
+// var i = 0
+// while (i < bits.length) {
+// r += java.lang.Long.bitCount(bits(i))
+// i += 1
+// }
+// r
+// }
+
+ /**
+ * An iterator returning the elements in a that are not also in b (a &~ b).
+ *
+ * If `thisAndOther` is non-null, the iterator sets thisAndOther(i) to true for every value that
+ * is both in a and b (&).
+ */
+ def andNotIterator(a: AliasSet, b: AliasSet, thisAndOther: Array[Boolean]): IntIterator = new AndNotIt(a, b, thisAndOther)
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
new file mode 100644
index 0000000000..90da570f01
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
@@ -0,0 +1,514 @@
+package scala.tools.nsc
+package backend.jvm
+package analysis
+
+import java.lang.invoke.LambdaMetafactory
+
+import scala.annotation.switch
+import scala.collection.JavaConverters._
+import scala.collection.mutable
+import scala.tools.asm.Opcodes._
+import scala.tools.asm.tree._
+import scala.tools.asm.tree.analysis._
+import scala.tools.asm.{Handle, Type}
+import scala.tools.nsc.backend.jvm.BTypes._
+import scala.tools.nsc.backend.jvm.GenBCode._
+import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
+
+/**
+ * This component hosts tools and utilities used in the backend that require access to a `BTypes`
+ * instance.
+ *
+ * One example is the AsmAnalyzer class, which runs `computeMaxLocalsMaxStack` on the methodNode to
+ * be analyzed. This method in turn lives inside the BTypes assembly because it queries the per-run
+ * cache `maxLocalsMaxStackComputed` defined in there.
+ */
+class BackendUtils[BT <: BTypes](val btypes: BT) {
+ import btypes._
+ import btypes.coreBTypes._
+ import callGraph.ClosureInstantiation
+
+ /**
+ * A wrapper to make ASM's Analyzer a bit easier to use.
+ */
+ class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, val analyzer: Analyzer[V] = new Analyzer(new BasicInterpreter)) {
+ computeMaxLocalsMaxStack(methodNode)
+ try {
+ analyzer.analyze(classInternalName, methodNode)
+ } catch {
+ case ae: AnalyzerException =>
+ throw new AnalyzerException(null, "While processing " + classInternalName + "." + methodNode.name, ae)
+ }
+ def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode)
+ }
+
+ /**
+ * See the doc comment on package object `analysis` for a discussion on performance.
+ */
+ object AsmAnalyzer {
+ // jvm limit is 65535 for both number of instructions and number of locals
+
+ private def size(method: MethodNode) = method.instructions.size.toLong * method.maxLocals * method.maxLocals
+
+ // with the limits below, analysis should not take more than one second
+
+ private val nullnessSizeLimit = 5000l * 600l * 600l // 5000 insns, 600 locals
+ private val basicValueSizeLimit = 9000l * 1000l * 1000l
+ private val sourceValueSizeLimit = 8000l * 950l * 950l
+
+ def sizeOKForAliasing(method: MethodNode): Boolean = size(method) < nullnessSizeLimit
+ def sizeOKForNullness(method: MethodNode): Boolean = size(method) < nullnessSizeLimit
+ def sizeOKForBasicValue(method: MethodNode): Boolean = size(method) < basicValueSizeLimit
+ def sizeOKForSourceValue(method: MethodNode): Boolean = size(method) < sourceValueSizeLimit
+ }
+
+ class ProdConsAnalyzer(val methodNode: MethodNode, classInternalName: InternalName) extends AsmAnalyzer(methodNode, classInternalName, new Analyzer(new InitialProducerSourceInterpreter)) with ProdConsAnalyzerImpl
+
+ class NonLubbingTypeFlowAnalyzer(val methodNode: MethodNode, classInternalName: InternalName) extends AsmAnalyzer(methodNode, classInternalName, new Analyzer(new NonLubbingTypeFlowInterpreter))
+
+ /**
+ * Add:
+ * private static Object $deserializeLambda$(SerializedLambda l) {
+ * return indy[scala.runtime.LambdaDeserialize.bootstrap](l)
+ * }
+ *
+ * We use invokedynamic here to enable caching within the deserializer without needing to
+ * host a static field in the enclosing class. This allows us to add this method to interfaces
+ * that define lambdas in default methods.
+ */
+ def addLambdaDeserialize(classNode: ClassNode, implMethods: Iterable[Handle]): Unit = {
+ val cw = classNode
+
+ // Make sure to reference the ClassBTypes of all types that are used in the code generated
+ // here (e.g. java/util/Map) are initialized. Initializing a ClassBType adds it to the
+ // `classBTypeFromInternalName` map. When writing the classfile, the asm ClassWriter computes
+ // stack map frames and invokes the `getCommonSuperClass` method. This method expects all
+ // ClassBTypes mentioned in the source code to exist in the map.
+
+ val nilLookupDesc = MethodBType(Nil, jliMethodHandlesLookupRef).descriptor
+ val serlamObjDesc = MethodBType(jliSerializedLambdaRef :: Nil, ObjectRef).descriptor
+
+ {
+ val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", serlamObjDesc, null, null)
+ mv.visitCode()
+ mv.visitVarInsn(ALOAD, 0)
+ mv.visitInvokeDynamicInsn("lambdaDeserialize", serlamObjDesc, lambdaDeserializeBootstrapHandle, implMethods.toArray: _*)
+ mv.visitInsn(ARETURN)
+ mv.visitEnd()
+ }
+ }
+
+ /**
+ * 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, and
+ * a list of lambda implementation methods references by invokedynamic[LambdaMetafactory] for a
+ * serializable SAM types.
+ */
+ def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], keepLineNumbers: Boolean): (InsnList, Map[AbstractInsnNode, AbstractInsnNode], List[Handle]) = {
+ val javaLabelMap = labelMap.asJava
+ val result = new InsnList
+ var map = Map.empty[AbstractInsnNode, AbstractInsnNode]
+ var inlinedTargetHandles = mutable.ListBuffer[Handle]()
+ for (ins <- methodNode.instructions.iterator.asScala) {
+ ins match {
+ case callGraph.LambdaMetaFactoryCall(indy, _, _, _) => indy.bsmArgs match {
+ case Array(_, targetHandle: Handle, _, flags: Integer, xs@_*) if (flags.intValue & LambdaMetafactory.FLAG_SERIALIZABLE) != 0 =>
+ inlinedTargetHandles += targetHandle
+ case _ =>
+ }
+ case _ =>
+ }
+ if (keepLineNumbers || !ins.isInstanceOf[LineNumberNode]) {
+ val cloned = ins.clone(javaLabelMap)
+ result add cloned
+ map += ((ins, cloned))
+ }
+ }
+ (result, map, inlinedTargetHandles.toList)
+ }
+
+ def getBoxedUnit: FieldInsnNode = new FieldInsnNode(GETSTATIC, srBoxedUnitRef.internalName, "UNIT", srBoxedUnitRef.descriptor)
+
+ private val anonfunAdaptedName = """.*\$anonfun\$.*\$\d+\$adapted""".r
+ def hasAdaptedImplMethod(closureInit: ClosureInstantiation): Boolean = {
+ anonfunAdaptedName.pattern.matcher(closureInit.lambdaMetaFactoryCall.implMethod.getName).matches
+ }
+
+ private def primitiveAsmTypeToBType(primitiveType: Type): PrimitiveBType = (primitiveType.getSort: @switch) match {
+ case Type.BOOLEAN => BOOL
+ case Type.BYTE => BYTE
+ case Type.CHAR => CHAR
+ case Type.SHORT => SHORT
+ case Type.INT => INT
+ case Type.LONG => LONG
+ case Type.FLOAT => FLOAT
+ case Type.DOUBLE => DOUBLE
+ case _ => null
+ }
+
+ def isScalaBox(insn: MethodInsnNode): Boolean = {
+ insn.owner == srBoxesRunTimeRef.internalName && {
+ val args = Type.getArgumentTypes(insn.desc)
+ args.length == 1 && (srBoxesRuntimeBoxToMethods.get(primitiveAsmTypeToBType(args(0))) match {
+ case Some(MethodNameAndType(name, tp)) => name == insn.name && tp.descriptor == insn.desc
+ case _ => false
+ })
+ }
+ }
+
+ def getScalaBox(primitiveType: Type): MethodInsnNode = {
+ val bType = primitiveAsmTypeToBType(primitiveType)
+ val MethodNameAndType(name, methodBType) = srBoxesRuntimeBoxToMethods(bType)
+ new MethodInsnNode(INVOKESTATIC, srBoxesRunTimeRef.internalName, name, methodBType.descriptor, /*itf =*/ false)
+ }
+
+ def isScalaUnbox(insn: MethodInsnNode): Boolean = {
+ insn.owner == srBoxesRunTimeRef.internalName && (srBoxesRuntimeUnboxToMethods.get(primitiveAsmTypeToBType(Type.getReturnType(insn.desc))) match {
+ case Some(MethodNameAndType(name, tp)) => name == insn.name && tp.descriptor == insn.desc
+ case _ => false
+ })
+ }
+
+ def getScalaUnbox(primitiveType: Type): MethodInsnNode = {
+ val bType = primitiveAsmTypeToBType(primitiveType)
+ val MethodNameAndType(name, methodBType) = srBoxesRuntimeUnboxToMethods(bType)
+ new MethodInsnNode(INVOKESTATIC, srBoxesRunTimeRef.internalName, name, methodBType.descriptor, /*itf =*/ false)
+ }
+
+ private def calleeInMap(insn: MethodInsnNode, map: Map[InternalName, MethodNameAndType]): Boolean = map.get(insn.owner) match {
+ case Some(MethodNameAndType(name, tp)) => insn.name == name && insn.desc == tp.descriptor
+ case _ => false
+ }
+
+ def isJavaBox(insn: MethodInsnNode): Boolean = calleeInMap(insn, javaBoxMethods)
+ def isJavaUnbox(insn: MethodInsnNode): Boolean = calleeInMap(insn, javaUnboxMethods)
+
+ def isPredefAutoBox(insn: MethodInsnNode): Boolean = {
+ insn.owner == PredefRef.internalName && (predefAutoBoxMethods.get(insn.name) match {
+ case Some(tp) => insn.desc == tp.descriptor
+ case _ => false
+ })
+ }
+
+ def isPredefAutoUnbox(insn: MethodInsnNode): Boolean = {
+ insn.owner == PredefRef.internalName && (predefAutoUnboxMethods.get(insn.name) match {
+ case Some(tp) => insn.desc == tp.descriptor
+ case _ => false
+ })
+ }
+
+ def isRefCreate(insn: MethodInsnNode): Boolean = calleeInMap(insn, srRefCreateMethods)
+ def isRefZero(insn: MethodInsnNode): Boolean = calleeInMap(insn, srRefZeroMethods)
+
+ def runtimeRefClassBoxedType(refClass: InternalName): Type = Type.getArgumentTypes(srRefCreateMethods(refClass).methodType.descriptor)(0)
+
+ def isSideEffectFreeCall(insn: MethodInsnNode): Boolean = {
+ isScalaBox(insn) || isScalaUnbox(insn) ||
+ isJavaBox(insn) || // not java unbox, it may NPE
+ isSideEffectFreeConstructorCall(insn)
+ }
+
+ def isNonNullMethodInvocation(mi: MethodInsnNode): Boolean = {
+ isJavaBox(mi) || isScalaBox(mi) || isPredefAutoBox(mi) || isRefCreate(mi) || isRefZero(mi)
+ }
+
+ def isModuleLoad(insn: AbstractInsnNode, moduleName: InternalName): Boolean = insn match {
+ case fi: FieldInsnNode => fi.getOpcode == GETSTATIC && fi.owner == moduleName && fi.name == "MODULE$" && fi.desc == ("L" + moduleName + ";")
+ case _ => false
+ }
+
+ def isPredefLoad(insn: AbstractInsnNode) = isModuleLoad(insn, PredefRef.internalName)
+
+ def isPrimitiveBoxConstructor(insn: MethodInsnNode): Boolean = calleeInMap(insn, primitiveBoxConstructors)
+ def isRuntimeRefConstructor(insn: MethodInsnNode): Boolean = calleeInMap(insn, srRefConstructors)
+ def isTupleConstructor(insn: MethodInsnNode): Boolean = calleeInMap(insn, tupleClassConstructors)
+
+ // unused objects created by these constructors are eliminated by pushPop
+ private lazy val sideEffectFreeConstructors: Set[(String, String)] = {
+ val ownerDesc = (p: (InternalName, MethodNameAndType)) => (p._1, p._2.methodType.descriptor)
+ primitiveBoxConstructors.map(ownerDesc).toSet ++
+ srRefConstructors.map(ownerDesc) ++
+ tupleClassConstructors.map(ownerDesc) ++ Set(
+ (ObjectRef.internalName, MethodBType(Nil, UNIT).descriptor),
+ (StringRef.internalName, MethodBType(Nil, UNIT).descriptor),
+ (StringRef.internalName, MethodBType(List(StringRef), UNIT).descriptor),
+ (StringRef.internalName, MethodBType(List(ArrayBType(CHAR)), UNIT).descriptor))
+ }
+
+ def isSideEffectFreeConstructorCall(insn: MethodInsnNode): Boolean = {
+ insn.name == INSTANCE_CONSTRUCTOR_NAME && sideEffectFreeConstructors((insn.owner, insn.desc))
+ }
+
+ private lazy val classesOfSideEffectFreeConstructors = sideEffectFreeConstructors.map(_._1)
+
+ def isNewForSideEffectFreeConstructor(insn: AbstractInsnNode) = {
+ insn.getOpcode == NEW && {
+ val ti = insn.asInstanceOf[TypeInsnNode]
+ classesOfSideEffectFreeConstructors.contains(ti.desc)
+ }
+ }
+
+ def isBoxedUnit(insn: AbstractInsnNode) = {
+ insn.getOpcode == GETSTATIC && {
+ val fi = insn.asInstanceOf[FieldInsnNode]
+ fi.owner == srBoxedUnitRef.internalName && fi.name == "UNIT" && fi.desc == srBoxedUnitRef.descriptor
+ }
+ }
+
+ /**
+ * Visit the class node and collect all referenced nested classes.
+ */
+ def collectNestedClasses(classNode: ClassNode): List[ClassBType] = {
+ val innerClasses = mutable.Set.empty[ClassBType]
+
+ def visitInternalName(internalName: InternalName): Unit = if (internalName != null) {
+ val t = classBTypeFromParsedClassfile(internalName)
+ if (t.isNestedClass.get) innerClasses += t
+ }
+
+ // either an internal/Name or [[Linternal/Name; -- there are certain references in classfiles
+ // that are either an internal name (without the surrounding `L;`) or an array descriptor
+ // `[Linternal/Name;`.
+ def visitInternalNameOrArrayReference(ref: String): Unit = if (ref != null) {
+ val bracket = ref.lastIndexOf('[')
+ if (bracket == -1) visitInternalName(ref)
+ else if (ref.charAt(bracket + 1) == 'L') visitInternalName(ref.substring(bracket + 2, ref.length - 1))
+ }
+
+ // we are only interested in the class references in the descriptor, so we can skip over
+ // primitives and the brackets of array descriptors
+ def visitDescriptor(desc: String): Unit = (desc.charAt(0): @switch) match {
+ case '(' =>
+ val internalNames = mutable.ListBuffer.empty[String]
+ var i = 1
+ while (i < desc.length) {
+ if (desc.charAt(i) == 'L') {
+ val start = i + 1 // skip the L
+ while (desc.charAt(i) != ';') i += 1
+ internalNames append desc.substring(start, i)
+ }
+ // skips over '[', ')', primitives
+ i += 1
+ }
+ internalNames foreach visitInternalName
+
+ case 'L' =>
+ visitInternalName(desc.substring(1, desc.length - 1))
+
+ case '[' =>
+ visitInternalNameOrArrayReference(desc)
+
+ case _ => // skip over primitive types
+ }
+
+ def visitConstant(const: AnyRef): Unit = const match {
+ case t: Type => visitDescriptor(t.getDescriptor)
+ case _ =>
+ }
+
+ // in principle we could references to annotation types, as they only end up as strings in the
+ // constant pool, not as class references. however, the java compiler still includes nested
+ // annotation classes in the innerClass table, so we do the same. explained in detail in the
+ // large comment in class BTypes.
+ def visitAnnotation(annot: AnnotationNode): Unit = {
+ visitDescriptor(annot.desc)
+ if (annot.values != null) annot.values.asScala foreach visitConstant
+ }
+
+ def visitAnnotations(annots: java.util.List[_ <: AnnotationNode]) = if (annots != null) annots.asScala foreach visitAnnotation
+ def visitAnnotationss(annotss: Array[java.util.List[AnnotationNode]]) = if (annotss != null) annotss foreach visitAnnotations
+
+ def visitHandle(handle: Handle): Unit = {
+ visitInternalNameOrArrayReference(handle.getOwner)
+ visitDescriptor(handle.getDesc)
+ }
+
+ visitInternalName(classNode.name)
+ innerClasses ++= classBTypeFromParsedClassfile(classNode.name).info.get.nestedClasses
+
+ visitInternalName(classNode.superName)
+ classNode.interfaces.asScala foreach visitInternalName
+ visitInternalName(classNode.outerClass)
+
+ visitAnnotations(classNode.visibleAnnotations)
+ visitAnnotations(classNode.visibleTypeAnnotations)
+ visitAnnotations(classNode.invisibleAnnotations)
+ visitAnnotations(classNode.invisibleTypeAnnotations)
+
+ for (f <- classNode.fields.asScala) {
+ visitDescriptor(f.desc)
+ visitAnnotations(f.visibleAnnotations)
+ visitAnnotations(f.visibleTypeAnnotations)
+ visitAnnotations(f.invisibleAnnotations)
+ visitAnnotations(f.invisibleTypeAnnotations)
+ }
+
+ for (m <- classNode.methods.asScala) {
+ visitDescriptor(m.desc)
+
+ visitAnnotations(m.visibleAnnotations)
+ visitAnnotations(m.visibleTypeAnnotations)
+ visitAnnotations(m.invisibleAnnotations)
+ visitAnnotations(m.invisibleTypeAnnotations)
+ visitAnnotationss(m.visibleParameterAnnotations)
+ visitAnnotationss(m.invisibleParameterAnnotations)
+ visitAnnotations(m.visibleLocalVariableAnnotations)
+ visitAnnotations(m.invisibleLocalVariableAnnotations)
+
+ m.exceptions.asScala foreach visitInternalName
+ for (tcb <- m.tryCatchBlocks.asScala) visitInternalName(tcb.`type`)
+
+ val iter = m.instructions.iterator()
+ while (iter.hasNext) iter.next() match {
+ case ti: TypeInsnNode => visitInternalNameOrArrayReference(ti.desc)
+ case fi: FieldInsnNode => visitInternalNameOrArrayReference(fi.owner); visitDescriptor(fi.desc)
+ case mi: MethodInsnNode => visitInternalNameOrArrayReference(mi.owner); visitDescriptor(mi.desc)
+ case id: InvokeDynamicInsnNode => visitDescriptor(id.desc); visitHandle(id.bsm); id.bsmArgs foreach visitConstant
+ case ci: LdcInsnNode => visitConstant(ci.cst)
+ case ma: MultiANewArrayInsnNode => visitDescriptor(ma.desc)
+ case _ =>
+ }
+ }
+ innerClasses.toList
+ }
+
+ /**
+ * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM
+ * framework only computes these values during bytecode generation.
+ *
+ * NOTE 1: as explained in the `analysis` package object, the maxStack value used by the Analyzer
+ * may be smaller than the correct maxStack value in the classfile (Analyzers only use a single
+ * slot for long / double values). The maxStack computed here are correct for running an analyzer,
+ * but not for writing in the classfile. We let the ClassWriter recompute max's.
+ *
+ * NOTE 2: the maxStack value computed here may be larger than the smallest correct value
+ * that would allow running an analyzer, see `InstructionStackEffect.forAsmAnalysis` and
+ * `InstructionStackEffect.maxStackGrowth`.
+ *
+ * NOTE 3: the implementation doesn't look at instructions that cannot be reached, it computes
+ * the max local / stack size in the reachable code. These max's work just fine for running an
+ * Analyzer: its implementation also skips over unreachable code in the same way.
+ */
+ def computeMaxLocalsMaxStack(method: MethodNode): Unit = {
+ if (isAbstractMethod(method) || isNativeMethod(method)) {
+ method.maxLocals = 0
+ method.maxStack = 0
+ } else if (!maxLocalsMaxStackComputed(method)) {
+ val size = method.instructions.size
+
+ var maxLocals = parametersSize(method)
+ var maxStack = 0
+
+ // queue of instruction indices where analysis should start
+ var queue = new Array[Int](8)
+ var top = -1
+ def enq(i: Int): Unit = {
+ if (top == queue.length - 1) {
+ val nq = new Array[Int](queue.length * 2)
+ Array.copy(queue, 0, nq, 0, queue.length)
+ queue = nq
+ }
+ top += 1
+ queue(top) = i
+ }
+ def deq(): Int = {
+ val r = queue(top)
+ top -= 1
+ r
+ }
+
+ val subroutineRetTargets = new mutable.Stack[AbstractInsnNode]
+
+ // for each instruction in the queue, contains the stack height at this instruction.
+ // once an instruction has been treated, contains -1 to prevent re-enqueuing
+ val stackHeights = new Array[Int](size)
+
+ def enqInsn(insn: AbstractInsnNode, height: Int): Unit = {
+ enqInsnIndex(method.instructions.indexOf(insn), height)
+ }
+
+ def enqInsnIndex(insnIndex: Int, height: Int): Unit = {
+ if (insnIndex < size && stackHeights(insnIndex) != -1) {
+ stackHeights(insnIndex) = height
+ enq(insnIndex)
+ }
+ }
+
+ val tcbIt = method.tryCatchBlocks.iterator()
+ while (tcbIt.hasNext) {
+ val tcb = tcbIt.next()
+ enqInsn(tcb.handler, 1)
+ if (maxStack == 0) maxStack = 1
+ }
+
+ enq(0)
+ while (top != -1) {
+ val insnIndex = deq()
+ val insn = method.instructions.get(insnIndex)
+ val initHeight = stackHeights(insnIndex)
+ stackHeights(insnIndex) = -1 // prevent i from being enqueued again
+
+ if (insn.getOpcode == -1) { // frames, labels, line numbers
+ enqInsnIndex(insnIndex + 1, initHeight)
+ } else {
+ val stackGrowth = InstructionStackEffect.maxStackGrowth(insn)
+ val heightAfter = initHeight + stackGrowth
+ if (heightAfter > maxStack) maxStack = heightAfter
+
+ // update maxLocals
+ insn match {
+ case v: VarInsnNode =>
+ val longSize = if (isSize2LoadOrStore(v.getOpcode)) 1 else 0
+ maxLocals = math.max(maxLocals, v.`var` + longSize + 1) // + 1 because local numbers are 0-based
+
+ case i: IincInsnNode =>
+ maxLocals = math.max(maxLocals, i.`var` + 1)
+
+ case _ =>
+ }
+
+ insn match {
+ case j: JumpInsnNode =>
+ if (j.getOpcode == JSR) {
+ val jsrTargetHeight = heightAfter + 1
+ if (jsrTargetHeight > maxStack) maxStack = jsrTargetHeight
+ subroutineRetTargets.push(j.getNext)
+ enqInsn(j.label, jsrTargetHeight)
+ } else {
+ enqInsn(j.label, heightAfter)
+ val opc = j.getOpcode
+ if (opc != GOTO) enqInsnIndex(insnIndex + 1, heightAfter) // jump is conditional, so the successor is also a possible control flow target
+ }
+
+ case l: LookupSwitchInsnNode =>
+ var j = 0
+ while (j < l.labels.size) {
+ enqInsn(l.labels.get(j), heightAfter); j += 1
+ }
+ enqInsn(l.dflt, heightAfter)
+
+ case t: TableSwitchInsnNode =>
+ var j = 0
+ while (j < t.labels.size) {
+ enqInsn(t.labels.get(j), heightAfter); j += 1
+ }
+ enqInsn(t.dflt, heightAfter)
+
+ case r: VarInsnNode if r.getOpcode == RET =>
+ enqInsn(subroutineRetTargets.pop(), heightAfter)
+
+ case _ =>
+ val opc = insn.getOpcode
+ if (opc != ATHROW && !isReturn(insn))
+ enqInsnIndex(insnIndex + 1, heightAfter)
+ }
+ }
+ }
+
+ method.maxLocals = maxLocals
+ method.maxStack = maxStack
+
+ maxLocalsMaxStackComputed += method
+ }
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala
index 8d8ea839e6..dd19ad594f 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala
@@ -5,35 +5,74 @@ package analysis
import scala.annotation.switch
import scala.tools.asm.Opcodes._
import scala.tools.asm.Type
-import scala.tools.asm.tree.{MultiANewArrayInsnNode, InvokeDynamicInsnNode, MethodInsnNode, AbstractInsnNode}
+import scala.tools.asm.tree._
import scala.tools.asm.tree.analysis.{Frame, Value}
import opt.BytecodeUtils._
-import collection.immutable
object InstructionStackEffect {
- private var cache: immutable.IntMap[(Int, Int)] = immutable.IntMap.empty
- private def t(x: Int, y: Int): (Int, Int) = {
- // x can go up to 255 (number of parameters of a method, dimensions in multianewarray) we cache
- // x up to 10, which covers most cases and limits the cache. y doesn't go above 6 (see cases).
- if (x > 10 || y > 6) (x, y)
- else {
- val key = (x << 8) + y // this would work for any x < 256
- if (cache contains key) {
- cache(key)
- } else {
- val r = (x, y)
- cache += key -> r
- r
- }
- }
+ val consShift = 3
+ val prodMask = (1 << consShift) - 1
+
+ def cons(i: Int) = i >>> consShift
+ def prod(i: Int) = i & prodMask
+
+ private def t(x: Int, y: Int): Int = (x << consShift) | y
+
+ /**
+ * Returns the number of stack values consumed and produced by `insn`, encoded in a single `Int`
+ * (the `cons` / `prod` extract individual values). The returned values are correct for use in
+ * asm's Analyzer framework. For example, a LLOAD instruction produces one stack value. See also
+ * doc in `analysis` package object.
+ *
+ * This method requires the `frame` to be in the state **before** executing / interpreting the
+ * `insn`.
+ */
+ def forAsmAnalysis[V <: Value](insn: AbstractInsnNode, frame: Frame[V]): Int = computeConsProd(insn, forClassfile = false, conservative = false, frame = frame)
+
+ /**
+ * Returns the maximal possible growth of the stack when executing `insn`. The returned value
+ * is usually the same as expected by asm's Analyzer framework, but it may be larger. For
+ * example, consider a POP2 instruction:
+ * - if two size-1 values are popped, then the asm Analyzer consumes two values
+ * - if a size-2 value is popped, the asm Analyzer consumes only one stack slot (see doc in the
+ * `analysis` package object)
+ *
+ * If a precise result is needed, invoke the `forAsmAnalysis` and provide a `frame` value that
+ * allows looking up the sizes of values on the stack.
+ */
+ def maxStackGrowth(insn: AbstractInsnNode): Int = {
+ val prodCons = computeConsProd(insn, forClassfile = false, conservative = true)
+ prod(prodCons) - cons(prodCons)
}
/**
- * Returns a pair with the number of stack values consumed and produced by `insn`.
- * This method requires the `frame` to be in the state **before** executing / interpreting
- * the `insn`.
+ * Returns the number of stack values consumed and produced by `insn`, encoded in a single `Int`
+ * (the `cons` / `prod` extract individual values). The returned values are correct for writing
+ * into a classfile (see doc on the `analysis` package object).
*/
- def apply[V <: Value](insn: AbstractInsnNode, frame: Frame[V]): (Int, Int) = {
+ def forClassfile(insn: AbstractInsnNode): Int = computeConsProd(insn, forClassfile = true, conservative = false)
+
+ private def invokeConsProd(methodDesc: String, insn: AbstractInsnNode, forClassfile: Boolean): Int = {
+ val consumesReceiver = insn.getOpcode != INVOKESTATIC && insn.getOpcode != INVOKEDYNAMIC
+ if (forClassfile) {
+ val sizes = Type.getArgumentsAndReturnSizes(methodDesc)
+ val cons = (sizes >> 2) - (if (consumesReceiver) 0 else 1)
+ val prod = sizes & 0x03
+ t(cons, prod)
+ } else {
+ val cons = Type.getArgumentTypes(methodDesc).length + (if (consumesReceiver) 1 else 0)
+ val prod = if (Type.getReturnType(methodDesc) == Type.VOID_TYPE) 0 else 1
+ t(cons, prod)
+ }
+ }
+
+ private def fieldInsnIsLongOrDouble(insn: AbstractInsnNode) = {
+ val d = insn.asInstanceOf[FieldInsnNode].desc
+ d == "J" || d == "D"
+ }
+
+ private def computeConsProd[V <: Value](insn: AbstractInsnNode, forClassfile: Boolean, conservative: Boolean, frame: Frame[V] = null): Int = {
+ // not used if `forClassfile || conservative`: in these cases, `frame` is allowed to be `null`
def peekStack(n: Int): V = frame.peekStack(n)
(insn.getOpcode: @switch) match {
@@ -48,142 +87,176 @@ object InstructionStackEffect {
ICONST_3 |
ICONST_4 |
ICONST_5 |
- LCONST_0 |
- LCONST_1 |
FCONST_0 |
FCONST_1 |
FCONST_2 |
- DCONST_0 |
- DCONST_1 |
BIPUSH |
SIPUSH |
- LDC |
ILOAD |
- LLOAD |
FLOAD |
- DLOAD |
ALOAD => t(0, 1)
+ case LDC =>
+ if (forClassfile) insn.asInstanceOf[LdcInsnNode].cst match {
+ case _: java.lang.Long | _: java.lang.Double => t(0, 2)
+ case _ => t(0, 1)
+ } else
+ t(0, 1)
+
+ case LCONST_0 |
+ LCONST_1 |
+ DCONST_0 |
+ DCONST_1 |
+ LLOAD |
+ DLOAD => if (forClassfile) t(0, 2) else t(0, 1)
+
case IALOAD |
- LALOAD |
FALOAD |
- DALOAD |
AALOAD |
BALOAD |
CALOAD |
SALOAD => t(2, 1)
+ case LALOAD |
+ DALOAD => if (forClassfile) t(2, 2) else t(2, 1)
+
case ISTORE |
- LSTORE |
FSTORE |
- DSTORE |
ASTORE => t(1, 0)
+ case LSTORE |
+ DSTORE => if (forClassfile) t(2, 0) else t(1, 0)
+
case IASTORE |
- LASTORE |
FASTORE |
- DASTORE |
AASTORE |
BASTORE |
CASTORE |
SASTORE => t(3, 0)
+ case LASTORE |
+ DASTORE => if (forClassfile) t(4, 0) else t(3, 0)
+
case POP => t(1, 0)
case POP2 =>
- val isSize2 = peekStack(0).getSize == 2
- if (isSize2) t(1, 0) else t(2, 0)
+ if (forClassfile) t(2, 0)
+ else if (conservative) t(1, 0)
+ else {
+ val isSize2 = peekStack(0).getSize == 2
+ if (isSize2) t(1, 0) else t(2, 0)
+ }
case DUP => t(1, 2)
case DUP_X1 => t(2, 3)
case DUP_X2 =>
- val isSize2 = peekStack(1).getSize == 2
- if (isSize2) t(2, 3) else t(3, 4)
+ if (forClassfile || conservative) t(3, 4)
+ else {
+ val isSize2 = peekStack(1).getSize == 2
+ if (isSize2) t(2, 3) else t(3, 4)
+ }
case DUP2 =>
- val isSize2 = peekStack(0).getSize == 2
- if (isSize2) t(1, 2) else t(2, 4)
+ if (forClassfile || conservative) t(2, 4)
+ else {
+ val isSize2 = peekStack(0).getSize == 2
+ if (isSize2) t(1, 2) else t(2, 4)
+ }
case DUP2_X1 =>
- val isSize2 = peekStack(0).getSize == 2
- if (isSize2) t(2, 3) else t(3, 4)
+ if (forClassfile || conservative) t(3, 5)
+ else {
+ val isSize2 = peekStack(0).getSize == 2
+ if (isSize2) t(2, 3) else t(3, 5)
+ }
case DUP2_X2 =>
- val v1isSize2 = peekStack(0).getSize == 2
- if (v1isSize2) {
- val v2isSize2 = peekStack(1).getSize == 2
- if (v2isSize2) t(2, 3) else t(3, 4)
- } else {
- val v3isSize2 = peekStack(2).getSize == 2
- if (v3isSize2) t(3, 5) else t(4, 6)
+ if (forClassfile || conservative) t(4, 6)
+ else {
+ val v1isSize2 = peekStack(0).getSize == 2
+ if (v1isSize2) {
+ val v2isSize2 = peekStack(1).getSize == 2
+ if (v2isSize2) t(2, 3) else t(3, 4)
+ } else {
+ val v3isSize2 = peekStack(2).getSize == 2
+ if (v3isSize2) t(3, 5) else t(4, 6)
+ }
}
case SWAP => t(2, 2)
case IADD |
- LADD |
FADD |
- DADD |
ISUB |
- LSUB |
FSUB |
- DSUB |
IMUL |
- LMUL |
FMUL |
- DMUL |
IDIV |
- LDIV |
FDIV |
- DDIV |
IREM |
+ FREM => t(2, 1)
+
+ case LADD |
+ DADD |
+ LSUB |
+ DSUB |
+ LMUL |
+ DMUL |
+ LDIV |
+ DDIV |
LREM |
- FREM |
- DREM => t(2, 1)
+ DREM => if (forClassfile) t(4, 2) else t(2, 1)
case INEG |
- LNEG |
- FNEG |
- DNEG => t(1, 1)
+ FNEG => t(1, 1)
+
+ case LNEG |
+ DNEG => if (forClassfile) t(2, 2) else t(1, 1)
case ISHL |
- LSHL |
ISHR |
- LSHR |
IUSHR |
- LUSHR |
IAND |
- LAND |
IOR |
+ IXOR => t(2, 1)
+
+ case LSHL |
+ LSHR |
+ LUSHR => if (forClassfile) t(3, 2) else t(2, 1)
+
+ case LAND |
LOR |
- IXOR |
- LXOR => t(2, 1)
+ LXOR => if (forClassfile) t(4, 2) else t(2, 1)
case IINC => t(0, 0)
- case I2L |
- I2F |
- I2D |
- L2I |
- L2F |
- L2D |
+ case I2F |
F2I |
- F2L |
- F2D |
- D2I |
- D2L |
- D2F |
I2B |
I2C |
I2S => t(1, 1)
+ case I2L |
+ I2D |
+ F2L |
+ F2D => if (forClassfile) t(1, 2) else t(1, 1)
+
+ case L2I |
+ L2F |
+ D2I |
+ D2F => if (forClassfile) t(2, 1) else t(1, 1)
+
+ case L2D |
+ D2L => if (forClassfile) t(2, 2) else t(1, 1)
+
+ case FCMPL |
+ FCMPG => t(2, 1)
+
case LCMP |
- FCMPL |
- FCMPG |
DCMPL |
- DCMPG => t(2, 1)
+ DCMPG => if (forClassfile) t(4, 1) else t(2, 1)
case IFEQ |
IFNE |
@@ -211,35 +284,36 @@ object InstructionStackEffect {
LOOKUPSWITCH => t(1, 0)
case IRETURN |
- LRETURN |
FRETURN |
- DRETURN |
ARETURN => t(1, 0) // Frame.execute consumes one stack value
+ case LRETURN |
+ DRETURN => if (forClassfile) t(2, 0) else t(1, 0)
+
case RETURN => t(0, 0) // Frame.execute does not change the stack
- case GETSTATIC => t(0, 1)
+ case GETSTATIC =>
+ val prod = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1
+ t(0, prod)
- case PUTSTATIC => t(1, 0)
+ case PUTSTATIC =>
+ val cons = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1
+ t(cons, 0)
- case GETFIELD => t(1, 1)
+ case GETFIELD =>
+ val prod = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1
+ t(1, prod)
- case PUTFIELD => t(2, 0)
+ case PUTFIELD =>
+ val cons = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 3 else 2
+ t(cons, 0)
case INVOKEVIRTUAL |
INVOKESPECIAL |
INVOKESTATIC |
- INVOKEINTERFACE =>
- val desc = insn.asInstanceOf[MethodInsnNode].desc
- val cons = Type.getArgumentTypes(desc).length + (if (insn.getOpcode == INVOKESTATIC) 0 else 1)
- val prod = if (Type.getReturnType(desc) == Type.VOID_TYPE) 0 else 1
- t(cons, prod)
-
- case INVOKEDYNAMIC =>
- val desc = insn.asInstanceOf[InvokeDynamicInsnNode].desc
- val cons = Type.getArgumentTypes(desc).length
- val prod = if (Type.getReturnType(desc) == Type.VOID_TYPE) 0 else 1
- t(cons, prod)
+ INVOKEINTERFACE => invokeConsProd(insn.asInstanceOf[MethodInsnNode].desc, insn, forClassfile)
+
+ case INVOKEDYNAMIC => invokeConsProd(insn.asInstanceOf[InvokeDynamicInsnNode].desc, insn, forClassfile)
case NEW => t(0, 1)
@@ -261,5 +335,4 @@ object InstructionStackEffect {
IFNONNULL => t(1, 0)
}
}
-
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala
index 31b62f747e..01afd0d2ef 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala
@@ -5,68 +5,14 @@ package analysis
import java.util
import scala.annotation.switch
-import scala.tools.asm.{Type, Opcodes}
-import scala.tools.asm.tree.{MethodInsnNode, LdcInsnNode, AbstractInsnNode}
-import scala.tools.asm.tree.analysis.{Frame, Analyzer, Interpreter, Value}
+import scala.tools.asm.{Opcodes, Type}
+import scala.tools.asm.tree.{AbstractInsnNode, LdcInsnNode, MethodInsnNode, MethodNode}
+import scala.tools.asm.tree.analysis._
import scala.tools.nsc.backend.jvm.opt.BytecodeUtils
import BytecodeUtils._
/**
- * Some notes on the ASM analyzer framework.
- *
- * Value
- * - Abstract, needs to be implemented for each analysis.
- * - Represents the desired information about local variables and stack values, for example:
- * - Is this value known to be null / not null?
- * - What are the instructions that could potentially have produced this value?
- *
- * Interpreter
- * - Abstract, needs to be implemented for each analysis. Sometimes one can subclass an existing
- * interpreter, e.g., SourceInterpreter or BasicInterpreter.
- * - Multiple abstract methods that receive an instruction and the instruction's input values, and
- * return a value representing the result of that instruction.
- * - Note: due to control flow, the interpreter can be invoked multiple times for the same
- * instruction, until reaching a fixed point.
- * - Abstract `merge` function that computes the least upper bound of two values. Used by
- * Frame.merge (see below).
- *
- * Frame
- * - Can be used directly for many analyses, no subclass required.
- * - Every frame has an array of values: one for each local variable and for each stack slot.
- * - A `top` index stores the index of the current stack top
- * - NOTE: for a size-2 local variable at index i, the local variable at i+1 is set to an empty
- * value. However, for a size-2 value at index i on the stack, the value at i+1 holds the next
- * stack value.
- * - Defines the `execute(instruction)` method.
- * - executing mutates the state of the frame according to the effect of the instruction
- * - pop consumed values from the stack
- * - pass them to the interpreter together with the instruction
- * - if applicable, push the resulting value on the stack
- * - Defines the `merge(otherFrame)` method
- * - called by the analyzer when multiple control flow paths lead to an instruction
- * - the frame at the branching instruction is merged into the current frame of the
- * instruction (held by the analyzer)
- * - mutates the values of the current frame, merges all values using interpreter.merge.
- *
- * Analyzer
- * - Stores a frame for each instruction
- * - `merge` function takes an instruction and a frame, merges the existing frame for that instr
- * (from the frames array) with the new frame passed as argument.
- * if the frame changed, puts the instruction on the work queue (fixpiont).
- * - initial frame: initialized for first instr by calling interpreter.new[...]Value
- * for each slot (locals and params), stored in frames[firstInstr] by calling `merge`
- * - work queue of instructions (`queue` array, `top` index for next instruction to analyze)
- * - analyze(method): simulate control flow. while work queue non-empty:
- * - copy the state of `frames[instr]` into a local frame `current`
- * - call `current.execute(instr, interpreter)`, mutating the `current` frame
- * - if it's a branching instruction
- * - for all potential destination instructions
- * - merge the destination instruction frame with the `current` frame
- * (this enqueues the destination instr if its frame changed)
- * - invoke `newControlFlowEdge` (see below)
- * - the analyzer also tracks active exception handlers at each instruction
- * - the empty method `newControlFlowEdge` can be overridden to track control flow if required
- *
+ * See the package object `analysis` for details on the ASM analysis framework.
*
* Some notes on nullness analysis.
*
@@ -87,59 +33,37 @@ import BytecodeUtils._
*/
/**
- * Type to represent nullness of values.
- */
-sealed trait Nullness {
- final def merge(other: Nullness) = if (this == other) this else Unknown
-}
-case object NotNull extends Nullness
-case object Unknown extends Nullness
-case object Null extends Nullness
-
-/**
* Represents the nullness state for a local variable or stack value.
*
- * Note that nullness of primitive values is not tracked, it will be always [[Unknown]].
+ * Note that nullness of primitive values is not tracked, it will be always unknown.
*/
-sealed trait NullnessValue extends Value {
- /**
- * The nullness of this value.
- */
- def nullness: Nullness
-
- /**
- * True if this value is a long or double. The Analyzer framework needs to know
- * the size of each value when interpreting instructions, see `Frame.execute`.
- */
- def isSize2: Boolean
+sealed abstract class NullnessValue(final val isSize2: Boolean) extends Value {
/**
* The size of the slot described by this value. Cannot be 0 because no values are allocated
* for void-typed slots, see NullnessInterpreter.newValue.
**/
def getSize: Int = if (isSize2) 2 else 1
- def merge(other: NullnessValue) = NullnessValue(nullness merge other.nullness, isSize2)
+ def merge(other: NullnessValue) = {
+ if (this eq other) this
+ else if (this eq UnknownValue2) this // the only possible value of size two
+ else UnknownValue1
+ }
+
+ final override def equals(other: Any) = this eq other.asInstanceOf[Object]
}
-object NullValue extends NullnessValue { def nullness = Null; def isSize2 = false; override def toString = "Null" }
-object UnknownValue1 extends NullnessValue { def nullness = Unknown; def isSize2 = false; override def toString = "Unknown1" }
-object UnknownValue2 extends NullnessValue { def nullness = Unknown; def isSize2 = true; override def toString = "Unknown2" }
-object NotNullValue extends NullnessValue { def nullness = NotNull; def isSize2 = false; override def toString = "NotNull" }
+object NullValue extends NullnessValue(isSize2 = false) { override def toString = "Null" }
+object UnknownValue1 extends NullnessValue(isSize2 = false) { override def toString = "Unknown1" }
+object UnknownValue2 extends NullnessValue(isSize2 = true ) { override def toString = "Unknown2" }
+object NotNullValue extends NullnessValue(isSize2 = false) { override def toString = "NotNull" }
object NullnessValue {
- def apply(nullness: Nullness, isSize2: Boolean): NullnessValue = {
- if (nullness == Null) NullValue
- else if (nullness == NotNull) NotNullValue
- else if (isSize2) UnknownValue2
- else UnknownValue1
- }
-
- def apply(nullness: Nullness, insn: AbstractInsnNode): NullnessValue = {
- apply(nullness, isSize2 = BytecodeUtils.instructionResultSize(insn) == 2)
- }
+ def unknown(isSize2: Boolean) = if (isSize2) UnknownValue2 else UnknownValue1
+ def unknown(insn: AbstractInsnNode) = if (BytecodeUtils.instructionResultSize(insn) == 2) UnknownValue2 else UnknownValue1
}
-final class NullnessInterpreter extends Interpreter[NullnessValue](Opcodes.ASM5) {
+final class NullnessInterpreter(bTypes: BTypes, method: MethodNode) extends Interpreter[NullnessValue](Opcodes.ASM5) {
def newValue(tp: Type): NullnessValue = {
// ASM loves giving semantics to null. The behavior here is the same as in SourceInterpreter,
// which is provided by the framework.
@@ -151,29 +75,31 @@ final class NullnessInterpreter extends Interpreter[NullnessValue](Opcodes.ASM5)
// (2) `tp` may also be `null`. When creating the initial frame, the analyzer invokes
// `newValue(null)` for each local variable. We have to return a value of size 1.
if (tp == Type.VOID_TYPE) null // (1)
- else NullnessValue(Unknown, isSize2 = tp != null /*(2)*/ && tp.getSize == 2 )
+ else NullnessValue.unknown(isSize2 = tp != null /*(2)*/ && tp.getSize == 2 )
}
override def newParameterValue(isInstanceMethod: Boolean, local: Int, tp: Type): NullnessValue = {
// For instance methods, the `this` parameter is known to be not null.
- if (isInstanceMethod && local == 0) NullnessValue(NotNull, isSize2 = false)
+ val isThis = local == 0 && (isInstanceMethod || {
+ method.parameters != null && !method.parameters.isEmpty && {
+ val p = method.parameters.get(0)
+ (p.access & Opcodes.ACC_SYNTHETIC) != 0 && p.name == "$this"
+ }
+ })
+ if (isThis) NotNullValue
else super.newParameterValue(isInstanceMethod, local, tp)
}
- def newOperation(insn: AbstractInsnNode): NullnessValue = {
- val nullness = (insn.getOpcode: @switch) match {
- case Opcodes.ACONST_NULL => Null
+ def newOperation(insn: AbstractInsnNode): NullnessValue = (insn.getOpcode: @switch) match {
+ case Opcodes.ACONST_NULL => NullValue
- case Opcodes.LDC => insn.asInstanceOf[LdcInsnNode].cst match {
- case _: String | _: Type => NotNull
- case _ => Unknown
- }
-
- case _ => Unknown
+ case Opcodes.LDC => insn.asInstanceOf[LdcInsnNode].cst match {
+ case _: String | _: Type => NotNullValue
+ case _ => NullnessValue.unknown(insn)
}
// for Opcodes.NEW, we use Unknown. The value will become NotNull after the constructor call.
- NullnessValue(nullness, insn)
+ case _ => NullnessValue.unknown(insn)
}
def copyOperation(insn: AbstractInsnNode, value: NullnessValue): NullnessValue = value
@@ -182,26 +108,24 @@ final class NullnessInterpreter extends Interpreter[NullnessValue](Opcodes.ASM5)
case Opcodes.CHECKCAST => value
case Opcodes.NEWARRAY |
- Opcodes.ANEWARRAY => NullnessValue(NotNull, isSize2 = false)
+ Opcodes.ANEWARRAY => NotNullValue
- case _ => NullnessValue(Unknown, insn)
+ case _ => NullnessValue.unknown(insn)
}
def binaryOperation(insn: AbstractInsnNode, value1: NullnessValue, value2: NullnessValue): NullnessValue = {
- NullnessValue(Unknown, insn)
+ NullnessValue.unknown(insn)
}
- def ternaryOperation(insn: AbstractInsnNode, value1: NullnessValue, value2: NullnessValue, value3: NullnessValue): NullnessValue = {
- NullnessValue(Unknown, isSize2 = false)
- }
+ def ternaryOperation(insn: AbstractInsnNode, value1: NullnessValue, value2: NullnessValue, value3: NullnessValue): NullnessValue = UnknownValue1
- def naryOperation(insn: AbstractInsnNode, values: util.List[_ <: NullnessValue]): NullnessValue = (insn.getOpcode: @switch) match {
- case Opcodes.MULTIANEWARRAY =>
- NullnessValue(NotNull, isSize2 = false)
+ def naryOperation(insn: AbstractInsnNode, values: util.List[_ <: NullnessValue]): NullnessValue = insn match {
+ case mi: MethodInsnNode if bTypes.backendUtils.isNonNullMethodInvocation(mi) =>
+ NotNullValue
case _ =>
- // TODO: use a list of methods that are known to return non-null values
- NullnessValue(Unknown, insn)
+ if (insn.getOpcode == Opcodes.MULTIANEWARRAY) NotNullValue
+ else NullnessValue.unknown(insn)
}
def returnOperation(insn: AbstractInsnNode, value: NullnessValue, expected: NullnessValue): Unit = ()
@@ -219,8 +143,10 @@ class NullnessFrame(nLocals: Int, nStack: Int) extends AliasingFrame[NullnessVal
override def execute(insn: AbstractInsnNode, interpreter: Interpreter[NullnessValue]): Unit = {
import Opcodes._
- // get the object id of the object that is known to be not-null after this operation
- val nullCheckedAliasId: Long = (insn.getOpcode: @switch) match {
+ // get the alias set the object that is known to be not-null after this operation.
+ // alias sets are mutable / mutated, so after super.execute, this set contains the remaining
+ // aliases of the value that becomes not-null.
+ val nullCheckedAliases: AliasSet = (insn.getOpcode: @switch) match {
case IALOAD |
LALOAD |
FALOAD |
@@ -229,7 +155,7 @@ class NullnessFrame(nLocals: Int, nStack: Int) extends AliasingFrame[NullnessVal
BALOAD |
CALOAD |
SALOAD =>
- aliasId(this.stackTop - 1)
+ aliasesOf(this.stackTop - 1)
case IASTORE |
FASTORE |
@@ -239,35 +165,36 @@ class NullnessFrame(nLocals: Int, nStack: Int) extends AliasingFrame[NullnessVal
SASTORE |
LASTORE |
DASTORE =>
- aliasId(this.stackTop - 2)
+ aliasesOf(this.stackTop - 2)
case GETFIELD =>
- aliasId(this.stackTop)
+ aliasesOf(this.stackTop)
case PUTFIELD =>
- aliasId(this.stackTop - 1)
+ aliasesOf(this.stackTop - 1)
case INVOKEVIRTUAL |
INVOKESPECIAL |
INVOKEINTERFACE =>
val desc = insn.asInstanceOf[MethodInsnNode].desc
val numArgs = Type.getArgumentTypes(desc).length
- aliasId(this.stackTop - numArgs)
+ aliasesOf(this.stackTop - numArgs)
case ARRAYLENGTH |
MONITORENTER |
MONITOREXIT =>
- aliasId(this.stackTop)
+ aliasesOf(this.stackTop)
case _ =>
- -1
+ null
}
super.execute(insn, interpreter)
- if (nullCheckedAliasId != -1) {
- for (i <- valuesWithAliasId(nullCheckedAliasId))
- this.setValue(i, NotNullValue)
+ if (nullCheckedAliases != null) {
+ val it = nullCheckedAliases.iterator
+ while (it.hasNext)
+ this.setValue(it.next(), NotNullValue)
}
}
}
@@ -276,7 +203,7 @@ class NullnessFrame(nLocals: Int, nStack: Int) extends AliasingFrame[NullnessVal
* This class is required to override the `newFrame` methods, which makes makes sure the analyzer
* uses NullnessFrames.
*/
-class NullnessAnalyzer extends Analyzer[NullnessValue](new NullnessInterpreter) {
+class NullnessAnalyzer(bTypes: BTypes, method: MethodNode) extends Analyzer[NullnessValue](new NullnessInterpreter(bTypes, method)) {
override def newFrame(nLocals: Int, nStack: Int): NullnessFrame = new NullnessFrame(nLocals, nStack)
override def newFrame(src: Frame[_ <: NullnessValue]): NullnessFrame = new NullnessFrame(src)
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala
index 594fd8923c..8af4bd4d5d 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala
@@ -15,11 +15,10 @@ import scala.tools.asm.{Type, MethodVisitor}
import scala.tools.asm.Opcodes._
import scala.tools.asm.tree._
import scala.tools.asm.tree.analysis._
-import scala.tools.nsc.backend.jvm.BTypes.InternalName
import opt.BytecodeUtils._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
/**
* This class provides additional queries over ASM's built-in `SourceValue` analysis.
@@ -55,24 +54,16 @@ import scala.collection.convert.decorateAsScala._
*
* If ever needed, we could introduce a mode where primitive conversions (l2i) are considered as
* copying operations.
+ *
+ * Note on performance: thee data flow analysis (SourceValue / SourceInterpreter, provided by ASM)
+ * is roughly 2-3x slower than a simple analysis (like BasicValue). The reason is that the merge
+ * function (merging producer sets) is more complex than merging simple basic values.
+ * See also the doc comment in the package object `analysis`.
*/
-class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) {
-
- /* Timers for benchmarking ProdCons
- import scala.reflect.internal.util.Statistics._
- import ProdConsAnalyzer._
- val analyzerTimer = newSubTimer(classInternalName + "#" + methodNode.name + " - analysis", prodConsAnalyzerTimer)
- val consumersTimer = newSubTimer(classInternalName + "#" + methodNode.name + " - consumers", prodConsAnalyzerTimer)
- */
-
- val analyzer = new Analyzer(new InitialProducerSourceInterpreter)
+trait ProdConsAnalyzerImpl {
+ val methodNode: MethodNode
-// val start = analyzerTimer.start()
- analyzer.analyze(classInternalName, methodNode)
-// analyzerTimer.stop(start)
-// println(analyzerTimer.line)
-
- def frameAt(insn: AbstractInsnNode) = analyzer.frameAt(insn, methodNode)
+ def frameAt(insn: AbstractInsnNode): Frame[SourceValue]
/**
* Returns the potential producer instructions of a (local or stack) value in the frame of `insn`.
@@ -102,8 +93,13 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
inputValues(insn).iterator.flatMap(v => v.insns.asScala).toSet
}
- def consumersOfOutputsFrom(insn: AbstractInsnNode): Set[AbstractInsnNode] =
- _consumersOfOutputsFrom.get(insn).map(v => v.indices.flatMap(v.apply)(collection.breakOut): Set[AbstractInsnNode]).getOrElse(Set.empty)
+ def consumersOfOutputsFrom(insn: AbstractInsnNode): Set[AbstractInsnNode] = insn match {
+ case _: UninitializedLocalProducer => Set.empty
+ case ParameterProducer(local) => consumersOfValueAt(methodNode.instructions.getFirst, local)
+ case ExceptionProducer(handlerLabel, handlerFrame) => consumersOfValueAt(handlerLabel, handlerFrame.stackTop)
+ case _ =>
+ _consumersOfOutputsFrom.get(insn).map(v => v.indices.flatMap(v.apply)(collection.breakOut): Set[AbstractInsnNode]).getOrElse(Set.empty)
+ }
/**
* Returns the potential initial producer instructions of a value in the frame of `insn`.
@@ -159,13 +155,19 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
inputValueSlots(insn).flatMap(slot => initialProducersForValueAt(insn, slot)).toSet
}
- def ultimateConsumersOfOutputsFrom(insn: AbstractInsnNode): Set[AbstractInsnNode] = {
- lazy val next = insn.getNext
- outputValueSlots(insn).flatMap(slot => ultimateConsumersOfValueAt(next, slot)).toSet
+ def ultimateConsumersOfOutputsFrom(insn: AbstractInsnNode): Set[AbstractInsnNode] = insn match {
+ case _: UninitializedLocalProducer => Set.empty
+ case _ =>
+ lazy val next = insn match {
+ case _: ParameterProducer => methodNode.instructions.getFirst
+ case ExceptionProducer(handlerLabel, _) => handlerLabel
+ case _ => insn.getNext
+ }
+ outputValueSlots(insn).flatMap(slot => ultimateConsumersOfValueAt(next, slot)).toSet
}
private def isCopyOperation(insn: AbstractInsnNode): Boolean = {
- isVarInstruction(insn) || {
+ isLoadOrStore(insn) || {
(insn.getOpcode: @switch) match {
case DUP | DUP_X1 | DUP_X2 | DUP2 | DUP2_X1 | DUP2_X2 | SWAP | CHECKCAST => true
case _ => false
@@ -376,9 +378,9 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
Seq(insn.asInstanceOf[IincInsnNode].`var`)
} else {
val frame = frameAt(insn)
- val stackEffect = InstructionStackEffect(insn, frame)
+ val prodCons = InstructionStackEffect.forAsmAnalysis(insn, frame)
val stackSize = frame.getLocals + frame.getStackSize
- (stackSize - stackEffect._1) until stackSize
+ (stackSize - InstructionStackEffect.cons(prodCons)) until stackSize
}
}
@@ -386,7 +388,7 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
private def outputValueSlots(insn: AbstractInsnNode): Seq[Int] = insn match {
case ParameterProducer(local) => Seq(local)
case UninitializedLocalProducer(local) => Seq(local)
- case ExceptionProducer(frame) => Seq(frame.stackTop)
+ case ExceptionProducer(_, frame) => Seq(frame.stackTop)
case _ =>
if (insn.getOpcode == -1) return Seq.empty
if (isStore(insn)) {
@@ -395,16 +397,15 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
Seq(insn.asInstanceOf[IincInsnNode].`var`)
} else {
val frame = frameAt(insn)
- val stackEffect = InstructionStackEffect(insn, frame)
+ val prodCons = InstructionStackEffect.forAsmAnalysis(insn, frame)
val nextFrame = frameAt(insn.getNext)
val stackSize = nextFrame.getLocals + nextFrame.getStackSize
- (stackSize - stackEffect._2) until stackSize
+ (stackSize - InstructionStackEffect.prod(prodCons)) until stackSize
}
}
/** For each instruction, a set of potential consumers of the produced values. */
private lazy val _consumersOfOutputsFrom: Map[AbstractInsnNode, Vector[Set[AbstractInsnNode]]] = {
-// val start = consumersTimer.start()
var res = Map.empty[AbstractInsnNode, Vector[Set[AbstractInsnNode]]]
for {
insn <- methodNode.instructions.iterator.asScala
@@ -417,8 +418,6 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
val outputIndex = producedSlots.indexOf(i)
res = res.updated(producer, currentConsumers.updated(outputIndex, currentConsumers(outputIndex) + insn))
}
-// consumersTimer.stop(start)
-// println(consumersTimer.line)
res
}
@@ -426,11 +425,6 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
private val _ultimateConsumersCache: mutable.AnyRefMap[(AbstractInsnNode, Int), Set[AbstractInsnNode]] = mutable.AnyRefMap.empty
}
-object ProdConsAnalyzer {
- import scala.reflect.internal.util.Statistics._
- val prodConsAnalyzerTimer = newTimer("Time in ProdConsAnalyzer", "jvm")
-}
-
/**
* A class for pseudo-instructions representing the initial producers of local values that have
* no producer instruction in the method:
@@ -446,10 +440,10 @@ object ProdConsAnalyzer {
* return a;
* }
*
- * In the first frame of the method, the SoruceValue for parameter `a` gives an empty set of
+ * In the first frame of the method, the SourceValue for parameter `a` gives an empty set of
* producer instructions.
*
- * In the frame of the `IRETURN` instruction, the SoruceValue for parameter `a` lists a single
+ * In the frame of the `IRETURN` instruction, the SourceValue for parameter `a` lists a single
* producer instruction: the `ISTORE 1`. This makes it look as if there was a single producer for
* `a`, where in fact it might still hold the parameter's initial value.
*/
@@ -459,9 +453,9 @@ abstract class InitialProducer extends AbstractInsnNode(-1) {
override def accept(cv: MethodVisitor): Unit = throw new UnsupportedOperationException
}
-case class ParameterProducer(local: Int) extends InitialProducer
-case class UninitializedLocalProducer(local: Int) extends InitialProducer
-case class ExceptionProducer(handlerFrame: Frame[_ <: Value]) extends InitialProducer
+case class ParameterProducer(local: Int) extends InitialProducer
+case class UninitializedLocalProducer(local: Int) extends InitialProducer
+case class ExceptionProducer[V <: Value](handlerLabel: LabelNode, handlerFrame: Frame[V]) extends InitialProducer
class InitialProducerSourceInterpreter extends SourceInterpreter {
override def newParameterValue(isInstanceMethod: Boolean, local: Int, tp: Type): SourceValue = {
@@ -473,6 +467,6 @@ class InitialProducerSourceInterpreter extends SourceInterpreter {
}
override def newExceptionValue(tryCatchBlockNode: TryCatchBlockNode, handlerFrame: Frame[_ <: Value], exceptionType: Type): SourceValue = {
- new SourceValue(1, ExceptionProducer(handlerFrame))
+ new SourceValue(1, ExceptionProducer(tryCatchBlockNode.handler, handlerFrame))
}
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/TypeFlowInterpreter.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/TypeFlowInterpreter.scala
new file mode 100644
index 0000000000..bcf9978c16
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/TypeFlowInterpreter.scala
@@ -0,0 +1,36 @@
+package scala.tools.nsc
+package backend.jvm
+package analysis
+
+import scala.tools.asm.Type
+import scala.tools.asm.tree.analysis.{BasicValue, BasicInterpreter}
+
+abstract class TypeFlowInterpreter extends BasicInterpreter {
+ override def newValue(tp: Type) = {
+ if (tp == null) super.newValue(tp)
+ else if (isRef(tp)) new BasicValue(tp)
+ else super.newValue(tp)
+ }
+
+ def isRef(tp: Type) = tp != null && (tp.getSort match {
+ case Type.OBJECT | Type.ARRAY => true
+ case _ => false
+ })
+
+ def refLub(a: BasicValue, b: BasicValue): BasicValue
+
+ override def merge(a: BasicValue, b: BasicValue): BasicValue = {
+ if (a == b) a
+ else if (isRef(a.getType) && isRef(b.getType)) refLub(a, b)
+ else BasicValue.UNINITIALIZED_VALUE
+ }
+}
+
+/**
+ * A [[TypeFlowInterpreter]] which collapses LUBs of non-equal reference types to Object.
+ * This could be made more precise by looking up ClassBTypes for the two reference types and using
+ * the `jvmWiseLUB` method.
+ */
+class NonLubbingTypeFlowInterpreter extends TypeFlowInterpreter {
+ def refLub(a: BasicValue, b: BasicValue): BasicValue = BasicValue.REFERENCE_VALUE // java/lang/Object
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/package.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/package.scala
new file mode 100644
index 0000000000..999c686aac
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/package.scala
@@ -0,0 +1,374 @@
+package scala.tools.nsc.backend.jvm
+
+/**
+ * Summary on the ASM analyzer framework
+ * --------------------------------------
+ *
+ * Value
+ * - Abstract, needs to be implemented for each analysis.
+ * - Represents the desired information about local variables and stack values, for example:
+ * - Is this value known to be null / not null?
+ * - What are the instructions that could potentially have produced this value?
+ *
+ * Interpreter
+ * - Abstract, needs to be implemented for each analysis. Sometimes one can subclass an existing
+ * interpreter, e.g., SourceInterpreter or BasicInterpreter.
+ * - Multiple abstract methods that receive an instruction and the instruction's input values, and
+ * return a value representing the result of that instruction.
+ * - Note: due to control flow, the interpreter can be invoked multiple times for the same
+ * instruction, until reaching a fixed point.
+ * - Abstract `merge` function that computes the least upper bound of two values. Used by
+ * Frame.merge (see below).
+ *
+ * Frame
+ * - Can be used directly for many analyses, no subclass required.
+ * - Every frame has an array of values: one for each local variable and for each stack slot.
+ * - A `top` index stores the index of the current stack top
+ * - NOTE: for a size-2 local variable at index i, the local variable at i+1 is set to an empty
+ * value. However, for a size-2 value at index i on the stack, the value at i+1 holds the next
+ * stack value. IMPORTANT: this is only the case in ASM's analysis framework, not in bytecode.
+ * See comment below.
+ * - Defines the `execute(instruction)` method.
+ * - executing mutates the state of the frame according to the effect of the instruction
+ * - pop consumed values from the stack
+ * - pass them to the interpreter together with the instruction
+ * - if applicable, push the resulting value on the stack
+ * - Defines the `merge(otherFrame)` method
+ * - called by the analyzer when multiple control flow paths lead to an instruction
+ * - the frame at the branching instruction is merged into the current frame of the
+ * instruction (held by the analyzer)
+ * - mutates the values of the current frame, merges all values using interpreter.merge.
+ *
+ * Analyzer
+ * - Stores a frame for each instruction
+ * - `merge` function takes an instruction and a frame, merges the existing frame for that instr
+ * (from the frames array) with the new frame passed as argument.
+ * if the frame changed, puts the instruction on the work queue (fixpoint).
+ * - initial frame: initialized for first instr by calling interpreter.new[...]Value
+ * for each slot (locals and params), stored in frames[firstInstr] by calling `merge`
+ * - work queue of instructions (`queue` array, `top` index for next instruction to analyze)
+ * - analyze(method): simulate control flow. while work queue non-empty:
+ * - copy the state of `frames[instr]` into a local frame `current`
+ * - call `current.execute(instr, interpreter)`, mutating the `current` frame
+ * - if it's a branching instruction
+ * - for all potential destination instructions
+ * - merge the destination instruction frame with the `current` frame
+ * (this enqueues the destination instr if its frame changed)
+ * - invoke `newControlFlowEdge` (see below)
+ * - the analyzer also tracks active exception handlers at each instruction
+ * - the empty method `newControlFlowEdge` can be overridden to track control flow if required
+ *
+ *
+ * MaxLocals and MaxStack
+ * ----------------------
+ *
+ * At the JVM level, long and double values occupy two slots, both as local variables and on the
+ * stack, as specified in the JVM spec 2.6.2:
+ * "At any point in time, an operand stack has an associated depth, where a value of type long or
+ * double contributes two units to the depth and a value of any other type contributes one unit."
+ *
+ * For example, a method
+ * class A { def f(a: Long, b: Long) = a + b }
+ * has MAXSTACK=4 in the classfile. This value is computed by the ClassWriter / MethodWriter when
+ * generating the classfile (we always pass COMPUTE_MAXS to the ClassWriter).
+ *
+ * For running an ASM Analyzer, long and double values occupy two local variable slots, but only
+ * a single slot on the call stack, as shown by the following snippet:
+ *
+ * import scala.tools.nsc.backend.jvm._
+ * import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
+ * import scala.collection.convert.decorateAsScala._
+ * import scala.tools.asm.tree.analysis._
+ *
+ * val cn = AsmUtils.readClass("/Users/luc/scala/scala/sandbox/A.class")
+ * val m = cn.methods.iterator.asScala.find(_.name == "f").head
+ *
+ * // the value is read from the classfile, so it's 4
+ * println(s"maxLocals: ${m.maxLocals}, maxStack: ${m.maxStack}") // maxLocals: 5, maxStack: 4
+ *
+ * // we can safely set it to 2 for running the analyzer.
+ * m.maxStack = 2
+ *
+ * val a = new Analyzer(new BasicInterpreter)
+ * a.analyze(cn.name, m)
+ * val addInsn = m.instructions.iterator.asScala.find(_.getOpcode == 97).get // LADD Opcode
+ * val addFrame = a.frameAt(addInsn, m)
+ *
+ * addFrame.getStackSize // 2: the two long values only take one slot each
+ * addFrame.getLocals // 5: this takes one slot, the two long parameters take 2 slots each
+ *
+ *
+ * While running the optimizer, we need to make sure that the `maxStack` value of a method is
+ * large enough for running an ASM analyzer. We don't need to worry if the value is incorrect in
+ * the JVM perspective: the value will be re-computed and overwritten in the ClassWriter.
+ *
+ *
+ * Lessons learnt while benchmarking the alias tracking analysis
+ * -------------------------------------------------------------
+ *
+ * Profiling
+ * - Use YourKit for finding hotspots (cpu profiling). when it comes to drilling down into the details
+ * of a hotspot, don't pay too much attention to the percentages / time counts.
+ * - Should also try other profilers.
+ * - Use timers. When a method showed up as a hotspot, I added a timer around that method, and a
+ * second one within the method to measure specific parts. The timers slow things down, but the
+ * relative numbers show what parts of a method are slow.
+ *
+ * ASM analyzer insights
+ * - The time for running an analysis depends on the number of locals and the number of instructions.
+ * Reducing the number of locals helps speeding up the analysis: there are less values to
+ * merge when merging to frames.
+ * See also https://github.com/scala/scala-dev/issues/47
+ * - The common hot spot of an ASM analysis is Frame.merge, for example in producers / consumers.
+ * - For nullness analysis the time is spent as follows
+ * - 20% merging nullness values. this is as expected: for example, the same absolute amount of
+ * time is spent in merging BasicValues when running a BasicInterpreter.
+ * - 50% merging alias sets. i tried to optimize what i could out of this.
+ * - 20% is spent creating new frames from existing ones, see comment on AliasingFrame.init.
+ * - The implementation of Frame.merge (the main hot spot) contains a megamorphic callsite to
+ * `interpreter.merge`. This can be observed easily by running a test program that either runs
+ * a BasicValue analysis only, versus a program that first runs a nullness analysis and then
+ * a BasicValue. In an example, the time for the BasicValue analysis goes from 519ms to 1963ms,
+ * a 3.8x slowdown.
+ * - I added counters to the Frame.merge methods for nullness and BasicValue analysis. In the
+ * examples I benchmarked, the number of merge invocations was always exactly the same.
+ * It would probably be possible to come up with an example where alias set merging forces
+ * additional analysis rounds until reaching the fixpoint, but I did not observe such cases.
+ *
+ * To benchmark an analysis, instead of benchmarking analysis while it runs in the compiler
+ * backend, one can easily run it from a separate program (or the repl). The bytecode to analyze
+ * can simply be parsed from a classfile. See example at the end of this comment.
+ *
+ *
+ * Nullness Analysis in Miguel's Optimizer
+ * ---------------------------------------
+ *
+ * Miguel implemented alias tracking for nullness analysis differently [1]. Remember that every
+ * frame has an array of values. Miguel's idea was to represent aliasing using reference equality
+ * in the values array: if two entries in the array point to the same value object, the two entries
+ * are aliases in the frame of the given instruction.
+ *
+ * While this idea seems elegant at first sight, Miguel's implementation does not merge frames
+ * correctly when it comes to aliasing. Assume in frame 1, values (a, b, c) are aliases, while in
+ * frame 2 (a, b) are aliases. When merging the second into the first, we have to make sure that
+ * c is removed as an alias of (a, b).
+ *
+ * It would be possible to implement correct alias set merging in Miguel's approach. However, frame
+ * merging is the main hot spot of analysis. The computational complexity of implementing alias set
+ * merging by traversing the values array and comparing references is too high. The concrete
+ * alias set representation that is used in the current implementation (see class AliasingFrame)
+ * makes alias set merging more efficient.
+ *
+ * [1] https://github.com/scala-opt/scala/blob/opt/rebase/src/compiler/scala/tools/nsc/backend/bcode/NullnessPropagator.java
+ *
+ *
+ * Complexity and scaling of analysis
+ * ----------------------------------
+ *
+ * The time complexity of a data flow analysis depends on:
+ *
+ * - The size of the method. The complexity factor is linear (assuming the number of locals and
+ * branching instructions remains constant). The main analysis loop runs through all
+ * instructions of a method once. Instructions are only re-enqueued if a control flow merge
+ * changes the frame at some instruction.
+ *
+ * - The branching instructions. When a second (third, ..) control flow edge arrives at an
+ * instruction, the existing frame at the instruction is merged with the one computed on the
+ * new branch. If the merge function changes the existing frame, the instruction is enqueued
+ * for another analysis. This results in a merge operation for the successors of the
+ * instruction.
+ *
+ * - The number of local variables. The hot spot of analysis is frame merging. The merge function
+ * iterates through the values in the frame (locals and stack values) and merges them.
+ *
+ * I measured the running time of an analysis for two examples:
+ * - Keep the number of locals and branching instructions constant, increase the number of
+ * instructions. The running time grows linearly with the method size.
+ * - Increase the size and number of locals in a method. The method size and number of locals
+ * grow in the same pace. Here, the running time increase is polynomial. It looks like the
+ * complexity is be #instructions * #locals^2 (see below).
+ *
+ * I measured nullness analysis (which tracks aliases) and a SimpleValue analysis. Nullness runs
+ * roughly 5x slower (because of alias tracking) at every problem size - this factor doesn't change.
+ *
+ * The numbers below are for nullness. Note that the last column is constant, i.e., the running
+ * time is proportional to #ins * #loc^2. Therefore we use this factor when limiting the maximal
+ * method size for running an analysis.
+ *
+ * #insns #locals time (ms) time / #ins * #loc^2 * 10^6
+ * 1305 156 34 1.07
+ * 2610 311 165 0.65
+ * 3915 466 490 0.57
+ * 5220 621 1200 0.59
+ * 6525 776 2220 0.56
+ * 7830 931 3830 0.56
+ * 9135 1086 6570 0.60
+ * 10440 1241 9700 0.60
+ * 11745 1396 13800 0.60
+ *
+ * As a second experiment, nullness analysis was run with varying #insns but constant #locals.
+ * The last column shows linear complexity with respect to the method size (linearOffset = 2279):
+ *
+ * #insns #locals time (ms) (time + linearOffset) / #insns
+ * 5220 621 1090 0.645
+ * 6224 621 1690 0.637
+ * 7226 621 2280 0.630
+ * 8228 621 2870 0.625
+ * 9230 621 3530 0.629
+ * 10232 621 4130 0.626
+ * 11234 621 4770 0.627
+ * 12236 621 5520 0.637
+ * 13238 621 6170 0.638
+ *
+ *
+ * When running a BasicValue analysis, the complexity observation is the same (time is proportional
+ * to #ins * #loc^2).
+ *
+ *
+ * Measuring analysis execution time
+ * ---------------------------------
+ *
+ * See code below.
+ */
+
+/*
+object Test {
+ val overwrite: Option[String] = null
+
+ @noinline def serialize(o: AnyRef): String = null
+
+ @noinline def deserialize(string: String): AnyRef = null
+
+ @inline def checkRoundTrip[T <: AnyRef](instance: T)(f: T => AnyRef) {
+ val result = serialize(instance)
+ val reconstituted = deserialize(result).asInstanceOf[T]
+ assert(f(instance) == f(reconstituted), (f(instance), f(reconstituted)))
+ }
+
+ @inline def check[T <: AnyRef](instance: => T)(prevResult: String, f: T => AnyRef = (x: T) => x) {
+ // pattern match to introduce a lot of control flow, i.e., a lot of frame merges
+ overwrite match {
+ case Some(f) =>
+ case None =>
+ checkRoundTrip(instance)(f)
+ assert(f(deserialize(prevResult).asInstanceOf[T]) == f(instance), instance)
+ assert(prevResult == "res", instance)
+ }
+ }
+
+ // @inline def fun[T <: AnyRef](instance: => T) = (x: T) => x
+
+ def testMain(): Unit = {
+ // every call to check creates quite a number of locals, and also quite a number of aliases
+ // of the same value (x1). First of all, the default argument call is expanded as below. Then
+ // method check is inlined, and within the body of check, checkRoundTrip and assert have
+ // already been inlined as well.
+
+ // {
+ // val x1 = () => ""
+ // val x2 = fun(x1()) // the compiler optimizes this: instead of passing `() => x1()`, it just passes x1
+ // check(x1())("", x2) // same here for x1
+ // }
+
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 5
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 10
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 15
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 20
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 25
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 30
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 35
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 40
+ // check("")("")
+ // check("")("")
+ // check("")("")
+ // check("")("")
+ // check("")("") // 45
+ // check("")("")
+ // check("")("")
+ // check("")("")
+ // check("")("")
+ // check("")("") // 50
+ // check("")("")
+ // check("")("")
+ // check("")("")
+ // check("")("")
+ // check("")("") // 55
+
+ // 1000 bytecode instructions, 0 locals
+ // println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10));
+ }
+
+ def timed[T](f: => T): T = {
+ val start = System.nanoTime()
+ val r = f
+ val nanos = System.nanoTime() - start
+ println(s"took ${nanos/1000000}ms")
+ r
+ }
+
+ def main(args: Array[String]): Unit = {
+ import scala.tools.nsc.backend.jvm._
+ val cn = AsmUtils.readClass("/Users/luc/scala/scala/sandbox/Test$.class")
+ import scala.collection.convert.decorateAsScala._
+ val m = cn.methods.iterator.asScala.find(_.name == "testMain").head
+
+ println(s"${m.instructions.size} instructions - ${m.maxLocals} locals")
+
+ val a = new analysis.NullnessAnalyzer
+ a.analyze(cn.name, m) // warm up
+
+ analysis.AliasingFrame.reset()
+ timed(a.analyze(cn.name, m))
+ analysis.AliasingFrame.timers foreach println
+
+ println("---")
+
+ // NOTE: if we don't run nullness analysis above (comment it out), then the BasicValue
+ // analysis runs 3.5x faster. Most likely because the call to Interpreter.merge inside
+ // Frame.merge is no longer megamorphic.
+
+ import scala.tools.asm.tree.analysis._
+ val ba = new Analyzer(new BasicInterpreter)
+ ba.analyze(cn.name, m) // warm up
+
+ timed(ba.analyze(cn.name, m))
+
+ println("---")
+
+ timed(a.analyze(cn.name, m))
+ }
+}
+*/
+package object analysis
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala
new file mode 100644
index 0000000000..78fc7e1ecf
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala
@@ -0,0 +1,907 @@
+/* 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.Type
+import scala.tools.asm.Opcodes._
+import scala.tools.asm.tree._
+import scala.collection.mutable
+import scala.collection.JavaConverters._
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
+
+class BoxUnbox[BT <: BTypes](val btypes: BT) {
+ import btypes._
+ import backendUtils._
+
+ /**
+ * Eliminate box-unbox pairs within `method`. Such appear commonly after closure elimination:
+ *
+ * def t2 = {
+ * val f = (b: Byte, i: Int) => i + b // no specialized variant for this function type
+ * f(1, 2) // invokes the generic `apply`
+ * }
+ *
+ * The closure optimizer re-writes the `apply` call to `anonfun$adapted` method, which takes
+ * boxed arguments. After inlining this method, we get
+ *
+ * def t2 = {
+ * val a = boxByte(1)
+ * val b = boxInteger(2)
+ * val r = boxInteger(anonfun$(unboxByte(a), unboxInt(b)))
+ * unboxInt(r)
+ * }
+ *
+ * All these box/unbox operations are eliminated here.
+ *
+ * Implementation: for every box operation, find all consumers of the boxed value, then all
+ * producers of these consumers, repeat until reaching a fixpoint. If this results in a set of
+ * boxing and unboxing operations, the box can be eliminated.
+ *
+ * There are two methods for eliminating boxes:
+ * M1: If there is a single boxing operation, the boxed value(s) are stored into new local
+ * variable(s) at the allocation site. Accesses to the boxed value are re-written to reads /
+ * writes of these locals. Advantages:
+ * - supports mutable boxes (IntRef and friends)
+ * - supports eliminating unbox operations even if the box object needs to be created
+ * because it escapes (see E4)
+ * - works by keeping the unboxed value(s) in locals AND the box in its original form
+ * - only for immutable boxes: modifications to the escaped box cannot be applied to
+ * the local variable(s) holding the boxed value(s).
+ * Restriction:
+ * - does not work if there are multiple boxing operations (see E1)
+ *
+ * M2: If there are multiple boxing operations, the boxing operations are simply eliminated,
+ * leaving the unboxed value(s) on the stack. Store / load operations that previously
+ * acted on the box are adapted to handle the boxed type(s). If the box contains multiple
+ * values (or a size-2 value, which doesn't fit into locals that were used for the box),
+ * new local slots are used for store / load operations. Restrictions:
+ * - does not support re-writing writes to (mutable) boxes (see E2)
+ * - does not support re-writing reads of boxes that also escape (see E3)
+ *
+ *
+ * E1: M1 only works if there's a single boxing operation.
+ * def e1(b: Boolean) = {
+ * val i: Integer = box(10) // 10 is stored into a new local, box operation and i removed
+ * val j: Integer = box(20) // 20 is stored into a new local, box operation and j removed
+ * val r = if (b) i else j // loads and stores of the box are eliminated, r no longer exists
+ * unbox(r) // cannot rewrite: we don't know which local to load
+ * }
+ * Note: the example has no write and the box does not escape, so M2 works here.
+ *
+ * E2: mutable boxes with multiple boxing operations cannot be eliminated.
+ * M1: see E1
+ * M2: cannot replace an `IntRef` on the stack by an `Int` value on the stack, an Int on the
+ * stack cannot be modified.
+ *
+ * def e2(b: Boolean) = {
+ * val r1 = new IntRef(0)
+ * val r2 = new IntRef(1)
+ * val modRef = if (b) r1 else r2
+ * modRef.elem += 10 // M1: cannot rewrite: which local to write? same as E1.
+ * (if (b) r1 else r2).elem += 10 // M2: cannot change an Int on the stack
+ * (r1.elem, r2.elem)
+ * }
+ *
+ *
+ * E3: escaping boxes with multiple boxing operations cannot be rewritten.
+ * M1: see E1.
+ * M2: at *, instead of an Integer, an Int is on the stack, but the escape method expects an
+ * Integer. We cannot just create a box at this point: if there are multiple escapes (or
+ * an escape is executed more than once), the difference could be observed (reference
+ * equality).
+ *
+ * def e3(b: Boolean) = {
+ * val i: Integer = box(1)
+ * val j: Integer = box(2)
+ * escape(if (b) i else j) // *
+ * unbox(if (b) i else j)
+ * }
+ *
+ *
+ * E4: M1 supports rewriting unbox operations of immutable boxes that escape
+ * def e4 = {
+ * val i: Integer = box(10) // 10 is stored into a new local, loaded as argument for the box call
+ * escape(i) // not changed, still loads the local i holding the box
+ * unbox(i) // rewritten to a pop (of the box) and a load of the local variable
+ * }
+ *
+ *
+ * E4 seems to be a bit of a corner case, but it's necessary to unblock box eliminations with
+ * mutual dependencies. Example:
+ *
+ * val ((a, b), c) = ((1, 2), 3)
+ * a + b + c
+ *
+ * generates (after a few cleanups) the following (pseudo-bytecode, ignoring primitive boxing, specialization):
+ *
+ * load 1, load 2, new Tuple2 // stack: Tuple2
+ * load 3 // stack: Tuple2; Int
+ * val local1 = new Tuple2
+ * val local2 = local1._1.asInstanceOf[Tuple2]
+ * val c = local1._2.asInstanceOf[Int]
+ * if (local2 == null) throw new MatchError(local1)
+ * val a = local2._1
+ * val b = local2._2
+ * a + b + c
+ *
+ * In order to eliminate the tuples, we first need to eliminate the outer tuple (stored in local1)
+ * - single box operation, so we use M1
+ * - there are three consumers of the outer tuple: `local1._1`, `local1._2` and
+ * `new MatchError(local1)`. in the last one, the tuple escapes.
+ * - note that the MatchError creation is dead code: local2 is never null. However, our nullness
+ * analysis cannot identify this: it does not track nullness through tuple stores and loads.
+ * - if we re-write the non-escaping consumers of the outer tuple, but keep the tuple allocation
+ * and the escaping consumer, we get the following:
+ *
+ * load 1, load 2
+ * val newLocal1 = new Tuple2; load newLocal1 // stack: Tuple2
+ * val newLocal2 = 3; load newLocal2 // stack: Tuple2; Int
+ * val local1 = new Tuple2
+ * val local2 = newLocal1
+ * val c = newLocal2
+ * if (local2 == null) throw new MatchError(local1)
+ * val a = local2._1
+ * val b = local2._2
+ * a + b + c
+ *
+ * At this point, the nullness analysis sees that `local2 == null` is false, dead code elimination
+ * removes the `throw new MatchError(local1)`. After eliminating the allocation of the outer tuple,
+ * the inner tuple (stored in newLocal1) can also be eliminated.
+ *
+ *
+ * Special case for tuples wrt specialization: a tuple getter may box or unbox the value stored
+ * in the tuple: calling `_1` on a `Tuple2$mcII$sp` boxes the primitive Int stored in the tuple.
+ * Similarly, calling `_1$mcI$sp` on a non-specialized `Tuple2` unboxes the Integer in the tuple.
+ * When eliminating such getters, we have to introduce appropriate box / unbox calls.
+ *
+ *
+ * TODO: add new calls (box / unbox) to the call graph (not urgent)
+ * TODO: update the call graph because stack heights change (not urgent).
+ * this may also affect other optimizations, we ignored the issue so far. check how stack
+ * heights stored in the call graph are used.
+ * Note: these tasks are not urgent because the call graph is not currently used during / after
+ * method-local optimizations, only before to perform inlining and closure rewriting.
+ */
+ def boxUnboxElimination(method: MethodNode, owner: InternalName): Boolean = {
+ AsmAnalyzer.sizeOKForSourceValue(method) && {
+ val toInsertBefore = mutable.Map.empty[AbstractInsnNode, List[AbstractInsnNode]]
+ val toReplace = mutable.Map.empty[AbstractInsnNode, List[AbstractInsnNode]]
+ val toDelete = mutable.Set.empty[AbstractInsnNode]
+
+ val knownHandled = mutable.Set.empty[AbstractInsnNode]
+
+ lazy val prodCons = new ProdConsAnalyzer(method, owner)
+
+ var nextLocal = method.maxLocals
+ def getLocal(size: Int) = {
+ val r = nextLocal
+ nextLocal += size
+ r
+ }
+
+ var maxStackGrowth = 0
+
+ /** Method M1 for eliminating box-unbox pairs (see doc comment in the beginning of this file) */
+ def replaceBoxOperationsSingleCreation(creation: BoxCreation, finalCons: Set[BoxConsumer], boxKind: BoxKind, keepBox: Boolean): Unit = {
+ /**
+ * If the box is eliminated, all copy operations (loads, stores, others) of the box need to
+ * be removed. This method returns all copy operations that should be removed.
+ *
+ * Returns `None` in case some exotic copy operation is found that cannot be removed
+ * (DUP2_X1 and friends - these are never emitted by scalac). In this case, the box cannot
+ * be eliminated.
+ */
+ def copyOpsToEliminate: Option[Set[AbstractInsnNode]] = {
+ var elidableCopyOps = Set.empty[AbstractInsnNode]
+ var replaceOK = true
+ val copyOps = new CopyOpsIterator(Set(creation), finalCons, prodCons)
+ while (replaceOK && copyOps.hasNext) copyOps.next() match {
+ case vi: VarInsnNode =>
+ elidableCopyOps += vi
+
+ case copyOp if copyOp.getOpcode == DUP =>
+ elidableCopyOps += copyOp
+
+ case _ =>
+ replaceOK = false
+ }
+ if (replaceOK) Some(elidableCopyOps) else None
+ }
+
+ val canRewrite = keepBox || (copyOpsToEliminate match {
+ case Some(copyOps) =>
+ toDelete ++= copyOps
+ true
+
+ case _ => false
+ })
+
+ if (canRewrite) {
+ val localSlots: Vector[(Int, Type)] = boxKind.boxedTypes.map(tp => (getLocal(tp.getSize), tp))(collection.breakOut)
+
+ // store boxed value(s) into localSlots
+ val storeOps = localSlots.toList reverseMap { case (slot, tp) =>
+ new VarInsnNode(tp.getOpcode(ISTORE), slot)
+ }
+ val storeInitialValues = creation.loadInitialValues match {
+ case Some(ops) => ops ::: storeOps
+ case None => storeOps
+ }
+ if (keepBox) {
+ val loadOps: List[VarInsnNode] = localSlots.map({ case (slot, tp) =>
+ new VarInsnNode(tp.getOpcode(ILOAD), slot)
+ })(collection.breakOut)
+ toInsertBefore(creation.valuesConsumer) = storeInitialValues ::: loadOps
+ } else {
+ toReplace(creation.valuesConsumer) = storeInitialValues
+ toDelete ++= creation.allInsns - creation.valuesConsumer
+ }
+
+ // rewrite consumers
+ finalCons foreach {
+ case write: StaticSetterOrInstanceWrite =>
+ assert(!keepBox, s"cannot eliminate box write if the box remains (and escapes): $write")
+ val (slot, tp) = localSlots(boxKind.extractedValueIndex(write))
+ val storeOp = new VarInsnNode(tp.getOpcode(ISTORE), slot)
+ toReplace(write.consumer) = List(storeOp)
+
+ case c: EscapingConsumer =>
+ assert(keepBox, s"found escaping consumer, but box is eliminated: $c")
+
+ case extraction =>
+ val (slot, tp) = localSlots(boxKind.extractedValueIndex(extraction))
+ val loadOps = new VarInsnNode(tp.getOpcode(ILOAD), slot) :: extraction.postExtractionAdaptationOps(tp)
+ if (keepBox) toReplace(extraction.consumer) = getPop(1) :: loadOps
+ else toReplace(extraction.consumer) = loadOps
+ toDelete ++= extraction.allInsns - extraction.consumer
+ }
+ }
+ }
+
+ /** Method M2 for eliminating box-unbox pairs (see doc comment in the beginning of this file) */
+ def replaceBoxOperationsMultipleCreations(allCreations: Set[BoxCreation], allConsumers: Set[BoxConsumer], boxKind: BoxKind): Unit = {
+ /**
+ * If a single-value size-1 box is eliminated, local variables slots holding the box are
+ * reused to hold the unboxed value. In case there's an entry for that local variable in the
+ * method's local variables table (debug info), adapt the type.
+ *
+ * If there are multiple entries for a local variable that's changing types, then all
+ * entries for that variable are deleted - it's not obvious how to find the correct entry.
+ * Note that scalac never re-uses local variable slots for non-overlapping locals. Also note
+ * that all locals that are newly created during the optimizer don't have an entry either.
+ *
+ * Finally, note that variables that become unused are removed later from the table by
+ * removeUnusedLocalVariableNodes in LocalOpt.
+ *
+ * Unlike modifications that affect the method's instructions (which uses toReplace etc),
+ * we can directly modify the local variable table - it does not affect the frames of the
+ * ProdCons analysis.
+ */
+ def updateLocalVariableTypes(reTypedLocals: Map[Int, Type]): Unit = {
+ lazy val localsByIndex = method.localVariables.asScala.groupBy(_.index)
+ for ((index, tp) <- reTypedLocals) localsByIndex.get(index).map(_.toList) match {
+ case Some(List(local)) =>
+ local.desc = tp.getDescriptor
+ case Some(locals) =>
+ locals foreach method.localVariables.remove
+ case _ =>
+ }
+ }
+
+ /** Remove box creations - leave the boxed value(s) on the stack instead. */
+ def replaceCreationOps(): Unit = {
+ for (creation <- allCreations) creation.loadInitialValues match {
+ case None =>
+ toDelete ++= creation.allInsns
+
+ case Some(ops) =>
+ toReplace(creation.valuesConsumer) = ops
+ toDelete ++= (creation.allInsns - creation.valuesConsumer)
+ }
+ }
+
+ /**
+ * Replace a value extraction operation. For a single-value box, the extraction operation can
+ * just be removed. An extraction from a multi-value box is replaced by POP operations for the
+ * non-used values, and an xSTORE / xLOAD for the extracted value. Example: tuple3._2 becomes
+ * POP; xSTORE n; POP; xLOAD n.
+ */
+ def replaceExtractionOps(): Unit = {
+ if (boxKind.boxedTypes.lengthCompare(1) == 0) {
+ // fast path for single-value boxes
+ allConsumers.foreach(extraction => extraction.postExtractionAdaptationOps(boxKind.boxedTypes.head) match {
+ case Nil =>
+ toDelete ++= extraction.allInsns
+ case ops =>
+ toReplace(extraction.consumer) = ops
+ toDelete ++= extraction.allInsns - extraction.consumer
+ })
+ } else {
+ for (extraction <- allConsumers) {
+ val valueIndex = boxKind.extractedValueIndex(extraction)
+ val replacementOps = if (valueIndex == 0) {
+ val pops = boxKind.boxedTypes.tail.map(t => getPop(t.getSize))
+ pops ::: extraction.postExtractionAdaptationOps(boxKind.boxedTypes.head)
+ } else {
+ var loadOps: List[AbstractInsnNode] = null
+ val consumeStack = boxKind.boxedTypes.zipWithIndex reverseMap {
+ case (tp, i) =>
+ if (i == valueIndex) {
+ val resultSlot = getLocal(tp.getSize)
+ loadOps = new VarInsnNode(tp.getOpcode(ILOAD), resultSlot) :: extraction.postExtractionAdaptationOps(tp)
+ new VarInsnNode(tp.getOpcode(ISTORE), resultSlot)
+ } else {
+ getPop(tp.getSize)
+ }
+ }
+ consumeStack ::: loadOps
+ }
+ toReplace(extraction.consumer) = replacementOps
+ toDelete ++= extraction.allInsns - extraction.consumer
+ }
+ }
+ }
+
+ checkCopyOpReplacements(allCreations, allConsumers, boxKind.boxedTypes, nextLocal, prodCons) match {
+ case Some((replacements, nextCopyOpLocal, reTypedLocals)) =>
+ toReplace ++= replacements
+ updateLocalVariableTypes(reTypedLocals)
+ nextLocal = nextCopyOpLocal
+ replaceCreationOps()
+ replaceExtractionOps()
+ // Conservative (safe) value for stack growth. In every frame that initially has a multi-value
+ // box on the stack, the stack now contains all of the individual values. So for every eliminated
+ // box, the maxStack may be up to N-1 slots larger.
+ maxStackGrowth += boxKind.boxedTypes.length - 1
+
+ case None =>
+ }
+ }
+
+ val it = method.instructions.iterator
+ while (it.hasNext) {
+ val insn = it.next()
+ if (!knownHandled(insn)) BoxKind.valueCreationKind(insn, prodCons) match {
+ case Some((boxCreation, boxKind)) =>
+ allCreationsConsumers(boxCreation, boxKind, prodCons) match {
+ case Some((allCreations, allConsumers)) =>
+ val (escapingConsumers, boxConsumers) = allConsumers.partition(_.isEscaping)
+ if (boxConsumers.nonEmpty) {
+ for (c <- allCreations) knownHandled ++= c.allInsns
+ for (e <- allConsumers) knownHandled ++= e.allInsns
+
+ val hasEscaping = escapingConsumers.nonEmpty
+ val hasWrite = allConsumers.exists(_.isWrite)
+ if (!hasEscaping && !hasWrite) {
+ // M2 -- see doc comment in the beginning of this file
+ // If both M1 and M2 can be applied, we prefer M2 because it doesn't introduce new locals.
+ replaceBoxOperationsMultipleCreations(allCreations, allConsumers, boxKind)
+ } else if (allCreations.size == 1 && (!hasEscaping || !boxKind.isMutable)) {
+ // M1 -- see doc comment in the beginning of this file
+ replaceBoxOperationsSingleCreation(allCreations.head, allConsumers, boxKind, keepBox = hasEscaping)
+ }
+ }
+
+ case None =>
+ }
+
+ case None =>
+ }
+ }
+
+ def removeFromCallGraph(insn: AbstractInsnNode): Unit = insn match {
+ case mi: MethodInsnNode => callGraph.removeCallsite(mi, method)
+ case _ =>
+ }
+
+ for ((location, ops) <- toInsertBefore; op <- ops)
+ method.instructions.insertBefore(location, op)
+
+ for ((oldOp, newOps) <- toReplace) {
+ for (newOp <- newOps) method.instructions.insertBefore(oldOp, newOp)
+ method.instructions.remove(oldOp)
+ removeFromCallGraph(oldOp)
+ }
+
+ for (op <- toDelete) {
+ method.instructions.remove(op)
+ removeFromCallGraph(op)
+ }
+
+ method.maxLocals = nextLocal
+ method.maxStack += maxStackGrowth
+ toInsertBefore.nonEmpty || toReplace.nonEmpty || toDelete.nonEmpty
+ }
+ }
+
+ /**
+ * Given a box creations operation
+ * - find all ultimate consumers for the produced value. then:
+ * - for all consumed values, find all producer operations. check that all are box creations
+ * - recurse until reaching a fixpoint
+ *
+ * Returns a set of box creations and a set of box consumers. Note that the box consumers may
+ * contain [[EscapingConsumer]]s, even if there are multiple box creation operations. The callee
+ * will handle this case (and not attempt to eliminate the box).
+ */
+ def allCreationsConsumers(initialCreation: BoxCreation, boxKind: BoxKind, prodCons: ProdConsAnalyzer): Option[(Set[BoxCreation], Set[BoxConsumer])] = {
+ var creations = Set(initialCreation)
+ var consumers = Set.empty[BoxConsumer]
+
+ def addCreations(boxConsumer: BoxConsumer): Boolean = {
+ val newProds = boxConsumer.boxProducers(prodCons).filterNot(prod => creations.exists(_.producer == prod))
+ newProds.forall(prod => boxKind.checkBoxCreation(prod, prodCons) match {
+ case Some(boxCreation) =>
+ creations += boxCreation
+ addBoxConsumers(boxCreation)
+
+ case _ => false
+ })
+ }
+
+ def addBoxConsumers(creation: BoxCreation): Boolean = {
+ val newCons = creation.boxConsumers(prodCons, ultimate = true).filterNot(cons => consumers.exists(_.consumer == cons))
+ newCons.forall(cons => boxKind.checkBoxConsumer(cons, prodCons) match {
+ case Some(boxConsumer) =>
+ consumers += boxConsumer
+ addCreations(boxConsumer)
+
+ case _ =>
+ creations.size <= 1 && {
+ // If there's a single box creation, the box operations can still be rewritten
+ consumers += EscapingConsumer(cons)
+ true
+ }
+ })
+ }
+
+ if (addBoxConsumers(initialCreation)) Some((creations, consumers))
+ else None
+ }
+
+ /**
+ * Takes two sets `initialProds` and `finalCons` such that all boxes produced by the first set
+ * are only consumed by an operation in the second set.
+ *
+ * Returns a map that replaces copy operations (ALOAD / ASTORE) between the producers and
+ * consumers with corresponding copy operations for the values stored in the box. The returned
+ * `Int` value returns the next free local variable slot.
+ *
+ * Examples:
+ * - for an Integer box, an ASTORE x is simply replaced by ISTORE x
+ * - for a pair of two references, an ASTORE x is replaced by `ASTORE x1; ASTORE x2` where x1
+ * and x2 are fresh locals
+ *
+ * Not all copy operations can be supported: DUP only works for single-value boxes, the more
+ * exotic copy operations (DUP2_X2) are not supported (note that Scalac never emits them). If a
+ * copy operation cannot be replaced, this method returns `None`.
+ */
+ def checkCopyOpReplacements(initialProds: Set[BoxCreation], finalCons: Set[BoxConsumer], valueTypes: List[Type], nextLocal: Int, prodCons: ProdConsAnalyzer): Option[(Map[AbstractInsnNode, List[AbstractInsnNode]], Int, Map[Int, Type])] = {
+ var replacements = Map.empty[AbstractInsnNode, List[AbstractInsnNode]]
+ var reTypedLocals = Map.empty[Int, Type]
+
+ var nextCopyOpLocal = nextLocal
+ val newLocalsMap: mutable.LongMap[List[(Type, Int)]] = mutable.LongMap.empty
+ def newLocals(index: Int) = newLocalsMap.getOrElseUpdate(index, valueTypes match {
+ case List(t) if t.getSize == 1 =>
+ reTypedLocals += index -> t
+ List((t, index))
+ case _ => valueTypes.map(t => {
+ val newIndex = nextCopyOpLocal
+ nextCopyOpLocal += t.getSize
+ (t, newIndex)
+ })
+ })
+
+ var replaceOK = true
+ val copyOps = new CopyOpsIterator(initialProds, finalCons, prodCons)
+ while (replaceOK && copyOps.hasNext) copyOps.next() match {
+ case vi: VarInsnNode =>
+ val isLoad = vi.getOpcode == ALOAD
+ val typedVarOp = (tp: (Type, Int)) => {
+ val opc = tp._1.getOpcode(if (isLoad) ILOAD else ISTORE)
+ new VarInsnNode(opc, tp._2)
+ }
+ val locs = newLocals(vi.`var`)
+ replacements += vi -> (if (isLoad) locs.map(typedVarOp) else locs.reverseMap(typedVarOp))
+
+ case copyOp =>
+ if (copyOp.getOpcode == DUP && valueTypes.lengthCompare(1) == 0) {
+ if (valueTypes.head.getSize == 2)
+ replacements += copyOp -> List(new InsnNode(DUP2))
+ } else {
+ replaceOK = false
+ }
+ }
+ if (replaceOK) Some((replacements, nextCopyOpLocal, reTypedLocals)) else None
+ }
+
+ /**
+ * For a set of box creation operations and a corresponding set of box consumer operations,
+ * this iterator returns all copy operations (load, store, dup) that are in between.
+ */
+ class CopyOpsIterator(initialCreations: Set[BoxCreation], finalCons: Set[BoxConsumer], prodCons: ProdConsAnalyzer) extends Iterator[AbstractInsnNode] {
+ private var queue = mutable.Queue.empty[AbstractInsnNode] ++ initialCreations.iterator.flatMap(_.boxConsumers(prodCons, ultimate = false))
+
+ // a single copy operation can consume multiple producers: val a = if (b) box(1) else box(2).
+ // the `ASTORE a` has two producers (the two box operations). we need to handle it only once.
+ private val visited = mutable.Set.empty[AbstractInsnNode]
+
+ private val boxConsumingOps = finalCons.map(_.consumer)
+
+ @tailrec private def advanceToNextCopyOp(): Unit = {
+ if (queue.nonEmpty) {
+ val h = queue.front
+ if (visited(h) || boxConsumingOps(h)) {
+ queue.dequeue()
+ advanceToNextCopyOp()
+ }
+ }
+ }
+
+ def hasNext: Boolean = {
+ advanceToNextCopyOp()
+ queue.nonEmpty
+ }
+
+ def next(): AbstractInsnNode = {
+ advanceToNextCopyOp()
+ val r = queue.dequeue()
+ visited += r
+ queue ++= prodCons.consumersOfOutputsFrom(r)
+ r
+ }
+ }
+
+ trait BoxKind {
+ def checkBoxCreation(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxCreation]
+ def checkBoxConsumer(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxConsumer]
+ def boxedTypes: List[Type]
+ def extractedValueIndex(extraction: BoxConsumer): Int
+ def isMutable: Boolean
+ }
+
+ object BoxKind {
+ def valueCreationKind(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[(BoxCreation, BoxKind)] = {
+ PrimitiveBox.checkPrimitiveBox(insn, None, prodCons) orElse
+ Ref.checkRefCreation(insn, None, prodCons) orElse
+ Tuple.checkTupleCreation(insn, None, prodCons)
+ }
+
+ /**
+ * Check if `newOp` is part of a standard object construction pattern in which:
+ *
+ * NEW T
+ * DUP
+ * [load constructor args]
+ * INVOKESPECIAL T.init
+ *
+ * The method ensures that the entire construction pattern is closed in itself, without any
+ * branches going in or out. This is checked by looking at producers / consumers:
+ * - `DUP` is the only consumer of `NEW`, and vice versa
+ * - `DUP` the only producer for the receiver of the constructor call
+ * - The set of consumers of `DUP` without the constructor call is the same as
+ * the set of consumers of the value on the stack top after the constructor call
+ */
+ def checkInstanceCreation(newOp: TypeInsnNode, prodCons: ProdConsAnalyzer): Option[(InsnNode, MethodInsnNode)] = {
+ val newCons = prodCons.consumersOfOutputsFrom(newOp)
+ if (newCons.size == 1 && newCons.head.getOpcode == DUP) {
+ val dupOp = newCons.head.asInstanceOf[InsnNode]
+ if (prodCons.producersForInputsOf(dupOp) == Set(newOp)) {
+ val dupCons = prodCons.consumersOfOutputsFrom(dupOp)
+ val initCalls = dupCons collect {
+ case mi: MethodInsnNode if mi.name == GenBCode.INSTANCE_CONSTRUCTOR_NAME && mi.owner == newOp.desc => mi
+ }
+ if (initCalls.size == 1) {
+ val initCall = initCalls.head
+ val numArgs = Type.getArgumentTypes(initCall.desc).length
+ val receiverProds = prodCons.producersForValueAt(initCall, prodCons.frameAt(initCall).stackTop - numArgs)
+ if (receiverProds == Set(dupOp)) {
+ val dupConsWithoutInit = dupCons - initCall
+ val afterInit = initCall.getNext
+ val stackTopAfterInit = prodCons.frameAt(afterInit).stackTop
+ val initializedInstanceCons = prodCons.consumersOfValueAt(afterInit, stackTopAfterInit)
+ if (initializedInstanceCons == dupConsWithoutInit && prodCons.producersForValueAt(afterInit, stackTopAfterInit) == Set(dupOp)) {
+ return Some((dupOp, initCall))
+ }
+ }
+ }
+ }
+ }
+ None
+ }
+
+ /**
+ * If `mi` is an invocation of a method on Predef, check if the receiver is a GETSTATIC of
+ * Predef.MODULE$ and return it.
+ */
+ def checkReceiverPredefLoad(mi: MethodInsnNode, prodCons: ProdConsAnalyzer): Option[AbstractInsnNode] = {
+ val numArgs = Type.getArgumentTypes(mi.desc).length
+ val receiverProds = prodCons.producersForValueAt(mi, prodCons.frameAt(mi).stackTop - numArgs)
+ if (receiverProds.size == 1) {
+ val prod = receiverProds.head
+ if (isPredefLoad(prod) && prodCons.consumersOfOutputsFrom(prod) == Set(mi)) return Some(prod)
+ }
+ None
+ }
+ }
+
+ case class PrimitiveBox(boxedType: Type, boxClass: InternalName) extends BoxKind {
+ import PrimitiveBox._
+ def checkBoxCreation(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxCreation] = checkPrimitiveBox(insn, Some(this), prodCons).map(_._1)
+ def checkBoxConsumer(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = checkPrimitiveUnbox(insn, this, prodCons)
+ def boxedTypes: List[Type] = List(boxedType)
+ def extractedValueIndex(extraction: BoxConsumer): Int = 0
+ def isMutable = false
+ }
+
+ object PrimitiveBox {
+ private def boxedType(mi: MethodInsnNode) = Type.getArgumentTypes(mi.desc)(0)
+
+ private def boxClass(mi: MethodInsnNode) = {
+ if (mi.name == GenBCode.INSTANCE_CONSTRUCTOR_NAME) mi.owner
+ else Type.getReturnType(mi.desc).getInternalName
+ }
+
+ def checkPrimitiveBox(insn: AbstractInsnNode, expectedKind: Option[PrimitiveBox], prodCons: ProdConsAnalyzer): Option[(BoxCreation, PrimitiveBox)] = {
+ // mi is either a box factory or a box constructor invocation
+ def checkKind(mi: MethodInsnNode) = expectedKind match {
+ case Some(kind) => if (kind.boxClass == boxClass(mi)) expectedKind else None
+ case None => Some(PrimitiveBox(boxedType(mi), boxClass(mi)))
+ }
+
+ insn match {
+ case mi: MethodInsnNode =>
+ if (isScalaBox(mi) || isJavaBox(mi)) checkKind(mi).map((StaticFactory(mi, loadInitialValues = None), _))
+ else if (isPredefAutoBox(mi))
+ for (predefLoad <- BoxKind.checkReceiverPredefLoad(mi, prodCons); kind <- checkKind(mi))
+ yield (ModuleFactory(predefLoad, mi), kind)
+ else None
+
+ case ti: TypeInsnNode if ti.getOpcode == NEW =>
+ for ((dupOp, initCall) <- BoxKind.checkInstanceCreation(ti, prodCons) if isPrimitiveBoxConstructor(initCall); kind <- checkKind(initCall))
+ yield (InstanceCreation(ti, dupOp, initCall), kind)
+
+ case _ => None
+ }
+ }
+
+ def checkPrimitiveUnbox(insn: AbstractInsnNode, kind: PrimitiveBox, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = {
+ def typeOK(mi: MethodInsnNode) = kind.boxedType == Type.getReturnType(mi.desc)
+ insn match {
+ case mi: MethodInsnNode =>
+ if ((isScalaUnbox(mi) || isJavaUnbox(mi)) && typeOK(mi)) Some(StaticGetterOrInstanceRead(mi))
+ else if (isPredefAutoUnbox(mi) && typeOK(mi)) BoxKind.checkReceiverPredefLoad(mi, prodCons).map(ModuleGetter(_, mi))
+ else None
+
+ case _ => None
+ }
+ }
+ }
+
+ case class Ref(boxedType: Type, refClass: InternalName) extends BoxKind {
+ import Ref._
+ def checkBoxCreation(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxCreation] = checkRefCreation(insn, Some(this), prodCons).map(_._1)
+ def checkBoxConsumer(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = checkRefConsumer(insn, this, prodCons)
+ def boxedTypes: List[Type] = List(boxedType)
+ def extractedValueIndex(extraction: BoxConsumer): Int = 0
+ def isMutable = true
+ }
+
+ object Ref {
+ private def boxedType(mi: MethodInsnNode): Type = runtimeRefClassBoxedType(mi.owner)
+ private def refClass(mi: MethodInsnNode): InternalName = mi.owner
+ private def loadZeroValue(refZeroCall: MethodInsnNode): List[AbstractInsnNode] = List(loadZeroForTypeSort(runtimeRefClassBoxedType(refZeroCall.owner).getSort))
+
+ def checkRefCreation(insn: AbstractInsnNode, expectedKind: Option[Ref], prodCons: ProdConsAnalyzer): Option[(BoxCreation, Ref)] = {
+ def checkKind(mi: MethodInsnNode): Option[Ref] = expectedKind match {
+ case Some(kind) => if (kind.refClass == refClass(mi)) expectedKind else None
+ case None => Some(Ref(boxedType(mi), refClass(mi)))
+ }
+
+ insn match {
+ case mi: MethodInsnNode =>
+ if (isRefCreate(mi)) checkKind(mi).map((StaticFactory(mi, loadInitialValues = None), _))
+ else if (isRefZero(mi)) checkKind(mi).map((StaticFactory(mi, loadInitialValues = Some(loadZeroValue(mi))), _))
+ else None
+
+ case ti: TypeInsnNode if ti.getOpcode == NEW =>
+ for ((dupOp, initCall) <- BoxKind.checkInstanceCreation(ti, prodCons) if isRuntimeRefConstructor(initCall); kind <- checkKind(initCall))
+ yield (InstanceCreation(ti, dupOp, initCall), kind)
+
+ case _ => None
+ }
+ }
+
+ def checkRefConsumer(insn: AbstractInsnNode, kind: Ref, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = insn match {
+ case fi: FieldInsnNode if fi.owner == kind.refClass && fi.name == "elem" =>
+ if (fi.getOpcode == GETFIELD) Some(StaticGetterOrInstanceRead(fi))
+ else if (fi.getOpcode == PUTFIELD) Some(StaticSetterOrInstanceWrite(fi))
+ else None
+
+ case _ => None
+ }
+ }
+
+ case class Tuple(boxedTypes: List[Type], tupleClass: InternalName) extends BoxKind {
+ import Tuple._
+ def checkBoxCreation(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxCreation] = checkTupleCreation(insn, Some(this), prodCons).map(_._1)
+ def checkBoxConsumer(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = checkTupleExtraction(insn, this, prodCons)
+ def extractedValueIndex(extraction: BoxConsumer): Int = extraction match {
+ case StaticGetterOrInstanceRead(mi: MethodInsnNode) => tupleGetterIndex(mi.name)
+ case PrimitiveBoxingGetter(mi) => tupleGetterIndex(mi.name)
+ case PrimitiveUnboxingGetter(mi, _) => tupleGetterIndex(mi.name)
+ case _ => throw new AssertionError(s"Expected tuple getter, found $extraction")
+ }
+ def isMutable = false
+ }
+
+ object Tuple {
+ private def boxedTypes(mi: MethodInsnNode): List[Type] = Type.getArgumentTypes(mi.desc).toList
+ private def tupleClass(mi: MethodInsnNode): InternalName = mi.owner
+
+ def checkTupleCreation(insn: AbstractInsnNode, expectedKind: Option[Tuple], prodCons: ProdConsAnalyzer): Option[(BoxCreation, Tuple)] = {
+ def checkKind(mi: MethodInsnNode): Option[Tuple] = expectedKind match {
+ case Some(kind) => if (kind.tupleClass == tupleClass(mi)) expectedKind else None
+ case None => Some(Tuple(boxedTypes(mi), tupleClass(mi)))
+ }
+
+ insn match {
+ // no need to check for TupleN.apply: the compiler transforms case companion apply calls to constructor invocations
+ case ti: TypeInsnNode if ti.getOpcode == NEW =>
+ for ((dupOp, initCall) <- BoxKind.checkInstanceCreation(ti, prodCons) if isTupleConstructor(initCall); kind <- checkKind(initCall))
+ yield (InstanceCreation(ti, dupOp, initCall), kind)
+
+ case _ => None
+ }
+ }
+
+ private val specializedTupleClassR = "scala/Tuple[12]\\$mc[IJDCZ]{1,2}\\$sp".r
+ private def isSpecializedTupleClass(tupleClass: InternalName) = specializedTupleClassR.pattern.matcher(tupleClass).matches
+
+ private val specializedTupleGetterR = "_[12]\\$mc[IJDCZ]\\$sp".r
+ private def isSpecializedTupleGetter(mi: MethodInsnNode) = specializedTupleGetterR.pattern.matcher(mi.name).matches
+
+ private val tupleGetterR = "_\\d\\d?".r
+ private def isTupleGetter(mi: MethodInsnNode) = tupleGetterR.pattern.matcher(mi.name).matches
+
+ def checkTupleExtraction(insn: AbstractInsnNode, kind: Tuple, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = {
+ val expectedTupleClass = kind.tupleClass
+ insn match {
+ case mi: MethodInsnNode =>
+ val tupleClass = mi.owner
+ if (isSpecializedTupleClass(expectedTupleClass)) {
+ val typeOK = tupleClass == expectedTupleClass || tupleClass == expectedTupleClass.substring(0, expectedTupleClass.indexOf('$'))
+ if (typeOK) {
+ if (isSpecializedTupleGetter(mi)) return Some(StaticGetterOrInstanceRead(mi))
+ else if (isTupleGetter(mi)) return Some(PrimitiveBoxingGetter(mi))
+ }
+ } else if (expectedTupleClass == tupleClass) {
+ if (isSpecializedTupleGetter(mi)) return Some(PrimitiveUnboxingGetter(mi, Type.getReturnType(mi.desc)))
+ else if (isTupleGetter(mi)) return Some(StaticGetterOrInstanceRead(mi))
+ }
+
+ case _ =>
+ }
+ None
+ }
+
+ private val getterIndexPattern = "_(\\d{1,2}).*".r
+ def tupleGetterIndex(getterName: String) = getterName match { case getterIndexPattern(i) => i.toInt - 1 }
+ }
+
+ // TODO: add more
+ // case class ValueClass(valueClass: Type, valueType: Type) extends BoxKind
+
+ sealed trait BoxCreation {
+ // to support box creation operations that don't consume an initial value from the stack, e.g., IntRef.zero
+ val loadInitialValues: Option[List[AbstractInsnNode]]
+
+ /**
+ * The instruction that produces the box value; for instance creations, the `NEW` operation.
+ */
+ def producer: AbstractInsnNode
+
+ /**
+ * The instruction that consumes the boxed values; for instance creations, the `init` call.
+ */
+ def valuesConsumer: MethodInsnNode = this match {
+ case StaticFactory(call, _) => call
+ case ModuleFactory(_, call) => call
+ case InstanceCreation(_, _, initCall) => initCall
+ }
+
+ def allInsns: Set[AbstractInsnNode] = this match {
+ case StaticFactory(c, _) => Set(c)
+ case ModuleFactory(m, c) => Set(m, c)
+ case InstanceCreation(n, d, i) => Set(n, d, i)
+ }
+
+ /**
+ * The consumers of the box produced by this box creation. If `ultimate` is true, then the
+ * final consumers are returned (e.g., an unbox operation), otherwise direct consumers (e.g.,
+ * a store operation).
+ */
+ def boxConsumers(prodCons: ProdConsAnalyzer, ultimate: Boolean): Set[AbstractInsnNode] = {
+ val startInsn = this match {
+ // for the non-transitive case (ultimate == false), it's important to start at the `dupOp`,
+ // not the `newOp` - look at the BoxCreation as a black box, get its consumers.
+ case InstanceCreation(_, dupOp, _) => dupOp
+ case _ => producer
+ }
+ val cons = if (ultimate) prodCons.ultimateConsumersOfOutputsFrom(startInsn) else prodCons.consumersOfOutputsFrom(startInsn)
+ this match {
+ case InstanceCreation(_, _, initCall) => cons - initCall
+ case _ => cons
+ }
+ }
+ }
+
+ case class StaticFactory(producer: MethodInsnNode, loadInitialValues: Option[List[AbstractInsnNode]]) extends BoxCreation
+ case class ModuleFactory(moduleLoad: AbstractInsnNode, producer: MethodInsnNode) extends BoxCreation {
+ val loadInitialValues: Option[List[AbstractInsnNode]] = None
+ }
+ case class InstanceCreation(newOp: TypeInsnNode, dupOp: InsnNode, initCall: MethodInsnNode) extends BoxCreation {
+ def producer = newOp
+ val loadInitialValues: Option[List[AbstractInsnNode]] = None
+ }
+
+ sealed trait BoxConsumer {
+ val consumer: AbstractInsnNode
+
+ def allInsns: Set[AbstractInsnNode] = this match {
+ case ModuleGetter(m, c) => Set(m, c)
+ case _ => Set(consumer)
+ }
+
+ /**
+ * The initial producers of the box value consumed by this box consumer
+ */
+ def boxProducers(prodCons: ProdConsAnalyzer): Set[AbstractInsnNode] = {
+ val stackTop = prodCons.frameAt(consumer).stackTop
+ val slot = if (isWrite) stackTop - 1 else stackTop
+ prodCons.initialProducersForValueAt(consumer, slot)
+ }
+
+ def isEscaping = this match {
+ case _: EscapingConsumer => true
+ case _ => false
+ }
+
+ def isWrite = this match {
+ case _: StaticSetterOrInstanceWrite => true
+ case _ => false
+ }
+
+ /**
+ * If this box consumer extracts a boxed value and applies a conversion, this method returns
+ * equivalent conversion operations. For example, invoking `_1$mcI$sp` on a non-specialized
+ * `Tuple2` extracts the Integer value and unboxes it.
+ */
+ def postExtractionAdaptationOps(typeOfExtractedValue: Type): List[AbstractInsnNode] = this match {
+ case PrimitiveBoxingGetter(_) => List(getScalaBox(typeOfExtractedValue))
+ case PrimitiveUnboxingGetter(_, unboxedPrimitive) => List(getScalaUnbox(unboxedPrimitive))
+ case _ => Nil
+ }
+ }
+
+ /** Static extractor (BoxesRunTime.unboxToInt) or GETFIELD or getter invocation */
+ case class StaticGetterOrInstanceRead(consumer: AbstractInsnNode) extends BoxConsumer
+ /** A getter that boxes the returned value, e.g., `Tuple2$mcII$sp._1` */
+ case class PrimitiveBoxingGetter(consumer: MethodInsnNode) extends BoxConsumer
+ /** A getter that unboxes the returned value, e.g., `Tuple2._1$mcI$sp` */
+ case class PrimitiveUnboxingGetter(consumer: MethodInsnNode, unboxedPrimitive: Type) extends BoxConsumer
+ /** An extractor method in a Scala module, e.g., `Predef.Integer2int` */
+ case class ModuleGetter(moduleLoad: AbstractInsnNode, consumer: MethodInsnNode) extends BoxConsumer
+ /** PUTFIELD or setter invocation */
+ case class StaticSetterOrInstanceWrite(consumer: AbstractInsnNode) extends BoxConsumer
+ /** An unknown box consumer */
+ case class EscapingConsumer(consumer: AbstractInsnNode) extends BoxConsumer
+}
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 a5b85e54e7..f2ff73c44d 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
@@ -9,13 +9,12 @@ package opt
import scala.tools.asm
import asm.tree._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
+import scala.collection.{concurrent, mutable}
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 scala.tools.nsc.util.ClassPath
import BytecodeUtils._
-import ByteCodeRepository._
import BTypes.InternalName
import java.util.concurrent.atomic.AtomicLong
@@ -24,58 +23,91 @@ import java.util.concurrent.atomic.AtomicLong
* classpath. Parsed classes are cached in the `classes` map.
*
* @param classPath The compiler classpath where classfiles are searched and read from.
- * @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 isJavaSourceDefined: InternalName => Boolean, val classes: collection.concurrent.Map[InternalName, Either[ClassNotFound, (ClassNode, Source, Long)]]) {
+class ByteCodeRepository[BT <: BTypes](val classPath: ClassPath, val btypes: BT) {
+ import btypes._
+
+ /**
+ * Contains ClassNodes and the canonical path of the source file path of classes being compiled in
+ * the current compilation run.
+ */
+ val compilingClasses: concurrent.Map[InternalName, (ClassNode, String)] = recordPerRunCache(concurrent.TrieMap.empty)
+
+ /**
+ * Cache for parsed ClassNodes.
+ * The `Long` field encodes the age of the node in the map, which allows removing old entries when
+ * the map grows too large (see limitCacheSize).
+ * 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.
+ */
+ val parsedClasses: concurrent.Map[InternalName, Either[ClassNotFound, (ClassNode, Long)]] = recordPerRunCache(concurrent.TrieMap.empty)
private val maxCacheSize = 1500
private val targetSize = 500
- private val idCounter = new AtomicLong(0)
+ private object lruCounter extends AtomicLong(0l) with collection.generic.Clearable {
+ def clear(): Unit = { this.set(0l) }
+ }
+ recordPerRunCache(lruCounter)
/**
* 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
+ if (parsedClasses.size > maxCacheSize) {
+ // OK if multiple threads get here
+ val minimalLRU = parsedClasses.valuesIterator.collect({
+ case Right((_, lru)) => lru
+ }).toList.sorted(Ordering.Long.reverse).drop(targetSize).headOption.getOrElse(Long.MaxValue)
+ parsedClasses retain {
+ case (_, Right((_, lru))) => lru > minimalLRU
+ case _ => false
+ }
}
}
- def add(classNode: ClassNode, source: Source) = {
- classes(classNode.name) = Right((classNode, source, idCounter.incrementAndGet()))
+ def add(classNode: ClassNode, sourceFilePath: Option[String]) = sourceFilePath match {
+ case Some(path) if path != "<no file>" => compilingClasses(classNode.name) = (classNode, path)
+ case _ => parsedClasses(classNode.name) = Right((classNode, lruCounter.incrementAndGet()))
+ }
+
+ private def parsedClassNode(internalName: InternalName): Either[ClassNotFound, ClassNode] = {
+ val r = parsedClasses.get(internalName) match {
+ case Some(l @ Left(_)) => l
+ case Some(r @ Right((classNode, _))) =>
+ parsedClasses(internalName) = Right((classNode, lruCounter.incrementAndGet()))
+ r
+ case None =>
+ limitCacheSize()
+ val res = parseClass(internalName).map((_, lruCounter.incrementAndGet()))
+ parsedClasses(internalName) = res
+ res
+ }
+ r.map(_._1)
}
/**
- * 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.
+ * The class node and source file path (if the class is being compiled) 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): Either[ClassNotFound, (ClassNode, Source)] = {
- val r = classes.getOrElseUpdate(internalName, {
- limitCacheSize()
- parseClass(internalName).map((_, Classfile, idCounter.incrementAndGet()))
- })
- r.map(v => (v._1, v._2))
+ def classNodeAndSourceFilePath(internalName: InternalName): Either[ClassNotFound, (ClassNode, Option[String])] = {
+ compilingClasses.get(internalName) match {
+ case Some((c, p)) => Right((c, Some(p)))
+ case _ => parsedClassNode(internalName).map((_, None))
+ }
}
/**
* 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): Either[ClassNotFound, ClassNode] = classNodeAndSource(internalName).map(_._1)
+ def classNode(internalName: InternalName): Either[ClassNotFound, ClassNode] = {
+ compilingClasses.get(internalName) match {
+ case Some((c, _)) => Right(c)
+ case None => parsedClassNode(internalName)
+ }
+ }
/**
* The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`.
@@ -86,7 +118,6 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav
*/
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) =>
@@ -105,33 +136,135 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav
* The method node for a method matching `name` and `descriptor`, accessed in class `ownerInternalNameOrArrayDescriptor`.
* The declaration of the method may be in one of the parents.
*
+ * Note that the JVM spec performs method lookup in two steps: resolution and selection.
+ *
+ * Method resolution, defined in jvms-5.4.3.3 and jvms-5.4.3.4, is the first step and is identical
+ * for all invocation styles (virtual, interface, special, static). If C is the receiver class
+ * in the invocation instruction:
+ * 1 find a matching method (name and descriptor) in C
+ * 2 then in C's superclasses
+ * 3 then find the maximally-specific matching superinterface methods, succeed if there's a
+ * single non-abstract one. static and private methods in superinterfaces are not considered.
+ * 4 then pick a random non-static, non-private superinterface method.
+ * 5 then fail.
+ *
+ * Note that for an `invokestatic` instruction, a method reference `B.m` may resolve to `A.m`, if
+ * class `B` doesn't specify a matching method `m`, but the parent `A` does.
+ *
+ * Selection depends on the invocation style and is defined in jvms-6.5.
+ * - invokestatic: invokes the resolved method
+ * - invokevirtual / invokeinterface: searches for an override of the resolved method starting
+ * at the dynamic receiver type. the search procedure is basically the same as in resolution,
+ * but it fails at 4 instead of picking a superinterface method at random.
+ * - invokespecial: if C is the receiver in the invocation instruction, searches for an override
+ * of the resolved method starting at
+ * - the superclass of the current class, if C is a superclass of the current class
+ * - C otherwise
+ * again, the search procedure is the same.
+ *
+ * In the method here we implement method *resolution*. Whether or not the returned method is
+ * actually invoked at runtime depends on the invocation instruction and the class hierarchy, so
+ * the users (e.g. the inliner) have to be aware of method selection.
+ *
+ * Note that the returned method may be abstract (ACC_ABSTRACT), native (ACC_NATIVE) or signature
+ * polymorphic (methods `invoke` and `invokeExact` in class `MethodHandles`).
+ *
* @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.
+ * class, or an error message if the method could not be found. An error message is also
+ * returned if method resolution results in multiple default methods.
*/
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)
- }
+ def findMethod(c: ClassNode): Option[MethodNode] = c.methods.asScala.find(m => m.name == name && m.desc == descriptor)
+
+ // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9: "In Java SE 8, the only
+ // signature polymorphic methods are the invoke and invokeExact methods of the class MethodHandle.
+ def isSignaturePolymorphic(owner: InternalName) = owner == coreBTypes.jliMethodHandleRef.internalName && (name == "invoke" || name == "invokeExact")
+
+ // Note: if `owner` is an interface, in the first iteration we search for a matching member in the interface itself.
+ // If that fails, the recursive invocation checks in the superclass (which is Object) with `publicInstanceOnly == true`.
+ // This is specified in jvms-5.4.3.4: interface method resolution only returns public, non-static methods of Object.
+ def findInSuperClasses(owner: ClassNode, publicInstanceOnly: Boolean = false): Either[ClassNotFound, Option[(MethodNode, InternalName)]] = {
+ findMethod(owner) match {
+ case Some(m) if !publicInstanceOnly || (isPublicMethod(m) && !isStaticMethod(m)) => Right(Some((m, owner.name)))
+ case None =>
+ if (isSignaturePolymorphic(owner.name)) Right(Some((owner.methods.asScala.find(_.name == name).get, owner.name)))
+ else if (owner.superName == null) Right(None)
+ else classNode(owner.superName).flatMap(findInSuperClasses(_, isInterface(owner)))
}
}
- // 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)
+ def findInInterfaces(initialOwner: ClassNode): Either[ClassNotFound, Option[(MethodNode, InternalName)]] = {
+ val visited = mutable.Set.empty[InternalName]
+ val found = mutable.ListBuffer.empty[(MethodNode, ClassNode)]
+
+ def findIn(owner: ClassNode): Option[ClassNotFound] = {
+ for (i <- owner.interfaces.asScala if !visited(i)) classNode(i) match {
+ case Left(e) => return Some(e)
+ case Right(c) =>
+ visited += i
+ // abstract and static methods are excluded, see jvms-5.4.3.3
+ for (m <- findMethod(c) if !isPrivateMethod(m) && !isStaticMethod(m)) found += ((m, c))
+ val recursionResult = findIn(c)
+ if (recursionResult.isDefined) return recursionResult
+ }
+ None
+ }
+
+ findIn(initialOwner)
+
+ val result =
+ if (found.size <= 1) found.headOption
+ else {
+ val maxSpecific = found.filterNot({
+ case (method, owner) =>
+ isAbstractMethod(method) || {
+ val ownerTp = classBTypeFromClassNode(owner)
+ found exists {
+ case (other, otherOwner) =>
+ (other ne method) && {
+ val otherTp = classBTypeFromClassNode(otherOwner)
+ otherTp.isSubtypeOf(ownerTp).get
+ }
+ }
+ }
+ })
+ // (*) note that if there's no single, non-abstract, maximally-specific method, the jvm
+ // method resolution (jvms-5.4.3.3) returns any of the non-private, non-static parent
+ // methods at random (abstract or concrete).
+ // we chose not to do this here, to prevent the inliner from potentially inlining the
+ // wrong method. in other words, we guarantee that a concrete method is only returned if
+ // it resolves deterministically.
+ // however, there may be multiple abstract methods inherited. in this case we *do* want
+ // to return a result to allow performing accessibility checks in the inliner. note that
+ // for accessibility it does not matter which of these methods is return, as they are all
+ // non-private (i.e., public, protected is not possible, jvms-4.1).
+ // the remaining case (when there's no max-specific method, but some non-abstract one)
+ // does not occur in bytecode generated by scalac or javac. we return no result in this
+ // case. this may at worst prevent some optimizations from happening.
+ if (maxSpecific.size == 1) maxSpecific.headOption
+ else if (found.forall(p => isAbstractMethod(p._1))) found.headOption // (*)
+ else None
+ }
+ Right(result.map(p => (p._1, p._2.name)))
}
// In a MethodInsnNode, the `owner` field may be an array descriptor, for example 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, _))
+ if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[') {
+ Left(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, None))
+ } else {
+ def notFound(cnf: Option[ClassNotFound]) = Left(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, cnf))
+ val res: Either[ClassNotFound, Option[(MethodNode, InternalName)]] = classNode(ownerInternalNameOrArrayDescriptor).flatMap(c =>
+ findInSuperClasses(c) flatMap {
+ case None => findInInterfaces(c)
+ case res => Right(res)
+ }
+ )
+ res match {
+ case Left(e) => notFound(Some(e))
+ case Right(None) => notFound(None)
+ case Right(Some(res)) => Right(res)
+ }
+ }
}
private def parseClass(internalName: InternalName): Either[ClassNotFound, ClassNode] = {
@@ -157,17 +290,7 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav
classNode
} match {
case Some(node) => Right(node)
- case None => Left(ClassNotFound(internalName, isJavaSourceDefined(internalName)))
+ case None => Left(ClassNotFound(internalName, javaDefinedClasses(internalName)))
}
}
}
-
-object ByteCodeRepository {
- /**
- * The source of a ClassNode in the ByteCodeRepository. Can be either [[CompilationUnit]] if the
- * class is being compiled or [[Classfile]] if the class was parsed from the compilation classpath.
- */
- sealed trait Source
- object CompilationUnit extends Source
- object Classfile extends Source
-}
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 7aadd2c466..bfd92cac5c 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
@@ -8,28 +8,29 @@ package backend.jvm
package opt
import scala.annotation.{tailrec, switch}
+
import scala.collection.mutable
import scala.reflect.internal.util.Collections._
import scala.tools.asm.commons.CodeSizeEvaluator
import scala.tools.asm.tree.analysis._
-import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes, Type}
+import scala.tools.asm.{Label, Type}
+import scala.tools.asm.Opcodes._
import scala.tools.asm.tree._
import GenBCode._
-import scala.collection.convert.decorateAsScala._
-import scala.collection.convert.decorateAsJava._
-import scala.tools.nsc.backend.jvm.BTypes._
+import scala.collection.JavaConverters._
+import scala.tools.nsc.backend.jvm.analysis.InstructionStackEffect
object BytecodeUtils {
// http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.9.1
- final val maxJVMMethodSize = 65535
+ final val maxJVMMethodSize = 65535
// 5% margin, more than enough for the instructions added by the inliner (store / load args, null check for instance methods)
final val maxMethodSizeAfterInline = maxJVMMethodSize - (maxJVMMethodSize / 20)
object Goto {
def unapply(instruction: AbstractInsnNode): Option[JumpInsnNode] = {
- if (instruction.getOpcode == Opcodes.GOTO) Some(instruction.asInstanceOf[JumpInsnNode])
+ if (instruction.getOpcode == GOTO) Some(instruction.asInstanceOf[JumpInsnNode])
else None
}
}
@@ -49,8 +50,9 @@ object BytecodeUtils {
}
object VarInstruction {
- def unapply(instruction: AbstractInsnNode): Option[VarInsnNode] = {
- if (isVarInstruction(instruction)) Some(instruction.asInstanceOf[VarInsnNode])
+ def unapply(instruction: AbstractInsnNode): Option[(AbstractInsnNode, Int)] = {
+ if (isLoadStoreOrRet(instruction)) Some((instruction, instruction.asInstanceOf[VarInsnNode].`var`))
+ else if (instruction.getOpcode == IINC) Some((instruction, instruction.asInstanceOf[IincInsnNode].`var`))
else None
}
@@ -59,30 +61,46 @@ object BytecodeUtils {
def isJumpNonJsr(instruction: AbstractInsnNode): Boolean = {
val op = instruction.getOpcode
// JSR is deprecated in classfile version 50, disallowed in 51. historically, it was used to implement finally.
- op == Opcodes.GOTO || isConditionalJump(instruction)
+ op == GOTO || isConditionalJump(instruction)
}
def isConditionalJump(instruction: AbstractInsnNode): Boolean = {
val op = instruction.getOpcode
- (op >= Opcodes.IFEQ && op <= Opcodes.IF_ACMPNE) || op == Opcodes.IFNULL || op == Opcodes.IFNONNULL
+ (op >= IFEQ && op <= IF_ACMPNE) || op == IFNULL || op == IFNONNULL
}
def isReturn(instruction: AbstractInsnNode): Boolean = {
val op = instruction.getOpcode
- op >= Opcodes.IRETURN && op <= Opcodes.RETURN
+ op >= IRETURN && op <= RETURN
}
def isLoad(instruction: AbstractInsnNode): Boolean = {
val op = instruction.getOpcode
- op >= Opcodes.ILOAD && op <= Opcodes.ALOAD
+ op >= ILOAD && op <= ALOAD
}
def isStore(instruction: AbstractInsnNode): Boolean = {
val op = instruction.getOpcode
- op >= Opcodes.ISTORE && op <= Opcodes.ASTORE
+ op >= ISTORE && op <= ASTORE
+ }
+
+ def isLoadStoreOrRet(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction) || instruction.getOpcode == RET
+
+ def isLoadOrStore(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction)
+
+ def isNonVirtualCall(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ op == INVOKESPECIAL || op == INVOKESTATIC
}
- def isVarInstruction(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction)
+ def isVirtualCall(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ op == INVOKEVIRTUAL || op == INVOKEINTERFACE
+ }
+
+ def isCall(instruction: AbstractInsnNode): Boolean = {
+ isNonVirtualCall(instruction) || isVirtualCall(instruction)
+ }
def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0
@@ -90,27 +108,40 @@ object BytecodeUtils {
methodNode.name == INSTANCE_CONSTRUCTOR_NAME || methodNode.name == CLASS_CONSTRUCTOR_NAME
}
- def isStaticMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STATIC) != 0
+ def isPublicMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_PUBLIC) != 0
- def isAbstractMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_ABSTRACT) != 0
+ def isPrivateMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_PRIVATE) != 0
- def isSynchronizedMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_SYNCHRONIZED) != 0
+ def isStaticMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_STATIC) != 0
- def isNativeMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_NATIVE) != 0
+ def isAbstractMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_ABSTRACT) != 0
- def isFinalClass(classNode: ClassNode): Boolean = (classNode.access & Opcodes.ACC_FINAL) != 0
+ def isSynchronizedMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_SYNCHRONIZED) != 0
- def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) != 0
+ def isNativeMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_NATIVE) != 0
- def isStrictfpMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STRICT) != 0
+ def hasCallerSensitiveAnnotation(methodNode: MethodNode): Boolean = methodNode.visibleAnnotations != null && methodNode.visibleAnnotations.asScala.exists(_.desc == "Lsun/reflect/CallerSensitive;")
+
+ def isFinalClass(classNode: ClassNode): Boolean = (classNode.access & ACC_FINAL) != 0
+
+ def isInterface(classNode: ClassNode): Boolean = (classNode.access & ACC_INTERFACE) != 0
+
+ def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (ACC_FINAL | ACC_PRIVATE | ACC_STATIC)) != 0
+
+ def isStrictfpMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_STRICT) != 0
def isReference(t: Type) = t.getSort == Type.OBJECT || t.getSort == Type.ARRAY
- def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = {
- var result = instruction
- do { result = result.getNext }
- while (result != null && !isExecutable(result) && !alsoKeep(result))
- Option(result)
+ @tailrec def nextExecutableInstruction(insn: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = {
+ val next = insn.getNext
+ if (next == null || isExecutable(next) || alsoKeep(next)) Option(next)
+ else nextExecutableInstruction(next, alsoKeep)
+ }
+
+ @tailrec def nextExecutableInstructionOrLabel(insn: AbstractInsnNode): Option[AbstractInsnNode] = {
+ val next = insn.getNext
+ if (next == null || isExecutable(next) || next.isInstanceOf[LabelNode]) Option(next)
+ else nextExecutableInstructionOrLabel(next)
}
def sameTargetExecutableInstruction(a: JumpInsnNode, b: JumpInsnNode): Boolean = {
@@ -124,14 +155,14 @@ object BytecodeUtils {
def removeJumpAndAdjustStack(method: MethodNode, jump: JumpInsnNode) {
val instructions = method.instructions
val op = jump.getOpcode
- if ((op >= Opcodes.IFEQ && op <= Opcodes.IFGE) || op == Opcodes.IFNULL || op == Opcodes.IFNONNULL) {
+ if ((op >= IFEQ && op <= IFLE) || op == IFNULL || op == IFNONNULL) {
instructions.insert(jump, getPop(1))
- } else if ((op >= Opcodes.IF_ICMPEQ && op <= Opcodes.IF_ICMPLE) || op == Opcodes.IF_ACMPEQ || op == Opcodes.IF_ACMPNE) {
+ } else if ((op >= IF_ICMPEQ && op <= IF_ICMPLE) || op == IF_ACMPEQ || op == IF_ACMPNE) {
instructions.insert(jump, getPop(1))
instructions.insert(jump, getPop(1))
} else {
// we can't remove JSR: its execution does not only jump, it also adds a return address to the stack
- assert(jump.getOpcode == Opcodes.GOTO)
+ assert(jump.getOpcode == GOTO)
}
instructions.remove(jump)
}
@@ -148,37 +179,61 @@ object BytecodeUtils {
}
def negateJumpOpcode(jumpOpcode: Int): Int = (jumpOpcode: @switch) match {
- case Opcodes.IFEQ => Opcodes.IFNE
- case Opcodes.IFNE => Opcodes.IFEQ
+ case IFEQ => IFNE
+ case IFNE => IFEQ
+
+ case IFLT => IFGE
+ case IFGE => IFLT
- case Opcodes.IFLT => Opcodes.IFGE
- case Opcodes.IFGE => Opcodes.IFLT
+ case IFGT => IFLE
+ case IFLE => IFGT
- case Opcodes.IFGT => Opcodes.IFLE
- case Opcodes.IFLE => Opcodes.IFGT
+ case IF_ICMPEQ => IF_ICMPNE
+ case IF_ICMPNE => IF_ICMPEQ
- case Opcodes.IF_ICMPEQ => Opcodes.IF_ICMPNE
- case Opcodes.IF_ICMPNE => Opcodes.IF_ICMPEQ
+ case IF_ICMPLT => IF_ICMPGE
+ case IF_ICMPGE => IF_ICMPLT
- case Opcodes.IF_ICMPLT => Opcodes.IF_ICMPGE
- case Opcodes.IF_ICMPGE => Opcodes.IF_ICMPLT
+ case IF_ICMPGT => IF_ICMPLE
+ case IF_ICMPLE => IF_ICMPGT
- case Opcodes.IF_ICMPGT => Opcodes.IF_ICMPLE
- case Opcodes.IF_ICMPLE => Opcodes.IF_ICMPGT
+ case IF_ACMPEQ => IF_ACMPNE
+ case IF_ACMPNE => IF_ACMPEQ
- case Opcodes.IF_ACMPEQ => Opcodes.IF_ACMPNE
- case Opcodes.IF_ACMPNE => Opcodes.IF_ACMPEQ
+ case IFNULL => IFNONNULL
+ case IFNONNULL => IFNULL
+ }
- case Opcodes.IFNULL => Opcodes.IFNONNULL
- case Opcodes.IFNONNULL => Opcodes.IFNULL
+ def isSize2LoadOrStore(opcode: Int): Boolean = (opcode: @switch) match {
+ case LLOAD | DLOAD | LSTORE | DSTORE => true
+ case _ => false
}
def getPop(size: Int): InsnNode = {
- val op = if (size == 1) Opcodes.POP else Opcodes.POP2
+ val op = if (size == 1) POP else POP2
new InsnNode(op)
}
- def instructionResultSize(instruction: AbstractInsnNode) = InstructionResultSize(instruction)
+ def instructionResultSize(insn: AbstractInsnNode) = InstructionStackEffect.prod(InstructionStackEffect.forClassfile(insn))
+
+ def loadZeroForTypeSort(sort: Int) = (sort: @switch) match {
+ case Type.BOOLEAN |
+ Type.BYTE |
+ Type.CHAR |
+ Type.SHORT |
+ Type.INT => new InsnNode(ICONST_0)
+ case Type.LONG => new InsnNode(LCONST_0)
+ case Type.FLOAT => new InsnNode(FCONST_0)
+ case Type.DOUBLE => new InsnNode(DCONST_0)
+ case Type.OBJECT => new InsnNode(ACONST_NULL)
+ }
+
+ /**
+ * The number of local variable slots used for parameters and for the `this` reference.
+ */
+ def parametersSize(methodNode: MethodNode): Int = {
+ (Type.getArgumentsAndReturnSizes(methodNode.desc) >> 2) - (if (isStaticMethod(methodNode)) 1 else 0)
+ }
def labelReferences(method: MethodNode): Map[LabelNode, Set[AnyRef]] = {
val res = mutable.Map.empty[LabelNode, Set[AnyRef]]
@@ -222,29 +277,6 @@ object BytecodeUtils {
}
}
- /**
- * 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): Unit = {
- 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 codeSizeOKForInlining(caller: MethodNode, callee: MethodNode): Boolean = {
// Looking at the implementation of CodeSizeEvaluator, all instructions except tableswitch and
// lookupswitch are <= 8 bytes. These should be rare enough for 8 to be an OK rough upper bound.
@@ -289,34 +321,36 @@ object BytecodeUtils {
}
/**
- * 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
+ def cloneLocalVariableNodes(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], calleeMethodName: String, shift: Int): List[LocalVariableNode] = {
+ methodNode.localVariables.iterator().asScala.map(localVariable => {
+ val name =
+ if (calleeMethodName.length + localVariable.name.length < BTypes.InlinedLocalVariablePrefixMaxLenght) {
+ calleeMethodName + "_" + localVariable.name
+ } else {
+ val parts = localVariable.name.split("_").toVector
+ val (methNames, varName) = (calleeMethodName +: parts.init, parts.last)
+ // keep at least 5 characters per method name
+ val maxNumMethNames = BTypes.InlinedLocalVariablePrefixMaxLenght / 5
+ val usedMethNames =
+ if (methNames.length < maxNumMethNames) methNames
+ else {
+ val half = maxNumMethNames / 2
+ methNames.take(half) ++ methNames.takeRight(half)
+ }
+ val charsPerMethod = BTypes.InlinedLocalVariablePrefixMaxLenght / usedMethNames.length
+ usedMethNames.foldLeft("")((res, methName) => res + methName.take(charsPerMethod) + "_") + varName
+ }
+ new LocalVariableNode(
+ name,
+ localVariable.desc,
+ localVariable.signature,
+ labelMap(localVariable.start),
+ labelMap(localVariable.end),
+ localVariable.index + shift)
+ }).toList
}
/**
@@ -344,23 +378,14 @@ object BytecodeUtils {
* method which explains the issue with such phantom values.
*/
def fixLoadedNothingOrNullValue(loadedType: Type, loadInstr: AbstractInsnNode, methodNode: MethodNode, bTypes: BTypes): Unit = {
- if (loadedType == bTypes.coreBTypes.RT_NOTHING.toASMType) {
- methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ATHROW))
- } else if (loadedType == bTypes.coreBTypes.RT_NULL.toASMType) {
- methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ACONST_NULL))
- methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.POP))
+ if (loadedType == bTypes.coreBTypes.srNothingRef.toASMType) {
+ methodNode.instructions.insert(loadInstr, new InsnNode(ATHROW))
+ } else if (loadedType == bTypes.coreBTypes.srNullRef.toASMType) {
+ methodNode.instructions.insert(loadInstr, new InsnNode(ACONST_NULL))
+ methodNode.instructions.insert(loadInstr, new InsnNode(POP))
}
}
- /**
- * 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.frameAt(instruction, methodNode)
- }
-
implicit class AnalyzerExtensions[V <: Value](val analyzer: Analyzer[V]) extends AnyVal {
def frameAt(instruction: AbstractInsnNode, methodNode: MethodNode): Frame[V] = analyzer.getFrames()(methodNode.instructions.indexOf(instruction))
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
index 96455c0e38..a740ca525c 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
@@ -7,182 +7,320 @@ package scala.tools.nsc
package backend.jvm
package opt
+import scala.collection.immutable.IntMap
import scala.reflect.internal.util.{NoPosition, Position}
-import scala.tools.asm.tree.analysis.{Value, Analyzer, BasicInterpreter}
-import scala.tools.asm.{Opcodes, Type, Handle}
+import scala.tools.asm.{Handle, Opcodes, Type}
import scala.tools.asm.tree._
-import scala.collection.concurrent
-import scala.collection.convert.decorateAsScala._
-import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.collection.{concurrent, mutable}
+import scala.collection.JavaConverters._
+import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo}
import scala.tools.nsc.backend.jvm.BackendReporting._
-import scala.tools.nsc.backend.jvm.analysis.{NotNull, NullnessAnalyzer}
-import ByteCodeRepository.{Source, CompilationUnit}
+import scala.tools.nsc.backend.jvm.analysis._
import BytecodeUtils._
class CallGraph[BT <: BTypes](val btypes: BT) {
import btypes._
+ import backendUtils._
- val callsites: concurrent.Map[MethodInsnNode, Callsite] = recordPerRunCache(concurrent.TrieMap.empty)
+ /**
+ * The call graph contains the callsites in the program being compiled.
+ *
+ * Indexing the call graph by the containing MethodNode and the invocation MethodInsnNode allows
+ * finding callsites efficiently. For example, an inlining heuristic might want to know all
+ * callsites within a callee method.
+ *
+ * Note that the call graph is not guaranteed to be complete: callsites may be missing. In
+ * particular, if a method is very large, all of its callsites might not be in the hash map.
+ * The reason is that adding a method to the call graph requires running an ASM analyzer, which
+ * can be too slow.
+ *
+ * Note that call graph entries (Callsite instances) keep a reference to the invocation
+ * MethodInsnNode, which keeps all AbstractInsnNodes of the method reachable. Adding classes
+ * from the classpath to the call graph (in addition to classes being compiled) may prevent
+ * method instruction nodes from being GCd. The ByteCodeRepository has a fixed size cache for
+ * parsed ClassNodes - keeping all ClassNodes alive consumed too much memory.
+ * The call graph is less problematic because only methods being called are kept alive, not entire
+ * classes. But we should keep an eye on this.
+ */
+ val callsites: mutable.Map[MethodNode, Map[MethodInsnNode, Callsite]] = recordPerRunCache(concurrent.TrieMap.empty withDefaultValue Map.empty)
+
+ /**
+ * Closure instantiations in the program being compiled.
+ *
+ * Indexing closure instantiations by the containing MethodNode is beneficial for the closure
+ * optimizer: finding callsites to re-write requires running a producers-consumers analysis on
+ * the method. Here the closure instantiations are already grouped by method.
+ */
+ val closureInstantiations: mutable.Map[MethodNode, Map[InvokeDynamicInsnNode, ClosureInstantiation]] = recordPerRunCache(concurrent.TrieMap.empty withDefaultValue Map.empty)
+
+ def removeCallsite(invocation: MethodInsnNode, methodNode: MethodNode): Option[Callsite] = {
+ val methodCallsites = callsites(methodNode)
+ val newCallsites = methodCallsites - invocation
+ if (newCallsites.isEmpty) callsites.remove(methodNode)
+ else callsites(methodNode) = newCallsites
+ methodCallsites.get(invocation)
+ }
+
+ def addCallsite(callsite: Callsite): Unit = {
+ val methodCallsites = callsites(callsite.callsiteMethod)
+ callsites(callsite.callsiteMethod) = methodCallsites + (callsite.callsiteInstruction -> callsite)
+ }
- val closureInstantiations: concurrent.Map[InvokeDynamicInsnNode, ClosureInstantiation] = recordPerRunCache(concurrent.TrieMap.empty)
+ def containsCallsite(callsite: Callsite): Boolean = callsites(callsite.callsiteMethod) contains callsite.callsiteInstruction
+ def findCallSite(method: MethodNode, call: MethodInsnNode): Option[Callsite] = callsites.getOrElse(method, Map.empty).get(call)
+
+ def removeClosureInstantiation(indy: InvokeDynamicInsnNode, methodNode: MethodNode): Option[ClosureInstantiation] = {
+ val methodClosureInits = closureInstantiations(methodNode)
+ val newClosureInits = methodClosureInits - indy
+ if (newClosureInits.isEmpty) closureInstantiations.remove(methodNode)
+ else closureInstantiations(methodNode) = newClosureInits
+ methodClosureInits.get(indy)
+ }
+
+ def addClosureInstantiation(closureInit: ClosureInstantiation) = {
+ val methodClosureInits = closureInstantiations(closureInit.ownerMethod)
+ closureInstantiations(closureInit.ownerMethod) = methodClosureInits + (closureInit.lambdaMetaFactoryCall.indy -> closureInit)
+ }
def addClass(classNode: ClassNode): Unit = {
val classType = classBTypeFromClassNode(classNode)
- for {
- m <- classNode.methods.asScala
- (calls, closureInits) = analyzeCallsites(m, classType)
- } {
- calls foreach (callsite => callsites(callsite.callsiteInstruction) = callsite)
- closureInits foreach (lmf => closureInstantiations(lmf.indy) = ClosureInstantiation(lmf, m, classType))
- }
+ classNode.methods.asScala.foreach(addMethod(_, classType))
}
- /**
- * Returns a list of callsites in the method, plus a list of closure instantiation indy instructions.
- */
- def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): (List[Callsite], List[LambdaMetaFactoryCall]) = {
+ def addIfMissing(methodNode: MethodNode, definingClass: ClassBType): Unit = {
+ if (!callsites.contains(methodNode)) addMethod(methodNode, definingClass)
+ }
- case class CallsiteInfo(safeToInline: Boolean, safeToRewrite: Boolean,
- annotatedInline: Boolean, annotatedNoInline: Boolean,
- warning: Option[CalleeInfoWarning])
+ def addMethod(methodNode: MethodNode, definingClass: ClassBType): Unit = {
+ if (!BytecodeUtils.isAbstractMethod(methodNode) && !BytecodeUtils.isNativeMethod(methodNode)) {
+ // 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
+
+ // For now we run a NullnessAnalyzer. It is used to determine if the receiver of an instance
+ // call is known to be not-null, in which case we don't have to emit a null check when inlining.
+ // It is also used to get the stack height at the call site.
+
+ val analyzer = {
+ if (compilerSettings.optNullnessTracking && AsmAnalyzer.sizeOKForNullness(methodNode)) {
+ Some(new AsmAnalyzer(methodNode, definingClass.internalName, new NullnessAnalyzer(btypes, methodNode)))
+ } else if (AsmAnalyzer.sizeOKForBasicValue(methodNode)) {
+ Some(new AsmAnalyzer(methodNode, definingClass.internalName))
+ } else None
+ }
- /**
- * 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
+ // if the method is too large to run an analyzer, it is not added to the call graph
+ if (analyzer.nonEmpty) {
+ val Some(a) = analyzer
+ def receiverNotNullByAnalysis(call: MethodInsnNode, numArgs: Int) = a.analyzer match {
+ case nullnessAnalyzer: NullnessAnalyzer =>
+ val frame = nullnessAnalyzer.frameAt(call, methodNode)
+ frame.getStack(frame.getStackSize - 1 - numArgs) eq NotNullValue
+ case _ => false
+ }
- 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 = compilerSettings.YoptInlineGlobal || calleeSource == CompilationUnit
+ var methodCallsites = Map.empty[MethodInsnNode, Callsite]
+ var methodClosureInstantiations = Map.empty[InvokeDynamicInsnNode, ClosureInstantiation]
+
+ // lazy so it is only computed if actually used by computeArgInfos
+ lazy val prodCons = new ProdConsAnalyzer(methodNode, definingClass.internalName)
+
+ methodNode.instructions.iterator.asScala foreach {
+ case call: MethodInsnNode if a.frameAt(call) != null => // skips over unreachable code
+ val callee: Either[OptimizerWarning, Callee] = for {
+ (method, declarationClass) <- byteCodeRepository.methodNode(call.owner, call.name, call.desc): Either[OptimizerWarning, (MethodNode, InternalName)]
+ (declarationClassNode, calleeSourceFilePath) <- byteCodeRepository.classNodeAndSourceFilePath(declarationClass): Either[OptimizerWarning, (ClassNode, Option[String])]
+ } yield {
+ val declarationClassBType = classBTypeFromClassNode(declarationClassNode)
+ val info = analyzeCallsite(method, declarationClassBType, call, calleeSourceFilePath)
+ import info._
+ Callee(
+ callee = method,
+ calleeDeclarationClass = declarationClassBType,
+ isStaticallyResolved = isStaticallyResolved,
+ sourceFilePath = sourceFilePath,
+ annotatedInline = annotatedInline,
+ annotatedNoInline = annotatedNoInline,
+ samParamTypes = info.samParamTypes,
+ calleeInfoWarning = warning)
+ }
- val isAbstract = BytecodeUtils.isAbstractMethod(calleeMethodNode)
+ val argInfos = computeArgInfos(callee, call, prodCons)
- // (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 receiverNotNull = call.getOpcode == Opcodes.INVOKESTATIC || {
+ val numArgs = Type.getArgumentTypes(call.desc).length
+ receiverNotNullByAnalysis(call, numArgs)
}
- 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 && // (1)
- !isAbstract &&
- !BytecodeUtils.isConstructor(calleeMethodNode) &&
- !BytecodeUtils.isNativeMethod(calleeMethodNode),
- 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))
+ methodCallsites += call -> Callsite(
+ callsiteInstruction = call,
+ callsiteMethod = methodNode,
+ callsiteClass = definingClass,
+ callee = callee,
+ argInfos = argInfos,
+ callsiteStackHeight = a.frameAt(call).getStackSize,
+ receiverKnownNotNull = receiverNotNull,
+ callsitePosition = callsitePositions.getOrElse(call, NoPosition),
+ annotatedInline = inlineAnnotatedCallsites(call),
+ annotatedNoInline = noInlineAnnotatedCallsites(call)
+ )
+
+ case LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) if a.frameAt(indy) != null =>
+ val lmf = LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType)
+ val capturedArgInfos = computeCapturedArgInfos(lmf, prodCons)
+ methodClosureInstantiations += indy -> ClosureInstantiation(
+ lmf,
+ methodNode,
+ definingClass,
+ capturedArgInfos)
+
+ case _ =>
}
- } catch {
- case Invalid(noInfo: NoClassBTypeInfo) =>
- val warning = MethodInlineInfoError(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, noInfo)
- CallsiteInfo(false, false, false, false, Some(warning))
+
+ callsites(methodNode) = methodCallsites
+ closureInstantiations(methodNode) = methodClosureInstantiations
}
}
+ }
- // 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
-
- // For now we run a NullnessAnalyzer. It is used to determine if the receiver of an instance
- // call is known to be not-null, in which case we don't have to emit a null check when inlining.
- // It is also used to get the stack height at the call site.
- localOpt.minimalRemoveUnreachableCode(methodNode, definingClass.internalName)
-
- val analyzer: Analyzer[_ <: Value] = {
- if (compilerSettings.YoptNullnessTracking) new NullnessAnalyzer
- else new Analyzer(new BasicInterpreter)
+ def computeArgInfos(callee: Either[OptimizerWarning, Callee], callsiteInsn: MethodInsnNode, prodCons: => ProdConsAnalyzer): IntMap[ArgInfo] = {
+ if (callee.isLeft) IntMap.empty
+ else {
+ lazy val numArgs = Type.getArgumentTypes(callsiteInsn.desc).length + (if (callsiteInsn.getOpcode == Opcodes.INVOKESTATIC) 0 else 1)
+ argInfosForSams(callee.get.samParamTypes, callsiteInsn, numArgs, prodCons)
}
- analyzer.analyze(definingClass.internalName, methodNode)
+ }
- def receiverNotNullByAnalysis(call: MethodInsnNode, numArgs: Int) = analyzer match {
- case nullnessAnalyzer: NullnessAnalyzer =>
- val frame = nullnessAnalyzer.frameAt(call, methodNode)
- frame.getStack(frame.getStackSize - 1 - numArgs).nullness == NotNull
+ def computeCapturedArgInfos(lmf: LambdaMetaFactoryCall, prodCons: => ProdConsAnalyzer): IntMap[ArgInfo] = {
+ val capturedSams = capturedSamTypes(lmf)
+ val numCaptures = Type.getArgumentTypes(lmf.indy.desc).length
+ argInfosForSams(capturedSams, lmf.indy, numCaptures, prodCons)
+ }
- case _ => false
+ private def argInfosForSams(sams: IntMap[ClassBType], consumerInsn: AbstractInsnNode, numConsumed: => Int, prodCons: => ProdConsAnalyzer): IntMap[ArgInfo] = {
+ // TODO: use type analysis instead of ProdCons - should be more efficient
+ // some random thoughts:
+ // - assign special types to parameters and indy-lambda-functions to track them
+ // - upcast should not change type flow analysis: don't lose information.
+ // - can we do something about factory calls? Foo(x) for case class foo gives a Foo.
+ // inline the factory? analysis across method boundary?
+
+ // assign to a lazy val to prevent repeated evaluation of the by-name arg
+ lazy val prodConsI = prodCons
+ lazy val firstConsumedSlot = {
+ val consumerFrame = prodConsI.frameAt(consumerInsn)
+ consumerFrame.stackTop - numConsumed + 1
}
-
- val callsites = new collection.mutable.ListBuffer[Callsite]
- val closureInstantiations = new collection.mutable.ListBuffer[LambdaMetaFactoryCall]
-
- methodNode.instructions.iterator.asScala foreach {
- 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)
+ sams flatMap {
+ case (index, _) =>
+ val prods = prodConsI.initialProducersForValueAt(consumerInsn, firstConsumedSlot + index)
+ if (prods.size != 1) None
+ else {
+ val argInfo = prods.head match {
+ case LambdaMetaFactoryCall(_, _, _, _) => Some(FunctionLiteral)
+ case ParameterProducer(local) => Some(ForwardedParam(local))
+ case _ => None
+ }
+ argInfo.map((index, _))
}
+ }
+ }
- 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
- }
+ def samParamTypes(methodNode: MethodNode, receiverType: ClassBType): IntMap[ClassBType] = {
+ val paramTypes = {
+ val params = Type.getMethodType(methodNode.desc).getArgumentTypes.map(t => bTypeForDescriptorOrInternalNameFromClassfile(t.getDescriptor))
+ val isStatic = BytecodeUtils.isStaticMethod(methodNode)
+ if (isStatic) params else receiverType +: params
+ }
+ samTypes(paramTypes)
+ }
- val receiverNotNull = call.getOpcode == Opcodes.INVOKESTATIC || {
- val numArgs = Type.getArgumentTypes(call.desc).length
- receiverNotNullByAnalysis(call, numArgs)
- }
+ def capturedSamTypes(lmf: LambdaMetaFactoryCall): IntMap[ClassBType] = {
+ val capturedTypes = Type.getArgumentTypes(lmf.indy.desc).map(t => bTypeForDescriptorOrInternalNameFromClassfile(t.getDescriptor))
+ samTypes(capturedTypes)
+ }
- callsites += Callsite(
- callsiteInstruction = call,
- callsiteMethod = methodNode,
- callsiteClass = definingClass,
- callee = callee,
- argInfos = argInfos,
- callsiteStackHeight = analyzer.frameAt(call, methodNode).getStackSize,
- receiverKnownNotNull = receiverNotNull,
- callsitePosition = callsitePositions.getOrElse(call, NoPosition)
- )
-
- case LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) =>
- closureInstantiations += LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType)
-
- case _ =>
- }
+ private def samTypes(types: Array[BType]): IntMap[ClassBType] = {
+ var res = IntMap.empty[ClassBType]
+ for (i <- types.indices) {
+ types(i) match {
+ case c: ClassBType =>
+ if (c.info.get.inlineInfo.sam.isDefined) res = res.updated(i, c)
- (callsites.toList, closureInstantiations.toList)
+ case _ =>
+ }
+ }
+ res
}
/**
+ * Just a named tuple used as return type of `analyzeCallsite`.
+ */
+ private case class CallsiteInfo(isStaticallyResolved: Boolean, sourceFilePath: Option[String],
+ annotatedInline: Boolean, annotatedNoInline: Boolean,
+ samParamTypes: IntMap[ClassBType],
+ warning: Option[CalleeInfoWarning])
+
+ /**
+ * Analyze a callsite and gather meta-data that can be used for inlining decisions.
+ */
+ private def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, call: MethodInsnNode, calleeSourceFilePath: Option[String]): 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 isAbstract = BytecodeUtils.isAbstractMethod(calleeMethodNode)
+
+ val receiverType = classBTypeFromParsedClassfile(call.owner)
+ // (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: (1) doesn't cover the following example:
+ // trait TravLike { def map = ... }
+ // sealed trait List extends TravLike { ... } // assume map is not overridden
+ // final case class :: / final case object Nil
+ // (l: List).map // can be inlined
+ // we need to know that
+ // - the receiver is sealed
+ // - what are the children of the receiver
+ // - all children are final
+ // - none of the children overrides map
+ //
+ // 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 = {
+ isNonVirtualCall(call) || // SD-86: super calls (invokespecial) can be inlined -- TODO: check if that's still needed, and if it's correct: scala-dev#143
+ methodInlineInfo.effectivelyFinal ||
+ receiverType.info.orThrow.inlineInfo.isEffectivelyFinal // (1)
+ }
+
+ val warning = calleeDeclarationClassBType.info.orThrow.inlineInfo.warning.map(
+ MethodInlineInfoIncomplete(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, _))
+
+ CallsiteInfo(
+ isStaticallyResolved = isStaticallyResolved,
+ sourceFilePath = calleeSourceFilePath,
+ annotatedInline = methodInlineInfo.annotatedInline,
+ annotatedNoInline = methodInlineInfo.annotatedNoInline,
+ samParamTypes = samParamTypes(calleeMethodNode, receiverType),
+ warning = warning)
+
+ case None =>
+ val warning = MethodInlineInfoMissing(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, calleeDeclarationClassBType.info.orThrow.inlineInfo.warning)
+ CallsiteInfo(false, None, false, false, IntMap.empty, Some(warning))
+ }
+ } catch {
+ case Invalid(noInfo: NoClassBTypeInfo) =>
+ val warning = MethodInlineInfoError(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, noInfo)
+ CallsiteInfo(false, None, false, false, IntMap.empty, Some(warning))
+ }
+ }
+
+ /**
* A callsite in the call graph.
*
* @param callsiteInstruction The invocation instruction
@@ -197,21 +335,35 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
* @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, receiverKnownNotNull: Boolean, callsitePosition: Position) {
+ callee: Either[OptimizerWarning, Callee], argInfos: IntMap[ArgInfo],
+ callsiteStackHeight: Int, receiverKnownNotNull: Boolean, callsitePosition: Position,
+ annotatedInline: Boolean, annotatedNoInline: Boolean) {
+ /**
+ * Contains callsites that were created during inlining by cloning this callsite. Used to find
+ * corresponding callsites when inlining post-inline requests.
+ */
+ val inlinedClones = mutable.Set.empty[ClonedCallsite]
+
+ // an annotation at the callsite takes precedence over an annotation at the definition site
+ def isInlineAnnotated = annotatedInline || (callee.get.annotatedInline && !annotatedNoInline)
+ def isNoInlineAnnotated = annotatedNoInline || (callee.get.annotatedNoInline && !annotatedInline)
+
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}"
+ s" in ${callsiteClass.internalName}.${callsiteMethod.name}${callsiteMethod.desc}"
}
+ final case class ClonedCallsite(callsite: Callsite, clonedWhenInlining: Callsite)
+
/**
* 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
+ case object FunctionLiteral extends ArgInfo
final case class ForwardedParam(index: Int) extends ArgInfo
+ // final case class ArgTypeInfo(argType: BType, isPrecise: Boolean, knownNotNull: Boolean) extends ArgInfo
// can be extended, e.g., with constant types
/**
@@ -221,46 +373,50 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
* 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 is the interface method of a concrete trait method
- * that can be safely re-written to the static implementation method.
+ * @param isStaticallyResolved True if the callee cannot be overridden
* @param annotatedInline True if the callee is annotated @inline
* @param annotatedNoInline True if the callee is annotated @noinline
+ * @param samParamTypes A map from parameter positions to SAM parameter types
* @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,
+ final case class Callee(callee: MethodNode, calleeDeclarationClass: btypes.ClassBType,
+ isStaticallyResolved: Boolean, sourceFilePath: Option[String],
annotatedInline: Boolean, annotatedNoInline: Boolean,
+ samParamTypes: IntMap[btypes.ClassBType],
calleeInfoWarning: Option[CalleeInfoWarning]) {
- assert(!(safeToInline && safeToRewrite), s"A callee of ${callee.name} can be either safeToInline or safeToRewrite, but not both.")
+ override def toString = s"Callee($calleeDeclarationClass.${callee.name})"
+
+ def canInlineFromSource = inlinerHeuristics.canInlineFromSource(sourceFilePath)
+ def isAbstract = isAbstractMethod(callee)
+ def isSpecialMethod = isConstructor(callee) || isNativeMethod(callee) || hasCallerSensitiveAnnotation(callee)
+
+ def safeToInline = isStaticallyResolved && canInlineFromSource && !isAbstract && !isSpecialMethod
}
- final case class ClosureInstantiation(lambdaMetaFactoryCall: LambdaMetaFactoryCall, ownerMethod: MethodNode, ownerClass: ClassBType) {
+ /**
+ * Metadata about a closure instantiation, stored in the call graph
+ *
+ * @param lambdaMetaFactoryCall the InvokeDynamic instruction
+ * @param ownerMethod the method where the closure is allocated
+ * @param ownerClass the class containing the above method
+ * @param capturedArgInfos information about captured arguments. Used for updating the call
+ * graph when re-writing a closure invocation to the body method.
+ */
+ final case class ClosureInstantiation(lambdaMetaFactoryCall: LambdaMetaFactoryCall, ownerMethod: MethodNode, ownerClass: ClassBType, capturedArgInfos: IntMap[ArgInfo]) {
+ /**
+ * Contains closure instantiations that were created during inlining by cloning this instantiation.
+ */
+ val inlinedClones = mutable.Set.empty[ClosureInstantiation]
override def toString = s"ClosureInstantiation($lambdaMetaFactoryCall, ${ownerMethod.name + ownerMethod.desc}, $ownerClass)"
}
final case class LambdaMetaFactoryCall(indy: InvokeDynamicInsnNode, samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type)
object LambdaMetaFactoryCall {
- private val lambdaMetaFactoryInternalName: InternalName = "java/lang/invoke/LambdaMetafactory"
-
- private val metafactoryHandle = {
- val metafactoryMethodName: String = "metafactory"
- val metafactoryDesc: String = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;"
- new Handle(Opcodes.H_INVOKESTATIC, lambdaMetaFactoryInternalName, metafactoryMethodName, metafactoryDesc)
- }
-
- private val altMetafactoryHandle = {
- val altMetafactoryMethodName: String = "altMetafactory"
- val altMetafactoryDesc: String = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"
- new Handle(Opcodes.H_INVOKESTATIC, lambdaMetaFactoryInternalName, altMetafactoryMethodName, altMetafactoryDesc)
- }
-
def unapply(insn: AbstractInsnNode): Option[(InvokeDynamicInsnNode, Type, Handle, Type)] = insn match {
- case indy: InvokeDynamicInsnNode if indy.bsm == metafactoryHandle || indy.bsm == altMetafactoryHandle =>
+ case indy: InvokeDynamicInsnNode if indy.bsm == coreBTypes.lambdaMetaFactoryMetafactoryHandle || indy.bsm == coreBTypes.lambdaMetaFactoryAltMetafactoryHandle =>
indy.bsmArgs match {
- case Array(samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type, xs@_*) => // xs binding because IntelliJ gets confused about _@_*
+ case Array(samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type, _@_*) =>
// LambdaMetaFactory performs a number of automatic adaptations when invoking the lambda
// implementation method (casting, boxing, unboxing, and primitive widening, see Javadoc).
//
@@ -284,7 +440,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
// When re-writing the closure callsite to the implMethod, we have to insert a cast.
//
// The check below ensures that
- // (1) the implMethod type has the expected singature (captured types plus argument types
+ // (1) the implMethod type has the expected signature (captured types plus argument types
// from instantiatedMethodType)
// (2) the receiver of the implMethod matches the first captured type
// (3) all parameters that are not the same in samMethodType and instantiatedMethodType
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
index b0dc6ead1b..2fca8991ab 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
@@ -8,21 +8,39 @@ package backend.jvm
package opt
import scala.annotation.switch
-import scala.collection.immutable
+import scala.collection.mutable
+import scala.collection.immutable.IntMap
import scala.reflect.internal.util.NoPosition
import scala.tools.asm.{Type, Opcodes}
import scala.tools.asm.tree._
import scala.tools.nsc.backend.jvm.BTypes.InternalName
-import scala.tools.nsc.backend.jvm.analysis.ProdConsAnalyzer
import BytecodeUtils._
import BackendReporting._
import Opcodes._
-import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository.CompilationUnit
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
import btypes._
import callGraph._
+ import coreBTypes._
+ import backendUtils._
+ import ClosureOptimizer._
+
+ private object closureInitOrdering extends Ordering[ClosureInstantiation] {
+ override def compare(x: ClosureInstantiation, y: ClosureInstantiation): Int = {
+ val cls = x.ownerClass.internalName compareTo y.ownerClass.internalName
+ if (cls != 0) return cls
+
+ val mName = x.ownerMethod.name compareTo y.ownerMethod.name
+ if (mName != 0) return mName
+
+ val mDesc = x.ownerMethod.desc compareTo y.ownerMethod.desc
+ if (mDesc != 0) return mDesc
+
+ def pos(inst: ClosureInstantiation) = inst.ownerMethod.instructions.indexOf(inst.lambdaMetaFactoryCall.indy)
+ pos(x) - pos(y)
+ }
+ }
/**
* If a closure is allocated and invoked within the same method, re-write the invocation to the
@@ -54,55 +72,51 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
* [invoke the closure body method]
*/
def rewriteClosureApplyInvocations(): Unit = {
- implicit object closureInitOrdering extends Ordering[ClosureInstantiation] {
- override def compare(x: ClosureInstantiation, y: ClosureInstantiation): Int = {
- val cls = x.ownerClass.internalName compareTo y.ownerClass.internalName
- if (cls != 0) return cls
-
- val mName = x.ownerMethod.name compareTo y.ownerMethod.name
- if (mName != 0) return mName
- val mDesc = x.ownerMethod.desc compareTo y.ownerMethod.desc
- if (mDesc != 0) return mDesc
-
- def pos(inst: ClosureInstantiation) = inst.ownerMethod.instructions.indexOf(inst.lambdaMetaFactoryCall.indy)
- pos(x) - pos(y)
- }
+ // sort all closure invocations to rewrite to ensure bytecode stability
+ val toRewrite = mutable.TreeMap.empty[ClosureInstantiation, mutable.ArrayBuffer[(MethodInsnNode, Int)]](closureInitOrdering)
+ def addRewrite(init: ClosureInstantiation, invocation: MethodInsnNode, stackHeight: Int): Unit = {
+ val callsites = toRewrite.getOrElseUpdate(init, mutable.ArrayBuffer.empty[(MethodInsnNode, Int)])
+ callsites += ((invocation, stackHeight))
}
- // Grouping the closure instantiations by method allows running the ProdConsAnalyzer only once per
- // method. Also sort the instantiations: If there are multiple closure instantiations in a method,
- // closure invocations need to be re-written in a consistent order for bytecode stability. The local
- // variable slots for storing captured values depends on the order of rewriting.
- val closureInstantiationsByMethod: Map[MethodNode, immutable.TreeSet[ClosureInstantiation]] = {
- closureInstantiations.values.groupBy(_.ownerMethod).mapValues(immutable.TreeSet.empty ++ _)
- }
+ // For each closure instantiation find callsites of the closure and add them to the toRewrite
+ // buffer (cannot change a method's bytecode while still looking for further invocations to
+ // rewrite, the frame indices of the ProdCons analysis would get out of date). If a callsite
+ // cannot be rewritten, for example because the lambda body method is not accessible, issue a
+ // warning. The `toList` in the next line prevents modifying closureInstantiations while
+ // iterating it: minimalRemoveUnreachableCode (called in the loop) removes elements.
+ for (method <- closureInstantiations.keysIterator.toList if AsmAnalyzer.sizeOKForBasicValue(method)) closureInstantiations.get(method) match {
+ case Some(closureInitsBeforeDCE) if closureInitsBeforeDCE.nonEmpty =>
+ val ownerClass = closureInitsBeforeDCE.head._2.ownerClass.internalName
+
+ // Advanced ProdCons queries (initialProducersForValueAt) expect no unreachable code.
+ localOpt.minimalRemoveUnreachableCode(method, ownerClass)
+
+ if (AsmAnalyzer.sizeOKForSourceValue(method)) closureInstantiations.get(method) match {
+ case Some(closureInits) =>
+ // A lazy val to ensure the analysis only runs if necessary (the value is passed by name to `closureCallsites`)
+ lazy val prodCons = new ProdConsAnalyzer(method, ownerClass)
+
+ for (init <- closureInits.valuesIterator) closureCallsites(init, prodCons) foreach {
+ case Left(warning) =>
+ backendReporting.inlinerWarning(warning.pos, warning.toString)
+
+ case Right((invocation, stackHeight)) =>
+ addRewrite(init, invocation, stackHeight)
+ }
+
+ case _ =>
+ }
- // For each closure instantiation, a list of callsites of the closure that can be re-written
- // If a callsite cannot be rewritten, for example because the lambda body method is not accessible,
- // a warning is returned instead.
- val callsitesToRewrite: List[(ClosureInstantiation, List[Either[RewriteClosureApplyToClosureBodyFailed, (MethodInsnNode, Int)]])] = {
- closureInstantiationsByMethod.iterator.flatMap({
- case (methodNode, closureInits) =>
- // A lazy val to ensure the analysis only runs if necessary (the value is passed by name to `closureCallsites`)
- lazy val prodCons = new ProdConsAnalyzer(methodNode, closureInits.head.ownerClass.internalName)
- closureInits.iterator.map(init => (init, closureCallsites(init, prodCons)))
- }).toList // mapping to a list (not a map) to keep the sorting of closureInstantiationsByMethod
+ case _ =>
}
- // Rewrite all closure callsites (or issue inliner warnings for those that cannot be rewritten)
- for ((closureInit, callsites) <- callsitesToRewrite) {
+ for ((closureInit, invocations) <- toRewrite) {
// Local variables that hold the captured values and the closure invocation arguments.
- // They are lazy vals to ensure that locals for captured values are only allocated if there's
- // actually a callsite to rewrite (an not only warnings to be issued).
- lazy val (localsForCapturedValues, argumentLocalsList) = localsForClosureRewrite(closureInit)
- for (callsite <- callsites) callsite match {
- case Left(warning) =>
- backendReporting.inlinerWarning(warning.pos, warning.toString)
-
- case Right((invocation, stackHeight)) =>
- rewriteClosureApplyInvocation(closureInit, invocation, stackHeight, localsForCapturedValues, argumentLocalsList)
- }
+ val (localsForCapturedValues, argumentLocalsList) = localsForClosureRewrite(closureInit)
+ for ((invocation, stackHeight) <- invocations)
+ rewriteClosureApplyInvocation(closureInit, invocation, stackHeight, localsForCapturedValues, argumentLocalsList)
}
}
@@ -122,20 +136,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
val argTypes = closureInit.lambdaMetaFactoryCall.samMethodType.getArgumentTypes
val firstArgLocal = ownerMethod.maxLocals
- // The comment in the unapply method of `LambdaMetaFactoryCall` explains why we have to introduce
- // casts for arguments that have different types in samMethodType and instantiatedMethodType.
- val castLoadTypes = {
- val instantiatedMethodType = closureInit.lambdaMetaFactoryCall.instantiatedMethodType
- (argTypes, instantiatedMethodType.getArgumentTypes).zipped map {
- case (samArgType, instantiatedArgType) if samArgType != instantiatedArgType =>
- // the LambdaMetaFactoryCall extractor ensures that the two types are reference types,
- // so we don't end up casting primitive values.
- Some(instantiatedArgType)
- case _ =>
- None
- }
- }
- val argLocals = LocalsList.fromTypes(firstArgLocal, argTypes, castLoadTypes)
+ val argLocals = LocalsList.fromTypes(firstArgLocal, argTypes)
ownerMethod.maxLocals = firstArgLocal + argLocals.size
(captureLocals, argLocals)
@@ -154,7 +155,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
// TODO: This is maybe over-cautious.
// We are checking if the closure body method is accessible at the closure callsite.
// If the closure allocation has access to the body method, then the callsite (in the same
- // method as the alloction) should have access too.
+ // method as the allocation) should have access too.
val bodyAccessible: Either[OptimizerWarning, Boolean] = for {
(bodyMethodNode, declClass) <- byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc): Either[OptimizerWarning, (MethodNode, InternalName)]
isAccessible <- inliner.memberIsAccessible(bodyMethodNode.access, classBTypeFromParsedClassfile(declClass), classBTypeFromParsedClassfile(lambdaBodyHandle.getOwner), ownerClass)
@@ -162,7 +163,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
isAccessible
}
- def pos = callGraph.callsites.get(invocation).map(_.callsitePosition).getOrElse(NoPosition)
+ def pos = callGraph.callsites(ownerMethod).get(invocation).map(_.callsitePosition).getOrElse(NoPosition)
val stackSize: Either[RewriteClosureApplyToClosureBodyFailed, Int] = bodyAccessible match {
case Left(w) => Left(RewriteClosureAccessCheckFailed(pos, w))
case Right(false) => Left(RewriteClosureIllegalAccess(pos, ownerClass.internalName))
@@ -173,6 +174,28 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
}).toList
}
+ /**
+ * Check whether `invocation` invokes the SAM of the IndyLambda `closureInit`.
+ *
+ * In addition to a perfect match, we also identify cases where a generic FunctionN is created
+ * but the invocation is to a specialized variant apply$sp... Vice-versa, we also allow the
+ * case where a specialized FunctionN$sp.. is created but the generic apply is invoked. In
+ * these cases, the translation will introduce the necessary box / unbox invocations. Example:
+ *
+ * val f: Int => Any = (x: Int) => 1
+ * f(10)
+ *
+ * The IndyLambda creates a specialized `JFunction1$mcII$sp`, whose SAM is `apply$mcII$sp(I)I`.
+ * The invocation calls `apply(Object)Object`: the method name and type don't match.
+ * We identify these cases, insert the necessary unbox operation for the arguments, and invoke
+ * the `$anonfun(I)I` method.
+ *
+ * Tests in InlinerTest.optimizeSpecializedClosures. In that test, methods t4/t4a/t5/t8 show
+ * examples where the parameters have to be unboxed because generic `apply` is called, but the
+ * lambda body method takes primitive types.
+ * The opposite case is in t9: a the specialized `apply$sp..` is invoked, but the lambda body
+ * method takes boxed arguments, so we have to insert boxing operations.
+ */
private def isSamInvocation(invocation: MethodInsnNode, closureInit: ClosureInstantiation, prodCons: => ProdConsAnalyzer): Boolean = {
val indy = closureInit.lambdaMetaFactoryCall.indy
if (invocation.getOpcode == INVOKESTATIC) false
@@ -187,11 +210,85 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
receiverProducers.size == 1 && receiverProducers.head == indy
}
- invocation.name == indy.name && {
- val indySamMethodDesc = closureInit.lambdaMetaFactoryCall.samMethodType.getDescriptor
- indySamMethodDesc == invocation.desc
- } &&
- closureIsReceiver // most expensive check last
+ def isSpecializedVersion(specName: String, nonSpecName: String) = specName.startsWith(nonSpecName) && specializationSuffix.pattern.matcher(specName.substring(nonSpecName.length)).matches
+
+ def sameOrSpecializedType(specTp: Type, nonSpecTp: Type) = {
+ specTp == nonSpecTp || {
+ val specDesc = specTp.getDescriptor
+ val nonSpecDesc = nonSpecTp.getDescriptor
+ specDesc.length == 1 && primitives.contains(specDesc) && nonSpecDesc == ObjectRef.descriptor
+ }
+ }
+
+ def specializedDescMatches(specMethodDesc: String, nonSpecMethodDesc: String) = {
+ val specArgs = Type.getArgumentTypes(specMethodDesc)
+ val nonSpecArgs = Type.getArgumentTypes(nonSpecMethodDesc)
+ specArgs.corresponds(nonSpecArgs)(sameOrSpecializedType) && sameOrSpecializedType(Type.getReturnType(specMethodDesc), Type.getReturnType(nonSpecMethodDesc))
+ }
+
+ def nameAndDescMatch = {
+ val aName = invocation.name
+ val bName = indy.name
+ val aDesc = invocation.desc
+ val bDesc = closureInit.lambdaMetaFactoryCall.samMethodType.getDescriptor
+ if (aName == bName) aDesc == bDesc
+ else if (isSpecializedVersion(aName, bName)) specializedDescMatches(aDesc, bDesc)
+ else if (isSpecializedVersion(bName, aName)) specializedDescMatches(bDesc, aDesc)
+ else false
+ }
+
+ nameAndDescMatch && closureIsReceiver // most expensive check last
+ }
+ }
+
+ private def isPrimitiveType(asmType: Type) = {
+ val sort = asmType.getSort
+ Type.VOID <= sort && sort <= Type.DOUBLE
+ }
+
+ /**
+ * The argument types of the lambda body method may differ in two ways from the argument types of
+ * the closure member method that is invoked (and replaced by a call to the body).
+ * - The lambda body method may have more specific types than the invoked closure member, see
+ * comment in [[LambdaMetaFactoryCall.unapply]].
+ * - The invoked closure member might be a specialized variant of the SAM or vice-versa, see
+ * comment method [[isSamInvocation]].
+ */
+ private def adaptStoredArguments(closureInit: ClosureInstantiation, invocation: MethodInsnNode): Int => Option[AbstractInsnNode] = {
+ val invokeDesc = invocation.desc
+ // The lambda body method has additional parameters for captured values. Here we need to consider
+ // only those parameters of the body method that correspond to lambda parameters. This happens
+ // to be exactly LMF.instantiatedMethodType. In fact, `LambdaMetaFactoryCall.unapply` ensures
+ // that the body method signature is exactly (capturedParams + instantiatedMethodType).
+ val lambdaBodyMethodDescWithoutCaptures = closureInit.lambdaMetaFactoryCall.instantiatedMethodType.getDescriptor
+ if (invokeDesc == lambdaBodyMethodDescWithoutCaptures) {
+ _ => None
+ } else {
+ val invokeArgTypes = Type.getArgumentTypes(invokeDesc)
+ val implMethodArgTypes = Type.getArgumentTypes(lambdaBodyMethodDescWithoutCaptures)
+ val res = new Array[Option[AbstractInsnNode]](invokeArgTypes.length)
+ for (i <- invokeArgTypes.indices) {
+ if (invokeArgTypes(i) == implMethodArgTypes(i)) {
+ res(i) = None
+ } else if (isPrimitiveType(implMethodArgTypes(i)) && invokeArgTypes(i).getDescriptor == ObjectRef.descriptor) {
+ res(i) = Some(getScalaUnbox(implMethodArgTypes(i)))
+ } else if (isPrimitiveType(invokeArgTypes(i)) && implMethodArgTypes(i).getDescriptor == ObjectRef.descriptor) {
+ res(i) = Some(getScalaBox(invokeArgTypes(i)))
+ } else {
+ assert(!isPrimitiveType(invokeArgTypes(i)), invokeArgTypes(i))
+ assert(!isPrimitiveType(implMethodArgTypes(i)), implMethodArgTypes(i))
+ // The comment in the unapply method of `LambdaMetaFactoryCall` explains why we have to introduce
+ // casts for arguments that have different types in samMethodType and instantiatedMethodType.
+ //
+ // Note:
+ // - invokeArgTypes is the same as the argument types in the IndyLambda's samMethodType,
+ // this is ensured by the `isSamInvocation` filter in this file
+ // - implMethodArgTypes is the same as the arg types in the IndyLambda's instantiatedMethodType,
+ // this is ensured by the unapply method in LambdaMetaFactoryCall (file CallGraph)
+ res(i) = Some(new TypeInsnNode(CHECKCAST, implMethodArgTypes(i).getInternalName))
+ }
+ }
+ res
}
}
@@ -200,7 +297,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
val lambdaBodyHandle = closureInit.lambdaMetaFactoryCall.implMethod
// store arguments
- insertStoreOps(invocation, ownerMethod, argumentLocalsList)
+ insertStoreOps(invocation, ownerMethod, argumentLocalsList, adaptStoredArguments(closureInit, invocation))
// drop the closure from the stack
ownerMethod.instructions.insertBefore(invocation, new InsnNode(POP))
@@ -210,8 +307,9 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
insertLoadOps(invocation, ownerMethod, argumentLocalsList)
// update maxStack
- val capturesStackSize = localsForCapturedValues.size
- val invocationStackHeight = stackHeight + capturesStackSize - 1 // -1 because the closure is gone
+ // One slot per value is correct for long / double, see comment in the `analysis` package object.
+ val numCapturedValues = localsForCapturedValues.locals.length
+ val invocationStackHeight = stackHeight + numCapturedValues - 1 // -1 because the closure is gone
if (invocationStackHeight > ownerMethod.maxStack)
ownerMethod.maxStack = invocationStackHeight
@@ -227,46 +325,75 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
insns.insertBefore(invocation, new InsnNode(DUP))
INVOKESPECIAL
}
- val isInterface = bodyOpcode == INVOKEINTERFACE
- val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, isInterface)
+ val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, lambdaBodyHandle.isInterface)
ownerMethod.instructions.insertBefore(invocation, bodyInvocation)
- val returnType = Type.getReturnType(lambdaBodyHandle.getDesc)
- fixLoadedNothingOrNullValue(returnType, bodyInvocation, ownerMethod, btypes) // see comment of that method
+ val bodyReturnType = Type.getReturnType(lambdaBodyHandle.getDesc)
+ val invocationReturnType = Type.getReturnType(invocation.desc)
+ if (isPrimitiveType(invocationReturnType) && bodyReturnType.getDescriptor == ObjectRef.descriptor) {
+ val op =
+ if (invocationReturnType.getSort == Type.VOID) getPop(1)
+ else getScalaUnbox(invocationReturnType)
+ ownerMethod.instructions.insertBefore(invocation, op)
+ } else if (isPrimitiveType(bodyReturnType) && invocationReturnType.getDescriptor == ObjectRef.descriptor) {
+ val op =
+ if (bodyReturnType.getSort == Type.VOID) getBoxedUnit
+ else getScalaBox(bodyReturnType)
+ ownerMethod.instructions.insertBefore(invocation, op)
+ } else {
+ // see comment of that method
+ fixLoadedNothingOrNullValue(bodyReturnType, bodyInvocation, ownerMethod, btypes)
+ }
ownerMethod.instructions.remove(invocation)
// update the call graph
- val originalCallsite = callGraph.callsites.remove(invocation)
+ val originalCallsite = callGraph.removeCallsite(invocation, ownerMethod)
// the method node is needed for building the call graph entry
val bodyMethod = byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc)
- def bodyMethodIsBeingCompiled = byteCodeRepository.classNodeAndSource(lambdaBodyHandle.getOwner).map(_._2 == CompilationUnit).getOrElse(false)
- val bodyMethodCallsite = Callsite(
- callsiteInstruction = bodyInvocation,
- callsiteMethod = ownerMethod,
- callsiteClass = closureInit.ownerClass,
- callee = bodyMethod.map({
- case (bodyMethodNode, bodyMethodDeclClass) => Callee(
+ val sourceFilePath = byteCodeRepository.compilingClasses.get(lambdaBodyHandle.getOwner).map(_._2)
+ val callee = bodyMethod.map({
+ case (bodyMethodNode, bodyMethodDeclClass) =>
+ val bodyDeclClassType = classBTypeFromParsedClassfile(bodyMethodDeclClass)
+ Callee(
callee = bodyMethodNode,
- calleeDeclarationClass = classBTypeFromParsedClassfile(bodyMethodDeclClass),
- safeToInline = compilerSettings.YoptInlineGlobal || bodyMethodIsBeingCompiled,
- safeToRewrite = false, // the lambda body method is not a trait interface method
+ calleeDeclarationClass = bodyDeclClassType,
+ isStaticallyResolved = true,
+ sourceFilePath = sourceFilePath,
annotatedInline = false,
annotatedNoInline = false,
+ samParamTypes = callGraph.samParamTypes(bodyMethodNode, bodyDeclClassType),
calleeInfoWarning = None)
- }),
- argInfos = Nil,
+ })
+ val argInfos = closureInit.capturedArgInfos ++ originalCallsite.map(cs => cs.argInfos map {
+ case (index, info) => (index + numCapturedValues, info)
+ }).getOrElse(IntMap.empty)
+ val bodyMethodCallsite = Callsite(
+ callsiteInstruction = bodyInvocation,
+ callsiteMethod = ownerMethod,
+ callsiteClass = closureInit.ownerClass,
+ callee = callee,
+ argInfos = argInfos,
callsiteStackHeight = invocationStackHeight,
receiverKnownNotNull = true, // see below (*)
- callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition)
+ callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition),
+ annotatedInline = false,
+ annotatedNoInline = false
)
// (*) The documentation in class LambdaMetafactory says:
// "if implMethod corresponds to an instance method, the first capture argument
// (corresponding to the receiver) must be non-null"
// Explanation: If the lambda body method is non-static, the receiver is a captured
// value. It can only be captured within some instance method, so we know it's non-null.
- callGraph.callsites(bodyInvocation) = bodyMethodCallsite
+ callGraph.addCallsite(bodyMethodCallsite)
+
+ // Rewriting a closure invocation may render code unreachable. For example, the body method of
+ // (x: T) => ??? has return type Nothing$, and an ATHROW is added (see fixLoadedNothingOrNullValue).
+ unreachableCodeEliminated -= ownerMethod
+
+ if (hasAdaptedImplMethod(closureInit) && inliner.canInlineCallsite(bodyMethodCallsite).isEmpty)
+ inliner.inlineCallsite(bodyMethodCallsite)
}
/**
@@ -283,13 +410,10 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
// local. On the other hand, further optimizations (copy propagation, remove unused locals) will
// clean it up.
- // Captured variables don't need to be cast when loaded at the callsite (castLoadTypes are None).
- // This is checked in `isClosureInstantiation`: the types of the captured variables in the indy
- // instruction match exactly the corresponding parameter types in the body method.
- val localsForCaptures = LocalsList.fromTypes(firstCaptureLocal, capturedTypes, castLoadTypes = _ => None)
+ val localsForCaptures = LocalsList.fromTypes(firstCaptureLocal, capturedTypes)
closureInit.ownerMethod.maxLocals = firstCaptureLocal + localsForCaptures.size
- insertStoreOps(indy, closureInit.ownerMethod, localsForCaptures)
+ insertStoreOps(indy, closureInit.ownerMethod, localsForCaptures, _ => None)
insertLoadOps(indy, closureInit.ownerMethod, localsForCaptures)
localsForCaptures
@@ -301,8 +425,16 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
*
* The lowest stack value is stored in the head of the locals list, so the last local is stored first.
*/
- private def insertStoreOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList) =
- insertLocalValueOps(before, methodNode, localsList, store = true)
+ private def insertStoreOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList, beforeStore: Int => Option[AbstractInsnNode]) = {
+ // The first instruction needs to store into the last local of the `localsList`.
+ // To avoid reversing the list, we use `insert(previous)`.
+ val previous = before.getPrevious
+ def ins(op: AbstractInsnNode) = methodNode.instructions.insert(previous, op)
+ for ((l, i) <- localsList.locals.zipWithIndex) {
+ ins(new VarInsnNode(l.storeOpcode, l.local))
+ beforeStore(i) foreach ins
+ }
+ }
/**
* Insert load operations in front of the `before` instruction to copy the local values denoted
@@ -310,20 +442,10 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
*
* The head of the locals list will be the lowest value on the stack, so the first local is loaded first.
*/
- private def insertLoadOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList) =
- insertLocalValueOps(before, methodNode, localsList, store = false)
-
- private def insertLocalValueOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList, store: Boolean): Unit = {
- // If `store` is true, the first instruction needs to store into the last local of the `localsList`.
- // Load instructions on the other hand are emitted in the order of the list.
- // To avoid reversing the list, we use `insert(previousInstr)` for stores and `insertBefore(before)` for loads.
- lazy val previous = before.getPrevious
+ private def insertLoadOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList) = {
for (l <- localsList.locals) {
- val varOp = new VarInsnNode(if (store) l.storeOpcode else l.loadOpcode, l.local)
- if (store) methodNode.instructions.insert(previous, varOp)
- else methodNode.instructions.insertBefore(before, varOp)
- if (!store) for (castType <- l.castLoadedValue)
- methodNode.instructions.insert(varOp, new TypeInsnNode(CHECKCAST, castType.getInternalName))
+ val op = new VarInsnNode(l.loadOpcode, l.local)
+ methodNode.instructions.insertBefore(before, op)
}
}
@@ -345,12 +467,12 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
* Local(6, refOpOffset) ::
* Nil
*/
- def fromTypes(firstLocal: Int, types: Array[Type], castLoadTypes: Int => Option[Type]): LocalsList = {
+ def fromTypes(firstLocal: Int, types: Array[Type]): LocalsList = {
var sizeTwoOffset = 0
val locals: List[Local] = types.indices.map(i => {
// The ASM method `type.getOpcode` returns the opcode for operating on a value of `type`.
val offset = types(i).getOpcode(ILOAD) - ILOAD
- val local = Local(firstLocal + i + sizeTwoOffset, offset, castLoadTypes(i))
+ val local = Local(firstLocal + i + sizeTwoOffset, offset)
if (local.size == 2) sizeTwoOffset += 1
local
})(collection.breakOut)
@@ -364,10 +486,15 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
* The xLOAD / xSTORE opcodes are in the following sequence: I, L, F, D, A, so the offset for
* a local variable holding a reference (`A`) is 4. See also method `getOpcode` in [[scala.tools.asm.Type]].
*/
- case class Local(local: Int, opcodeOffset: Int, castLoadedValue: Option[Type]) {
+ case class Local(local: Int, opcodeOffset: Int) {
def size = if (loadOpcode == LLOAD || loadOpcode == DLOAD) 2 else 1
def loadOpcode = ILOAD + opcodeOffset
def storeOpcode = ISTORE + opcodeOffset
}
}
+
+object ClosureOptimizer {
+ val primitives = "BSIJCFDZV"
+ val specializationSuffix = s"(\\$$mc[$primitives]+\\$$sp)".r
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala
new file mode 100644
index 0000000000..518646812e
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala
@@ -0,0 +1,635 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2014 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import scala.annotation.{switch, tailrec}
+import scala.tools.asm.tree.analysis.BasicInterpreter
+import scala.tools.asm.Type
+import scala.tools.asm.Opcodes._
+import scala.tools.asm.tree._
+import scala.collection.mutable
+import scala.collection.JavaConverters._
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.tools.nsc.backend.jvm.analysis._
+import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
+
+class CopyProp[BT <: BTypes](val btypes: BT) {
+ import btypes._
+ import backendUtils._
+
+
+ /**
+ * For every `xLOAD n`, find all local variable slots that are aliases of `n` using an
+ * AliasingAnalyzer and change the instruction to `xLOAD m` where `m` is the smallest alias.
+ * This leaves behind potentially stale `xSTORE n` instructions, which are then eliminated
+ * by [[eliminateStaleStores]].
+ */
+ def copyPropagation(method: MethodNode, owner: InternalName): Boolean = {
+ AsmAnalyzer.sizeOKForAliasing(method) && {
+ var changed = false
+ val numParams = parametersSize(method)
+ lazy val aliasAnalysis = new AsmAnalyzer(method, owner, new AliasingAnalyzer(new BasicInterpreter))
+
+ // Remember locals that are used in a `LOAD` instruction. Assume a program has two LOADs:
+ //
+ // ...
+ // LOAD 3 // aliases of 3 here: <3>
+ // ...
+ // LOAD 1 // aliases of 1 here: <1, 3>
+ //
+ // In this example, we should change the second load from 1 to 3, which might render the
+ // local variable 1 unused.
+ val knownUsed = new Array[Boolean](method.maxLocals)
+
+ def usedOrMinAlias(it: IntIterator, init: Int): Int = {
+ if (knownUsed(init)) init
+ else {
+ var r = init
+ while (it.hasNext) {
+ val n = it.next()
+ // knownUsed.length is the number of locals, `n` may be a stack slot
+ if (n < knownUsed.length && knownUsed(n)) return n
+ if (n < r) r = n
+ }
+ r
+ }
+ }
+
+ val it = method.instructions.iterator
+ while (it.hasNext) it.next() match {
+ case vi: VarInsnNode if vi.`var` >= numParams && isLoad(vi) =>
+ val aliases = aliasAnalysis.frameAt(vi).asInstanceOf[AliasingFrame[_]].aliasesOf(vi.`var`)
+ if (aliases.size > 1) {
+ val alias = usedOrMinAlias(aliases.iterator, vi.`var`)
+ if (alias != -1) {
+ changed = true
+ vi.`var` = alias
+ }
+ }
+ knownUsed(vi.`var`) = true
+
+ case _ =>
+ }
+
+ changed
+ }
+ }
+
+ /**
+ * Eliminate `xSTORE` instructions that have no consumer. If the instruction can be completely
+ * eliminated, it is replaced by a POP. The [[eliminatePushPop]] cleans up unnecessary POPs.
+ *
+ * Note that an `ASOTRE` can not always be eliminated: it removes a reference to the object that
+ * is currently stored in that local, which potentially frees it for GC (SI-5313). Therefore
+ * we replace such stores by `POP; ACONST_NULL; ASTORE x`.
+ */
+ def eliminateStaleStores(method: MethodNode, owner: InternalName): Boolean = {
+ AsmAnalyzer.sizeOKForSourceValue(method) && {
+ lazy val prodCons = new ProdConsAnalyzer(method, owner)
+ def hasNoCons(varIns: AbstractInsnNode, slot: Int) = prodCons.consumersOfValueAt(varIns.getNext, slot).isEmpty
+
+ // insns to delete: IINC that have no consumer
+ val toDelete = mutable.ArrayBuffer.empty[IincInsnNode]
+
+ // xSTORE insns to be replaced by POP or POP2
+ val storesToDrop = mutable.ArrayBuffer.empty[VarInsnNode]
+
+ // ASTORE insn that have no consumer.
+ // - if the local is not live, the store is replaced by POP
+ // - otherwise, pop the argument value and store NULL instead. Unless the boolean field is
+ // `true`: then the store argument is already known to be ACONST_NULL.
+ val toNullOut = mutable.ArrayBuffer.empty[(VarInsnNode, Boolean)]
+
+ // `true` for variables that are known to be live
+ val liveVars = new Array[Boolean](method.maxLocals)
+
+ val it = method.instructions.iterator
+ while (it.hasNext) it.next() match {
+ case vi: VarInsnNode if isStore(vi) && hasNoCons(vi, vi.`var`) =>
+ val canElim = vi.getOpcode != ASTORE || {
+ val currentFieldValueProds = prodCons.initialProducersForValueAt(vi, vi.`var`)
+ currentFieldValueProds.size == 1 && (currentFieldValueProds.head match {
+ case ParameterProducer(0) => !isStaticMethod(method) // current field value is `this`, which won't be gc'd anyway
+ case _: UninitializedLocalProducer => true // field is not yet initialized, so current value cannot leak
+ case _ => false
+ })
+ }
+ if (canElim) storesToDrop += vi
+ else {
+ val prods = prodCons.producersForValueAt(vi, prodCons.frameAt(vi).stackTop)
+ val isStoreNull = prods.size == 1 && prods.head.getOpcode == ACONST_NULL
+ toNullOut += ((vi, isStoreNull))
+ }
+
+ case ii: IincInsnNode if hasNoCons(ii, ii.`var`) =>
+ toDelete += ii
+
+ case vi: VarInsnNode =>
+ liveVars(vi.`var`) = true
+
+ case ii: IincInsnNode =>
+ liveVars(ii.`var`) = true
+
+ case _ =>
+ }
+
+ def replaceByPop(vi: VarInsnNode): Unit = {
+ val size = if (isSize2LoadOrStore(vi.getOpcode)) 2 else 1
+ method.instructions.set(vi, getPop(size))
+ }
+
+ toDelete foreach method.instructions.remove
+
+ storesToDrop foreach replaceByPop
+
+ for ((vi, isStoreNull) <- toNullOut) {
+ if (!liveVars(vi.`var`)) replaceByPop(vi) // can drop `ASTORE x` where x has only dead stores
+ else {
+ if (!isStoreNull) {
+ val prev = vi.getPrevious
+ method.instructions.insert(prev, new InsnNode(ACONST_NULL))
+ method.instructions.insert(prev, getPop(1))
+ }
+ }
+ }
+
+ toDelete.nonEmpty || storesToDrop.nonEmpty || toNullOut.nonEmpty
+ }
+ }
+
+ /**
+ * When a POP instruction has a single producer, remove the POP and eliminate the producer by
+ * bubbling up the POPs. For example, given
+ * ILOAD 1; ILOAD 2; IADD; POP
+ * we first eliminate the POP, then the IADD, then its inputs, so the entire sequence goes away.
+ * If a producer cannot be eliminated (need to keep side-effects), a POP is inserted.
+ *
+ * A special case eliminates the creation of unused objects with side-effect-free constructors:
+ * NEW scala/Tuple1; DUP; ALOAD 0; INVOKESPECIAL scala/Tuple1.<init>; POP
+ * The POP has a single producer (the DUP), it's easy to eliminate these two. A special case
+ * is needed to eliminate the INVOKESPECIAL and NEW.
+ */
+ def eliminatePushPop(method: MethodNode, owner: InternalName): Boolean = {
+ AsmAnalyzer.sizeOKForSourceValue(method) && {
+ // A queue of instructions producing a value that has to be eliminated. If possible, the
+ // instruction (and its inputs) will be removed, otherwise a POP is inserted after
+ val queue = mutable.Queue.empty[ProducedValue]
+ // Contains constructor invocations for values that can be eliminated if unused.
+ val sideEffectFreeConstructorCalls = mutable.ArrayBuffer.empty[MethodInsnNode]
+
+ // instructions to remove (we don't change the bytecode while analyzing it. this allows
+ // running the ProdConsAnalyzer only once.)
+ val toRemove = mutable.Set.empty[AbstractInsnNode]
+ // instructions to insert before some instruction
+ val toInsertBefore = mutable.Map.empty[AbstractInsnNode, List[InsnNode]]
+ // an instruction to insert after some instruction
+ val toInsertAfter = mutable.Map.empty[AbstractInsnNode, AbstractInsnNode]
+
+ lazy val prodCons = new ProdConsAnalyzer(method, owner)
+
+ /**
+ * Returns the producers for the stack value `inputSlot` consumed by `cons`, if the consumer
+ * instruction is the only consumer for all of these producers.
+ *
+ * If a producer has multiple consumers, or the value is the caught exception in a catch
+ * block, this method returns Set.empty.
+ */
+ def producersIfSingleConsumer(cons: AbstractInsnNode, inputSlot: Int): Set[AbstractInsnNode] = {
+ /**
+ * True if the values produced by `prod` are all the same. Most instructions produce a single
+ * value. DUP and DUP2 (with a size-2 input) produce two equivalent values. However, there
+ * are some exotic instructions that produce multiple non-equal values (DUP_X1, SWAP, ...).
+ *
+ * Assume we have `DUP_X2; POP`. In order to remove the `POP` we need to change the DUP_X2
+ * into something else, which is not straightforward.
+ *
+ * Since scalac never emits any of those exotic bytecodes, we don't optimize them.
+ */
+ def producerHasSingleOutput(prod: AbstractInsnNode): Boolean = prod match {
+ case _: ExceptionProducer[_] | _: UninitializedLocalProducer =>
+ // POP of an exception in a catch block cannot be removed. For an uninitialized local,
+ // there should not be a consumer. We are conservative and include it here, so the
+ // producer would not be removed.
+ false
+
+ case _: ParameterProducer =>
+ true
+
+ case _ => (prod.getOpcode: @switch) match {
+ case DUP => true
+ case DUP2 => prodCons.frameAt(prod).peekStack(0).getSize == 2
+ case _ => InstructionStackEffect.prod(InstructionStackEffect.forAsmAnalysis(prod, prodCons.frameAt(prod))) == 1
+ }
+ }
+
+ val prods = prodCons.producersForValueAt(cons, inputSlot)
+ val singleConsumer = prods forall { prod =>
+ producerHasSingleOutput(prod) && {
+ // for DUP / DUP2, we only consider the value that is actually consumed by cons
+ val conss = prodCons.consumersOfValueAt(prod.getNext, inputSlot)
+ conss.size == 1 && conss.head == cons
+ }
+ }
+ if (singleConsumer) prods else Set.empty
+ }
+
+ /**
+ * For a POP instruction that is the single consumer of its producers, remove the POP and
+ * enqueue the producers.
+ */
+ def handleInitialPop(pop: AbstractInsnNode): Unit = {
+ val prods = producersIfSingleConsumer(pop, prodCons.frameAt(pop).stackTop)
+ if (prods.nonEmpty) {
+ toRemove += pop
+ val size = if (pop.getOpcode == POP2) 2 else 1
+ queue ++= prods.map(ProducedValue(_, size))
+ }
+ }
+
+ /**
+ * Traverse the method in its initial state and collect all POP instructions and side-effect
+ * free constructor invocations that can be eliminated.
+ */
+ def collectInitialPopsAndPureConstrs(): Unit = {
+ val it = method.instructions.iterator
+ while (it.hasNext) {
+ val insn = it.next()
+ (insn.getOpcode: @switch) match {
+ case POP | POP2 =>
+ handleInitialPop(insn)
+
+ case INVOKESPECIAL =>
+ val mi = insn.asInstanceOf[MethodInsnNode]
+ if (isSideEffectFreeConstructorCall(mi)) sideEffectFreeConstructorCalls += mi
+
+ case _ =>
+ }
+ }
+ }
+
+ /**
+ * Eliminate the `numArgs` inputs of the instruction `prod` (which was eliminated). For
+ * each input value
+ * - if the `prod` instruction is the single consumer, enqueue the producers of the input
+ * - otherwise, insert a POP instruction to POP the input value
+ */
+ def handleInputs(prod: AbstractInsnNode, numArgs: Int): Unit = {
+ val frame = prodCons.frameAt(prod)
+ val pops = mutable.ListBuffer.empty[InsnNode]
+ @tailrec def handle(stackOffset: Int): Unit = {
+ if (stackOffset >= 0) {
+ val prods = producersIfSingleConsumer(prod, frame.stackTop - stackOffset)
+ val nSize = frame.peekStack(stackOffset).getSize
+ if (prods.isEmpty) pops append getPop(nSize)
+ else queue ++= prods.map(ProducedValue(_, nSize))
+ handle(stackOffset - 1)
+ }
+ }
+ handle(numArgs - 1) // handle stack offsets (numArgs - 1) to 0
+ if (pops.nonEmpty) toInsertBefore(prod) = pops.toList
+ }
+
+ /**
+ * Eliminate LMF `indy` and its inputs.
+ */
+ def handleClosureInst(indy: InvokeDynamicInsnNode): Unit = {
+ toRemove += indy
+ callGraph.removeClosureInstantiation(indy, method)
+ handleInputs(indy, Type.getArgumentTypes(indy.desc).length)
+ }
+
+ def runQueue(): Unit = while (queue.nonEmpty) {
+ val ProducedValue(prod, size) = queue.dequeue()
+
+ def prodString = s"Producer ${AsmUtils textify prod}@${method.instructions.indexOf(prod)}\n${AsmUtils textify method}"
+ def popAfterProd(): Unit = toInsertAfter(prod) = getPop(size)
+
+ (prod.getOpcode: @switch) match {
+ case ACONST_NULL | ICONST_M1 | ICONST_0 | ICONST_1 | ICONST_2 | ICONST_3 | ICONST_4 | ICONST_5 | LCONST_0 | LCONST_1 | FCONST_0 | FCONST_1 | FCONST_2 | DCONST_0 | DCONST_1 |
+ BIPUSH | SIPUSH | ILOAD | LLOAD | FLOAD | DLOAD | ALOAD=>
+ toRemove += prod
+
+ case opc @ (DUP | DUP2) =>
+ assert(opc != 2 || size == 2, s"DUP2 for two size-1 values; $prodString") // ensured in method `producerHasSingleOutput`
+ if (toRemove(prod))
+ // the DUP is already scheduled for removal because one of its consumers is a POP.
+ // now the second consumer is also a POP, so we need to eliminate the DUP's input.
+ handleInputs(prod, 1)
+ else
+ toRemove += prod
+
+ case DUP_X1 | DUP_X2 | DUP2_X1 | DUP2_X2 | SWAP =>
+ // these are excluded in method `producerHasSingleOutput`
+ assert(false, s"Cannot eliminate value pushed by an instruction with multiple output values; $prodString")
+
+ case IDIV | LDIV | IREM | LREM =>
+ popAfterProd() // keep potential division by zero
+
+ case IADD | LADD | FADD | DADD | ISUB | LSUB | FSUB | DSUB | IMUL | LMUL | FMUL | DMUL | FDIV | DDIV | FREM | DREM |
+ LSHL | LSHR | LUSHR |
+ IAND | IOR | IXOR | LAND | LOR | LXOR |
+ LCMP | FCMPL | FCMPG | DCMPL | DCMPG =>
+ toRemove += prod
+ handleInputs(prod, 2)
+
+ case INEG | LNEG | FNEG | DNEG |
+ I2L | I2F | I2D | L2I | L2F | L2D | F2I | F2L | F2D | D2I | D2L | D2F | I2B | I2C | I2S =>
+ toRemove += prod
+ handleInputs(prod, 1)
+
+ case GETFIELD | GETSTATIC =>
+ // TODO eliminate side-effect free module loads (https://github.com/scala/scala-dev/issues/16)
+ if (isBoxedUnit(prod)) toRemove += prod
+ else popAfterProd() // keep potential class initialization (static field) or NPE (instance field)
+
+ case INVOKEVIRTUAL | INVOKESPECIAL | INVOKESTATIC | INVOKEINTERFACE =>
+ val methodInsn = prod.asInstanceOf[MethodInsnNode]
+ if (isSideEffectFreeCall(methodInsn)) {
+ toRemove += prod
+ callGraph.removeCallsite(methodInsn, method)
+ val receiver = if (methodInsn.getOpcode == INVOKESTATIC) 0 else 1
+ handleInputs(prod, Type.getArgumentTypes(methodInsn.desc).length + receiver)
+ } else
+ popAfterProd()
+
+ case INVOKEDYNAMIC =>
+ prod match {
+ case callGraph.LambdaMetaFactoryCall(indy, _, _, _) => handleClosureInst(indy)
+ case _ => popAfterProd()
+ }
+
+ case NEW =>
+ if (isNewForSideEffectFreeConstructor(prod)) toRemove += prod
+ else popAfterProd()
+
+ case LDC => prod.asInstanceOf[LdcInsnNode].cst match {
+ case _: java.lang.Integer | _: java.lang.Float | _: java.lang.Long | _: java.lang.Double | _: String =>
+ toRemove += prod
+
+ case _ =>
+ // don't remove class literals, method types, method handles: keep a potential NoClassDefFoundError
+ popAfterProd()
+ }
+
+ case MULTIANEWARRAY =>
+ toRemove += prod
+ handleInputs(prod, prod.asInstanceOf[MultiANewArrayInsnNode].dims)
+
+ case _ =>
+ popAfterProd()
+ }
+ }
+
+ // there are two cases when we can eliminate a constructor call:
+ // - NEW T; INVOKESPECIAL T.<init> -- there's no DUP, the new object is consumed only by the constructor)
+ // - NEW T; DUP; INVOKESPECIAL T.<init>, where the DUP will be removed
+ def eliminateUnusedPureConstructorCalls(): Boolean = {
+ var changed = false
+
+ def removeConstructorCall(mi: MethodInsnNode): Unit = {
+ toRemove += mi
+ callGraph.removeCallsite(mi, method)
+ sideEffectFreeConstructorCalls -= mi
+ changed = true
+ }
+
+ for (mi <- sideEffectFreeConstructorCalls.toList) { // toList to allow removing elements while traversing
+ val frame = prodCons.frameAt(mi)
+ val stackTop = frame.stackTop
+ val numArgs = Type.getArgumentTypes(mi.desc).length
+ val receiverProds = producersIfSingleConsumer(mi, stackTop - numArgs)
+ if (receiverProds.size == 1) {
+ val receiverProd = receiverProds.head
+ if (receiverProd.getOpcode == NEW) {
+ removeConstructorCall(mi)
+ handleInputs(mi, numArgs + 1) // removes the producers of args and receiver
+ } else if (receiverProd.getOpcode == DUP && toRemove.contains(receiverProd)) {
+ val dupProds = producersIfSingleConsumer(receiverProd, prodCons.frameAt(receiverProd).stackTop)
+ if (dupProds.size == 1 && dupProds.head.getOpcode == NEW) {
+ removeConstructorCall(mi)
+ handleInputs(mi, numArgs) // removes the producers of args. the producer of the receiver is DUP and already in toRemove.
+ queue += ProducedValue(dupProds.head, 1) // removes the NEW (which is NOT the producer of the receiver!)
+ }
+ }
+ }
+ }
+ changed
+ }
+
+ collectInitialPopsAndPureConstrs()
+
+ // eliminating producers enables eliminating unused constructor calls (when a DUP gets removed).
+ // vice-versa, eliminating a constructor call adds producers of constructor parameters to the queue.
+ // so the two run in a loop.
+ runQueue()
+ while (eliminateUnusedPureConstructorCalls())
+ runQueue()
+
+ var changed = false
+ toInsertAfter foreach {
+ case (target, insn) =>
+ nextExecutableInstructionOrLabel(target) match {
+ // `insn` is of type `InsnNode`, so we only need to check the Opcode when comparing to another instruction
+ case Some(next) if next.getOpcode == insn.getOpcode && toRemove(next) =>
+ // Inserting and removing a POP at the same place should not enable `changed`. This happens
+ // when a POP directly follows a producer that cannot be eliminated, e.g. INVOKESTATIC A.m ()I; POP
+ // The POP is initially added to `toRemove`, and the `INVOKESTATIC` producer is added to the queue.
+ // Because the producer cannot be elided, a POP is added to `toInsertAfter`.
+ toRemove -= next
+
+ case _ =>
+ changed = true
+ method.instructions.insert(target, insn)
+ }
+ }
+ toInsertBefore foreach {
+ case (target, insns) =>
+ changed = true
+ insns.foreach(method.instructions.insertBefore(target, _))
+ }
+ toRemove foreach { insn =>
+ changed = true
+ method.instructions.remove(insn)
+ }
+ changed
+ }
+ }
+
+ case class ProducedValue(producer: AbstractInsnNode, size: Int) {
+ override def toString = s"<${AsmUtils textify producer}>"
+ }
+
+ /**
+ * Remove `xSTORE n; xLOAD n` pairs if
+ * - the local variable n is not used anywhere else in the method (1), and
+ * - there are no executable instructions and no live labels (jump targets) between the two (2)
+ *
+ * Note: store-load pairs that cannot be eliminated could be replaced by `DUP; xSTORE n`, but
+ * that's just cosmetic and doesn't help for anything.
+ *
+ * (1) This could be made more precise by running a prodCons analysis and checking that the load
+ * is the only user of the store. Then we could eliminate the pair even if the variable is live
+ * (except for ASTORE, SI-5313). Not needing an analyzer is more efficient, and catches most
+ * cases.
+ *
+ * (2) The implementation uses a conservative estimation for liveness (if some instruction uses
+ * local n, then n is considered live in the entire method). In return, it doesn't need to run an
+ * Analyzer on the method, making it more efficient.
+ *
+ * This method also removes `ACONST_NULL; ASTORE n` if the local n is not live. This pattern is
+ * introduced by [[eliminateStaleStores]].
+ *
+ * The implementation is a little tricky to support the following case:
+ * ISTORE 1; ISTORE 2; ILOAD 2; ACONST_NULL; ASTORE 3; ILOAD 1
+ * The outer store-load pair can be removed if two the inner pairs can be.
+ */
+ def eliminateStoreLoad(method: MethodNode): Boolean = {
+ val removePairs = mutable.Set.empty[RemovePair]
+ val liveVars = new Array[Boolean](method.maxLocals)
+ val liveLabels = mutable.Set.empty[LabelNode]
+
+ def mkRemovePair(store: VarInsnNode, other: AbstractInsnNode, depends: List[RemovePairDependency]): RemovePair = {
+ val r = RemovePair(store, other, depends)
+ removePairs += r
+ r
+ }
+
+ def registerLiveVarsLabels(insn: AbstractInsnNode): Unit = insn match {
+ case vi: VarInsnNode => liveVars(vi.`var`) = true
+ case ii: IincInsnNode => liveVars(ii.`var`) = true
+ case j: JumpInsnNode => liveLabels += j.label
+ case s: TableSwitchInsnNode => liveLabels += s.dflt; liveLabels ++= s.labels.asScala
+ case s: LookupSwitchInsnNode => liveLabels += s.dflt; liveLabels ++= s.labels.asScala
+ case _ =>
+ }
+
+ val pairStartStack = new mutable.Stack[(AbstractInsnNode, mutable.ListBuffer[RemovePairDependency])]
+
+ def push(insn: AbstractInsnNode) = {
+ pairStartStack push ((insn, mutable.ListBuffer.empty))
+ }
+
+ def addDepends(dependency: RemovePairDependency) = if (pairStartStack.nonEmpty) {
+ val (_, depends) = pairStartStack.top
+ depends += dependency
+ }
+
+ def completesStackTop(load: AbstractInsnNode) = isLoad(load) && pairStartStack.nonEmpty && {
+ pairStartStack.top match {
+ case (store: VarInsnNode, _) => store.`var` == load.asInstanceOf[VarInsnNode].`var`
+ case _ => false
+ }
+ }
+
+ /**
+ * Try to pair `insn` with its correspondent on the stack
+ * - if the stack top is a store and `insn` is a corresponding load, create a pair
+ * - otherwise, check the two top stack values for `null; store`. if it matches, create
+ * a pair and continue pairing `insn` on the remaining stack
+ * - otherwise, empty the stack and mark the local variables in it live
+ */
+ def tryToPairInstruction(insn: AbstractInsnNode): Unit = {
+ @tailrec def emptyStack(): Unit = if (pairStartStack.nonEmpty) {
+ registerLiveVarsLabels(pairStartStack.pop()._1)
+ emptyStack()
+ }
+
+ @tailrec def tryPairing(): Unit = {
+ if (completesStackTop(insn)) {
+ val (store: VarInsnNode, depends) = pairStartStack.pop()
+ addDepends(mkRemovePair(store, insn, depends.toList))
+ } else if (pairStartStack.nonEmpty) {
+ val (top, topDepends) = pairStartStack.pop()
+ if (pairStartStack.nonEmpty) {
+ (pairStartStack.top, top) match {
+ case ((ldNull: InsnNode, depends), store: VarInsnNode) if ldNull.getOpcode == ACONST_NULL && store.getOpcode == ASTORE =>
+ pairStartStack.pop()
+ addDepends(mkRemovePair(store, ldNull, depends.toList))
+ // example: store; (null; store;) (store; load;) load
+ // s1^ ^^^^^p1^^^^^ // p1 is added to s1's depends
+ // then: store; (null; store;) load
+ // s2^ ^^^^p2^^^^^ // p1 and p2 are added to s2's depends
+ topDepends foreach addDepends
+ tryPairing()
+
+ case _ =>
+ // empty the stack - a non-matching insn was found, cannot create any pairs to remove
+ registerLiveVarsLabels(insn)
+ registerLiveVarsLabels(top)
+ emptyStack()
+ }
+ } else {
+ // stack only has one element
+ registerLiveVarsLabels(insn)
+ registerLiveVarsLabels(top)
+ }
+ } else {
+ // stack is empty already
+ registerLiveVarsLabels(insn)
+ }
+ }
+
+ tryPairing()
+ }
+
+
+ var insn = method.instructions.getFirst
+
+ @tailrec def advanceToNextExecutableOrLabel(): Unit = {
+ insn = insn.getNext
+ if (insn != null && !isExecutable(insn) && !insn.isInstanceOf[LabelNode]) advanceToNextExecutableOrLabel()
+ }
+
+ while (insn != null) {
+ insn match {
+ case _ if insn.getOpcode == ACONST_NULL => push(insn)
+ case vi: VarInsnNode if isStore(vi) => push(insn)
+ case label: LabelNode if pairStartStack.nonEmpty => addDepends(LabelNotLive(label))
+ case _ => tryToPairInstruction(insn)
+ }
+ advanceToNextExecutableOrLabel()
+ }
+
+ // elide RemovePairs that depend on live labels or other RemovePair that have to be elided.
+ // example: store 1; store 2; label x; load 2; load 1
+ // if x is live, the inner pair has to be elided, causing the outer pair to be elided too.
+
+ var doneEliding = false
+
+ def elide(removePair: RemovePair) = {
+ doneEliding = false
+ liveVars(removePair.store.`var`) = true
+ removePairs -= removePair
+ }
+
+ while (!doneEliding) {
+ doneEliding = true
+ for (removePair <- removePairs.toList) {
+ val slot = removePair.store.`var`
+ if (liveVars(slot)) elide(removePair)
+ else removePair.depends foreach {
+ case LabelNotLive(label) => if (liveLabels(label)) elide(removePair)
+ case other: RemovePair => if (!removePairs(other)) elide(removePair)
+ }
+ }
+ }
+
+ for (removePair <- removePairs) {
+ method.instructions.remove(removePair.store)
+ method.instructions.remove(removePair.other)
+ }
+
+ removePairs.nonEmpty
+ }
+}
+
+trait RemovePairDependency
+case class RemovePair(store: VarInsnNode, other: AbstractInsnNode, depends: List[RemovePairDependency]) extends RemovePairDependency {
+ override def toString = s"<${AsmUtils textify store},${AsmUtils textify other}> [$depends]"
+}
+case class LabelNotLive(label: LabelNode) extends RemovePairDependency
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
index e7dd5abc57..7bc4ea2392 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
@@ -27,7 +27,7 @@ import scala.tools.nsc.backend.jvm.BackendReporting.UnknownScalaInlineInfoVersio
* 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
+ * (method names, method signatures) would exist in there anyway. So the
* ScalaInlineAttribute remains relatively compact.
*/
case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineInfoAttribute.attributeName) {
@@ -47,13 +47,16 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
result.putByte(InlineInfoAttribute.VERSION)
- var hasSelfIsFinal = 0
- if (inlineInfo.isEffectivelyFinal) hasSelfIsFinal |= 1
- if (inlineInfo.traitImplClassSelfType.isDefined) hasSelfIsFinal |= 2
- result.putByte(hasSelfIsFinal)
+ var flags = 0
+ if (inlineInfo.isEffectivelyFinal) flags |= 1
+ // flags |= 2 // no longer written
+ if (inlineInfo.sam.isDefined) flags |= 4
+ result.putByte(flags)
- for (selfInternalName <- inlineInfo.traitImplClassSelfType) {
- result.putShort(cw.newUTF8(selfInternalName))
+ for (samNameDesc <- inlineInfo.sam) {
+ val (name, desc) = samNameDesc.span(_ != '(')
+ result.putShort(cw.newUTF8(name))
+ result.putShort(cw.newUTF8(desc))
}
// The method count fits in a short (the methods_count in a classfile is also a short)
@@ -68,10 +71,10 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
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
+ if (info.effectivelyFinal) inlineInfo |= 1
+ // inlineInfo |= 2 // no longer written
+ if (info.annotatedInline) inlineInfo |= 4
+ if (info.annotatedNoInline) inlineInfo |= 8
result.putByte(inlineInfo)
}
@@ -79,7 +82,7 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
}
/**
- * De-serialize the attribute into an InlineInfo. The attribute starts at cr.b(off), but we don't
+ * Deserialize 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
@@ -94,15 +97,17 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
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 flags = nextByte()
+ val isFinal = (flags & 1) != 0
+ val hasSelf = (flags & 2) != 0
+ val hasSam = (flags & 4) != 0
+
+ if (hasSelf) nextUTF8() // no longer used
+
+ val sam = if (!hasSam) None else {
+ val name = nextUTF8()
+ val desc = nextUTF8()
+ Some(name + desc)
}
val numEntries = nextShort()
@@ -111,14 +116,15 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
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))
+ val isFinal = (inlineInfo & 1) != 0
+ // = (inlineInfo & 2) != 0 // no longer used
+ val isInline = (inlineInfo & 4) != 0
+ val isNoInline = (inlineInfo & 8) != 0
+ (name + desc, MethodInlineInfo(isFinal, isInline, isNoInline))
}).toMap
- InlineInfoAttribute(InlineInfo(self, isFinal, infos, None))
+ val info = InlineInfo(isFinal, sam, infos, None)
+ InlineInfoAttribute(info)
} else {
val msg = UnknownScalaInlineInfoVersion(cr.getClassName, version)
InlineInfoAttribute(BTypes.EmptyInlineInfo.copy(warning = Some(msg)))
@@ -128,9 +134,18 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
object InlineInfoAttribute {
/**
+ * Notes:
+ * - `traitImplClassSelfType` is no longer emitted, `hasTraitImplClassSelfType` is always emitted
+ * as 0. Similarly, `traitMethodWithStaticImplementation` is always emitted 0.
+ * - When reading an existing attribute where `hasTraitImplClassSelfType` is 1, the
+ * `traitImplClassSelfType` is ignored. Also the value of `traitMethodWithStaticImplementation`
+ * is ignored.
+ *
* [u1] version
- * [u1] isEffectivelyFinal (<< 0), hasTraitImplClassSelfType (<< 1)
+ * [u1] isEffectivelyFinal (<< 0), hasTraitImplClassSelfType (<< 1), hasSam (<< 2), hasLateInterfaces (<< 3)
* [u2]? traitImplClassSelfType (reference)
+ * [u2]? samName (reference)
+ * [u2]? samDescriptor (reference)
* [u2] numMethodEntries
* [u2] name (reference)
* [u2] descriptor (reference)
@@ -142,7 +157,7 @@ object InlineInfoAttribute {
}
/**
- * In order to instruct the ASM framework to de-serialize the ScalaInlineInfo attribute, we need
+ * In order to instruct the ASM framework to deserialize the ScalaInlineInfo attribute, we need
* to pass a prototype instance when running the class reader.
*/
-object InlineInfoAttributePrototype extends InlineInfoAttribute(InlineInfo(null, false, null, null))
+object InlineInfoAttributePrototype extends InlineInfoAttribute(InlineInfo(false, null, 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
index 6b2786c1a3..1c29859f46 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
@@ -9,59 +9,125 @@ package opt
import scala.annotation.tailrec
import scala.tools.asm
-import asm.Handle
import asm.Opcodes._
import asm.tree._
-import scala.collection.convert.decorateAsScala._
-import scala.collection.convert.decorateAsJava._
+import scala.collection.JavaConverters._
import AsmUtils._
import BytecodeUtils._
import collection.mutable
-import scala.tools.asm.tree.analysis.SourceInterpreter
import BackendReporting._
import scala.tools.nsc.backend.jvm.BTypes.InternalName
class Inliner[BT <: BTypes](val btypes: BT) {
import btypes._
import callGraph._
+ import inlinerHeuristics._
+ import backendUtils._
- def eliminateUnreachableCodeAndUpdateCallGraph(methodNode: MethodNode, definingClass: InternalName): Unit = {
- localOpt.minimalRemoveUnreachableCode(methodNode, definingClass) foreach {
- case invocation: MethodInsnNode => callGraph.callsites.remove(invocation)
- case indy: InvokeDynamicInsnNode => callGraph.closureInstantiations.remove(indy)
- case _ =>
+ sealed trait InlineLog {
+ def request: InlineRequest
+ }
+ final case class InlineLogSuccess(request: InlineRequest, sizeBefore: Int, sizeInlined: Int) extends InlineLog {
+ var downstreamLog: mutable.Buffer[InlineLog] = mutable.ListBuffer.empty
+ }
+ final case class InlineLogFail(request: InlineRequest, warning: CannotInlineWarning) extends InlineLog
+ final case class InlineLogRollback(request: InlineRequest, warnings: List[CannotInlineWarning]) extends InlineLog
+
+ object InlineLog {
+ private def shouldLog(request: InlineRequest): Boolean = {
+ def logEnabled = compilerSettings.YoptLogInline.isSetByUser
+ def matchesName = {
+ val prefix = compilerSettings.YoptLogInline.value match {
+ case "_" => ""
+ case p => p
+ }
+ val name: String = request.callsite.callsiteClass.internalName + "." + request.callsite.callsiteMethod.name
+ name startsWith prefix
+ }
+ logEnabled && (upstream != null || (isTopLevel && matchesName))
+ }
+
+ // indexed by callsite method
+ private val logs = mutable.Map.empty[MethodNode, mutable.LinkedHashSet[InlineLog]]
+
+ private var upstream: InlineLogSuccess = _
+ private var isTopLevel = true
+
+ def withInlineLogging[T](request: InlineRequest)(inlineRequest: => Unit)(inlinePost: => T): T = {
+ def doInlinePost(): T = {
+ val savedIsTopLevel = isTopLevel
+ isTopLevel = false
+ try inlinePost
+ finally isTopLevel = savedIsTopLevel
+ }
+ if (shouldLog(request)) {
+ val sizeBefore = request.callsite.callsiteMethod.instructions.size
+ inlineRequest
+ val log = InlineLogSuccess(request, sizeBefore, request.callsite.callee.get.callee.instructions.size)
+ apply(log)
+
+ val savedUpstream = upstream
+ upstream = log
+ try doInlinePost()
+ finally upstream = savedUpstream
+ } else {
+ inlineRequest
+ doInlinePost()
+ }
+ }
+
+ def apply(log: => InlineLog): Unit = if (shouldLog(log.request)) {
+ if (upstream != null) upstream.downstreamLog += log
+ else {
+ val methodLogs = logs.getOrElseUpdate(log.request.callsite.callsiteMethod, mutable.LinkedHashSet.empty)
+ methodLogs += log
+ }
+ }
+
+ def entryString(log: InlineLog, indent: Int = 0): String = {
+ val callee = log.request.callsite.callee.get
+ val calleeString = callee.calleeDeclarationClass.internalName + "." + callee.callee.name
+ val indentString = " " * indent
+ log match {
+ case s @ InlineLogSuccess(_, sizeBefore, sizeInlined) =>
+ val self = s"${indentString}inlined $calleeString. Before: $sizeBefore ins, inlined: $sizeInlined ins."
+ if (s.downstreamLog.isEmpty) self
+ else s.downstreamLog.iterator.map(entryString(_, indent + 2)).mkString(self + "\n", "\n", "")
+
+ case InlineLogFail(_, w) =>
+ s"${indentString}failed $calleeString. ${w.toString.replace('\n', ' ')}"
+
+ case InlineLogRollback(_, _) =>
+ s"${indentString}rolling back, nested inline failed."
+ }
+ }
+
+ def print(): Unit = if (compilerSettings.YoptLogInline.isSetByUser) {
+ val byClassAndMethod: List[(InternalName, mutable.Map[MethodNode, mutable.LinkedHashSet[InlineLog]])] = {
+ logs.
+ groupBy(_._2.head.request.callsite.callsiteClass.internalName).
+ toList.sortBy(_._1)
+ }
+ for {
+ (c, methodLogs) <- byClassAndMethod
+ (m, mLogs) <- methodLogs.toList.sortBy(_._1.name)
+ mLog <- mLogs // insertion order
+ } {
+ println(s"Inline into $c.${m.name}: ${entryString(mLog)}")
+ }
}
}
def runInliner(): Unit = {
- rewriteFinalTraitMethodInvocations()
-
for (request <- collectAndOrderInlineRequests) {
- val Right(callee) = request.callee // collectAndOrderInlineRequests returns callsites with a known callee
-
- // Inlining a method can create unreachable code. Example:
- // def f = throw e
- // def g = f; println() // println is unreachable after inlining f
- // If we have an inline request for a call to g, and f has been already inlined into g, we
- // need to run DCE before inlining g.
- eliminateUnreachableCodeAndUpdateCallGraph(callee.callee, callee.calleeDeclarationClass.internalName)
-
- // DCE above removes unreachable callsites from the call graph. If the inlining request denotes
- // such an eliminated callsite, do nothing.
- if (callGraph.callsites contains request.callsiteInstruction) {
- val r = inline(request.callsiteInstruction, request.callsiteStackHeight, request.callsiteMethod, request.callsiteClass,
- callee.callee, callee.calleeDeclarationClass,
- request.receiverKnownNotNull, keepLineNumbers = false)
-
- for (warning <- r) {
- if ((callee.annotatedInline && btypes.compilerSettings.YoptWarningEmitAtInlineFailed) || warning.emitWarning(compilerSettings)) {
- 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)
- }
- }
+ val Right(callee) = request.callsite.callee // collectAndOrderInlineRequests returns callsites with a known callee
+ val warnings = inline(request)
+ for (warning <- warnings) {
+ if (warning.emitWarning(compilerSettings))
+ backendReporting.inlinerWarning(request.callsite.callsitePosition, warning.toString)
}
}
+ InlineLog.print()
}
/**
@@ -69,165 +135,21 @@ class Inliner[BT <: BTypes](val btypes: BT) {
* - 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
+ object callsiteOrdering extends Ordering[InlineRequest] {
+ override def compare(x: InlineRequest, y: InlineRequest): Int = {
+ val xCs = x.callsite
+ val yCs = y.callsite
+ val cls = xCs.callsiteClass.internalName compareTo yCs.callsiteClass.internalName
if (cls != 0) return cls
- val name = x.callsiteMethod.name compareTo y.callsiteMethod.name
+ val name = xCs.callsiteMethod.name compareTo yCs.callsiteMethod.name
if (name != 0) return name
- val desc = x.callsiteMethod.desc compareTo y.callsiteMethod.desc
+ val desc = xCs.callsiteMethod.desc compareTo yCs.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.compilerSettings.YoptWarningEmitAtInlineFailed) {
- // 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(compilerSettings)) {
- // 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(compilerSettings))
- 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) =>
- if (compilerSettings.YoptInlineHeuristics.value == "everything") safeToInline
- else 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) {
- // there's no need to run eliminateUnreachableCode here. building the call graph does that
- // already, no code can become unreachable in the meantime.
- val analyzer = new AsmAnalyzer(callsite.callsiteMethod, callsite.callsiteClass.internalName, new SourceInterpreter)
- val receiverValue = analyzer.frameAt(callsite.callsiteInstruction).peekStack(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,
- receiverKnownNotNull = callsite.receiverKnownNotNull,
- 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))
- }
+ pos(xCs) - pos(yCs)
}
}
@@ -238,15 +160,13 @@ class Inliner[BT <: BTypes](val btypes: BT) {
* 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
+ private def collectAndOrderInlineRequests: List[InlineRequest] = {
+ val requestsByMethod = selectCallsitesForInlining withDefaultValue Set.empty
+
+ val elided = mutable.Set.empty[InlineRequest]
+ def nonElidedRequests(methodNode: MethodNode): Set[InlineRequest] = requestsByMethod(methodNode) diff elided
- // 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
+ def allCallees(r: InlineRequest): Set[MethodNode] = r.post.flatMap(allCallees).toSet + r.callsite.callee.get.callee
/**
* Break cycles in the inline request graph by removing callsites.
@@ -254,236 +174,454 @@ class Inliner[BT <: BTypes](val btypes: BT) {
* 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] = {
+ def breakInlineCycles: List[InlineRequest] = {
// 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 =>
+ def isReachable(start: Set[MethodNode], goal: MethodNode): Boolean = {
+ @tailrec def reachableImpl(check: Set[MethodNode], visited: Set[MethodNode]): Boolean = {
+ if (check.isEmpty) false
+ else {
+ val x = check.head
if (x == goal) true
- else if (visited(x)) reachableImpl(xs, visited)
+ else if (visited(x)) reachableImpl(check - x, visited)
else {
- val callees = inlineRequestsForMethod(x).map(_.callee.get.callee)
- reachableImpl(xs ::: callees.toList, visited + x)
+ val callees = nonElidedRequests(x).flatMap(allCallees)
+ reachableImpl(check - x ++ callees, visited + x)
}
-
- case Nil =>
- false
+ }
}
- reachableImpl(List(start), Set.empty)
+ reachableImpl(start, Set.empty)
}
- val result = new mutable.ListBuffer[Callsite]()
+ val result = new mutable.ListBuffer[InlineRequest]()
+ val requests = requestsByMethod.valuesIterator.flatten.toArray
// sort the inline requests to ensure that removing requests is deterministic
- for (r <- requests.sorted(callsiteOrdering)) {
+ java.util.Arrays.sort(requests, callsiteOrdering)
+ for (r <- requests) {
// 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
+ if (isReachable(allCallees(r), r.callsite.callsiteMethod))
+ elided += 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] = {
+ def leavesFirst(requests: List[InlineRequest], visited: Set[InlineRequest] = Set.empty): List[InlineRequest] = {
if (requests.isEmpty) Nil
else {
val (leaves, others) = requests.partition(r => {
- val inlineRequestsForCallee = inlineRequestsForMethod(r.callee.get.callee)
- inlineRequestsForCallee.forall(visited)
+ val inlineRequestsForCallees = allCallees(r).flatMap(nonElidedRequests)
+ inlineRequestsForCallees.forall(visited)
})
assert(leaves.nonEmpty, requests)
leaves ::: leavesFirst(others, visited ++ leaves)
}
}
- leavesFirst(breakInlineCycles(requests))
+ leavesFirst(breakInlineCycles)
}
-
/**
- * Copy and adapt the instructions of a method to a callsite.
+ * Given an InlineRequest(mainCallsite, post = List(postCallsite)), the postCallsite is a callsite
+ * in the method `mainCallsite.callee`. Once the mainCallsite is inlined into the target method
+ * (mainCallsite.callsiteMethod), we need to find the cloned callsite that corresponds to the
+ * postCallsite so we can inline that into the target method as well.
*
- * 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
+ * However, it is possible that there is no cloned callsite at all that corresponds to the
+ * postCallsite, for example if the corresponding callsite already inlined. Example:
+ *
+ * def a() = 1
+ * def b() = a() + 2
+ * def c() = b() + 3
+ * def d() = c() + 4
+ *
+ * We have the following callsite objects in the call graph:
+ *
+ * c1 = a() in b
+ * c2 = b() in c
+ * c3 = c() in d
*
- * @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
+ * Assume we have the following inline request
+ * r = InlineRequest(c3,
+ * post = List(InlineRequest(c2,
+ * post = List(InlineRequest(c1, post = Nil)))))
+ *
+ * But before inlining r, assume a separate InlineRequest(c2, post = Nil) is inlined first. We get
+ *
+ * c1' = a() in c // added to the call graph
+ * c1.inlinedClones += (c1' at c2) // remember that c1' was created when inlining c2
+ * ~c2~ // c2 is removed from the call graph
+ *
+ * If we now inline r, we first inline c3. We get
+ *
+ * c1'' = a() in d // added to call graph
+ * c1'.inlinedClones += (c1'' at c3) // remember that c1'' was created when inlining c3
+ * ~c3~
+ *
+ * Now we continue with the post-requests for r, i.e. c2.
+ * - we try to find the clone of c2 that was created when inlining c3 - but there is none. c2
+ * was already inlined before
+ * - we continue with the post-request of c2: c1
+ * - we search for the callsite of c1 that was cloned when inlining c2, we find c1'
+ * - recursively we search for the callsite of c1' that was cloned when inlining c3, we find c1''
+ * - so we create an inline request for c1''
*/
- 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)
+ def adaptPostRequestForMainCallsite(post: InlineRequest, mainCallsite: Callsite): List[InlineRequest] = {
+ def impl(post: InlineRequest, at: Callsite): List[InlineRequest] = {
+ post.callsite.inlinedClones.find(_.clonedWhenInlining == at) match {
+ case Some(clonedCallsite) =>
+ List(InlineRequest(clonedCallsite.callsite, post.post, post.reason))
+ case None =>
+ post.post.flatMap(impl(_, post.callsite)).flatMap(impl(_, at))
}
+ }
+ impl(post, mainCallsite)
+ }
+
+ class UndoLog(active: Boolean = true) {
+ import java.util.{ ArrayList => JArrayList }
+
+ private var actions = List.empty[() => Unit]
+ private var methodStateSaved = false
+
+ def apply(a: => Unit): Unit = if (active) actions = (() => a) :: actions
+ def rollback(): Unit = if (active) actions.foreach(_.apply())
- // 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 iinc: IincInsnNode => iinc.`var` += localVarShift
- case _ => ()
+ def saveMethodState(methodNode: MethodNode): Unit = if (active && !methodStateSaved) {
+ methodStateSaved = true
+ val currentInstructions = methodNode.instructions.toArray
+ val currentLocalVariables = new JArrayList(methodNode.localVariables)
+ val currentTryCatchBlocks = new JArrayList(methodNode.tryCatchBlocks)
+ val currentMaxLocals = methodNode.maxLocals
+ val currentMaxStack = methodNode.maxStack
+
+ apply {
+ // `methodNode.instructions.clear()` doesn't work: it keeps the `prev` / `next` / `index` of
+ // instruction nodes. `instructions.removeAll(true)` would work, but is not public.
+ methodNode.instructions.iterator.asScala.toList.foreach(methodNode.instructions.remove)
+ for (i <- currentInstructions) methodNode.instructions.add(i)
+
+ methodNode.localVariables.clear()
+ methodNode.localVariables.addAll(currentLocalVariables)
+
+ methodNode.tryCatchBlocks.clear()
+ methodNode.tryCatchBlocks.addAll(currentTryCatchBlocks)
+
+ methodNode.maxLocals = currentMaxLocals
+ methodNode.maxStack = currentMaxStack
}
+ }
+ }
- // 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)
+ val NoUndoLogging = new UndoLog(active = false)
+
+ /**
+ * Inline the callsite of an inlining request and its post-inlining requests.
+ *
+ * @return An inliner warning for each callsite that could not be inlined.
+ */
+ def inline(request: InlineRequest, undo: UndoLog = NoUndoLogging): List[CannotInlineWarning] = {
+ def doInline(undo: UndoLog, callRollback: Boolean = false): List[CannotInlineWarning] = {
+ InlineLog.withInlineLogging(request) {
+ inlineCallsite(request.callsite, undo)
+ } {
+ val postRequests = request.post.flatMap(adaptPostRequestForMainCallsite(_, request.callsite))
+ val warnings = postRequests.flatMap(inline(_, undo))
+ if (callRollback && warnings.nonEmpty) {
+ undo.rollback()
+ InlineLog(InlineLogRollback(request, warnings))
}
- argStores.add(new VarInsnNode(ASTORE, nextLocalIndex))
- nextLocalIndex += 1
+ warnings
}
+ }
- // We just use an asm.Type here, no need to create the MethodBType.
- val calleAsmType = asm.Type.getMethodType(callee.desc)
+ def inlinedByPost(insns: List[AbstractInsnNode]): Boolean =
+ insns.nonEmpty && insns.forall(ins => request.post.exists(_.callsite.callsiteInstruction == ins))
- 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
+ canInlineCallsite(request.callsite) match {
+ case None =>
+ doInline(undo)
+
+ case Some((_, illegalAccessInsns)) if inlinedByPost(illegalAccessInsns) =>
+ // speculatively inline, roll back if an illegalAccessInsn cannot be eliminated
+ if (undo == NoUndoLogging) doInline(new UndoLog(), callRollback = true)
+ else doInline(undo)
+
+ case Some((w, _)) =>
+ InlineLog(InlineLogFail(request, w))
+ List(w)
+ }
+ }
+
+ /**
+ * Copy and adapt the instructions of a method to a callsite.
+ *
+ * Preconditions:
+ * - The callsite can safely be inlined (canInlineBody is true)
+ * - The maxLocals and maxStack values of the callsite method are correctly computed
+ *
+ * @return A map associating instruction nodes of the callee with the corresponding cloned
+ * instruction in the callsite method.
+ */
+ def inlineCallsite(callsite: Callsite, undo: UndoLog = NoUndoLogging): Unit = {
+ import callsite.{callsiteClass, callsiteMethod, callsiteInstruction, receiverKnownNotNull, callsiteStackHeight}
+ val Right(callsiteCallee) = callsite.callee
+ import callsiteCallee.{callee, calleeDeclarationClass, sourceFilePath}
+
+ // Inlining requires the callee not to have unreachable code, the analyzer used below should not
+ // return any `null` frames. Note that inlining a method can create unreachable code. Example:
+ // def f = throw e
+ // def g = f; println() // println is unreachable after inlining f
+ // If we have an inline request for a call to g, and f has been already inlined into g, we
+ // need to run DCE on g's body before inlining g.
+ localOpt.minimalRemoveUnreachableCode(callee, calleeDeclarationClass.internalName)
+
+ // If the callsite was eliminated by DCE, do nothing.
+ if (!callGraph.containsCallsite(callsite)) return
+
+ // New labels for the cloned instructions
+ val labelsMap = cloneLabels(callee)
+ val sameSourceFile = sourceFilePath match {
+ case Some(calleeSource) => byteCodeRepository.compilingClasses.get(callsiteClass.internalName) match {
+ case Some((_, `calleeSource`)) => true
+ case _ => false
}
+ case _ => false
+ }
+ val (clonedInstructions, instructionMap, targetHandles) = cloneInstructions(callee, labelsMap, keepLineNumbers = sameSourceFile)
+
+ // 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 iinc: IincInsnNode => iinc.`var` += localVarShift
+ case _ => ()
+ }
- clonedInstructions.insert(argStores)
-
- // label for the exit of the inlined functions. xRETURNs are replaced 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)
+ // 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 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)
+ // We just use an asm.Type here, no need to create the MethodBType.
+ val calleAsmType = asm.Type.getMethodType(callee.desc)
+ val calleeParamTypes = calleAsmType.getArgumentTypes
- for (originalReturn <- callee.instructions.iterator().asScala if isReturn(originalReturn)) {
- val frame = analyzer.frameAt(originalReturn)
- var stackHeight = frame.getStackSize
+ for(argTp <- calleeParamTypes) {
+ 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
+ }
- val inlinedReturn = instructionMap(originalReturn)
- val returnReplacement = new InsnList
+ clonedInstructions.insert(argStores)
+
+ // label for the exit of the inlined functions. xRETURNs are replaced 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)
+ }
- def drop(slot: Int) = returnReplacement add getPop(frame.peekStack(slot).getSize)
+ // We run an interpreter to know the stack height at each xRETURN instruction and the sizes
+ // of the values on the stack.
+ // We don't need to worry about the method being too large for running an analysis. Callsites of
+ // large methods are not added to the call graph.
+ val analyzer = new AsmAnalyzer(callee, calleeDeclarationClass.internalName)
- // for non-void methods, store the stack top into the return local variable
- if (hasReturnValue) {
- returnReplacement add returnValueStore(originalReturn)
- stackHeight -= 1
- }
+ for (originalReturn <- callee.instructions.iterator().asScala if isReturn(originalReturn)) {
+ val frame = analyzer.frameAt(originalReturn)
+ var stackHeight = frame.getStackSize
- // drop the rest of the stack
- for (i <- 0 until stackHeight) drop(i)
+ val inlinedReturn = instructionMap(originalReturn)
+ val returnReplacement = new InsnList
- returnReplacement add new JumpInsnNode(GOTO, postCallLabel)
- clonedInstructions.insert(inlinedReturn, returnReplacement)
- clonedInstructions.remove(inlinedReturn)
- }
+ def drop(slot: Int) = returnReplacement add getPop(frame.peekStack(slot).getSize)
- // Load instruction for the return value
+ // for non-void methods, store the stack top into the return local variable
if (hasReturnValue) {
- val retVarLoad = {
- val opc = returnType.getOpcode(ILOAD)
- new VarInsnNode(opc, returnValueIndex)
- }
- clonedInstructions.insert(postCallLabel, retVarLoad)
+ returnReplacement add returnValueStore(originalReturn)
+ stackHeight -= 1
}
- 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 and closure instantiations 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,
- receiverKnownNotNull = originalCallsite.receiverKnownNotNull,
- callsitePosition = originalCallsite.callsitePosition
- )
-
- case None =>
- }
+ // drop the rest of the stack
+ for (i <- 0 until stackHeight) drop(i)
- case indy: InvokeDynamicInsnNode =>
- callGraph.closureInstantiations.get(indy) match {
- case Some(closureInit) =>
- val newIndy = instructionMap(indy).asInstanceOf[InvokeDynamicInsnNode]
- callGraph.closureInstantiations(newIndy) = ClosureInstantiation(closureInit.lambdaMetaFactoryCall.copy(indy = newIndy), callsiteMethod, callsiteClass)
-
- case None =>
- }
+ returnReplacement add new JumpInsnNode(GOTO, postCallLabel)
+ clonedInstructions.insert(inlinedReturn, returnReplacement)
+ clonedInstructions.remove(inlinedReturn)
+ }
- case _ =>
+ // Load instruction for the return value
+ if (hasReturnValue) {
+ val retVarLoad = {
+ val opc = returnType.getOpcode(ILOAD)
+ new VarInsnNode(opc, returnValueIndex)
}
- // Remove the elided invocation from the call graph
- callGraph.callsites.remove(callsiteInstruction)
+ clonedInstructions.insert(postCallLabel, retVarLoad)
+ }
- // Inlining a method body can render some code unreachable, see example above (in runInliner).
- unreachableCodeEliminated -= callsiteMethod
+ undo.saveMethodState(callsiteMethod)
- callsiteMethod.maxLocals += returnType.getSize + callee.maxLocals
- callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, callee.maxStack + callsiteStackHeight)
+ callsiteMethod.instructions.insert(callsiteInstruction, clonedInstructions)
+ callsiteMethod.instructions.remove(callsiteInstruction)
- None
+ callsiteMethod.localVariables.addAll(cloneLocalVariableNodes(callee, labelsMap, callee.name, localVarShift).asJava)
+ // prepend the handlers of the callee. the order of handlers matters: when an exception is thrown
+ // at some instruction, the first handler guarding that instruction and having a matching exception
+ // type is executed. prepending the callee's handlers makes sure to test those handlers first if
+ // an exception is thrown in the inlined code.
+ callsiteMethod.tryCatchBlocks.addAll(0, cloneTryCatchBlockNodes(callee, labelsMap).asJava)
+
+ callsiteMethod.maxLocals += returnType.getSize + callee.maxLocals
+ val maxStackOfInlinedCode = {
+ // One slot per value is correct for long / double, see comment in the `analysis` package object.
+ val numStoredArgs = calleeParamTypes.length + (if (isStaticMethod(callee)) 0 else 1)
+ callee.maxStack + callsiteStackHeight - numStoredArgs
+ }
+ val stackHeightAtNullCheck = {
+ // When adding a null check for the receiver, a DUP is inserted, which might cause a new maxStack.
+ // If the callsite has other argument values than the receiver on the stack, these are pop'ed
+ // and stored into locals before the null check, so in that case the maxStack doesn't grow.
+ val stackSlotForNullCheck = if (!isStaticMethod(callee) && !receiverKnownNotNull && calleeParamTypes.isEmpty) 1 else 0
+ callsiteStackHeight + stackSlotForNullCheck
}
+
+ callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, math.max(stackHeightAtNullCheck, maxStackOfInlinedCode))
+
+ val added = addIndyLambdaImplMethod(callsiteClass.internalName, targetHandles)
+ undo { removeIndyLambdaImplMethod(callsiteClass.internalName, added) }
+
+ callGraph.addIfMissing(callee, calleeDeclarationClass)
+
+ def mapArgInfo(argInfo: (Int, ArgInfo)): Option[(Int, ArgInfo)] = argInfo match {
+ case lit @ (_, FunctionLiteral) => Some(lit)
+ case (argIndex, ForwardedParam(paramIndex)) => callsite.argInfos.get(paramIndex).map((argIndex, _))
+ }
+
+ // Add all invocation instructions and closure instantiations that were inlined to the call graph
+ callGraph.callsites(callee).valuesIterator foreach { originalCallsite =>
+ val newCallsiteIns = instructionMap(originalCallsite.callsiteInstruction).asInstanceOf[MethodInsnNode]
+ val argInfos = originalCallsite.argInfos flatMap mapArgInfo
+ val newCallsite = originalCallsite.copy(
+ callsiteInstruction = newCallsiteIns,
+ callsiteMethod = callsiteMethod,
+ callsiteClass = callsiteClass,
+ argInfos = argInfos,
+ callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight
+ )
+ val clonedCallsite = ClonedCallsite(newCallsite, callsite)
+ originalCallsite.inlinedClones += clonedCallsite
+ callGraph.addCallsite(newCallsite)
+ undo {
+ originalCallsite.inlinedClones -= clonedCallsite
+ callGraph.removeCallsite(newCallsite.callsiteInstruction, newCallsite.callsiteMethod)
+ }
+ }
+
+ callGraph.closureInstantiations(callee).valuesIterator foreach { originalClosureInit =>
+ val newIndy = instructionMap(originalClosureInit.lambdaMetaFactoryCall.indy).asInstanceOf[InvokeDynamicInsnNode]
+ val capturedArgInfos = originalClosureInit.capturedArgInfos flatMap mapArgInfo
+ val newClosureInit = ClosureInstantiation(
+ originalClosureInit.lambdaMetaFactoryCall.copy(indy = newIndy),
+ callsiteMethod,
+ callsiteClass,
+ capturedArgInfos)
+ originalClosureInit.inlinedClones += newClosureInit
+ callGraph.addClosureInstantiation(newClosureInit)
+ undo {
+ callGraph.removeClosureInstantiation(newClosureInit.lambdaMetaFactoryCall.indy, newClosureInit.ownerMethod)
+ }
+ }
+
+ // Remove the elided invocation from the call graph
+ callGraph.removeCallsite(callsiteInstruction, callsiteMethod)
+ undo { callGraph.addCallsite(callsite) }
+
+ // Inlining a method body can render some code unreachable, see example above in this method.
+ unreachableCodeEliminated -= callsiteMethod
}
/**
- * Check whether an inling can be performed. Parmeters are described in method [[inline]].
+ * Check whether an inlining can be performed. This method performs tests that don't change even
+ * if the body of the callee is changed by the inliner / optimizer, so it can be used early
+ * (when looking at the call graph and collecting inline requests for the program).
+ *
+ * The tests that inspect the callee's instructions are implemented in method `canInlineBody`,
+ * which is queried when performing an 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 earlyCanInlineCheck(callsite: Callsite): Option[CannotInlineWarning] = {
+ import callsite.{callsiteMethod, callsiteClass}
+ val Right(callsiteCallee) = callsite.callee
+ import callsiteCallee.{callee, calleeDeclarationClass}
+
+ 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, callsite.isInlineAnnotated))
+ } else if (isStrictfpMethod(callsiteMethod) != isStrictfpMethod(callee)) {
+ Some(StrictfpMismatch(
+ calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated,
+ callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc))
+ } else
+ None
+ }
+
+ /**
+ * Check whether the body of the callee contains any instructions that prevent the callsite from
+ * being inlined. See also method `earlyCanInlineCheck`.
+ *
+ * The result of this check depends on changes to the callee method's body. For example, if the
+ * callee initially invokes a private method, it cannot be inlined into a different class. If the
+ * private method is inlined into the callee, inlining the callee becomes possible. Therefore
+ * we don't query it while traversing the call graph and selecting callsites to inline - it might
+ * rule out callsites that can be inlined just fine.
+ *
+ * Returns
+ * - `None` if the callsite can be inlined
+ * - `Some((message, Nil))` if there was an issue performing the access checks, for example
+ * because of a missing classfile
+ * - `Some((message, instructions))` if inlining `instructions` into the callsite method would
+ * cause an IllegalAccessError
+ */
+ def canInlineCallsite(callsite: Callsite): Option[(CannotInlineWarning, List[AbstractInsnNode])] = {
+ import callsite.{callsiteInstruction, callsiteMethod, callsiteClass, callsiteStackHeight}
+ val Right(callsiteCallee) = callsite.callee
+ import callsiteCallee.{callee, calleeDeclarationClass}
def calleeDesc = s"${callee.name} of type ${callee.desc} in ${calleeDeclarationClass.internalName}"
def methodMismatch = s"Wrong method node for inlining ${textify(callsiteInstruction)}: $calleeDesc"
@@ -511,31 +649,30 @@ class Inliner[BT <: BTypes](val btypes: BT) {
}
if (codeSizeOKForInlining(callsiteMethod, callee)) {
- Some(ResultingMethodTooLarge(
- calleeDeclarationClass.internalName, callee.name, callee.desc,
- callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc))
- } else 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 (isStrictfpMethod(callsiteMethod) != isStrictfpMethod(callee)) {
- Some(StrictfpMismatch(
- calleeDeclarationClass.internalName, callee.name, callee.desc,
- callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc))
+ val warning = ResultingMethodTooLarge(
+ calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated,
+ callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)
+ Some((warning, Nil))
} 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)
+ val warning = MethodWithHandlerCalledOnNonEmptyStack(
+ calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated,
+ callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)
+ Some((warning, Nil))
+ } else findIllegalAccess(callee.instructions, calleeDeclarationClass, callsiteClass) match {
+ case Right(Nil) =>
+ None
+
+ case Right(illegalAccessInsns) =>
+ val warning = IllegalAccessInstruction(
+ calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated,
+ callsiteClass.internalName, illegalAccessInsns.head)
+ Some((warning, illegalAccessInsns))
+
+ case Left((illegalAccessIns, cause)) =>
+ val warning = IllegalAccessCheckFailed(
+ calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated,
+ callsiteClass.internalName, illegalAccessIns, cause)
+ Some((warning, Nil))
}
}
@@ -545,7 +682,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
* (A2) C and D are members of the same run-time package
*/
def classIsAccessible(accessed: BType, from: ClassBType): 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?
+ // TODO: A2 requires "same run-time package", which seems to be package + classloader (JVMS 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)
@@ -587,7 +724,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
* type from there (https://github.com/scala-opt/scala/issues/13).
*/
def memberIsAccessible(memberFlags: Int, memberDeclClass: ClassBType, memberRefClass: ClassBType, from: ClassBType): Either[OptimizerWarning, Boolean] = {
- // TODO: B3 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok?
+ // TODO: B3 requires "same run-time package", which seems to be package + classloader (JVMS 5.3.). is the below ok?
def samePackageAsDestination = memberDeclClass.packageInternalName == from.packageInternalName
def targetObjectConformsToDestinationClass = false // needs type propagation analysis, see above
@@ -624,13 +761,14 @@ class Inliner[BT <: BTypes](val btypes: BT) {
}
/**
- * 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.
+ * Returns
+ * - `Right(Nil)` if all instructions can be safely inlined
+ * - `Right(insns)` if inlining any of `insns` would cause a [[java.lang.IllegalAccessError]]
+ * when inlined into the `destinationClass`
+ * - `Left((insn, warning))` if validity of some instruction could not be checked because an
+ * error occurred
*/
- def findIllegalAccess(instructions: InsnList, calleeDeclarationClass: ClassBType, destinationClass: ClassBType): Option[(AbstractInsnNode, Option[OptimizerWarning])] = {
+ def findIllegalAccess(instructions: InsnList, calleeDeclarationClass: ClassBType, destinationClass: ClassBType): Either[(AbstractInsnNode, OptimizerWarning), List[AbstractInsnNode]] = {
/**
* Check if `instruction` can be transplanted to `destinationClass`.
*
@@ -759,17 +897,15 @@ class Inliner[BT <: BTypes](val btypes: BT) {
}
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
- }
+ val illegalAccess = mutable.ListBuffer.empty[AbstractInsnNode]
+ while (it.hasNext) {
+ val i = it.next()
+ isLegal(i) match {
+ case Left(warning) => return Left((i, warning)) // checking isLegal for i failed
+ case Right(false) => illegalAccess += i // an illegal instruction was found
+ case _ =>
}
}
- find
+ Right(illegalAccess.toList)
}
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala
new file mode 100644
index 0000000000..63360e17ff
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala
@@ -0,0 +1,339 @@
+/* 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.collection.JavaConverters._
+import scala.tools.asm.Opcodes
+import scala.tools.asm.tree.{AbstractInsnNode, MethodInsnNode, MethodNode}
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.tools.nsc.backend.jvm.BackendReporting.{CalleeNotFinal, OptimizerWarning}
+
+class InlinerHeuristics[BT <: BTypes](val bTypes: BT) {
+ import bTypes._
+ import callGraph._
+
+ final case class InlineRequest(callsite: Callsite, post: List[InlineRequest], reason: String) {
+ // invariant: all post inline requests denote callsites in the callee of the main callsite
+ for (pr <- post) assert(pr.callsite.callsiteMethod == callsite.callee.get.callee, s"Callsite method mismatch: main $callsite - post ${pr.callsite}")
+ }
+
+ def canInlineFromSource(sourceFilePath: Option[String]) = compilerSettings.optInlineGlobal || sourceFilePath.isDefined
+
+ /**
+ * Select callsites from the call graph that should be inlined, grouped by the containing method.
+ * Cyclic inlining requests are allowed, the inliner will eliminate requests to break cycles.
+ */
+ def selectCallsitesForInlining: Map[MethodNode, Set[InlineRequest]] = {
+ // We should only create inlining requests for callsites being compiled (not for callsites in
+ // classes on the classpath). The call graph may contain callsites of classes parsed from the
+ // classpath. In order to get only the callsites being compiled, we start at the map of
+ // compilingClasses in the byteCodeRepository.
+ val compilingMethods = for {
+ (classNode, _) <- byteCodeRepository.compilingClasses.valuesIterator
+ methodNode <- classNode.methods.iterator.asScala
+ } yield methodNode
+
+ compilingMethods.map(methodNode => {
+ var requests = Set.empty[InlineRequest]
+ callGraph.callsites(methodNode).valuesIterator foreach {
+ case callsite @ Callsite(_, _, _, Right(Callee(callee, _, _, _, _, _, _, callsiteWarning)), _, _, _, pos, _, _) =>
+ inlineRequest(callsite, requests) match {
+ case Some(Right(req)) => requests += req
+
+ case Some(Left(w)) =>
+ if (w.emitWarning(compilerSettings)) {
+ backendReporting.inlinerWarning(callsite.callsitePosition, w.toString)
+ }
+
+ case None =>
+ if (callsiteWarning.isDefined && callsiteWarning.get.emitWarning(compilerSettings))
+ backendReporting.inlinerWarning(pos, s"there was a problem determining if method ${callee.name} can be inlined: \n"+ callsiteWarning.get)
+ }
+
+ case Callsite(ins, _, _, Left(warning), _, _, _, pos, _, _) =>
+ if (warning.emitWarning(compilerSettings))
+ backendReporting.inlinerWarning(pos, s"failed to determine if ${ins.name} should be inlined:\n$warning")
+ }
+ (methodNode, requests)
+ }).filterNot(_._2.isEmpty).toMap
+ }
+
+ private def isTraitStaticSuperAccessorName(s: String) = s.endsWith("$")
+ private def traitStaticSuperAccessorName(s: String) = s + "$"
+
+ private def isTraitSuperAccessor(method: MethodNode, owner: ClassBType): Boolean = {
+ owner.isInterface == Right(true) && BytecodeUtils.isStaticMethod(method) && isTraitStaticSuperAccessorName(method.name)
+ }
+
+ private def findSingleCall(method: MethodNode, such: MethodInsnNode => Boolean): Option[MethodInsnNode] = {
+ @tailrec def noMoreInvoke(insn: AbstractInsnNode): Boolean = {
+ insn == null || (!insn.isInstanceOf[MethodInsnNode] && noMoreInvoke(insn.getNext))
+ }
+ @tailrec def find(insn: AbstractInsnNode): Option[MethodInsnNode] = {
+ if (insn == null) None
+ else insn match {
+ case mi: MethodInsnNode =>
+ if (such(mi) && noMoreInvoke(insn.getNext)) Some(mi)
+ else None
+ case _ =>
+ find(insn.getNext)
+ }
+ }
+ find(method.instructions.getFirst)
+ }
+ private def superAccessorInvocation(method: MethodNode): Option[MethodInsnNode] =
+ findSingleCall(method, mi => mi.itf && mi.getOpcode == Opcodes.INVOKESTATIC && isTraitStaticSuperAccessorName(mi.name))
+
+ private def isMixinForwarder(method: MethodNode, owner: ClassBType): Boolean = {
+ owner.isInterface == Right(false) &&
+ !BytecodeUtils.isStaticMethod(method) &&
+ (superAccessorInvocation(method) match {
+ case Some(mi) => mi.name == traitStaticSuperAccessorName(method.name)
+ case _ => false
+ })
+ }
+
+ private def isTraitSuperAccessorOrMixinForwarder(method: MethodNode, owner: ClassBType): Boolean = {
+ isTraitSuperAccessor(method, owner) || isMixinForwarder(method, owner)
+ }
+
+
+ /**
+ * Returns the inline request for a callsite if the callsite should be inlined according to the
+ * current heuristics (`-Yopt-inline-heuristics`).
+ *
+ * The resulting inline request may contain post-inlining requests of callsites that in turn are
+ * also selected as individual inlining requests.
+ *
+ * @return `None` if this callsite should not be inlined according to the active heuristic
+ * `Some(Left)` if the callsite cannot be inlined (for example because that would cause
+ * an IllegalAccessError) but should be according to the heuristic
+ * TODO: what if a downstream inline request would cause an IAE and we don't create an
+ * InlineRequest for the original callsite? new subclass of OptimizerWarning.
+ * `Some(Right)` if the callsite should be and can be inlined
+ */
+ def inlineRequest(callsite: Callsite, selectedRequestsForCallee: Set[InlineRequest]): Option[Either[OptimizerWarning, InlineRequest]] = {
+ def requestIfCanInline(callsite: Callsite, reason: String): Option[Either[OptimizerWarning, InlineRequest]] = {
+ val callee = callsite.callee.get
+ if (!callee.safeToInline) {
+ if (callsite.isInlineAnnotated && callee.canInlineFromSource) {
+ // By default, we only emit inliner warnings for methods annotated @inline. However, we don't
+ // want to be unnecessarily noisy with `-opt-warnings:_`: for example, the inliner heuristic
+ // would attempt to inline `Function1.apply$sp$II`, as it's higher-order (the receiver is
+ // a function), and it's concrete (forwards to `apply`). But because it's non-final, it cannot
+ // be inlined. So we only create warnings here for methods annotated @inline.
+ Some(Left(CalleeNotFinal(
+ callee.calleeDeclarationClass.internalName,
+ callee.callee.name,
+ callee.callee.desc,
+ callsite.isInlineAnnotated)))
+ } else None
+ } else inliner.earlyCanInlineCheck(callsite) match {
+ case Some(w) => Some(Left(w))
+ case None =>
+ val postInlineRequest: List[InlineRequest] = {
+ val postCall =
+ if (isTraitSuperAccessor(callee.callee, callee.calleeDeclarationClass)) {
+ // scala-dev#259: when inlining a trait super accessor, also inline the callsite to the default method
+ val implName = callee.callee.name.dropRight(1)
+ findSingleCall(callee.callee, mi => mi.itf && mi.getOpcode == Opcodes.INVOKESPECIAL && mi.name == implName)
+ } else {
+ // scala-dev#259: when inlining a mixin forwarder, also inline the callsite to the static super accessor
+ superAccessorInvocation(callee.callee)
+ }
+ postCall.flatMap(call => {
+ callGraph.addIfMissing(callee.callee, callee.calleeDeclarationClass)
+ val maybeCallsite = callGraph.findCallSite(callee.callee, call)
+ maybeCallsite.flatMap(requestIfCanInline(_, reason).flatMap(_.right.toOption))
+ }).toList
+ }
+ Some(Right(InlineRequest(callsite, postInlineRequest, reason)))
+ }
+ }
+
+ // scala-dev#259: don't inline into static accessors and mixin forwarders
+ if (isTraitSuperAccessorOrMixinForwarder(callsite.callsiteMethod, callsite.callsiteClass)) None
+ else {
+ val callee = callsite.callee.get
+ compilerSettings.YoptInlineHeuristics.value match {
+ case "everything" =>
+ val reason = if (compilerSettings.YoptLogInline.isSetByUser) "the inline strategy is \"everything\"" else null
+ requestIfCanInline(callsite, reason)
+
+ case "at-inline-annotated" =>
+ def reason = if (!compilerSettings.YoptLogInline.isSetByUser) null else {
+ val what = if (callee.annotatedInline) "callee" else "callsite"
+ s"the $what is annotated `@inline`"
+ }
+ if (callsite.isInlineAnnotated && !callsite.isNoInlineAnnotated) requestIfCanInline(callsite, reason)
+ else None
+
+ case "default" =>
+ def reason = if (!compilerSettings.YoptLogInline.isSetByUser) null else {
+ if (callsite.isInlineAnnotated) {
+ val what = if (callee.annotatedInline) "callee" else "callsite"
+ s"the $what is annotated `@inline`"
+ } else {
+ val paramNames = Option(callee.callee.parameters).map(_.asScala.map(_.name).toVector)
+ def param(i: Int) = {
+ def syn = s"<param $i>"
+ paramNames.fold(syn)(v => v.applyOrElse(i, (_: Int) => syn))
+ }
+ def samInfo(i: Int, sam: String, arg: String) = s"the argument for parameter (${param(i)}: $sam) is a $arg"
+ val argInfos = for ((i, sam) <- callee.samParamTypes; info <- callsite.argInfos.get(i)) yield {
+ val argKind = info match {
+ case FunctionLiteral => "function literal"
+ case ForwardedParam(_) => "parameter of the callsite method"
+ }
+ samInfo(i, sam.internalName.split('/').last, argKind)
+ }
+ s"the callee is a higher-order method, ${argInfos.mkString(", ")}"
+ }
+ }
+ def shouldInlineHO = callee.samParamTypes.nonEmpty && (callee.samParamTypes exists {
+ case (index, _) => callsite.argInfos.contains(index)
+ })
+ if (!callsite.isNoInlineAnnotated && (callsite.isInlineAnnotated || shouldInlineHO)) requestIfCanInline(callsite, reason)
+ else None
+ }
+ }
+ }
+
+ /*
+ // using http://lihaoyi.github.io/Ammonite/
+
+ load.ivy("com.google.guava" % "guava" % "18.0")
+ val javaUtilFunctionClasses = {
+ val rt = System.getProperty("sun.boot.class.path").split(":").find(_.endsWith("lib/rt.jar")).get
+ val u = new java.io.File(rt).toURL
+ val l = new java.net.URLClassLoader(Array(u))
+ val cp = com.google.common.reflect.ClassPath.from(l)
+ cp.getTopLevelClasses("java.util.function").toArray.map(_.toString).toList
+ }
+
+ // found using IntelliJ's "Find Usages" on the @FunctionalInterface annotation
+ val otherClasses = List(
+ "com.sun.javafx.css.parser.Recognizer",
+ "java.awt.KeyEventDispatcher",
+ "java.awt.KeyEventPostProcessor",
+ "java.io.FileFilter",
+ "java.io.FilenameFilter",
+ "java.lang.Runnable",
+ "java.lang.Thread$UncaughtExceptionHandler",
+ "java.nio.file.DirectoryStream$Filter",
+ "java.nio.file.PathMatcher",
+ "java.time.temporal.TemporalAdjuster",
+ "java.time.temporal.TemporalQuery",
+ "java.util.Comparator",
+ "java.util.concurrent.Callable",
+ "java.util.logging.Filter",
+ "java.util.prefs.PreferenceChangeListener",
+ "javafx.animation.Interpolatable",
+ "javafx.beans.InvalidationListener",
+ "javafx.beans.value.ChangeListener",
+ "javafx.collections.ListChangeListener",
+ "javafx.collections.MapChangeListener",
+ "javafx.collections.SetChangeListener",
+ "javafx.event.EventHandler",
+ "javafx.util.Builder",
+ "javafx.util.BuilderFactory",
+ "javafx.util.Callback"
+ )
+
+ val allClasses = javaUtilFunctionClasses ::: otherClasses
+
+ load.ivy("org.ow2.asm" % "asm" % "5.0.4")
+ val classesAndSamNameDesc = allClasses.map(c => {
+ val cls = Class.forName(c)
+ val internalName = org.objectweb.asm.Type.getDescriptor(cls).drop(1).dropRight(1) // drop L and ;
+ val sams = cls.getMethods.filter(m => {
+ (m.getModifiers & java.lang.reflect.Modifier.ABSTRACT) != 0 &&
+ m.getName != "equals" // Comparator has an abstract override of "equals" for adding Javadoc
+ })
+ assert(sams.size == 1, internalName + sams.map(_.getName))
+ val sam = sams.head
+ val samDesc = org.objectweb.asm.Type.getMethodDescriptor(sam)
+ (internalName, sam.getName, samDesc)
+ })
+ println(classesAndSamNameDesc map {
+ case (cls, nme, desc) => s"""("$cls", "$nme$desc")"""
+ } mkString ("", ",\n", "\n"))
+ */
+ private val javaSams: Map[String, String] = Map(
+ ("java/util/function/BiConsumer", "accept(Ljava/lang/Object;Ljava/lang/Object;)V"),
+ ("java/util/function/BiFunction", "apply(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"),
+ ("java/util/function/BiPredicate", "test(Ljava/lang/Object;Ljava/lang/Object;)Z"),
+ ("java/util/function/BinaryOperator", "apply(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"),
+ ("java/util/function/BooleanSupplier", "getAsBoolean()Z"),
+ ("java/util/function/Consumer", "accept(Ljava/lang/Object;)V"),
+ ("java/util/function/DoubleBinaryOperator", "applyAsDouble(DD)D"),
+ ("java/util/function/DoubleConsumer", "accept(D)V"),
+ ("java/util/function/DoubleFunction", "apply(D)Ljava/lang/Object;"),
+ ("java/util/function/DoublePredicate", "test(D)Z"),
+ ("java/util/function/DoubleSupplier", "getAsDouble()D"),
+ ("java/util/function/DoubleToIntFunction", "applyAsInt(D)I"),
+ ("java/util/function/DoubleToLongFunction", "applyAsLong(D)J"),
+ ("java/util/function/DoubleUnaryOperator", "applyAsDouble(D)D"),
+ ("java/util/function/Function", "apply(Ljava/lang/Object;)Ljava/lang/Object;"),
+ ("java/util/function/IntBinaryOperator", "applyAsInt(II)I"),
+ ("java/util/function/IntConsumer", "accept(I)V"),
+ ("java/util/function/IntFunction", "apply(I)Ljava/lang/Object;"),
+ ("java/util/function/IntPredicate", "test(I)Z"),
+ ("java/util/function/IntSupplier", "getAsInt()I"),
+ ("java/util/function/IntToDoubleFunction", "applyAsDouble(I)D"),
+ ("java/util/function/IntToLongFunction", "applyAsLong(I)J"),
+ ("java/util/function/IntUnaryOperator", "applyAsInt(I)I"),
+ ("java/util/function/LongBinaryOperator", "applyAsLong(JJ)J"),
+ ("java/util/function/LongConsumer", "accept(J)V"),
+ ("java/util/function/LongFunction", "apply(J)Ljava/lang/Object;"),
+ ("java/util/function/LongPredicate", "test(J)Z"),
+ ("java/util/function/LongSupplier", "getAsLong()J"),
+ ("java/util/function/LongToDoubleFunction", "applyAsDouble(J)D"),
+ ("java/util/function/LongToIntFunction", "applyAsInt(J)I"),
+ ("java/util/function/LongUnaryOperator", "applyAsLong(J)J"),
+ ("java/util/function/ObjDoubleConsumer", "accept(Ljava/lang/Object;D)V"),
+ ("java/util/function/ObjIntConsumer", "accept(Ljava/lang/Object;I)V"),
+ ("java/util/function/ObjLongConsumer", "accept(Ljava/lang/Object;J)V"),
+ ("java/util/function/Predicate", "test(Ljava/lang/Object;)Z"),
+ ("java/util/function/Supplier", "get()Ljava/lang/Object;"),
+ ("java/util/function/ToDoubleBiFunction", "applyAsDouble(Ljava/lang/Object;Ljava/lang/Object;)D"),
+ ("java/util/function/ToDoubleFunction", "applyAsDouble(Ljava/lang/Object;)D"),
+ ("java/util/function/ToIntBiFunction", "applyAsInt(Ljava/lang/Object;Ljava/lang/Object;)I"),
+ ("java/util/function/ToIntFunction", "applyAsInt(Ljava/lang/Object;)I"),
+ ("java/util/function/ToLongBiFunction", "applyAsLong(Ljava/lang/Object;Ljava/lang/Object;)J"),
+ ("java/util/function/ToLongFunction", "applyAsLong(Ljava/lang/Object;)J"),
+ ("java/util/function/UnaryOperator", "apply(Ljava/lang/Object;)Ljava/lang/Object;"),
+ ("com/sun/javafx/css/parser/Recognizer", "recognize(I)Z"),
+ ("java/awt/KeyEventDispatcher", "dispatchKeyEvent(Ljava/awt/event/KeyEvent;)Z"),
+ ("java/awt/KeyEventPostProcessor", "postProcessKeyEvent(Ljava/awt/event/KeyEvent;)Z"),
+ ("java/io/FileFilter", "accept(Ljava/io/File;)Z"),
+ ("java/io/FilenameFilter", "accept(Ljava/io/File;Ljava/lang/String;)Z"),
+ ("java/lang/Runnable", "run()V"),
+ ("java/lang/Thread$UncaughtExceptionHandler", "uncaughtException(Ljava/lang/Thread;Ljava/lang/Throwable;)V"),
+ ("java/nio/file/DirectoryStream$Filter", "accept(Ljava/lang/Object;)Z"),
+ ("java/nio/file/PathMatcher", "matches(Ljava/nio/file/Path;)Z"),
+ ("java/time/temporal/TemporalAdjuster", "adjustInto(Ljava/time/temporal/Temporal;)Ljava/time/temporal/Temporal;"),
+ ("java/time/temporal/TemporalQuery", "queryFrom(Ljava/time/temporal/TemporalAccessor;)Ljava/lang/Object;"),
+ ("java/util/Comparator", "compare(Ljava/lang/Object;Ljava/lang/Object;)I"),
+ ("java/util/concurrent/Callable", "call()Ljava/lang/Object;"),
+ ("java/util/logging/Filter", "isLoggable(Ljava/util/logging/LogRecord;)Z"),
+ ("java/util/prefs/PreferenceChangeListener", "preferenceChange(Ljava/util/prefs/PreferenceChangeEvent;)V"),
+ ("javafx/animation/Interpolatable", "interpolate(Ljava/lang/Object;D)Ljava/lang/Object;"),
+ ("javafx/beans/InvalidationListener", "invalidated(Ljavafx/beans/Observable;)V"),
+ ("javafx/beans/value/ChangeListener", "changed(Ljavafx/beans/value/ObservableValue;Ljava/lang/Object;Ljava/lang/Object;)V"),
+ ("javafx/collections/ListChangeListener", "onChanged(Ljavafx/collections/ListChangeListener$Change;)V"),
+ ("javafx/collections/MapChangeListener", "onChanged(Ljavafx/collections/MapChangeListener$Change;)V"),
+ ("javafx/collections/SetChangeListener", "onChanged(Ljavafx/collections/SetChangeListener$Change;)V"),
+ ("javafx/event/EventHandler", "handle(Ljavafx/event/Event;)V"),
+ ("javafx/util/Builder", "build()Ljava/lang/Object;"),
+ ("javafx/util/BuilderFactory", "getBuilder(Ljava/lang/Class;)Ljavafx/util/Builder;"),
+ ("javafx/util/Callback", "call(Ljava/lang/Object;)Ljava/lang/Object;")
+ )
+ def javaSam(internalName: InternalName): Option[String] = javaSams.get(internalName)
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala
deleted file mode 100644
index 8d744f6d13..0000000000
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala
+++ /dev/null
@@ -1,240 +0,0 @@
-package scala.tools.nsc.backend.jvm.opt
-
-import scala.annotation.switch
-import scala.tools.asm.{Handle, Type, Opcodes}
-import scala.tools.asm.tree._
-
-object InstructionResultSize {
- import Opcodes._
- def apply(instruction: AbstractInsnNode): Int = (instruction.getOpcode: @switch) match {
- // The order of opcodes is (almost) the same as in Opcodes.java
- case ACONST_NULL => 1
-
- case ICONST_M1 |
- ICONST_0 |
- ICONST_1 |
- ICONST_2 |
- ICONST_3 |
- ICONST_4 |
- ICONST_5 => 1
-
- case LCONST_0 |
- LCONST_1 => 2
-
- case FCONST_0 |
- FCONST_1 |
- FCONST_2 => 1
-
- case DCONST_0 |
- DCONST_1 => 2
-
- case BIPUSH |
- SIPUSH => 1
-
- case LDC =>
- instruction.asInstanceOf[LdcInsnNode].cst match {
- case _: java.lang.Integer |
- _: java.lang.Float |
- _: String |
- _: Type |
- _: Handle => 1
-
- case _: java.lang.Long |
- _: java.lang.Double => 2
- }
-
- case ILOAD |
- FLOAD |
- ALOAD => 1
-
- case LLOAD |
- DLOAD => 2
-
- case IALOAD |
- FALOAD |
- AALOAD |
- BALOAD |
- CALOAD |
- SALOAD => 1
-
- case LALOAD |
- DALOAD => 2
-
- case ISTORE |
- LSTORE |
- FSTORE |
- DSTORE |
- ASTORE => 0
-
- case IASTORE |
- LASTORE |
- FASTORE |
- DASTORE |
- AASTORE |
- BASTORE |
- CASTORE |
- SASTORE => 0
-
- case POP |
- POP2 => 0
-
- case DUP |
- DUP_X1 |
- DUP_X2 |
- DUP2 |
- DUP2_X1 |
- DUP2_X2 |
- SWAP => throw new IllegalArgumentException("Can't compute the size of DUP/SWAP without knowing what's on stack top")
-
- case IADD |
- FADD => 1
-
- case LADD |
- DADD => 2
-
- case ISUB |
- FSUB => 1
-
- case LSUB |
- DSUB => 2
-
- case IMUL |
- FMUL => 1
-
- case LMUL |
- DMUL => 2
-
- case IDIV |
- FDIV => 1
-
- case LDIV |
- DDIV => 2
-
- case IREM |
- FREM => 1
-
- case LREM |
- DREM => 2
-
- case INEG |
- FNEG => 1
-
- case LNEG |
- DNEG => 2
-
- case ISHL |
- ISHR => 1
-
- case LSHL |
- LSHR => 2
-
- case IUSHR => 1
-
- case LUSHR => 2
-
- case IAND |
- IOR |
- IXOR => 1
-
- case LAND |
- LOR |
- LXOR => 2
-
- case IINC => 1
-
- case I2F |
- L2I |
- L2F |
- F2I |
- D2I |
- D2F |
- I2B |
- I2C |
- I2S => 1
-
- case I2L |
- I2D |
- L2D |
- F2L |
- F2D |
- D2L => 2
-
- case LCMP |
- FCMPL |
- FCMPG |
- DCMPL |
- DCMPG => 1
-
- case IFEQ |
- IFNE |
- IFLT |
- IFGE |
- IFGT |
- IFLE => 0
-
- case IF_ICMPEQ |
- IF_ICMPNE |
- IF_ICMPLT |
- IF_ICMPGE |
- IF_ICMPGT |
- IF_ICMPLE |
- IF_ACMPEQ |
- IF_ACMPNE => 0
-
- case GOTO => 0
-
- case JSR => throw new IllegalArgumentException("Subroutines are not supported.")
-
- case RET => 0
-
- case TABLESWITCH |
- LOOKUPSWITCH => 0
-
- case IRETURN |
- FRETURN |
- ARETURN => 1
-
- case LRETURN |
- DRETURN => 2
-
- case RETURN => 0
-
- case GETSTATIC => Type.getType(instruction.asInstanceOf[FieldInsnNode].desc).getSize
-
- case PUTSTATIC => 0
-
- case GETFIELD => Type.getType(instruction.asInstanceOf[FieldInsnNode].desc).getSize
-
- case PUTFIELD => 0
-
- case INVOKEVIRTUAL |
- INVOKESPECIAL |
- INVOKESTATIC |
- INVOKEINTERFACE =>
- val desc = instruction.asInstanceOf[MethodInsnNode].desc
- Type.getReturnType(desc).getSize
-
- case INVOKEDYNAMIC =>
- val desc = instruction.asInstanceOf[InvokeDynamicInsnNode].desc
- Type.getReturnType(desc).getSize
-
- case NEW => 1
-
- case NEWARRAY |
- ANEWARRAY |
- ARRAYLENGTH => 1
-
- case ATHROW => 0
-
- case CHECKCAST |
- INSTANCEOF => 1
-
- case MONITORENTER |
- MONITOREXIT => 0
-
- case MULTIANEWARRAY => 1
-
- case IFNULL |
- IFNONNULL => 0
- }
-}
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 4132710a96..9c22b09cdd 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
@@ -7,79 +7,180 @@ package scala.tools.nsc
package backend.jvm
package opt
-import scala.annotation.switch
-import scala.tools.asm.Opcodes
-import scala.tools.asm.tree.analysis.{Analyzer, BasicInterpreter}
+import scala.annotation.{tailrec, switch}
+
+import scala.tools.asm.Type
+import scala.tools.asm.tree.analysis.Frame
+import scala.tools.asm.Opcodes._
import scala.tools.asm.tree._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.mutable
+import scala.collection.JavaConverters._
import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.tools.nsc.backend.jvm.analysis._
import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
/**
- * Optimizations within a single method.
+ * Optimizations within a single method. Certain optimizations enable others, for example removing
+ * unreachable code can render a `try` block empty and enable removeEmptyExceptionHandlers. The
+ * latter in turn enables more unreachable code to be eliminated (the `catch` block), so there is
+ * a cyclic dependency. Optimizations that depend on each other are therefore executed in a loop
+ * until reaching a fixpoint.
+ *
+ * The optimizations marked UPSTREAM enable optimizations that were already executed, so they cause
+ * another iteration in the fixpoint loop.
+ *
+ * nullness optimizations: rewrite null-checking branches to GOTO if nullness is known
+ * + enables downstream
+ * - unreachable code (null / non-null branch becomes unreachable)
+ * - box-unbox elimination (may render an escaping consumer of a box unreachable)
+ * - stale stores (aload x is replaced by aconst_null if it's known null)
+ * - simplify jumps (replaces conditional jumps by goto, so may enable goto chains)
+ *
+ * unreachable code / DCE (removes instructions of basic blocks to which there is no branch)
+ * + enables downstream:
+ * - stale stores (loads may be eliminated, removing consumers of a store)
+ * - empty handlers (try blocks may become empty)
+ * - simplify jumps (goto l; [dead code]; l: ..) => remove goto
+ * - stale local variable descriptors
+ * - (not box-unbox, which is implemented using prod-cons, so it doesn't consider dead code)
+ *
+ * note that eliminating empty handlers and stale local variable descriptors is required for
+ * correctness, see the comment in the body of `methodOptimizations`.
+ *
+ * box-unbox elimination (eliminates box-unbox pairs within the same method)
+ * + enables UPSTREAM:
+ * - nullness optimizations (a box extraction operation (unknown nullness) may be rewritten to
+ * a read of a non-null local. example in doc comment of box-unbox implementation)
+ * - further box-unbox elimination (e.g. an Integer stored in a Tuple; eliminating the tuple may
+ * enable eliminating the Integer)
+ * + enables downstream:
+ * - copy propagation (new locals are introduced, may be aliases of existing)
+ * - stale stores (multi-value boxes where not all values are used)
+ * - redundant casts (`("a", "b")._1`: the generic `_1` method returns `Object`, a cast
+ * to String is added. The cast is redundant after eliminating the tuple.)
+ * - empty local variable descriptors (local variables that were holding the box may become unused)
+ *
+ * copy propagation (replaces LOAD n to the LOAD m for the smallest m that is an alias of n)
+ * + enables downstream:
+ * - stale stores (a stored value may not be loaded anymore)
+ * - store-load pairs (a load n may now be right after a store n)
+ * + NOTE: copy propagation is only executed once, in the first fixpoint loop iteration. none of
+ * the other optimizations enables further copy prop. we still run it as part of the loop
+ * because it requires unreachable code to be eliminated.
+ *
+ * stale stores (replace STORE by POP)
+ * + enables downstream:
+ * - push-pop (the new pop may be the single consumer for an instruction)
+ *
+ * redundant casts: eliminates casts that are statically known to succeed (uses type propagation)
+ * + enables UPSTREAM:
+ * - box-unbox elimination (a removed checkcast may be a box consumer)
+ * + enables downstream:
+ * - push-pop for closure allocation elimination (every indyLambda is followed by a checkcast, see SI-9540)
+ *
+ * push-pop (when a POP is the only consumer of a value, remove the POP and its producer)
+ * + enables UPSTREAM:
+ * - stale stores (if a LOAD is removed, a corresponding STORE may become stale)
+ * - box-unbox elimination (push-pop may eliminate a closure allocation, rendering a captured
+ * box non-escaping)
+ * + enables downstream:
+ * - store-load pairs (a variable may become non-live)
+ * - stale handlers (push-pop removes code)
+ * - simplify jumps (push-pop removes code)
+ *
+ * store-load pairs (remove `STORE x; LOAD x` if x is otherwise not used in the method)
+ * + enables downstream:
+ * - empty handlers (code is removes, a try block may become empty
+ * - simplify jumps (code is removed, a goto may become redundant for example)
+ * - stale local variable descriptors
*
- * unreachable code
- * - removes instructions of basic blocks to which no branch instruction points
- * + enables eliminating some exception handlers and local variable descriptors
- * > eliminating them is required for correctness, as explained in `removeUnreachableCode`
+ * empty handlers (removes exception handlers whose try block is empty)
+ * + enables UPSTREAM:
+ * - unreachable code (catch block becomes unreachable)
+ * - box-unbox (a box may be escape in an operation in a dead handler)
+ * + enables downstream:
+ * - simplify jumps
*
- * empty exception handlers
- * - removes exception handlers whose try block is empty
- * + eliminating a handler where the try block is empty and reachable will turn the catch block
- * unreachable. in this case "unreachable code" is invoked recursively until reaching a fixpoint.
- * > for try blocks that are unreachable, "unreachable code" removes also the instructions of the
- * catch block, and the recursive invocation is not necessary.
+ * simplify jumps (various, like `GOTO l; l: ...`, see doc comments of individual optimizations)
+ * + enables UPSTREAM
+ * - unreachable code (`GOTO a; a: GOTO b; b: ...`, the first jump is changed to `GOTO b`, the second becomes unreachable)
+ * - store-load pairs (a `GOTO l; l: ...` is removed between store and load)
+ * - push-pop (`IFNULL l; l: ...` is replaced by `POP`)
*
- * simplify jumps
- * - various simplifications, see doc comments of individual optimizations
- * + changing or eliminating jumps may render some code unreachable, therefore "simplify jumps" is
- * executed in a loop with "unreachable code"
*
- * empty local variable descriptors
- * - removes entries from the local variable table where the variable is not actually used
- * + enables eliminating labels that the entry points to (if they are not otherwise referenced)
+ * The following cleanup optimizations don't enable any upstream optimizations, so they can be
+ * executed once at the end, when the above optimizations reach a fixpoint.
*
- * empty line numbers
- * - eliminates line number nodes that describe no executable instructions
- * + enables eliminating the label of the line number node (if it's not otherwise referenced)
*
- * stale labels
- * - eliminate labels that are not referenced, merge sequences of label definitions.
+ * empty local variable descriptors (removes unused variables from the local variable table)
+ * + enables downstream:
+ * - stale labels (labels that the entry points to, if not otherwise referenced)
+ *
+ * empty line numbers (eliminates line number nodes that describe no executable instructions)
+ * + enables downstream:
+ * - stale labels (label of the line number node, if not otherwise referenced)
+ *
+ * stale labels (eliminate labels that are not referenced, merge sequences of label definitions)
+ *
+ *
+ * Note on a method's maxLocals / maxStack: the backend only uses those values for running
+ * Analyzers. The values can be conservative approximations: if an optimization removes code and
+ * the maximal stack size is now smaller, the larger maxStack value will still work fine for
+ * running an Analyzer (just that frames allocate more space than required). The correct max
+ * values written to the bytecode are re-computed during classfile serialization.
+ * To keep things simpler, we don't update the max values in every optimization:
+ * - we do it in `removeUnreachableCodeImpl`, because it's quite straightforward
+ * - maxLocals is updated in `compactLocalVariables`, which runs at the end of method optimizations
+ *
+ *
+ * Note on updating the call graph: whenever an optimization eliminates a callsite or a closure
+ * instantiation, we eliminate the corresponding entry from the call graph.
*/
class LocalOpt[BT <: BTypes](val btypes: BT) {
import LocalOptImpls._
import btypes._
+ import coreBTypes._
+ import backendUtils._
+
+ val boxUnbox = new BoxUnbox(btypes)
+ import boxUnbox._
+
+ val copyProp = new CopyProp(btypes)
+ import copyProp._
/**
* 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.
+ * depends on this property.
*
* @return A set containing the eliminated instructions
*/
- def minimalRemoveUnreachableCode(method: MethodNode, ownerClassName: InternalName): Set[AbstractInsnNode] = {
- if (method.instructions.size == 0) return Set.empty // fast path for abstract methods
- if (unreachableCodeEliminated(method)) return Set.empty // we know there is no unreachable code
+ def minimalRemoveUnreachableCode(method: MethodNode, ownerClassName: InternalName): Boolean = {
+ // In principle, for the inliner, a single removeUnreachableCodeImpl would be enough. But that
+ // would potentially leave behind stale handlers (empty try block) which is not legal in the
+ // classfile. So we run both removeUnreachableCodeImpl and removeEmptyExceptionHandlers.
+ if (method.instructions.size == 0) return false // fast path for abstract methods
+ if (unreachableCodeEliminated(method)) return false // we know there is no unreachable code
+ if (!AsmAnalyzer.sizeOKForBasicValue(method)) return false // the method is too large for running an analyzer
// 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(): Set[AbstractInsnNode] = {
- val (removedInstructions, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName)
- val removedRecursively = if (removedInstructions.nonEmpty) {
+ def removalRound(): Boolean = {
+ val (insnsRemoved, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName)
+ if (insnsRemoved) {
val liveHandlerRemoved = removeEmptyExceptionHandlers(method).exists(h => liveLabels(h.start))
if (liveHandlerRemoved) removalRound()
- else Set.empty
- } else Set.empty
- removedInstructions ++ removedRecursively
+ }
+ insnsRemoved
}
- val removedInstructions = removalRound()
- if (removedInstructions.nonEmpty) removeUnusedLocalVariableNodes(method)()
+ val changed = removalRound()
+ if (changed) removeUnusedLocalVariableNodes(method)()
unreachableCodeEliminated += method
- removedInstructions
+ changed
}
/**
@@ -90,21 +191,13 @@ class LocalOpt[BT <: BTypes](val btypes: BT) {
* @return `true` if unreachable code was eliminated in some method, `false` otherwise.
*/
def methodOptimizations(clazz: ClassNode): Boolean = {
- !compilerSettings.YoptNone && clazz.methods.asScala.foldLeft(false) {
+ !compilerSettings.optNone && clazz.methods.asScala.foldLeft(false) {
case (changed, method) => methodOptimizations(method, clazz.name) || changed
}
}
/**
- * Remove unreachable code from a method.
- *
- * We rely on dead code elimination provided by the ASM framework, as described in the ASM User
- * Guide (http://asm.ow2.org/index.html), Section 8.2.1. It runs a data flow analysis, which only
- * computes Frame information for reachable instructions. Instructions for which no Frame data is
- * available after the analysis are unreachable.
- *
- * Also simplifies branching instructions, removes unused local variable descriptors, empty
- * exception handlers, unnecessary label declarations and empty line number nodes.
+ * Run method-level optimizations, see comment on class [[LocalOpt]].
*
* Returns `true` if the bytecode of `method` was changed.
*/
@@ -137,36 +230,151 @@ class LocalOpt[BT <: BTypes](val btypes: BT) {
// 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.
- 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 (compilerSettings.YoptUnreachableCode) {
- val (removedInstructions, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName)
- val removedHandlers = removeEmptyExceptionHandlers(method)
- (removedInstructions.nonEmpty, removedHandlers.nonEmpty, removedHandlers.exists(h => liveLabels(h.start)))
- } else {
- (false, false, false)
+ var currentTrace: String = null
+ val methodPrefix = {val p = compilerSettings.YoptTrace.value; if (p == "_") "" else p }
+ val doTrace = compilerSettings.YoptTrace.isSetByUser && s"$ownerClassName.${method.name}".startsWith(methodPrefix)
+ def traceIfChanged(optName: String): Unit = if (doTrace) {
+ val after = AsmUtils.textify(method)
+ if (currentTrace != after) {
+ println(s"after $optName")
+ println(after)
}
-
- val jumpsChanged = if (compilerSettings.YoptSimplifyJumps) simplifyJumps(method) else false
-
- // Eliminating live handlers and simplifying jump instructions may render more code
- // unreachable, so we need to run another round.
- if (liveHandlerRemoved || jumpsChanged) removalRound()
-
- codeRemoved || handlersRemoved || jumpsChanged
+ currentTrace = after
}
- val codeHandlersOrJumpsChanged = removalRound()
+ /**
+ * Runs the optimizations that depend on each other in a loop until reaching a fixpoint. See
+ * comment in class [[LocalOpt]].
+ *
+ * Returns a pair of booleans (codeChanged, requireEliminateUnusedLocals).
+ */
+ def removalRound(
+ requestNullness: Boolean,
+ requestDCE: Boolean,
+ requestBoxUnbox: Boolean,
+ requestStaleStores: Boolean,
+ requestPushPop: Boolean,
+ requestStoreLoad: Boolean,
+ firstIteration: Boolean,
+ maxRecursion: Int = 10): (Boolean, Boolean) = {
+ if (maxRecursion == 0) return (false, false)
+
+ traceIfChanged("beforeMethodOpt")
+
+ // NULLNESS OPTIMIZATIONS
+ val runNullness = compilerSettings.optNullnessTracking && requestNullness
+ val nullnessOptChanged = runNullness && nullnessOptimizations(method, ownerClassName)
+ traceIfChanged("nullness")
+
+ // UNREACHABLE CODE
+ // Both AliasingAnalyzer (used in copyProp) and ProdConsAnalyzer (used in eliminateStaleStores,
+ // boxUnboxElimination) require not having unreachable instructions (null frames).
+ val runDCE = (compilerSettings.optUnreachableCode && (requestDCE || nullnessOptChanged)) ||
+ compilerSettings.optBoxUnbox ||
+ compilerSettings.optCopyPropagation
+ val (codeRemoved, liveLabels) = if (runDCE) removeUnreachableCodeImpl(method, ownerClassName) else (false, Set.empty[LabelNode])
+ traceIfChanged("dce")
+
+ // BOX-UNBOX
+ val runBoxUnbox = compilerSettings.optBoxUnbox && (requestBoxUnbox || nullnessOptChanged)
+ val boxUnboxChanged = runBoxUnbox && boxUnboxElimination(method, ownerClassName)
+ traceIfChanged("boxUnbox")
+
+ // COPY PROPAGATION
+ val runCopyProp = compilerSettings.optCopyPropagation && (firstIteration || boxUnboxChanged)
+ val copyPropChanged = runCopyProp && copyPropagation(method, ownerClassName)
+ traceIfChanged("copyProp")
+
+ // STALE STORES
+ val runStaleStores = compilerSettings.optCopyPropagation && (requestStaleStores || nullnessOptChanged || codeRemoved || boxUnboxChanged || copyPropChanged)
+ val storesRemoved = runStaleStores && eliminateStaleStores(method, ownerClassName)
+ traceIfChanged("staleStores")
+
+ // REDUNDANT CASTS
+ val runRedundantCasts = compilerSettings.optRedundantCasts && (firstIteration || boxUnboxChanged)
+ val castRemoved = runRedundantCasts && eliminateRedundantCasts(method, ownerClassName)
+ traceIfChanged("redundantCasts")
+
+ // PUSH-POP
+ val runPushPop = compilerSettings.optCopyPropagation && (requestPushPop || firstIteration || storesRemoved || castRemoved)
+ val pushPopRemoved = runPushPop && eliminatePushPop(method, ownerClassName)
+ traceIfChanged("pushPop")
+
+ // STORE-LOAD PAIRS
+ val runStoreLoad = compilerSettings.optCopyPropagation && (requestStoreLoad || boxUnboxChanged || copyPropChanged || pushPopRemoved)
+ val storeLoadRemoved = runStoreLoad && eliminateStoreLoad(method)
+ traceIfChanged("storeLoadPairs")
+
+ // STALE HANDLERS
+ val removedHandlers = if (runDCE) removeEmptyExceptionHandlers(method) else Set.empty[TryCatchBlockNode]
+ val handlersRemoved = removedHandlers.nonEmpty
+ val liveHandlerRemoved = removedHandlers.exists(h => liveLabels(h.start))
+ traceIfChanged("staleHandlers")
+
+ // SIMPLIFY JUMPS
+ // almost all of the above optimizations enable simplifying more jumps, so we just run it in every iteration
+ val runSimplifyJumps = compilerSettings.optSimplifyJumps
+ val jumpsChanged = runSimplifyJumps && simplifyJumps(method)
+ traceIfChanged("simplifyJumps")
+
+ // See doc comment in the beginning of this file (optimizations marked UPSTREAM)
+ val runNullnessAgain = boxUnboxChanged
+ val runDCEAgain = liveHandlerRemoved || jumpsChanged
+ val runBoxUnboxAgain = boxUnboxChanged || castRemoved || pushPopRemoved || liveHandlerRemoved
+ val runStaleStoresAgain = pushPopRemoved
+ val runPushPopAgain = jumpsChanged
+ val runStoreLoadAgain = jumpsChanged
+ val runAgain = runNullnessAgain || runDCEAgain || runBoxUnboxAgain || pushPopRemoved || runStaleStoresAgain || runPushPopAgain || runStoreLoadAgain
+
+ val downstreamRequireEliminateUnusedLocals = runAgain && removalRound(
+ requestNullness = runNullnessAgain,
+ requestDCE = runDCEAgain,
+ requestBoxUnbox = runBoxUnboxAgain,
+ requestStaleStores = runStaleStoresAgain,
+ requestPushPop = runPushPopAgain,
+ requestStoreLoad = runStoreLoadAgain,
+ firstIteration = false,
+ maxRecursion = maxRecursion - 1)._2
+
+ val requireEliminateUnusedLocals = downstreamRequireEliminateUnusedLocals ||
+ nullnessOptChanged || // nullness opt may eliminate stores / loads, rendering a local unused
+ codeRemoved || // see comment in method `methodOptimizations`
+ boxUnboxChanged || // box-unbox renders locals (holding boxes) unused
+ storesRemoved ||
+ storeLoadRemoved ||
+ handlersRemoved
+
+ val codeChanged = nullnessOptChanged || codeRemoved || boxUnboxChanged || castRemoved || copyPropChanged || storesRemoved || pushPopRemoved || storeLoadRemoved || handlersRemoved || jumpsChanged
+ (codeChanged, requireEliminateUnusedLocals)
+ }
- // (*) Removing stale local variable descriptors is required for correctness of unreachable-code
+ val (nullnessDceBoxesCastsCopypropPushpopOrJumpsChanged, requireEliminateUnusedLocals) = if (AsmAnalyzer.sizeOKForBasicValue(method)) {
+ // we run DCE even if the method is already in the `unreachableCodeEliminated` map: the DCE
+ // here is more thorough than `minimalRemoveUnreachableCode` that run before inlining.
+ val r = removalRound(
+ requestNullness = true,
+ requestDCE = true,
+ requestBoxUnbox = true,
+ requestStaleStores = true,
+ requestPushPop = true,
+ requestStoreLoad = true,
+ firstIteration = true)
+ if (compilerSettings.optUnreachableCode) unreachableCodeEliminated += method
+ r
+ } else (false, false)
+
+ // (*) Removing stale local variable descriptors is required for correctness, see comment in `methodOptimizations`
val localsRemoved =
- if (compilerSettings.YoptCompactLocals) compactLocalVariables(method) // also removes unused
- else if (compilerSettings.YoptUnreachableCode) removeUnusedLocalVariableNodes(method)() // (*)
+ if (compilerSettings.optCompactLocals) compactLocalVariables(method) // also removes unused
+ else if (requireEliminateUnusedLocals) removeUnusedLocalVariableNodes(method)() // (*)
else false
+ traceIfChanged("localVariables")
- val lineNumbersRemoved = if (compilerSettings.YoptEmptyLineNumbers) removeEmptyLineNumbers(method) else false
+ val lineNumbersRemoved = if (compilerSettings.optUnreachableCode) removeEmptyLineNumbers(method) else false
+ traceIfChanged("lineNumbers")
- val labelsRemoved = if (compilerSettings.YoptEmptyLabels) removeEmptyLabelNodes(method) else false
+ val labelsRemoved = if (compilerSettings.optUnreachableCode) removeEmptyLabelNodes(method) else false
+ traceIfChanged("labels")
// assert that local variable annotations are empty (we don't emit them) - otherwise we'd have
// to eliminate those covering an empty range, similar to removeUnusedLocalVariableNodes.
@@ -174,53 +382,198 @@ class LocalOpt[BT <: BTypes](val btypes: BT) {
assert(nullOrEmpty(method.visibleLocalVariableAnnotations), method.visibleLocalVariableAnnotations)
assert(nullOrEmpty(method.invisibleLocalVariableAnnotations), method.invisibleLocalVariableAnnotations)
- unreachableCodeEliminated += method
-
- codeHandlersOrJumpsChanged || localsRemoved || lineNumbersRemoved || labelsRemoved
+ nullnessDceBoxesCastsCopypropPushpopOrJumpsChanged || localsRemoved || lineNumbersRemoved || labelsRemoved
}
-}
+ /**
+ * Apply various optimizations based on nullness analysis information.
+ * - IFNULL / IFNONNULL are rewritten to GOTO if nullness is known
+ * - IF_ACMPEQ / IF_ACMPNE are rewritten to GOTO if the both references are known null, or if
+ * one is known null and the other known not-null
+ * - ALOAD is replaced by ACONST_NULL if the local is known to hold null
+ * - ASTORE of null is removed if the local is known to hold null
+ * - INSTANCEOF of null is replaced by `ICONST_0`
+ * - scala.runtime.BoxesRunTime.unboxToX(null) is rewritten to a zero-value load
+ */
+ def nullnessOptimizations(method: MethodNode, ownerClassName: InternalName): Boolean = {
+ AsmAnalyzer.sizeOKForNullness(method) && {
+ lazy val nullnessAnalyzer = new AsmAnalyzer(method, ownerClassName, new NullnessAnalyzer(btypes, method))
+
+ // When running nullness optimizations the method may still have unreachable code. Analyzer
+ // frames of unreachable instructions are `null`.
+ def frameAt(insn: AbstractInsnNode): Option[Frame[NullnessValue]] = Option(nullnessAnalyzer.frameAt(insn))
+
+ def nullness(insn: AbstractInsnNode, slot: Int): Option[NullnessValue] = {
+ frameAt(insn).map(_.getValue(slot))
+ }
+
+ def isNull(insn: AbstractInsnNode, slot: Int) = nullness(insn, slot).contains(NullValue)
+
+ // cannot change instructions while iterating, it gets the analysis out of synch (indexed by instructions)
+ val toReplace = mutable.Map.empty[AbstractInsnNode, List[AbstractInsnNode]]
+
+ val it = method.instructions.iterator()
+ while (it.hasNext) it.next() match {
+ case vi: VarInsnNode if isNull(vi, vi.`var`) =>
+ if (vi.getOpcode == ALOAD)
+ toReplace(vi) = List(new InsnNode(ACONST_NULL))
+ else if (vi.getOpcode == ASTORE)
+ for (frame <- frameAt(vi) if frame.peekStack(0) == NullValue)
+ toReplace(vi) = List(getPop(1))
+
+ case ji: JumpInsnNode =>
+ val isIfNull = ji.getOpcode == IFNULL
+ val isIfNonNull = ji.getOpcode == IFNONNULL
+ if (isIfNull || isIfNonNull) for (frame <- frameAt(ji)) {
+ val nullness = frame.peekStack(0)
+ val taken = nullness == NullValue && isIfNull || nullness == NotNullValue && isIfNonNull
+ val avoided = nullness == NotNullValue && isIfNull || nullness == NullValue && isIfNonNull
+ if (taken || avoided) {
+ val jump = if (taken) List(new JumpInsnNode(GOTO, ji.label)) else Nil
+ toReplace(ji) = getPop(1) :: jump
+ }
+ } else {
+ val isIfEq = ji.getOpcode == IF_ACMPEQ
+ val isIfNe = ji.getOpcode == IF_ACMPNE
+ if (isIfEq || isIfNe) for (frame <- frameAt(ji)) {
+ val aNullness = frame.peekStack(1)
+ val bNullness = frame.peekStack(0)
+ val eq = aNullness == NullValue && bNullness == NullValue
+ val ne = aNullness == NullValue && bNullness == NotNullValue || aNullness == NotNullValue && bNullness == NullValue
+ val taken = isIfEq && eq || isIfNe && ne
+ val avoided = isIfEq && ne || isIfNe && eq
+ if (taken || avoided) {
+ val jump = if (taken) List(new JumpInsnNode(GOTO, ji.label)) else Nil
+ toReplace(ji) = getPop(1) :: getPop(1) :: jump
+ }
+ }
+ }
+
+ case ti: TypeInsnNode =>
+ if (ti.getOpcode == INSTANCEOF) for (frame <- frameAt(ti) if frame.peekStack(0) == NullValue) {
+ toReplace(ti) = List(getPop(1), new InsnNode(ICONST_0))
+ }
+
+ case mi: MethodInsnNode =>
+ if (isScalaUnbox(mi)) for (frame <- frameAt(mi) if frame.peekStack(0) == NullValue) {
+ toReplace(mi) = List(
+ getPop(1),
+ loadZeroForTypeSort(Type.getReturnType(mi.desc).getSort))
+ }
+
+ case _ =>
+ }
+
+ def removeFromCallGraph(insn: AbstractInsnNode): Unit = insn match {
+ case mi: MethodInsnNode => callGraph.removeCallsite(mi, method)
+ case _ =>
+ }
+
+ for ((oldOp, newOps) <- toReplace) {
+ for (newOp <- newOps) method.instructions.insertBefore(oldOp, newOp)
+ method.instructions.remove(oldOp)
+ removeFromCallGraph(oldOp)
+ }
+
+ toReplace.nonEmpty
+ }
+ }
-object LocalOptImpls {
/**
* Removes unreachable basic blocks.
*
- * TODO: rewrite, don't use computeMaxLocalsMaxStack (runs a ClassWriter) / Analyzer. Too slow.
- *
* @return A set containing eliminated instructions, and a set containing all live label nodes.
*/
- def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: InternalName): (Set[AbstractInsnNode], Set[LabelNode]) = {
- // The data flow analysis requires the maxLocals / maxStack fields of the method to be computed.
- computeMaxLocalsMaxStack(method)
- val a = new Analyzer(new BasicInterpreter)
- a.analyze(ownerClassName, method)
- val frames = a.getFrames
+ def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: InternalName): (Boolean, Set[LabelNode]) = {
+ val a = new AsmAnalyzer(method, ownerClassName)
+ val frames = a.analyzer.getFrames
- val initialSize = method.instructions.size
var i = 0
var liveLabels = Set.empty[LabelNode]
- var removedInstructions = Set.empty[AbstractInsnNode]
+ var changed = false
+ var maxLocals = parametersSize(method)
+ var maxStack = 0
val itr = method.instructions.iterator()
while (itr.hasNext) {
- itr.next() match {
- case l: LabelNode =>
- if (frames(i) != null) liveLabels += l
+ val insn = itr.next()
+ val isLive = frames(i) != null
+ if (isLive) maxStack = math.max(maxStack, frames(i).getStackSize)
- case ins =>
+ insn match {
+ case l: LabelNode =>
// label nodes are not removed: they might be referenced for example in a LocalVariableNode
- if (frames(i) == null || ins.getOpcode == Opcodes.NOP) {
+ if (isLive) liveLabels += l
+
+ case v: VarInsnNode if isLive =>
+ val longSize = if (isSize2LoadOrStore(v.getOpcode)) 1 else 0
+ maxLocals = math.max(maxLocals, v.`var` + longSize + 1) // + 1 because local numbers are 0-based
+
+ case i: IincInsnNode if isLive =>
+ maxLocals = math.max(maxLocals, i.`var` + 1)
+
+ case _ =>
+ if (!isLive || insn.getOpcode == NOP) {
// Instruction iterators allow removing during iteration.
// Removing is O(1): instructions are doubly linked list elements.
itr.remove()
- removedInstructions += ins
+ changed = true
+ insn match {
+ case invocation: MethodInsnNode => callGraph.removeCallsite(invocation, method)
+ case indy: InvokeDynamicInsnNode => callGraph.removeClosureInstantiation(indy, method)
+ case _ =>
+ }
}
}
i += 1
}
- (removedInstructions, liveLabels)
+ method.maxLocals = maxLocals
+ method.maxStack = maxStack
+ (changed, liveLabels)
}
/**
+ * Eliminate `CHECKCAST` instructions that are statically known to succeed. This is safe if the
+ * tested object is null: `null.asInstanceOf` always succeeds.
+ *
+ * The type of the tested object is determined using a NonLubbingTypeFlowAnalyzer. Note that this
+ * analysis collapses LUBs of non-equal references types to Object for simplicity. Example:
+ * given `B <: A <: Object`, the cast in `(if (..) new B else new A).asInstanceOf[A]` would not
+ * be eliminated.
+ *
+ * Note: we cannot replace `INSTANCEOF` tests by only looking at the types, `null.isInstanceOf`
+ * always returns false, so we'd also need nullness information.
+ */
+ def eliminateRedundantCasts(method: MethodNode, owner: InternalName): Boolean = {
+ AsmAnalyzer.sizeOKForBasicValue(method) && {
+ def isSubType(aRefDesc: String, bClass: InternalName): Boolean = aRefDesc == bClass || bClass == ObjectRef.internalName || {
+ (bTypeForDescriptorOrInternalNameFromClassfile(aRefDesc) conformsTo classBTypeFromParsedClassfile(bClass)).getOrElse(false)
+ }
+
+ lazy val typeAnalyzer = new NonLubbingTypeFlowAnalyzer(method, owner)
+
+ // cannot remove instructions while iterating, it gets the analysis out of synch (indexed by instructions)
+ val toRemove = mutable.Set.empty[TypeInsnNode]
+
+ val it = method.instructions.iterator()
+ while (it.hasNext) it.next() match {
+ case ti: TypeInsnNode if ti.getOpcode == CHECKCAST =>
+ val frame = typeAnalyzer.frameAt(ti)
+ val valueTp = frame.getValue(frame.stackTop)
+ if (valueTp.isReference && isSubType(valueTp.getType.getDescriptor, ti.desc)) {
+ toRemove += ti
+ }
+
+ case _ =>
+ }
+
+ toRemove foreach method.instructions.remove
+ toRemove.nonEmpty
+ }
+ }
+}
+
+object LocalOptImpls {
+ /**
* Remove exception handlers that cover empty code blocks. A block is considered empty if it
* consist only of labels, frames, line numbers, nops and gotos.
*
@@ -235,16 +588,16 @@ object LocalOptImpls {
def removeEmptyExceptionHandlers(method: MethodNode): Set[TryCatchBlockNode] = {
/** True if there exists code between start and end. */
def containsExecutableCode(start: AbstractInsnNode, end: LabelNode): Boolean = {
- start != end && ((start.getOpcode : @switch) match {
+ start != end && ((start.getOpcode: @switch) match {
// FrameNode, LabelNode and LineNumberNode have opcode == -1.
- case -1 | Opcodes.GOTO => containsExecutableCode(start.getNext, end)
+ case -1 | GOTO => containsExecutableCode(start.getNext, end)
case _ => true
})
}
var removedHandlers = Set.empty[TryCatchBlockNode]
val handlersIter = method.tryCatchBlocks.iterator()
- while(handlersIter.hasNext) {
+ while (handlersIter.hasNext) {
val handler = handlersIter.next()
if (!containsExecutableCode(handler.start, handler.end)) {
removedHandlers += handler
@@ -263,9 +616,10 @@ object LocalOptImpls {
* same type or name.
*/
def removeUnusedLocalVariableNodes(method: MethodNode)(firstLocalIndex: Int = parametersSize(method), renumber: Int => Int = identity): Boolean = {
- def variableIsUsed(start: AbstractInsnNode, end: LabelNode, varIndex: Int): Boolean = {
+ @tailrec def variableIsUsed(start: AbstractInsnNode, end: LabelNode, varIndex: Int): Boolean = {
start != end && (start match {
case v: VarInsnNode if v.`var` == varIndex => true
+ case i: IincInsnNode if i.`var` == varIndex => true
case _ => variableIsUsed(start.getNext, end, varIndex)
})
}
@@ -285,17 +639,6 @@ object LocalOptImpls {
}
/**
- * The number of local variable slots used for parameters and for the `this` reference.
- */
- private def parametersSize(method: MethodNode): Int = {
- // Double / long fields occupy two slots, so we sum up the sizes. Since getSize returns 0 for
- // void, we have to add `max 1`.
- val paramsSize = scala.tools.asm.Type.getArgumentTypes(method.desc).iterator.map(_.getSize max 1).sum
- val thisSize = if ((method.access & Opcodes.ACC_STATIC) == 0) 1 else 0
- paramsSize + thisSize
- }
-
- /**
* Compact the local variable slots used in the method's implementation. This prevents having
* unused slots for example after eliminating unreachable code.
*
@@ -310,12 +653,9 @@ object LocalOptImpls {
val renumber = collection.mutable.ArrayBuffer.empty[Int]
// Add the index of the local variable used by `varIns` to the `renumber` array.
- def addVar(varIns: VarInsnNode): Unit = {
- val index = varIns.`var`
- val isWide = (varIns.getOpcode: @switch) match {
- case Opcodes.LLOAD | Opcodes.DLOAD | Opcodes.LSTORE | Opcodes.DSTORE => true
- case _ => false
- }
+ def addVar(varIns: AbstractInsnNode, slot: Int): Unit = {
+ val index = slot
+ val isWide = isSize2LoadOrStore(varIns.getOpcode)
// Ensure the length of `renumber`. Unused variable indices are mapped to -1.
val minLength = if (isWide) index + 2 else index + 1
@@ -332,7 +672,7 @@ object LocalOptImpls {
val firstLocalIndex = parametersSize(method)
for (i <- 0 until firstLocalIndex) renumber += i // parameters and `this` are always used.
method.instructions.iterator().asScala foreach {
- case VarInstruction(varIns) => addVar(varIns)
+ case VarInstruction(varIns, slot) => addVar(varIns, slot)
case _ =>
}
@@ -353,10 +693,12 @@ object LocalOptImpls {
// update variable instructions according to the renumber table
method.maxLocals = nextIndex
method.instructions.iterator().asScala.foreach {
- case VarInstruction(varIns) =>
- val oldIndex = varIns.`var`
- if (oldIndex >= firstLocalIndex && renumber(oldIndex) != oldIndex)
- varIns.`var` = renumber(varIns.`var`)
+ case VarInstruction(varIns, slot) =>
+ val oldIndex = slot
+ if (oldIndex >= firstLocalIndex && renumber(oldIndex) != oldIndex) varIns match {
+ case vi: VarInsnNode => vi.`var` = renumber(slot)
+ case ii: IincInsnNode => ii.`var` = renumber(slot)
+ }
case _ =>
}
true
@@ -431,154 +773,181 @@ object LocalOptImpls {
// A set of all exception handlers that guard the current instruction, required for simplifyGotoReturn
var activeHandlers = Set.empty[TryCatchBlockNode]
- // Instructions that need to be removed. simplifyBranchOverGoto returns an instruction to be
- // removed. It cannot remove it itself because the instruction may be the successor of the current
- // instruction of the iterator, which is not supported in ASM.
- var instructionsToRemove = Set.empty[AbstractInsnNode]
+ val jumpInsns = mutable.LinkedHashMap.empty[JumpInsnNode, Boolean]
- val iterator = method.instructions.iterator()
- while (iterator.hasNext) {
- val instruction = iterator.next()
+ for (insn <- method.instructions.iterator().asScala) insn match {
+ case l: LabelNode =>
+ activeHandlers ++= allHandlers.filter(_.start == l)
+ activeHandlers = activeHandlers.filter(_.end != l)
- instruction match {
- case l: LabelNode =>
- activeHandlers ++= allHandlers.filter(_.start == l)
- activeHandlers = activeHandlers.filter(_.end != l)
- case _ =>
+ case ji: JumpInsnNode =>
+ jumpInsns(ji) = activeHandlers.nonEmpty
+
+ case _ =>
+ }
+
+ var _jumpTargets: Set[AbstractInsnNode] = null
+ def jumpTargets = {
+ if (_jumpTargets == null) {
+ _jumpTargets = jumpInsns.keysIterator.map(_.label).toSet
}
+ _jumpTargets
+ }
- if (instructionsToRemove(instruction)) {
- iterator.remove()
- instructionsToRemove -= instruction
- } else if (isJumpNonJsr(instruction)) { // fast path - all of the below only treat jumps
- var jumpRemoved = simplifyThenElseSameTarget(method, instruction)
+ def removeJumpFromMap(jump: JumpInsnNode) = {
+ jumpInsns.remove(jump)
+ _jumpTargets = null
+ }
- if (!jumpRemoved) {
- changed = collapseJumpChains(instruction) || changed
- jumpRemoved = removeJumpToSuccessor(method, instruction)
+ def replaceJumpByPop(jump: JumpInsnNode) = {
+ removeJumpAndAdjustStack(method, jump)
+ removeJumpFromMap(jump)
+ }
- if (!jumpRemoved) {
- val staleGoto = simplifyBranchOverGoto(method, instruction)
- instructionsToRemove ++= staleGoto
- changed ||= staleGoto.nonEmpty
- changed = simplifyGotoReturn(method, instruction, inTryBlock = activeHandlers.nonEmpty) || changed
- }
+ /**
+ * Removes a conditional jump if it is followed by a GOTO to the same destination.
+ *
+ * CondJump l; [nops]; GOTO l; [...]
+ * POP*; [nops]; GOTO l; [...]
+ *
+ * Introduces 1 or 2 POP instructions, depending on the number of values consumed by the CondJump.
+ */
+ def simplifyThenElseSameTarget(insn: AbstractInsnNode): Boolean = insn match {
+ case ConditionalJump(jump) =>
+ nextExecutableInstruction(insn) match {
+ case Some(Goto(elseJump)) if sameTargetExecutableInstruction(jump, elseJump) =>
+ replaceJumpByPop(jump)
+ true
+
+ case _ => false
}
- changed ||= jumpRemoved
- }
+
+ case _ => false
}
- assert(instructionsToRemove.isEmpty, "some optimization required removing a previously traversed instruction. add `instructionsToRemove.foreach(method.instructions.remove)`")
- changed
- }
- /**
- * Removes a conditional jump if it is followed by a GOTO to the same destination.
- *
- * CondJump l; [nops]; GOTO l; [...]
- * POP*; [nops]; GOTO l; [...]
- *
- * Introduces 1 or 2 POP instructions, depending on the number of values consumed by the CondJump.
- */
- private def simplifyThenElseSameTarget(method: MethodNode, instruction: AbstractInsnNode): Boolean = instruction match {
- case ConditionalJump(jump) =>
- nextExecutableInstruction(instruction) match {
- case Some(Goto(elseJump)) if sameTargetExecutableInstruction(jump, elseJump) =>
- removeJumpAndAdjustStack(method, jump)
+ /**
+ * Replace jumps to a sequence of GOTO instructions by a jump to the final destination.
+ *
+ * {{{
+ * Jump l; [any ops]; l: GOTO m; [any ops]; m: GOTO n; [any ops]; n: NotGOTO; [...]
+ * => Jump n; [rest unchanged]
+ * }}}
+ *
+ * If there's a loop of GOTOs, the initial jump is replaced by one of the labels in the loop.
+ */
+ def collapseJumpChains(insn: AbstractInsnNode): Boolean = insn match {
+ case JumpNonJsr(jump) =>
+ val target = finalJumpTarget(jump)
+ if (jump.label == target) false else {
+ jump.label = target
+ _jumpTargets = null
true
+ }
- case _ => false
- }
- case _ => false
- }
+ case _ => false
+ }
- /**
- * Replace jumps to a sequence of GOTO instructions by a jump to the final destination.
- *
- * Jump l; [any ops]; l: GOTO m; [any ops]; m: GOTO n; [any ops]; n: NotGOTO; [...]
- * => Jump n; [rest unchanged]
- *
- * If there's a loop of GOTOs, the initial jump is replaced by one of the labels in the loop.
- */
- private def collapseJumpChains(instruction: AbstractInsnNode): Boolean = instruction match {
- case JumpNonJsr(jump) =>
- val target = finalJumpTarget(jump)
- if (jump.label == target) false else {
- jump.label = target
+ /**
+ * Eliminates unnecessary jump instructions
+ *
+ * {{{
+ * Jump l; [nops]; l: [...]
+ * => POP*; [nops]; l: [...]
+ * }}}
+ *
+ * Introduces 0, 1 or 2 POP instructions, depending on the number of values consumed by the Jump.
+ */
+ def removeJumpToSuccessor(insn: AbstractInsnNode): Boolean = insn match {
+ case JumpNonJsr(jump) if nextExecutableInstruction(jump, alsoKeep = Set(jump.label)) contains jump.label =>
+ replaceJumpByPop(jump)
true
- }
- case _ => false
- }
+ case _ => false
+ }
- /**
- * Eliminates unnecessary jump instructions
- *
- * Jump l; [nops]; l: [...]
- * => POP*; [nops]; l: [...]
- *
- * Introduces 0, 1 or 2 POP instructions, depending on the number of values consumed by the Jump.
- */
- private def removeJumpToSuccessor(method: MethodNode, instruction: AbstractInsnNode) = instruction match {
- case JumpNonJsr(jump) if nextExecutableInstruction(jump, alsoKeep = Set(jump.label)) == Some(jump.label) =>
- removeJumpAndAdjustStack(method, jump)
- true
- case _ => false
- }
+ /**
+ * If the "else" part of a conditional branch is a simple GOTO, negates the conditional branch
+ * and eliminates the GOTO.
+ *
+ * {{{
+ * CondJump l; [nops, no jump targets]; GOTO m; [nops]; l: [...]
+ * => NegatedCondJump m; [nops, no jump targets]; [nops]; l: [...]
+ * }}}
+ *
+ * Note that no jump targets are allowed in the first [nops] section. Otherwise, there could
+ * be some other jump to the GOTO, and eliminating it would change behavior.
+ */
+ def simplifyBranchOverGoto(insn: AbstractInsnNode, inTryBlock: Boolean): Boolean = insn match {
+ case ConditionalJump(jump) =>
+ // don't skip over jump targets, see doc comment
+ nextExecutableInstruction(jump, alsoKeep = jumpTargets) match {
+ case Some(Goto(goto)) =>
+ if (nextExecutableInstruction(goto, alsoKeep = Set(jump.label)) contains jump.label) {
+ val newJump = new JumpInsnNode(negateJumpOpcode(jump.getOpcode), goto.label)
+ method.instructions.set(jump, newJump)
+ removeJumpFromMap(jump)
+ jumpInsns(newJump) = inTryBlock
+ replaceJumpByPop(goto)
+ true
+ } else false
+
+ case _ => false
+ }
+ case _ => false
+ }
- /**
- * If the "else" part of a conditional branch is a simple GOTO, negates the conditional branch
- * and eliminates the GOTO.
- *
- * CondJump l; [nops, no labels]; GOTO m; [nops]; l: [...]
- * => NegatedCondJump m; [nops, no labels]; [nops]; l: [...]
- *
- * Note that no label definitions are allowed in the first [nops] section. Otherwise, there could
- * be some other jump to the GOTO, and eliminating it would change behavior.
- *
- * For technical reasons, we cannot remove the GOTO here (*).Instead this method returns an Option
- * containing the GOTO that needs to be eliminated.
- *
- * (*) The ASM instruction iterator (used in the caller [[simplifyJumps]]) has an undefined
- * behavior if the successor of the current instruction is removed, which may be the case here
- */
- private def simplifyBranchOverGoto(method: MethodNode, instruction: AbstractInsnNode): Option[JumpInsnNode] = instruction match {
- case ConditionalJump(jump) =>
- // don't skip over labels, see doc comment
- nextExecutableInstruction(jump, alsoKeep = _.isInstanceOf[LabelNode]) match {
- case Some(Goto(goto)) =>
- if (nextExecutableInstruction(goto, alsoKeep = Set(jump.label)) == Some(jump.label)) {
- val newJump = new JumpInsnNode(negateJumpOpcode(jump.getOpcode), goto.label)
- method.instructions.set(jump, newJump)
- Some(goto)
- } else None
-
- case _ => None
- }
- case _ => None
- }
+ /**
+ * Inlines xRETURN and ATHROW
+ *
+ * {{{
+ * GOTO l; [any ops]; l: xRETURN/ATHROW
+ * => xRETURN/ATHROW; [any ops]; l: xRETURN/ATHROW
+ * }}}
+ *
+ * inlining is only done if the GOTO instruction is not part of a try block, otherwise the
+ * rewrite might change the behavior. For xRETURN, the reason is that return instructions may throw
+ * an IllegalMonitorStateException, as described here:
+ * http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.return
+ */
+ def simplifyGotoReturn(instruction: AbstractInsnNode, inTryBlock: Boolean): Boolean = !inTryBlock && (instruction match {
+ case Goto(jump) =>
+ nextExecutableInstruction(jump.label) match {
+ case Some(target) =>
+ if (isReturn(target) || target.getOpcode == ATHROW) {
+ method.instructions.set(jump, target.clone(null))
+ removeJumpFromMap(jump)
+ true
+ } else false
+
+ case _ => false
+ }
+ case _ => false
+ })
- /**
- * Inlines xRETURN and ATHROW
- *
- * GOTO l; [any ops]; l: xRETURN/ATHROW
- * => xRETURN/ATHROW; [any ops]; l: xRETURN/ATHROW
- *
- * inlining is only done if the GOTO instruction is not part of a try block, otherwise the
- * rewrite might change the behavior. For xRETURN, the reason is that return instructions may throw
- * an IllegalMonitorStateException, as described here:
- * http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.return
- */
- private def simplifyGotoReturn(method: MethodNode, instruction: AbstractInsnNode, inTryBlock: Boolean): Boolean = !inTryBlock && (instruction match {
- case Goto(jump) =>
- nextExecutableInstruction(jump.label) match {
- case Some(target) =>
- if (isReturn(target) || target.getOpcode == Opcodes.ATHROW) {
- method.instructions.set(jump, target.clone(null))
- true
- } else false
+ def run(): Boolean = {
+ var changed = false
+
+ // `.toList` because we're modifying the map while iterating over it
+ for ((jumpInsn, inTryBlock) <- jumpInsns.toList if jumpInsns.contains(jumpInsn) && isJumpNonJsr(jumpInsn)) {
+ var jumpRemoved = simplifyThenElseSameTarget(jumpInsn)
+
+ if (!jumpRemoved) {
+ changed = collapseJumpChains(jumpInsn) || changed
+ jumpRemoved = removeJumpToSuccessor(jumpInsn)
+
+ if (!jumpRemoved) {
+ changed = simplifyBranchOverGoto(jumpInsn, inTryBlock) || changed
+ changed = simplifyGotoReturn(jumpInsn, inTryBlock) || changed
+ }
+ }
- case _ => false
+ changed ||= jumpRemoved
}
- case _ => false
- })
+
+ if (changed) run()
+ changed
+ }
+
+ run()
+ }
}
diff --git a/src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala b/src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala
deleted file mode 100644
index a866173a88..0000000000
--- a/src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala
+++ /dev/null
@@ -1,235 +0,0 @@
- /* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Iulian Dragos
- */
-
-package scala.tools.nsc
-package backend.opt
-
-import scala.tools.nsc.backend.icode.analysis.LubException
-
-/**
- * @author Iulian Dragos
- */
-abstract class ClosureElimination extends SubComponent {
- import global._
- import icodes._
- import icodes.opcodes._
-
- val phaseName = "closelim"
-
- override val enabled: Boolean = settings.Xcloselim
-
- /** Create a new phase */
- override def newPhase(p: Phase) = new ClosureEliminationPhase(p)
-
- /** A simple peephole optimizer. */
- val peephole = new PeepholeOpt {
-
- def peep(bb: BasicBlock, i1: Instruction, i2: Instruction) = (i1, i2) match {
- case (CONSTANT(c), DROP(_)) =>
- if (c.tag == UnitTag) Some(List(i2)) else Some(Nil)
-
- case (LOAD_LOCAL(x), STORE_LOCAL(y)) =>
- if (x eq y) Some(Nil) else None
-
- case (STORE_LOCAL(x), LOAD_LOCAL(y)) if (x == y) =>
- var liveOut = liveness.out(bb)
- if (!liveOut(x)) {
- debuglog("store/load to a dead local? " + x)
- val instrs = bb.getArray
- var idx = instrs.length - 1
- while (idx > 0 && (instrs(idx) ne i2)) {
- liveOut = liveness.interpret(liveOut, instrs(idx))
- idx -= 1
- }
- if (!liveOut(x)) {
- log("Removing dead store/load of " + x.sym.initialize.defString)
- Some(Nil)
- } else None
- } else
- Some(List(DUP(x.kind), STORE_LOCAL(x)))
-
- case (LOAD_LOCAL(_), DROP(_)) | (DUP(_), DROP(_)) =>
- Some(Nil)
-
- case (BOX(t1), UNBOX(t2)) if (t1 == t2) =>
- Some(Nil)
-
- case (LOAD_FIELD(sym, /* isStatic */false), DROP(_)) if !sym.hasAnnotation(definitions.VolatileAttr) && inliner.isClosureClass(sym.owner) =>
- Some(DROP(REFERENCE(definitions.ObjectClass)) :: Nil)
-
- case _ => None
- }
- }
-
- /** The closure elimination phase.
- */
- class ClosureEliminationPhase(prev: Phase) extends ICodePhase(prev) {
-
- def name = phaseName
- val closser = new ClosureElim
-
- override def apply(c: IClass): Unit = {
- if (closser ne null)
- closser analyzeClass c
- }
- }
-
- /**
- * Remove references to the environment through fields of a closure object.
- * This has to be run after an 'apply' method has been inlined, but it still
- * references the closure object.
- *
- */
- class ClosureElim {
- def analyzeClass(cls: IClass): Unit = if (settings.Xcloselim) {
- log(s"Analyzing ${cls.methods.size} methods in $cls.")
- cls.methods foreach { m =>
- analyzeMethod(m)
- peephole(m)
- }}
-
- val cpp = new copyPropagation.CopyAnalysis
-
- import copyPropagation._
-
- /* Some embryonic copy propagation. */
- def analyzeMethod(m: IMethod): Unit = try {if (m.hasCode) {
- cpp.init(m)
- cpp.run()
-
- m.linearizedBlocks() foreach { bb =>
- var info = cpp.in(bb)
- debuglog("Cpp info at entry to block " + bb + ": " + info)
-
- for (i <- bb) {
- i match {
- case LOAD_LOCAL(l) if info.bindings isDefinedAt LocalVar(l) =>
- val t = info.getBinding(l)
- t match {
- case Deref(This) | Const(_) =>
- bb.replaceInstruction(i, valueToInstruction(t))
- debuglog(s"replaced $i with $t")
-
- case _ =>
- val t = info.getAlias(l)
- bb.replaceInstruction(i, LOAD_LOCAL(t))
- debuglog(s"replaced $i with $t")
- }
-
- case LOAD_FIELD(f, false) /* if accessible(f, m.symbol) */ =>
- def replaceFieldAccess(r: Record) {
- val Record(cls, _) = r
- info.getFieldNonRecordValue(r, f) foreach { v =>
- bb.replaceInstruction(i, DROP(REFERENCE(cls)) :: valueToInstruction(v) :: Nil)
- debuglog(s"replaced $i with $v")
- }
- }
-
- info.stack(0) match {
- case r @ Record(_, bindings) if bindings isDefinedAt f =>
- replaceFieldAccess(r)
-
- case Deref(LocalVar(l)) =>
- info.getBinding(l) match {
- case r @ Record(_, bindings) if bindings isDefinedAt f =>
- replaceFieldAccess(r)
- case _ =>
- }
- case Deref(Field(r1, f1)) =>
- info.getFieldValue(r1, f1) match {
- case Some(r @ Record(_, bindings)) if bindings isDefinedAt f =>
- replaceFieldAccess(r)
- case _ =>
- }
-
- case _ =>
- }
-
- case UNBOX(boxType) =>
- info.stack match {
- case Deref(LocalVar(loc1)) :: _ if info.bindings isDefinedAt LocalVar(loc1) =>
- val value = info.getBinding(loc1)
- value match {
- case Boxed(LocalVar(loc2)) if loc2.kind == boxType =>
- bb.replaceInstruction(i, DROP(icodes.ObjectReference) :: valueToInstruction(info.getBinding(loc2)) :: Nil)
- debuglog("replaced " + i + " with " + info.getBinding(loc2))
- case _ =>
- ()
- }
- case Boxed(LocalVar(loc1)) :: _ if loc1.kind == boxType =>
- val loc2 = info.getAlias(loc1)
- bb.replaceInstruction(i, DROP(icodes.ObjectReference) :: valueToInstruction(Deref(LocalVar(loc2))) :: Nil)
- debuglog("replaced " + i + " with " + LocalVar(loc2))
- case _ =>
- }
-
- case _ =>
- }
- info = cpp.interpret(info, i)
- }
- }
- }} catch {
- case e: LubException =>
- Console.println("In method: " + m)
- Console.println(e)
- e.printStackTrace
- }
-
- /* Partial mapping from values to instructions that load them. */
- def valueToInstruction(v: Value): Instruction = (v: @unchecked) match {
- case Deref(LocalVar(v)) =>
- LOAD_LOCAL(v)
- case Const(k) =>
- CONSTANT(k)
- case Deref(This) =>
- THIS(definitions.ObjectClass)
- case Boxed(LocalVar(v)) =>
- LOAD_LOCAL(v)
- }
- } /* class ClosureElim */
-
-
- /** Peephole optimization. */
- abstract class PeepholeOpt {
- /** Concrete implementations will perform their optimizations here */
- def peep(bb: BasicBlock, i1: Instruction, i2: Instruction): Option[List[Instruction]]
-
- var liveness: global.icodes.liveness.LivenessAnalysis = null
-
- def apply(m: IMethod): Unit = if (m.hasCode) {
- liveness = new global.icodes.liveness.LivenessAnalysis
- liveness.init(m)
- liveness.run()
- m foreachBlock transformBlock
- }
-
- def transformBlock(b: BasicBlock): Unit = if (b.size >= 2) {
- var newInstructions: List[Instruction] = b.toList
- var redo = false
-
- do {
- var h = newInstructions.head
- var t = newInstructions.tail
- var seen: List[Instruction] = Nil
- redo = false
-
- while (t != Nil) {
- peep(b, h, t.head) match {
- case Some(newInstrs) =>
- newInstructions = seen reverse_::: newInstrs ::: t.tail
- redo = true
- case None =>
- ()
- }
- seen = h :: seen
- h = t.head
- t = t.tail
- }
- } while (redo)
- b fromList newInstructions
- }
- }
-
-} /* class ClosureElimination */
diff --git a/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala b/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala
deleted file mode 100644
index eafaf41932..0000000000
--- a/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala
+++ /dev/null
@@ -1,626 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author James Iry
- */
-
-package scala
-package tools.nsc
-package backend.opt
-
-import scala.annotation.tailrec
-
-/**
- * ConstantOptimization uses abstract interpretation to approximate for
- * each instruction what constants a variable or stack slot might hold
- * or cannot hold. From this it will eliminate unreachable conditionals
- * where only one branch is reachable, e.g. to eliminate unnecessary
- * null checks.
- *
- * With some more work it could be extended to
- * - cache stable values (final fields, modules) in locals
- * - replace the copy propagation in ClosureElimination
- * - fold constants
- * - eliminate unnecessary stores and loads
- * - propagate knowledge gathered from conditionals for further optimization
- */
-abstract class ConstantOptimization extends SubComponent {
- import global._
- import icodes._
- import icodes.opcodes._
-
- val phaseName = "constopt"
-
- /** Create a new phase */
- override def newPhase(p: Phase) = new ConstantOptimizationPhase(p)
-
- override val enabled: Boolean = settings.YconstOptimization
-
- /**
- * The constant optimization phase.
- */
- class ConstantOptimizationPhase(prev: Phase) extends ICodePhase(prev) {
-
- def name = phaseName
-
- override def apply(c: IClass) {
- if (settings.YconstOptimization) {
- val analyzer = new ConstantOptimizer
- analyzer optimizeClass c
- }
- }
- }
-
- class ConstantOptimizer {
- def optimizeClass(cls: IClass) {
- log(s"Analyzing ${cls.methods.size} methods in $cls.")
- cls.methods foreach { m =>
- optimizeMethod(m)
- }
- }
-
- def optimizeMethod(m: IMethod) {
- if (m.hasCode) {
- log(s"Analyzing ${m.symbol}")
- val replacementInstructions = interpretMethod(m)
- for (block <- m.blocks) {
- if (replacementInstructions contains block) {
- val instructions = replacementInstructions(block)
- block.replaceInstruction(block.lastInstruction, instructions)
- }
- }
- }
- }
-
- /**
- * A single possible (or impossible) datum that can be held in Contents
- */
- private sealed abstract class Datum
- /**
- * A constant datum
- */
- private case class Const(c: Constant) extends Datum {
- def isIntAssignable = c.tag >= BooleanTag && c.tag <= IntTag
- def toInt = c.tag match {
- case BooleanTag => if (c.booleanValue) 1 else 0
- case _ => c.intValue
- }
-
- /**
- * True if this constant would compare to other as true under primitive eq
- */
- override def equals(other: Any) = other match {
- case oc @ Const(o) => (this eq oc) || (if (this.isIntAssignable && oc.isIntAssignable) this.toInt == oc.toInt else c.value == o.value)
- case _ => false
- }
-
- /**
- * Hash code consistent with equals
- */
- override def hashCode = if (this.isIntAssignable) this.toInt else c.hashCode
-
- }
- /**
- * A datum that has been Boxed via a BOX instruction
- */
- private case class Boxed(c: Datum) extends Datum
-
- /**
- * The knowledge we have about the abstract state of one location in terms
- * of what constants it might or cannot hold. Forms a lower
- * lattice where lower elements in the lattice indicate less knowledge.
- *
- * With the following partial ordering (where '>' indicates more precise knowledge)
- *
- * Possible(xs) > Possible(xs + y)
- * Possible(xs) > Impossible(ys)
- * Impossible(xs + y) > Impossible(xs)
- *
- * and the following merges, which indicate merging knowledge from two paths through
- * the code,
- *
- * // left must be 1 or 2, right must be 2 or 3 then we must have a 1, 2 or 3
- * Possible(xs) merge Possible(ys) => Possible(xs union ys)
- *
- * // Left says can't be 2 or 3, right says can't be 3 or 4
- * // then it's not 3 (it could be 2 from the right or 4 from the left)
- * Impossible(xs) merge Impossible(ys) => Impossible(xs intersect ys)
- *
- * // Left says it can't be 2 or 3, right says it must be 3 or 4, then
- * // it can't be 2 (left rules out 4 and right says 3 is possible)
- * Impossible(xs) merge Possible(ys) => Impossible(xs -- ys)
- *
- * Intuitively, Possible(empty) says that a location can't hold anything,
- * it's uninitialized. However, Possible(empty) never appears in the code.
- *
- * Conversely, Impossible(empty) says nothing is impossible, it could be
- * anything. Impossible(empty) is given a synonym UNKNOWN and is used
- * for, e.g., the result of an arbitrary method call.
- */
- private sealed abstract class Contents {
- /**
- * Join this Contents with another coming from another path. Join enforces
- * the lattice structure. It is symmetrical and never moves upward in the
- * lattice
- */
- final def merge(other: Contents): Contents = if (this eq other) this else (this, other) match {
- case (Possible(possible1), Possible(possible2)) =>
- Possible(possible1 union possible2)
- case (Impossible(impossible1), Impossible(impossible2)) =>
- Impossible(impossible1 intersect impossible2)
- case (Impossible(impossible), Possible(possible)) =>
- Impossible(impossible -- possible)
- case (Possible(possible), Impossible(impossible)) =>
- Impossible(impossible -- possible)
- }
- // TODO we could have more fine-grained knowledge, e.g. know that 0 < x < 3. But for now equality/inequality is a good start.
- def mightEqual(other: Contents): Boolean
- def mightNotEqual(other: Contents): Boolean
- }
- private def SingleImpossible(x: Datum) = new Impossible(Set(x))
-
- /**
- * The location is known to have one of a set of values.
- */
- private case class Possible(possible: Set[Datum]) extends Contents {
- assert(possible.nonEmpty, "Contradiction: had an empty possible set indicating an uninitialized location")
- def mightEqual(other: Contents): Boolean = (this eq other) || (other match {
- // two Possibles might be equal if they have any possible members in common
- case Possible(possible2) => (possible intersect possible2).nonEmpty
- // a possible can be equal to an impossible if the impossible doesn't rule
- // out all the possibilities
- case Impossible(possible2) => (possible -- possible2).nonEmpty
- })
- def mightNotEqual(other: Contents): Boolean = (other match {
- case Possible(possible2) =>
- // two Possibles must equal if each is known to be of the same, single value
- val mustEqual = possible.size == 1 && possible == possible2
- !mustEqual
- case Impossible(_) => true
- })
- }
- private def SinglePossible(x: Datum) = new Possible(Set(x))
-
- /**
- * The location is known to not have any of a set of values value (e.g null).
- */
- private case class Impossible(impossible: Set[Datum]) extends Contents {
- def mightEqual(other: Contents): Boolean = (this eq other) || (other match {
- case Possible(_) => other mightEqual this
- case _ => true
- })
- def mightNotEqual(other: Contents): Boolean = (this eq other) || (other match {
- case Possible(_) => other mightNotEqual this
- case _ => true
- })
- }
-
- /**
- * Our entire knowledge about the contents of all variables and the stack. It forms
- * a lattice primarily driven by the lattice structure of Contents.
- *
- * In addition to the rules of contents, State has the following properties:
- * - The merge of two sets of locals holds the merges of locals found in the intersection
- * of the two sets of locals. Locals not found in a
- * locals map are thus possibly uninitialized and attempting to load them results
- * in an error.
- * - The stack heights of two states must match otherwise it's an error to merge them
- *
- * State is immutable in order to aid in structure sharing of local maps and stacks
- */
- private case class State(locals: Map[Local, Contents], stack: List[Contents]) {
- def mergeLocals(olocals: Map[Local, Contents]): Map[Local, Contents] = if (locals eq olocals) locals else Map((for {
- key <- (locals.keySet intersect olocals.keySet).toSeq
- } yield (key, locals(key) merge olocals(key))): _*)
-
- def merge(other: State): State = if (this eq other) this else {
- @tailrec def mergeStacks(l: List[Contents], r: List[Contents], out: List[Contents]): List[Contents] = (l, r) match {
- case (Nil, Nil) => out.reverse
- case (l, r) if l eq r => out.reverse ++ l
- case (lhead :: ltail, rhead :: rtail) => mergeStacks(ltail, rtail, (lhead merge rhead) :: out)
- case _ => sys.error("Mismatched stack heights")
- }
-
- val newLocals = mergeLocals(other.locals)
-
- val newStack = if (stack eq other.stack) stack else mergeStacks(stack, other.stack, Nil)
- State(newLocals, newStack)
- }
-
- /**
- * Peek at the top of the stack without modifying it. Error if the stack is empty
- */
- def peek(n: Int): Contents = stack(n)
- /**
- * Push contents onto a stack
- */
- def push(contents: Contents): State = this copy (stack = contents :: stack)
- /**
- * Drop n elements from the stack
- */
- def drop(number: Int): State = this copy (stack = stack drop number)
- /**
- * Store the top of the stack into the specified local. An error if the stack
- * is empty
- */
- def store(variable: Local): State = {
- val contents = stack.head
- val newVariables = locals + ((variable, contents))
- new State(newVariables, stack.tail)
- }
- /**
- * Load the specified local onto the top of the stack. An error if the local is uninitialized.
- */
- def load(variable: Local): State = {
- val contents: Contents = locals.getOrElse(variable, sys.error(s"$variable is not initialized"))
- push(contents)
- }
- /**
- * A copy of this State with an empty stack
- */
- def cleanStack: State = if (stack.isEmpty) this else this copy (stack = Nil)
- }
-
- // some precomputed constants
- private val NULL = Const(Constant(null: Any))
- private val UNKNOWN = Impossible(Set.empty)
- private val NOT_NULL = SingleImpossible(NULL)
- private val CONST_UNIT = SinglePossible(Const(Constant(())))
- private val CONST_FALSE = SinglePossible(Const(Constant(false)))
- private val CONST_ZERO_BYTE = SinglePossible(Const(Constant(0: Byte)))
- private val CONST_ZERO_SHORT = SinglePossible(Const(Constant(0: Short)))
- private val CONST_ZERO_CHAR = SinglePossible(Const(Constant(0: Char)))
- private val CONST_ZERO_INT = SinglePossible(Const(Constant(0: Int)))
- private val CONST_ZERO_LONG = SinglePossible(Const(Constant(0: Long)))
- private val CONST_ZERO_FLOAT = SinglePossible(Const(Constant(0.0f)))
- private val CONST_ZERO_DOUBLE = SinglePossible(Const(Constant(0.0d)))
- private val CONST_NULL = SinglePossible(NULL)
-
- /**
- * Given a TypeKind, figure out what '0' for it means in order to interpret CZJUMP
- */
- private def getZeroOf(k: TypeKind): Contents = k match {
- case UNIT => CONST_UNIT
- case BOOL => CONST_FALSE
- case BYTE => CONST_ZERO_BYTE
- case SHORT => CONST_ZERO_SHORT
- case CHAR => CONST_ZERO_CHAR
- case INT => CONST_ZERO_INT
- case LONG => CONST_ZERO_LONG
- case FLOAT => CONST_ZERO_FLOAT
- case DOUBLE => CONST_ZERO_DOUBLE
- case REFERENCE(_) => CONST_NULL
- case ARRAY(_) => CONST_NULL
- case BOXED(_) => CONST_NULL
- case ConcatClass => abort("no zero of ConcatClass")
- }
-
- // normal locals can't be null, so we use null to mean the magic 'this' local
- private val THIS_LOCAL: Local = null
-
- /**
- * interpret a single instruction to find its impact on the abstract state
- */
- private def interpretInst(in: State, inst: Instruction): State = {
- // pop the consumed number of values off the `in` state's stack, producing a new state
- def dropConsumed: State = in drop inst.consumed
-
- inst match {
- case THIS(_) =>
- in load THIS_LOCAL
-
- case CONSTANT(k) =>
- // treat NaN as UNKNOWN because NaN must never equal NaN
- val const = if (k.isNaN) UNKNOWN
- else SinglePossible(Const(k))
- in push const
-
- case LOAD_ARRAY_ITEM(_) | LOAD_FIELD(_, _) | CALL_PRIMITIVE(_) =>
- dropConsumed push UNKNOWN
-
- case LOAD_LOCAL(local) =>
- // TODO if a local is known to hold a constant then we can replace this instruction with a push of that constant
- in load local
-
- case STORE_LOCAL(local) =>
- in store local
-
- case STORE_THIS(_) =>
- // if a local is already known to have a constant and we're replacing with the same constant then we can
- // replace this with a drop
- in store THIS_LOCAL
-
- case CALL_METHOD(_, _) =>
- // TODO we could special case implementations of equals that are known, e.g. String#equals
- // We could turn Possible(string constants).equals(Possible(string constants) into an eq check
- // We could turn nonConstantString.equals(constantString) into constantString.equals(nonConstantString)
- // and eliminate the null check that likely precedes this call
- val initial = dropConsumed
- (0 until inst.produced).foldLeft(initial) { case (know, _) => know push UNKNOWN }
-
- case BOX(_) =>
- val value = in peek 0
- // we simulate boxing by, um, boxing the possible/impossible contents
- // so if we have Possible(1,2) originally then we'll end up with
- // a Possible(Boxed(1), Boxed(2))
- // Similarly, if we know the input is not a 0 then we'll know the
- // output is not a Boxed(0)
- val newValue = value match {
- case Possible(values) => Possible(values map Boxed)
- case Impossible(values) => Impossible(values map Boxed)
- }
- dropConsumed push newValue
-
- case UNBOX(_) =>
- val value = in peek 0
- val newValue = value match {
- // if we have a Possible, then all the possibilities
- // should themselves be Boxes. In that
- // case we can merge them to figure out what the UNBOX will produce
- case Possible(inners) =>
- assert(inners.nonEmpty, "Empty possible set indicating an uninitialized location")
- val sanitized: Set[Contents] = (inners map {
- case Boxed(content) => SinglePossible(content)
- case _ => UNKNOWN
- })
- sanitized reduce (_ merge _)
- // if we have an impossible then the thing that's impossible
- // should be a box. We'll unbox that to see what we get
- case unknown@Impossible(inners) =>
- if (inners.isEmpty) {
- unknown
- } else {
- val sanitized: Set[Contents] = (inners map {
- case Boxed(content) => SingleImpossible(content)
- case _ => UNKNOWN
- })
- sanitized reduce (_ merge _)
- }
- }
- dropConsumed push newValue
-
- case LOAD_MODULE(_) | NEW(_) | LOAD_EXCEPTION(_) =>
- in push NOT_NULL
-
- case CREATE_ARRAY(_, _) =>
- dropConsumed push NOT_NULL
-
- case IS_INSTANCE(_) =>
- // TODO IS_INSTANCE is going to be followed by a C(Z)JUMP
- // and if IS_INSTANCE/C(Z)JUMP the branch for "true" can
- // know that whatever was checked was not a null
- // see the TODO on CJUMP for more information about propagating null
- // information
- // TODO if the top of stack is guaranteed null then we can eliminate this IS_INSTANCE check and
- // replace with a constant false, but how often is a knowable null checked for instanceof?
- // TODO we could track type information and statically know to eliminate IS_INSTANCE
- // which might be a nice win under specialization
- dropConsumed push UNKNOWN // it's actually a Possible(true, false) but since the following instruction
- // will be a conditional jump comparing to true or false there
- // nothing to be gained by being more precise
-
- case CHECK_CAST(_) =>
- // TODO we could track type information and statically know to eliminate CHECK_CAST
- // but that's probably not a huge win
- in
-
- case DUP(_) =>
- val value = in peek 0
- in push value
-
- case DROP(_) | MONITOR_ENTER() | MONITOR_EXIT() | STORE_ARRAY_ITEM(_) | STORE_FIELD(_, _) =>
- dropConsumed
-
- case SCOPE_ENTER(_) | SCOPE_EXIT(_) =>
- in
-
- case JUMP(_) | CJUMP(_, _, _, _) | CZJUMP(_, _, _, _) | RETURN(_) | THROW(_) | SWITCH(_, _) =>
- dumpClassesAndAbort("Unexpected block ending instruction: " + inst)
- }
- }
- /**
- * interpret the last instruction of a block which will be jump, a conditional branch, a throw, or a return.
- * It will result in a map from target blocks to the input state computed for that block. It
- * also computes a replacement list of instructions
- */
- private def interpretLast(in: State, inst: Instruction): (Map[BasicBlock, State], List[Instruction]) = {
- def canSwitch(in1: Contents, tagSet: List[Int]) = {
- in1 mightEqual Possible(tagSet.toSet map { tag: Int => Const(Constant(tag)) })
- }
-
- /* common code for interpreting CJUMP and CZJUMP */
- def interpretConditional(kind: TypeKind, val1: Contents, val2: Contents, success: BasicBlock, failure: BasicBlock, cond: TestOp): (Map[BasicBlock, State], List[Instruction]) = {
- // TODO use reaching analysis to update the state in the two branches
- // e.g. if the comparison was checking null equality on local x
- // then the in the success branch we know x is null and
- // on the failure branch we know it is not
- // in fact, with copy propagation we could propagate that knowledge
- // back through a chain of locations
- //
- // TODO if we do all that we need to be careful in the
- // case that success and failure are the same target block
- // because we're using a Map and don't want one possible state to clobber the other
- // alternative maybe we should just replace the conditional with a jump if both targets are the same
-
- def mightEqual = val1 mightEqual val2
- def mightNotEqual = val1 mightNotEqual val2
- def guaranteedEqual = mightEqual && !mightNotEqual
-
- def succPossible = cond match {
- case EQ => mightEqual
- case NE => mightNotEqual
- case LT | GT => !guaranteedEqual // if the two are guaranteed to be equal then they can't be LT/GT
- case LE | GE => true
- }
-
- def failPossible = cond match {
- case EQ => mightNotEqual
- case NE => mightEqual
- case LT | GT => true
- case LE | GE => !guaranteedEqual // if the two are guaranteed to be equal then they must be LE/GE
- }
-
- val out = in drop inst.consumed
-
- var result = Map[BasicBlock, State]()
- if (succPossible) {
- result += ((success, out))
- }
-
- if (failPossible) {
- result += ((failure, out))
- }
-
- val replacements = if (result.size == 1) List.fill(inst.consumed)(DROP(kind)) :+ JUMP(result.keySet.head)
- else inst :: Nil
-
- (result, replacements)
- }
-
- inst match {
- case JUMP(whereto) =>
- (Map((whereto, in)), inst :: Nil)
-
- case CJUMP(success, failure, cond, kind) =>
- val in1 = in peek 0
- val in2 = in peek 1
- interpretConditional(kind, in1, in2, success, failure, cond)
-
- case CZJUMP(success, failure, cond, kind) =>
- val in1 = in peek 0
- val in2 = getZeroOf(kind)
- interpretConditional(kind, in1, in2, success, failure, cond)
-
- case SWITCH(tags, labels) =>
- val in1 = in peek 0
- val reachableNormalLabels = tags zip labels collect { case (tagSet, label) if canSwitch(in1, tagSet) => label }
- val reachableLabels = if (tags.isEmpty) {
- assert(labels.size == 1, s"When SWITCH node has empty array of tags it should have just one (default) label: $labels")
- labels
- } else if (labels.lengthCompare(tags.length) > 0) {
- // if we've got an extra label then it's the default
- val defaultLabel = labels.last
- // see if the default is reachable by seeing if the input might be out of the set
- // of all tags
- val allTags = Possible(tags.flatten.toSet map { tag: Int => Const(Constant(tag)) })
- if (in1 mightNotEqual allTags) {
- reachableNormalLabels :+ defaultLabel
- } else {
- reachableNormalLabels
- }
- } else {
- reachableNormalLabels
- }
- // TODO similar to the comment in interpretConditional, we should update our the State going into each
- // branch based on which tag is being matched. Also, just like interpretConditional, if target blocks
- // are the same we need to merge State rather than clobber
-
- // alternative, maybe we should simplify the SWITCH to not have same target labels
- val newState = in drop inst.consumed
- val result = Map(reachableLabels map { label => (label, newState) }: _*)
- if (reachableLabels.size == 1) (result, DROP(INT) :: JUMP(reachableLabels.head) :: Nil)
- else (result, inst :: Nil)
-
- // these instructions don't have target blocks
- // (exceptions are assumed to be reachable from all instructions)
- case RETURN(_) | THROW(_) =>
- (Map.empty, inst :: Nil)
-
- case _ =>
- dumpClassesAndAbort("Unexpected non-block ending instruction: " + inst)
- }
- }
-
- /**
- * Analyze a single block to find how it transforms an input state into a states for its successor blocks
- * Also computes a list of instructions to be used to replace its last instruction
- */
- private def interpretBlock(in: State, block: BasicBlock): (Map[BasicBlock, State], Map[BasicBlock, State], List[Instruction]) = {
- debuglog(s"interpreting block $block")
- // number of instructions excluding the last one
- val normalCount = block.size - 1
-
- var exceptionState = in.cleanStack
- var normalExitState = in
- var idx = 0
- while (idx < normalCount) {
- val inst = block(idx)
- normalExitState = interpretInst(normalExitState, inst)
- if (normalExitState.locals ne exceptionState.locals)
- exceptionState = exceptionState.copy(locals = exceptionState mergeLocals normalExitState.locals)
- idx += 1
- }
-
- val pairs = block.exceptionSuccessors map { b => (b, exceptionState) }
- val exceptionMap = Map(pairs: _*)
-
- val (normalExitMap, newInstructions) = interpretLast(normalExitState, block.lastInstruction)
-
- (normalExitMap, exceptionMap, newInstructions)
- }
-
- /**
- * Analyze a single method to find replacement instructions
- */
- private def interpretMethod(m: IMethod): Map[BasicBlock, List[Instruction]] = {
- import scala.collection.mutable.{ Set => MSet, Map => MMap }
-
- debuglog(s"interpreting method $m")
- var iterations = 0
-
- // initially we know that 'this' is not null and the params are initialized to some unknown value
- val initThis: Iterator[(Local, Contents)] = if (m.isStatic) Iterator.empty else Iterator.single((THIS_LOCAL, NOT_NULL))
- val initOtherLocals: Iterator[(Local, Contents)] = m.params.iterator map { param => (param, UNKNOWN) }
- val initialLocals: Map[Local, Contents] = Map((initThis ++ initOtherLocals).toSeq: _*)
- val initialState = State(initialLocals, Nil)
-
- // worklist of basic blocks to process, initially the start block
- val worklist = MSet(m.startBlock)
- // worklist of exception basic blocks. They're kept in a separate set so they can be
- // processed after normal flow basic blocks. That's because exception basic blocks
- // are more likely to have multiple predecessors and queueing them for later
- // increases the chances that they'll only need to be interpreted once
- val exceptionlist = MSet[BasicBlock]()
- // our current best guess at what the input state is for each block
- // initially we only know about the start block
- val inputState = MMap[BasicBlock, State]((m.startBlock, initialState))
-
- // update the inputState map based on new information from interpreting a block
- // When the input state of a block changes, add it back to the work list to be
- // reinterpreted
- def updateInputStates(outputStates: Map[BasicBlock, State], worklist: MSet[BasicBlock]) {
- for ((block, newState) <- outputStates) {
- val oldState = inputState get block
- val updatedState = oldState map (x => x merge newState) getOrElse newState
- if (oldState != Some(updatedState)) {
- worklist add block
- inputState(block) = updatedState
- }
- }
- }
-
- // the instructions to be used as the last instructions on each block
- val replacements = MMap[BasicBlock, List[Instruction]]()
-
- while (worklist.nonEmpty || exceptionlist.nonEmpty) {
- if (worklist.isEmpty) {
- // once the worklist is empty, start processing exception blocks
- val block = exceptionlist.head
- exceptionlist remove block
- worklist add block
- } else {
- iterations += 1
- val block = worklist.head
- worklist remove block
- val (normalExitMap, exceptionMap, newInstructions) = interpretBlock(inputState(block), block)
-
- updateInputStates(normalExitMap, worklist)
- updateInputStates(exceptionMap, exceptionlist)
- replacements(block) = newInstructions
- }
- }
-
- debuglog(s"method $m with ${m.blocks.size} reached fixpoint in $iterations iterations")
- replacements.toMap
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala b/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala
deleted file mode 100644
index 8911a3a28c..0000000000
--- a/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala
+++ /dev/null
@@ -1,450 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Iulian Dragos
- */
-
-
-package scala.tools.nsc
-package backend.opt
-
-import scala.collection.{ mutable, immutable }
-
-/**
- */
-abstract class DeadCodeElimination extends SubComponent {
- import global._
- import icodes._
- import icodes.opcodes._
- import definitions.RuntimePackage
-
- /** The block and index where an instruction is located */
- type InstrLoc = (BasicBlock, Int)
-
- val phaseName = "dce"
-
- override val enabled: Boolean = settings.Xdce
-
- /** Create a new phase */
- override def newPhase(p: Phase) = new DeadCodeEliminationPhase(p)
-
- /** Dead code elimination phase.
- */
- class DeadCodeEliminationPhase(prev: Phase) extends ICodePhase(prev) {
-
- def name = phaseName
- val dce = new DeadCode()
-
- override def apply(c: IClass) {
- if (settings.Xdce && (dce ne null))
- dce.analyzeClass(c)
- }
- }
-
- /** closures that are instantiated at least once, after dead code elimination */
- val liveClosures = perRunCaches.newSet[Symbol]()
-
- /** closures that are eliminated, populated by GenASM.AsmPhase.run()
- * these class symbols won't have a .class physical file, thus shouldn't be included in InnerClasses JVM attribute,
- * otherwise some tools get confused or slow (SI-6546)
- * */
- val elidedClosures = perRunCaches.newSet[Symbol]()
-
- /** Remove dead code.
- */
- class DeadCode {
-
- def analyzeClass(cls: IClass) {
- log(s"Analyzing ${cls.methods.size} methods in $cls.")
- cls.methods.foreach { m =>
- this.method = m
- dieCodeDie(m)
- global.closureElimination.peephole(m)
- }
- }
-
- val rdef = new reachingDefinitions.ReachingDefinitionsAnalysis
-
- /** Use-def chain: give the reaching definitions at the beginning of given instruction. */
- var defs: immutable.Map[InstrLoc, immutable.Set[rdef.lattice.Definition]] = immutable.HashMap.empty
-
- /** Useful instructions which have not been scanned yet. */
- val worklist: mutable.Set[InstrLoc] = new mutable.LinkedHashSet
-
- /** what instructions have been marked as useful? */
- val useful: mutable.Map[BasicBlock, mutable.BitSet] = perRunCaches.newMap()
-
- /** what local variables have been accessed at least once? */
- var accessedLocals: List[Local] = Nil
-
- /** Map from a local and a basic block to the instructions that store to that local in that basic block */
- val localStores = mutable.Map[(Local, BasicBlock), mutable.BitSet]() withDefault {_ => mutable.BitSet()}
-
- /** Stores that clobber previous stores to array or ref locals. See SI-5313 */
- val clobbers = mutable.Set[InstrLoc]()
-
- /** the current method. */
- var method: IMethod = _
-
- /** Map instructions who have a drop on some control path, to that DROP instruction. */
- val dropOf: mutable.Map[InstrLoc, List[InstrLoc]] = perRunCaches.newMap()
-
- def dieCodeDie(m: IMethod) {
- if (m.hasCode) {
- debuglog("dead code elimination on " + m)
- dropOf.clear()
- localStores.clear()
- clobbers.clear()
- m.code.blocks.clear()
- m.code.touched = true
- accessedLocals = m.params.reverse
- m.code.blocks ++= linearizer.linearize(m)
- m.code.touched = true
- collectRDef(m)
- mark()
- sweep(m)
- accessedLocals = accessedLocals.distinct
- val diff = m.locals diff accessedLocals
- if (diff.nonEmpty) {
- val msg = diff.map(_.sym.name)mkString(", ")
- log(s"Removed ${diff.size} dead locals: $msg")
- m.locals = accessedLocals.reverse
- }
- }
- }
-
- /** collect reaching definitions and initial useful instructions for this method. */
- def collectRDef(m: IMethod): Unit = if (m.hasCode) {
- defs = immutable.HashMap.empty; worklist.clear(); useful.clear()
- rdef.init(m)
- rdef.run()
-
- m foreachBlock { bb =>
- useful(bb) = new mutable.BitSet(bb.size)
- var rd = rdef.in(bb)
- for ((i, idx) <- bb.toList.zipWithIndex) {
-
- // utility for adding to worklist
- def moveToWorkList() = moveToWorkListIf(cond = true)
-
- // utility for (conditionally) adding to worklist
- def moveToWorkListIf(cond: Boolean) =
- if (cond) {
- debuglog("in worklist: " + i)
- worklist += ((bb, idx))
- } else {
- debuglog("not in worklist: " + i)
- }
-
- // instruction-specific logic
- i match {
-
- case LOAD_LOCAL(_) =>
- defs = defs + (((bb, idx), rd.vars))
- moveToWorkListIf(cond = false)
-
- case STORE_LOCAL(l) =>
- /* SI-4935 Check whether a module is stack top, if so mark the instruction that loaded it
- * (otherwise any side-effects of the module's constructor go lost).
- * (a) The other two cases where a module's value is stored (STORE_FIELD and STORE_ARRAY_ITEM)
- * are already marked (case clause below).
- * (b) A CALL_METHOD targeting a method `m1` where the receiver is potentially a module (case clause below)
- * will have the module's load marked provided `isSideEffecting(m1)`.
- * TODO check for purity (the ICode?) of the module's constructor (besides m1's purity).
- * See also https://github.com/paulp/scala/blob/topic/purity-analysis/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala
- */
- val necessary = rdef.findDefs(bb, idx, 1) exists { p =>
- val (bb1, idx1) = p
- bb1(idx1) match {
- case LOAD_MODULE(module) => isLoadNeeded(module)
- case _ => false
- }
- }
- moveToWorkListIf(necessary)
-
- // add it to the localStores map
- val key = (l, bb)
- val set = localStores(key)
- set += idx
- localStores(key) = set
-
- case RETURN(_) | JUMP(_) | CJUMP(_, _, _, _) | CZJUMP(_, _, _, _) | STORE_FIELD(_, _) |
- THROW(_) | LOAD_ARRAY_ITEM(_) | STORE_ARRAY_ITEM(_) | SCOPE_ENTER(_) | SCOPE_EXIT(_) | STORE_THIS(_) |
- LOAD_EXCEPTION(_) | SWITCH(_, _) | MONITOR_ENTER() | MONITOR_EXIT() | CHECK_CAST(_) | CREATE_ARRAY(_, _) =>
- moveToWorkList()
-
- case LOAD_FIELD(sym, isStatic) if isStatic || !inliner.isClosureClass(sym.owner) =>
- // static load may trigger static initialization.
- // non-static load can throw NPE (but we know closure fields can't be accessed via a
- // null reference.
- moveToWorkList()
- case CALL_METHOD(m1, _) if isSideEffecting(m1) =>
- moveToWorkList()
-
- case CALL_METHOD(m1, SuperCall(_)) =>
- moveToWorkList() // super calls to constructor
-
- case DROP(_) =>
- val necessary = rdef.findDefs(bb, idx, 1) exists { p =>
- val (bb1, idx1) = p
- bb1(idx1) match {
- case CALL_METHOD(m1, _) if isSideEffecting(m1) => true
- case LOAD_EXCEPTION(_) | DUP(_) | LOAD_MODULE(_) => true
- case _ =>
- dropOf((bb1, idx1)) = (bb,idx) :: dropOf.getOrElse((bb1, idx1), Nil)
- debuglog("DROP is inessential: " + i + " because of: " + bb1(idx1) + " at " + bb1 + ":" + idx1)
- false
- }
- }
- moveToWorkListIf(necessary)
- case LOAD_MODULE(sym) if isLoadNeeded(sym) =>
- moveToWorkList() // SI-4859 Module initialization might side-effect.
- case CALL_PRIMITIVE(Arithmetic(DIV | REM, INT | LONG) | ArrayLength(_)) =>
- moveToWorkList() // SI-8601 Might divide by zero
- case _ => ()
- moveToWorkListIf(cond = false)
- }
- rd = rdef.interpret(bb, idx, rd)
- }
- }
- }
-
- private def isLoadNeeded(module: Symbol): Boolean = {
- module.info.member(nme.CONSTRUCTOR).filter(isSideEffecting) != NoSymbol
- }
-
- /** Mark useful instructions. Instructions in the worklist are each inspected and their
- * dependencies are marked useful too, and added to the worklist.
- */
- def mark() {
-// log("Starting with worklist: " + worklist)
- while (!worklist.isEmpty) {
- val (bb, idx) = worklist.head
- worklist -= ((bb, idx))
- debuglog("Marking instr: \tBB_" + bb + ": " + idx + " " + bb(idx))
-
- val instr = bb(idx)
- // adds the instructions that define the stack values about to be consumed to the work list to
- // be marked useful
- def addDefs() = for ((bb1, idx1) <- rdef.findDefs(bb, idx, instr.consumed) if !useful(bb1)(idx1)) {
- debuglog(s"\t${bb1(idx1)} is consumed by $instr")
- worklist += ((bb1, idx1))
- }
-
- // DROP logic -- if an instruction is useful, its drops are also useful
- // and we don't mark the DROPs as useful directly but add them to the
- // worklist so we also mark their reaching defs as useful - see SI-7060
- if (!useful(bb)(idx)) {
- useful(bb) += idx
- dropOf.get((bb, idx)) foreach {
- for ((bb1, idx1) <- _) {
- /*
- * SI-7060: A drop that we now mark as useful can be reached via several paths,
- * so we should follow by marking all its reaching definition as useful too:
- */
- debuglog("\tAdding: " + bb1(idx1) + " to the worklist, as a useful DROP.")
- worklist += ((bb1, idx1))
- }
- }
-
- // per-instruction logic
- instr match {
- case LOAD_LOCAL(l1) =>
- for ((l2, bb1, idx1) <- defs((bb, idx)) if l1 == l2; if !useful(bb1)(idx1)) {
- debuglog("\tAdding " + bb1(idx1))
- worklist += ((bb1, idx1))
- }
-
- case STORE_LOCAL(l1) if l1.kind.isRefOrArrayType =>
- addDefs()
- // see SI-5313
- // search for clobbers of this store if we aren't doing l1 = null
- // this doesn't catch the second store in x=null;l1=x; but in practice this catches
- // a lot of null stores very cheaply
- if (idx == 0 || bb(idx - 1) != CONSTANT(Constant(null)))
- findClobbers(l1, bb, idx + 1)
-
- case nw @ NEW(REFERENCE(sym)) =>
- assert(nw.init ne null, "null new.init at: " + bb + ": " + idx + "(" + instr + ")")
- worklist += findInstruction(bb, nw.init)
- if (inliner.isClosureClass(sym)) {
- liveClosures += sym
- }
-
- // it may be better to move static initializers from closures to
- // the enclosing class, to allow the optimizer to remove more closures.
- // right now, the only static fields in closures are created when caching
- // 'symbol literals.
- case LOAD_FIELD(sym, true) if inliner.isClosureClass(sym.owner) =>
- log("added closure class for field " + sym)
- liveClosures += sym.owner
-
- case LOAD_EXCEPTION(_) =>
- ()
-
- case _ =>
- addDefs()
- }
- }
- }
- }
-
- /**
- * Finds and marks all clobbers of the given local starting in the given
- * basic block at the given index
- *
- * Storing to local variables of reference or array type may be indirectly
- * observable because it may remove a reference to an object which may allow the object
- * to be gc'd. See SI-5313. In this code I call the LOCAL_STORE(s) that immediately follow a
- * LOCAL_STORE and that store to the same local "clobbers." If a LOCAL_STORE is marked
- * useful then its clobbers must go into the set of clobbers, which will be
- * compensated for later
- */
- def findClobbers(l: Local, bb: BasicBlock, idx: Int) {
- // previously visited blocks tracked to prevent searching forever in a cycle
- val inspected = mutable.Set[BasicBlock]()
- // our worklist of blocks that still need to be checked
- val blocksToBeInspected = mutable.Set[BasicBlock]()
-
- // Tries to find the next clobber of l1 in bb1 starting at idx1.
- // if it finds one it adds the clobber to clobbers set for later
- // handling. If not it adds the direct successor blocks to
- // the uninspectedBlocks to try to find clobbers there. Either way
- // it adds the exception successor blocks for further search
- def findClobberInBlock(idx1: Int, bb1: BasicBlock) {
- val key = ((l, bb1))
- val foundClobber = (localStores contains key) && {
- def minIdx(s : mutable.BitSet) = if(s.isEmpty) -1 else s.min
-
- // find the smallest index greater than or equal to idx1
- val clobberIdx = minIdx(localStores(key) dropWhile (_ < idx1))
- if (clobberIdx == -1)
- false
- else {
- debuglog(s"\t${bb1(clobberIdx)} is a clobber of ${bb(idx)}")
- clobbers += ((bb1, clobberIdx))
- true
- }
- }
-
- // always need to look into the exception successors for additional clobbers
- // because we don't know when flow might enter an exception handler
- blocksToBeInspected ++= (bb1.exceptionSuccessors filterNot inspected)
- // If we didn't find a clobber here then we need to look at successor blocks.
- // if we found a clobber then we don't need to search in the direct successors
- if (!foundClobber) {
- blocksToBeInspected ++= (bb1.directSuccessors filterNot inspected)
- }
- }
-
- // first search starting at the current index
- // note we don't put bb in the inspected list yet because a loop may later force
- // us back around to search from the beginning of bb
- findClobberInBlock(idx, bb)
- // then loop until we've exhausted the set of uninspected blocks
- while(!blocksToBeInspected.isEmpty) {
- val bb1 = blocksToBeInspected.head
- blocksToBeInspected -= bb1
- inspected += bb1
- findClobberInBlock(0, bb1)
- }
- }
-
- def sweep(m: IMethod) {
- val compensations = computeCompensations(m)
-
- debuglog("Sweeping: " + m)
-
- m foreachBlock { bb =>
- debuglog(bb + ":")
- val oldInstr = bb.toList
- bb.open()
- bb.clear()
- for ((i, idx) <- oldInstr.zipWithIndex) {
- if (useful(bb)(idx)) {
- debuglog(" * " + i + " is useful")
- bb.emit(i, i.pos)
- compensations.get((bb, idx)) match {
- case Some(is) => is foreach bb.emit
- case None => ()
- }
- // check for accessed locals
- i match {
- case LOAD_LOCAL(l) if !l.arg =>
- accessedLocals = l :: accessedLocals
- case STORE_LOCAL(l) if !l.arg =>
- accessedLocals = l :: accessedLocals
- case _ => ()
- }
- } else {
- i match {
- case NEW(REFERENCE(sym)) =>
- log(s"Eliminated instantiation of $sym inside $m")
- case STORE_LOCAL(l) if clobbers contains ((bb, idx)) =>
- // if an unused instruction was a clobber of a used store to a reference or array type
- // then we'll replace it with the store of a null to make sure the reference is
- // eliminated. See SI-5313
- bb emit CONSTANT(Constant(null))
- bb emit STORE_LOCAL(l)
- case _ => ()
- }
- debuglog(" " + i + " [swept]")
- }
- }
-
- if (bb.nonEmpty) bb.close()
- else log(s"empty block encountered in $m")
- }
- }
-
- private def computeCompensations(m: IMethod): mutable.Map[InstrLoc, List[Instruction]] = {
- val compensations: mutable.Map[InstrLoc, List[Instruction]] = new mutable.HashMap
-
- m foreachBlock { bb =>
- assert(bb.closed, "Open block in computeCompensations")
- foreachWithIndex(bb.toList) { (i, idx) =>
- if (!useful(bb)(idx)) {
- foreachWithIndex(i.consumedTypes.reverse) { (consumedType, depth) =>
- debuglog("Finding definitions of: " + i + "\n\t" + consumedType + " at depth: " + depth)
- val defs = rdef.findDefs(bb, idx, 1, depth)
- for (d <- defs) {
- val (bb, idx) = d
- debuglog("rdef: "+ bb(idx))
- bb(idx) match {
- case DUP(_) if idx > 0 =>
- bb(idx - 1) match {
- case nw @ NEW(_) =>
- val init = findInstruction(bb, nw.init)
- log("Moving DROP to after <init> call: " + nw.init)
- compensations(init) = List(DROP(consumedType))
- case _ =>
- compensations(d) = List(DROP(consumedType))
- }
- case _ =>
- compensations(d) = List(DROP(consumedType))
- }
- }
- }
- }
- }
- }
- compensations
- }
-
- private def findInstruction(bb: BasicBlock, i: Instruction): InstrLoc = {
- for (b <- linearizer.linearizeAt(method, bb)) {
- val idx = b.toList indexWhere (_ eq i)
- if (idx != -1)
- return (b, idx)
- }
- abort("could not find init in: " + method)
- }
-
- private def isPure(sym: Symbol) = (
- (sym.isGetter && sym.isEffectivelyFinalOrNotOverridden && !sym.isLazy)
- || (sym.isPrimaryConstructor && (sym.enclosingPackage == RuntimePackage || inliner.isClosureClass(sym.owner)))
- )
- /** Is 'sym' a side-effecting method? TODO: proper analysis. */
- private def isSideEffecting(sym: Symbol) = !isPure(sym)
-
- } /* DeadCode */
-}
diff --git a/src/compiler/scala/tools/nsc/backend/opt/InlineExceptionHandlers.scala b/src/compiler/scala/tools/nsc/backend/opt/InlineExceptionHandlers.scala
deleted file mode 100644
index 9f6883f03f..0000000000
--- a/src/compiler/scala/tools/nsc/backend/opt/InlineExceptionHandlers.scala
+++ /dev/null
@@ -1,392 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- */
-
-package scala.tools.nsc
-package backend.opt
-
-import java.util.concurrent.TimeUnit
-
-/**
- * This optimization phase inlines the exception handlers so that further phases can optimize the code better
- *
- * {{{
- * try {
- * ...
- * if (condition)
- * throw IllegalArgumentException("sth")
- * } catch {
- * case e: IllegalArgumentException => <handler code>
- * case e: ... => ...
- * }
- * }}}
- *
- * will inline the exception handler code to:
- *
- * {{{
- * try {
- * ...
- * if (condition)
- * <handler code> // + jump to the end of the catch statement
- * } catch {
- * case e: IllegalArgumentException => <handler code>
- * case e: ... => ...
- * }
- * }}}
- *
- * Q: How does the inlining work, ICode level?
- * A: if a block contains a THROW(A) instruction AND there is a handler that takes A or a superclass of A we do:
- * 1. We duplicate the handler code such that we can transform THROW into a JUMP
- * 2. We analyze the handler to see what local it expects the exception to be placed in
- * 3. We place the exception that is thrown in the correct "local variable" slot and clean up the stack
- * 4. We finally JUMP to the duplicate handler
- * All the above logic is implemented in InlineExceptionHandlersPhase.apply(bblock: BasicBlock)
- *
- * Q: Why do we need to duplicate the handler?
- * A: An exception might be thrown in a method that we invoke in the function and we cannot see that THROW command
- * directly. In order to catch such exceptions, we keep the exception handler in place and duplicate it in order
- * to inline its code.
- *
- * @author Vlad Ureche
- */
-abstract class InlineExceptionHandlers extends SubComponent {
- import global._
- import icodes._
- import icodes.opcodes._
-
- val phaseName = "inlinehandlers"
-
- /** Create a new phase */
- override def newPhase(p: Phase) = new InlineExceptionHandlersPhase(p)
-
- override def enabled = settings.inlineHandlers
-
- /**
- * Inlining Exception Handlers
- */
- class InlineExceptionHandlersPhase(prev: Phase) extends ICodePhase(prev) {
- def name = phaseName
-
- /* This map is used to keep track of duplicated exception handlers
- * explanation: for each exception handler basic block, there is a copy of it
- * -some exception handler basic blocks might not be duplicated because they have an unknown format => Option[(...)]
- * -some exception handler duplicates expect the exception on the stack while others expect it in a local
- * => Option[Local]
- */
- private val handlerCopies = perRunCaches.newMap[BasicBlock, Option[(Option[Local], BasicBlock)]]()
- /* This map is the inverse of handlerCopies, used to compute the stack of duplicate blocks */
- private val handlerCopiesInverted = perRunCaches.newMap[BasicBlock, (BasicBlock, TypeKind)]()
- private def handlerLocal(bb: BasicBlock): Option[Local] =
- for (v <- handlerCopies get bb ; (local, block) <- v ; l <- local) yield l
-
- /* Type Flow Analysis */
- private val tfa: analysis.MethodTFA = new analysis.MethodTFA()
- private var tfaCache: Map[Int, tfa.lattice.Elem] = Map.empty
- private var analyzedMethod: IMethod = NoIMethod
-
- /* Blocks that need to be analyzed */
- private var todoBlocks: List[BasicBlock] = Nil
-
- /* Used only for warnings */
- private var currentClass: IClass = null
-
- /** Apply exception handler inlining to a class */
- override def apply(c: IClass): Unit =
- if (settings.inlineHandlers) {
- val startTime = System.nanoTime()
- currentClass = c
-
- debuglog("Starting InlineExceptionHandlers on " + c)
- c.methods foreach applyMethod
- debuglog("Finished InlineExceptionHandlers on " + c + "... " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + "ms")
- currentClass = null
- }
-
- /**
- * Apply exception handler inlining to a method
- *
- * Note: for each exception handling block, we (might) create duplicates. Therefore we iterate until we get to a
- * fixed point where all the possible handlers have been inlined.
- *
- * TODO: Should we have an inlining depth limit? A nested sequence of n try-catch blocks can lead to at most 2n
- * inlined blocks, so worst case scenario we double the size of the code
- */
- private def applyMethod(method: IMethod): Unit = {
- if (method.hasCode) {
- // create the list of starting blocks
- todoBlocks = global.icodes.linearizer.linearize(method)
-
- while (todoBlocks.nonEmpty) {
- val levelBlocks = todoBlocks
- todoBlocks = Nil
- levelBlocks foreach applyBasicBlock // new blocks will be added to todoBlocks
- }
- }
-
- // Cleanup the references after we finished the file
- handlerCopies.clear()
- handlerCopiesInverted.clear()
- todoBlocks = Nil
-
- // Type flow analysis cleanup
- analyzedMethod = NoIMethod
- tfaCache = Map.empty
- //TODO: Need a way to clear tfa structures
- }
-
- /** Apply exception handler inlining to a basic block */
- private def applyBasicBlock(bblock: BasicBlock): Unit = {
- /*
- * The logic of this entire method:
- * - for each basic block, we look at each instruction until we find a THROW instruction
- * - once we found a THROW instruction, we decide if it is DECIDABLE which of handler will catch the exception
- * (see method findExceptionHandler for more details)
- * - if we decided there is a handler that will catch the exception, we need to replace the THROW instruction by
- * a set of equivalent instructions:
- * * we need to compute the static types of the stack slots
- * * we need to clear the stack, everything but the exception instance on top (or in a local variable slot)
- * * we need to JUMP to the duplicate exception handler
- * - we compute the static types of the stack slots in function getTypesAtInstruction
- * - we duplicate the exception handler (and we get back the information of whether the duplicate expects the
- * exception instance on top of the stack or in a local variable slot)
- * - we compute the necessary code to put the exception in its place, clear the stack and JUMP
- * - we change the THROW exception to the new Clear stack + JUMP code
- */
- for {
- (instr @ THROW(clazz), index) <- bblock.iterator.zipWithIndex
- // Decide if any handler fits this exception
- // If not, then nothing to do, we cannot determine statically which handler will catch the exception
- (handler, caughtException) <- findExceptionHandler(toTypeKind(clazz.tpe), bblock.exceptionSuccessors)
- } {
- log(" Replacing " + instr + " in " + bblock + " to new handler")
-
- // Solve the stack and drop the element that we already stored, which should be the exception
- // needs to be done here to be the first thing before code becomes altered
- val typeInfo = getTypesAtInstruction(bblock, index)
-
- // Duplicate exception handler
- duplicateExceptionHandlerCache(handler) match {
- case None =>
- log(" Could not duplicate handler for " + instr + " in " + bblock)
-
- case Some((exceptionLocalOpt, newHandler)) =>
- val onStackException = typeInfo.head
- val thrownException = toTypeKind(clazz.tpe)
-
- // A couple of sanity checks, to make sure we don't touch code we can't safely handle
- val canReplaceHandler = (
- typeInfo.nonEmpty
- && (index == bblock.length - 1)
- && (onStackException <:< thrownException)
- )
- // in other words: what's on the stack MUST conform to what's in the THROW(..)!
-
- if (!canReplaceHandler) {
- reporter.warning(NoPosition, "Unable to inline the exception handler inside incorrect" +
- " block:\n" + bblock.iterator.mkString("\n") + "\nwith stack: " + typeInfo + " just " +
- "before instruction index " + index)
- }
- else {
- // Prepare the new code to replace the THROW instruction
- val newCode = exceptionLocalOpt match {
- // the handler duplicate expects the exception in a local: easy one :)
- case Some(local) =>
- // in the first cycle we remove the exception Type
- STORE_LOCAL(local) +: typeInfo.tail.map(x => DROP(x)) :+ JUMP(newHandler)
-
- // we already have the exception on top of the stack, only need to JUMP
- case None if typeInfo.length == 1 =>
- JUMP(newHandler) :: Nil
-
- // we have the exception on top of the stack but we have other stuff on the stack
- // create a local, load exception, clear the stack and finally store the exception on the stack
- case _ =>
- val exceptionType = typeInfo.head
- // Here we could create a single local for all exceptions of a certain type. TODO: try that.
- val localName = currentClass.cunit.freshTermName("exception$")
- val localType = exceptionType
- val localSymbol = bblock.method.symbol.newValue(localName).setInfo(localType.toType)
- val local = new Local(localSymbol, localType, false)
-
- bblock.method.addLocal(local)
-
- // Save the exception, drop the stack and place back the exception
- STORE_LOCAL(local) :: typeInfo.tail.map(x => DROP(x)) ::: List(LOAD_LOCAL(local), JUMP(newHandler))
- }
- // replace THROW by the new code
- bblock.replaceInstruction(instr, newCode)
-
- // notify the successors changed for the current block
- // notify the predecessors changed for the inlined handler block
- bblock.touched = true
- newHandler.touched = true
-
- log(" Replaced " + instr + " in " + bblock + " to new handler")
- log("OPTIMIZED class " + currentClass + " method " +
- bblock.method + " block " + bblock + " newhandler " +
- newHandler + ":\n\t\t" + onStackException + " <:< " +
- thrownException + " <:< " + caughtException)
-
- }
- }
- }
- }
-
- /**
- * Gets the types on the stack at a certain point in the program. Note that we want to analyze the method lazily
- * and therefore use the analyzedMethod variable
- */
- private def getTypesAtInstruction(bblock: BasicBlock, index: Int): List[TypeKind] = {
- // get the stack at the block entry
- var typeInfo = getTypesAtBlockEntry(bblock)
-
- // perform tfa to the current instruction
- log(" stack at the beginning of block " + bblock + " in function " +
- bblock.method + ": " + typeInfo.stack)
- for (i <- 0 to (index - 1)) {
- typeInfo = tfa.interpret(typeInfo, bblock(i))
- log(" stack after interpret: " + typeInfo.stack + " after instruction " +
- bblock(i))
- }
- log(" stack before instruction " + index + " of block " + bblock + " in function " +
- bblock.method + ": " + typeInfo.stack)
-
- // return the result
- typeInfo.stack.types
- }
-
- /**
- * Gets the stack at the block entry. Normally the typeFlowAnalysis should be run again, but we know how to compute
- * the stack for handler duplicates. For the locals, it's safe to assume the info from the original handler is
- * still valid (a more precise analysis can be done, but it's not necessary)
- */
- private def getTypesAtBlockEntry(bblock: BasicBlock): tfa.lattice.Elem = {
- // lazily perform tfa, because it's expensive
- // cache results by block label, as rewriting the code messes up the block's hashCode
- if (analyzedMethod eq NoIMethod) {
- analyzedMethod = bblock.method
- tfa.init(bblock.method)
- tfa.run()
- log(" performed tfa on method: " + bblock.method)
-
- for (block <- bblock.method.blocks.sortBy(_.label))
- tfaCache += block.label -> tfa.in(block)
- }
-
- log(" getting typeinfo at the beginning of block " + bblock)
-
- tfaCache.getOrElse(bblock.label, {
- // this block was not analyzed, but it's a copy of some other block so its stack should be the same
- log(" getting typeinfo at the beginning of block " + bblock + " as a copy of " +
- handlerCopiesInverted(bblock))
- val (origBlock, exception) = handlerCopiesInverted(bblock)
- val typeInfo = getTypesAtBlockEntry(origBlock)
- val stack =
- if (handlerLocal(origBlock).nonEmpty) Nil // empty stack, the handler copy expects an empty stack
- else List(exception) // one slot on the stack for the exception
-
- // If we use the mutability property, it crashes the analysis
- tfa.lattice.IState(new analysis.VarBinding(typeInfo.vars), new icodes.TypeStack(stack))
- })
- }
-
- /**
- * Finds the first exception handler that matches the current exception
- *
- * Note the following code:
- * {{{
- * try {
- * throw new IllegalArgumentException("...")
- * } catch {
- * case e: RuntimeException => log("RuntimeException")
- * case i: IllegalArgumentException => log("IllegalArgumentException")
- * }
- * }}}
- *
- * will print "RuntimeException" => we need the *first* valid handler
- *
- * There's a hidden catch here: say we have the following code:
- * {{{
- * try {
- * val exception: Throwable =
- * if (scala.util.Random.nextInt % 2 == 0)
- * new IllegalArgumentException("even")
- * else
- * new StackOverflowError("odd")
- * throw exception
- * } catch {
- * case e: IllegalArgumentException =>
- * println("Correct, IllegalArgumentException")
- * case e: StackOverflowError =>
- * println("Correct, StackOverflowException")
- * case t: Throwable =>
- * println("WROOOONG, not Throwable!")
- * }
- * }}}
- *
- * We don't want to select a handler if there's at least one that's more specific!
- */
- def findExceptionHandler(thrownException: TypeKind, handlers: List[BasicBlock]): Option[(BasicBlock, TypeKind)] = {
- for (handler <- handlers ; LOAD_EXCEPTION(clazz) <- handler take 1) {
- val caughtException = toTypeKind(clazz.tpe)
- // we'll do inlining here: createdException <:< thrownException <:< caughtException, good!
- if (thrownException <:< caughtException)
- return Some((handler, caughtException))
- // we can't do inlining here, the handling mechanism is more precise than we can reason about
- if (caughtException <:< thrownException)
- return None
- // no result yet, look deeper in the handler stack
- }
- None
- }
-
- /**
- * This function takes care of duplicating the basic block code for inlining the handler
- *
- * Note: This function does not duplicate the same basic block twice. It will contain a map of the duplicated
- * basic blocks
- */
- private def duplicateExceptionHandlerCache(handler: BasicBlock) =
- handlerCopies.getOrElseUpdate(handler, duplicateExceptionHandler(handler))
-
- /** This function takes care of actual duplication */
- private def duplicateExceptionHandler(handler: BasicBlock): Option[(Option[Local], BasicBlock)] = {
- log(" duplicating handler block " + handler)
-
- handler take 2 match {
- case Seq(LOAD_EXCEPTION(caughtClass), next) =>
- val (dropCount, exceptionLocal) = next match {
- case STORE_LOCAL(local) => (2, Some(local)) // we drop both LOAD_EXCEPTION and STORE_LOCAL
- case _ => (1, None) // we only drop the LOAD_EXCEPTION and expect the exception on the stack
- }
- val caughtException = toTypeKind(caughtClass.tpe)
- // copy the exception handler code once again, dropping the LOAD_EXCEPTION
- val copy = handler.code.newBlock()
- copy.emitOnly((handler.iterator drop dropCount).toSeq: _*)
-
- // extend the handlers of the handler to the copy
- for (parentHandler <- handler.method.exh ; if parentHandler covers handler) {
- parentHandler.addCoveredBlock(copy)
- // notify the parent handler that the successors changed
- parentHandler.startBlock.touched = true
- }
-
- // notify the successors of the inlined handler might have changed
- copy.touched = true
- handler.touched = true
- log(" duplicated handler block " + handler + " to " + copy)
-
- // announce the duplicate handler
- handlerCopiesInverted(copy) = ((handler, caughtException))
- todoBlocks ::= copy
-
- Some((exceptionLocal, copy))
-
- case _ =>
- reporter.warning(NoPosition, "Unable to inline the exception handler due to incorrect format:\n" +
- handler.iterator.mkString("\n"))
- None
- }
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala
deleted file mode 100644
index 8cd2a14066..0000000000
--- a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala
+++ /dev/null
@@ -1,1075 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Iulian Dragos
- */
-
-
-package scala.tools.nsc
-package backend.opt
-
-import scala.collection.mutable
-import scala.tools.nsc.symtab._
-import scala.reflect.internal.util.NoSourceFile
-
-/**
- * Inliner balances two competing goals:
- * (a) aggressive inlining of:
- * (a.1) the apply methods of anonymous closures, so that their anon-classes can be eliminated;
- * (a.2) higher-order-methods defined in an external library, e.g. `Range.foreach()` among many others.
- * (b) circumventing the barrier to inter-library inlining that private accesses in the callee impose.
- *
- * Summing up the discussion in SI-5442 and SI-5891,
- * the current implementation achieves to a large degree both goals above, and
- * overcomes a problem exhibited by previous versions:
- *
- * (1) Problem: Attempting to access a private member `p` at runtime resulting in an `IllegalAccessError`,
- * where `p` is defined in a library L, and is accessed from a library C (for Client),
- * where C was compiled against L', an optimized version of L where the inliner made `p` public at the bytecode level.
- * The only such members are fields, either synthetic or isParamAccessor, and thus having a dollar sign in their name
- * (the accessibility of methods and constructors isn't touched by the inliner).
- *
- * Thus we add one more goal to our list:
- * (c) Compile C (either optimized or not) against any of L or L',
- * so that it runs with either L or L' (in particular, compile against L' and run with L).
- *
- * The chosen strategy is described in some detail in the comments for `accessRequirements()` and `potentiallyPublicized()`.
- * Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2011Q4/Inliner.pdf
- *
- * @author Iulian Dragos
- */
-abstract class Inliners extends SubComponent {
- import global._
- import icodes._
- import icodes.opcodes._
- import definitions.{
- NullClass, NothingClass, ObjectClass,
- PredefModule, RuntimePackage, ScalaInlineClass, ScalaNoInlineClass,
- isFunctionType, isByNameParamType
- }
-
- val phaseName = "inliner"
-
- override val enabled: Boolean = settings.inline
-
- /** Debug - for timing the inliner. */
- /****
- private def timed[T](s: String, body: => T): T = {
- val t1 = System.currentTimeMillis()
- val res = body
- val t2 = System.currentTimeMillis()
- val ms = (t2 - t1).toInt
- if (ms >= MAX_INLINE_MILLIS)
- println("%s: %d milliseconds".format(s, ms))
-
- res
- }
- ****/
-
- /** Look up implementation of method 'sym in 'clazz'.
- */
- def lookupImplFor(sym: Symbol, clazz: Symbol): Symbol = {
- // TODO: verify that clazz.superClass is equivalent here to clazz.tpe.parents(0).typeSymbol (.tpe vs .info)
- def needsLookup = (
- (clazz != NoSymbol)
- && (clazz != sym.owner)
- && !sym.isEffectivelyFinalOrNotOverridden
- && clazz.isEffectivelyFinalOrNotOverridden
- )
- def lookup(clazz: Symbol): Symbol = {
- // println("\t\tlooking up " + meth + " in " + clazz.fullName + " meth.owner = " + meth.owner)
- assert(clazz != NoSymbol, "Walked up past Object.superClass looking for " + sym +
- ", most likely this reveals the TFA at fault (receiver and callee don't match).")
- if (sym.owner == clazz || isBottomType(clazz)) sym
- else sym.overridingSymbol(clazz) orElse (
- if (sym.owner.isTrait) sym
- else lookup(clazz.superClass)
- )
- }
- if (needsLookup) {
- val concreteMethod = lookup(clazz)
- debuglog("\tlooked up method: " + concreteMethod.fullName)
-
- concreteMethod
- }
- else sym
- }
-
- /* A warning threshold */
- private final val MAX_INLINE_MILLIS = 2000
-
- /** The maximum size in basic blocks of methods considered for inlining. */
- final val MAX_INLINE_SIZE = 16
-
- /** Maximum loop iterations. */
- final val MAX_INLINE_RETRY = 15
-
- /** Small method size (in blocks) */
- val SMALL_METHOD_SIZE = 1
-
- /** Create a new phase */
- override def newPhase(p: Phase) = new InliningPhase(p)
-
- /** The Inlining phase.
- */
- class InliningPhase(prev: Phase) extends ICodePhase(prev) {
- def name = phaseName
- val inliner = new Inliner
-
- object iclassOrdering extends Ordering[IClass] {
- def compare(a: IClass, b: IClass) = {
- val sourceNamesComparison = (a.cunit.toString() compare b.cunit.toString())
- if(sourceNamesComparison != 0) sourceNamesComparison
- else {
- val namesComparison = (a.toString() compare b.toString())
- if(namesComparison != 0) namesComparison
- else {
- a.symbol.id compare b.symbol.id
- }
- }
- }
- }
- val queue = new mutable.PriorityQueue[IClass]()(iclassOrdering)
-
- override def apply(c: IClass) { queue += c }
-
- override def run() {
- knownLacksInline.clear()
- knownHasInline.clear()
- try {
- super.run()
- for(c <- queue) { inliner analyzeClass c }
- } finally {
- inliner.clearCaches()
- knownLacksInline.clear()
- knownHasInline.clear()
- }
- }
- }
-
- def isBottomType(sym: Symbol) = sym == NullClass || sym == NothingClass
-
- /** Is the given class a closure? */
- def isClosureClass(cls: Symbol): Boolean =
- cls.isFinal && cls.isSynthetic && !cls.isModuleClass && cls.isAnonymousFunction
-
- /*
- TODO now that Inliner runs faster we could consider additional "monadic methods" (in the limit, all those taking a closure as last arg)
- Any "monadic method" occurring in a given caller C that is not `isMonadicMethod()` will prevent CloseElim from eliminating
- any anonymous-closure-class any whose instances are given as argument to C invocations.
- */
- def isMonadicMethod(sym: Symbol) = {
- nme.unspecializedName(sym.name) match {
- case nme.foreach | nme.filter | nme.withFilter | nme.map | nme.flatMap => true
- case _ => false
- }
- }
-
- val knownLacksInline = mutable.Set.empty[Symbol] // cache to avoid multiple inliner.hasInline() calls.
- val knownHasInline = mutable.Set.empty[Symbol] // as above. Motivated by the need to warn on "inliner failures".
-
- def hasInline(sym: Symbol) = {
- if (knownLacksInline(sym)) false
- else if(knownHasInline(sym)) true
- else {
- val b = (sym hasAnnotation ScalaInlineClass)
- if(b) { knownHasInline += sym }
- else { knownLacksInline += sym }
-
- b
- }
- }
-
- def hasNoInline(sym: Symbol) = sym hasAnnotation ScalaNoInlineClass
-
- /**
- * Simple inliner.
- */
- class Inliner {
- object NonPublicRefs extends Enumeration {
- val Private, Protected, Public = Value
-
- /** Cache whether a method calls private members. */
- val usesNonPublics = mutable.Map.empty[IMethod, Value]
- }
- import NonPublicRefs._
-
- /** The current iclass */
- private var currentIClazz: IClass = _
- private def warn(pos: Position, msg: String) = currentRun.reporting.inlinerWarning(pos, msg)
-
- private def ownedName(sym: Symbol): String = exitingUncurry {
- val count = (
- if (!sym.isMethod) 1
- else if (sym.owner.isAnonymousFunction) 3
- else 2
- )
- (sym.ownerChain take count filterNot (_.isPackageClass)).reverseMap(_.nameString).mkString(".")
- }
- private def inlineLog(what: String, main: => String, comment: => String) {
- def cstr = comment match {
- case "" => ""
- case str => " // " + str
- }
- val width = if (currentIClazz eq null) 40 else currentIClazz.symbol.enclosingPackage.fullName.length + 25
- val fmt = "%8s %-" + width + "s" + cstr
- log(fmt.format(what, main))
- }
- private def inlineLog(what: String, main: Symbol, comment: => String) {
- inlineLog(what, ownedName(main), comment)
- }
-
- val recentTFAs = mutable.Map.empty[Symbol, Tuple2[Boolean, analysis.MethodTFA]]
-
- private def getRecentTFA(incm: IMethod, forceable: Boolean): (Boolean, analysis.MethodTFA) = {
-
- def containsRETURN(blocks: List[BasicBlock]) = blocks exists { bb => bb.lastInstruction.isInstanceOf[RETURN] }
-
- val opt = recentTFAs.get(incm.symbol)
- if(opt.isDefined) {
- // FYI val cachedBBs = opt.get._2.in.keySet
- // FYI assert(incm.blocks.toSet == cachedBBs)
- // incm.code.touched plays no role here
- return opt.get
- }
-
- val hasRETURN = containsRETURN(incm.code.blocksList) || (incm.exh exists { eh => containsRETURN(eh.blocks) })
- var a: analysis.MethodTFA = null
- if(hasRETURN) { a = new analysis.MethodTFA(incm); a.run() }
-
- if(forceable) { recentTFAs.put(incm.symbol, (hasRETURN, a)) }
-
- (hasRETURN, a)
- }
-
- def clearCaches() {
- // methods
- NonPublicRefs.usesNonPublics.clear()
- recentTFAs.clear()
- tfa.knownUnsafe.clear()
- tfa.knownSafe.clear()
- tfa.knownNever.clear()
- // basic blocks
- tfa.preCandidates.clear()
- tfa.relevantBBs.clear()
- // callsites
- tfa.remainingCALLs.clear()
- tfa.isOnWatchlist.clear()
- }
-
- object imethodOrdering extends Ordering[IMethod] {
- def compare(a: IMethod, b: IMethod) = {
- val namesComparison = (a.toString() compare b.toString())
- if(namesComparison != 0) namesComparison
- else {
- a.symbol.id compare b.symbol.id
- }
- }
- }
-
- def analyzeClass(cls: IClass): Unit =
- if (settings.inline) {
- inlineLog("class", s"${cls.symbol.decodedName}", s"analyzing ${cls.methods.size} methods in $cls")
-
- this.currentIClazz = cls
- val ms = cls.methods sorted imethodOrdering
- ms foreach { im =>
- if (hasInline(im.symbol)) {
- inlineLog("skip", im.symbol, "no inlining into @inline methods")
- }
- else if(im.hasCode && !im.symbol.isBridge) {
- analyzeMethod(im)
- }
- }
- }
-
- val tfa = new analysis.MTFAGrowable()
- tfa.stat = global.settings.YstatisticsEnabled
- val staleOut = new mutable.ListBuffer[BasicBlock]
- val splicedBlocks = mutable.Set.empty[BasicBlock]
- val staleIn = mutable.Set.empty[BasicBlock]
-
- /**
- * A transformation local to the body of the IMethod received as argument.
- * An inlining decision consists in replacing a callsite with the body of the callee.
- * Please notice that, because `analyzeMethod()` itself may modify a method body,
- * the particular callee bodies that end up being inlined depend on the particular order in which methods are visited
- * (no topological sorting over the call-graph is attempted).
- *
- * Making an inlining decision requires type-flow information for both caller and callee.
- * Regarding the caller, such information is needed only for basic blocks containing inlining candidates
- * (and their transitive predecessors). This observation leads to using a custom type-flow analysis (MTFAGrowable)
- * that can be re-inited, i.e. that reuses lattice elements (type-flow information computed in a previous iteration)
- * as starting point for faster convergence in a new iteration.
- *
- * The mechanics of inlining are iterative for a given invocation of `analyzeMethod(m)`,
- * and are affected by inlinings from previous iterations
- * (ie, "heuristic" rules are based on statistics tracked for that purpose):
- *
- * (1) before the iterations proper start, so-called preinlining is performed.
- * Those callsites whose (receiver, concreteMethod) are both known statically
- * can be analyzed for inlining before computing a type-flow. Details in `preInline()`
- *
- * (2) the first iteration computes type-flow information for basic blocks containing inlining candidates
- * (and their transitive predecessors), so called `relevantBBs` basic blocks.
- * The ensuing analysis of each candidate (performed by `analyzeInc()`)
- * may result in a CFG isomorphic to that of the callee being inserted in place of the callsite
- * (i.e. a CALL_METHOD instruction is replaced with a single-entry single-exit CFG,
- * a substitution we call "successful inlining").
- *
- * (3) following iterations have `relevantBBs` updated to focus on the inlined basic blocks and their successors only.
- * Details in `MTFAGrowable.reinit()`
- * */
- def analyzeMethod(m: IMethod): Unit = {
- // m.normalize
- if (settings.debug)
- inlineLog("caller", ownedName(m.symbol), "in " + m.symbol.owner.fullName)
-
- val sizeBeforeInlining = m.code.blockCount
- val instrBeforeInlining = m.code.instructionCount
- var retry = false
- var count = 0
-
- // fresh name counter
- val fresh = mutable.HashMap.empty[String, Int] withDefaultValue 0
- // how many times have we already inlined this method here?
- val inlinedMethodCount = mutable.HashMap.empty[Symbol, Int] withDefaultValue 0
- val caller = new IMethodInfo(m)
- def analyzeMessage = s"Analyzing ${caller.length} blocks of $m for inlining sites."
-
- def preInline(isFirstRound: Boolean): Int = {
- val inputBlocks = caller.m.linearizedBlocks()
- val callsites: Function1[BasicBlock, List[opcodes.CALL_METHOD]] = {
- if(isFirstRound) tfa.conclusives else tfa.knownBeforehand
- }
- inlineWithoutTFA(inputBlocks, callsites)
- }
-
- /*
- * Inline straightforward callsites (those that can be inlined without a TFA).
- *
- * To perform inlining, all we need to know is listed as formal params in `analyzeInc()`:
- * - callsite and block containing it
- * - actual (ie runtime) class of the receiver
- * - actual (ie runtime) method being invoked
- * - stack length just before the callsite (to check whether enough arguments have been pushed).
- * The assert below lists the conditions under which "no TFA is needed"
- * (the statically known receiver and method are both final, thus, at runtime they can't be any others than those).
- *
- */
- def inlineWithoutTFA(inputBlocks: Traversable[BasicBlock], callsites: Function1[BasicBlock, List[opcodes.CALL_METHOD]]): Int = {
- var inlineCount = 0
- import scala.util.control.Breaks._
- for(x <- inputBlocks; easyCake = callsites(x); if easyCake.nonEmpty) {
- breakable {
- for(ocm <- easyCake) {
- assert(ocm.method.isEffectivelyFinalOrNotOverridden && ocm.method.owner.isEffectivelyFinalOrNotOverridden)
- if(analyzeInc(ocm, x, ocm.method.owner, -1, ocm.method)) {
- inlineCount += 1
- break()
- }
- }
- }
- }
-
- inlineCount
- }
-
- /*
- * Decides whether it's feasible and desirable to inline the body of the method given by `concreteMethod`
- * at the program point given by `i` (a callsite). The boolean result indicates whether inlining was performed.
- *
- */
- def analyzeInc(i: CALL_METHOD, bb: BasicBlock, receiver: Symbol, stackLength: Int, concreteMethod: Symbol): Boolean = {
- assert(bb.toList contains i, "Candidate callsite does not belong to BasicBlock.")
- val shouldWarn = hasInline(i.method)
-
- def warnNoInline(reason: String): Boolean = {
- def msg = "Could not inline required method %s because %s.".format(i.method.unexpandedName.decode, reason)
- if (settings.debug)
- inlineLog("fail", i.method.fullName, reason)
- if (shouldWarn)
- warn(i.pos, msg)
-
- false
- }
-
- var isAvailable = icodes available concreteMethod.enclClass
-
- if (!isAvailable && shouldLoadImplFor(concreteMethod, receiver)) {
- // Until r22824 this line was:
- // icodes.icode(concreteMethod.enclClass, true)
- //
- // Changing it to
- // icodes.load(concreteMethod.enclClass)
- // was the proximate cause for SI-3882:
- // error: Illegal index: 0 overlaps List((variable par1,LONG))
- // error: Illegal index: 0 overlaps List((variable par1,LONG))
- isAvailable = icodes.load(concreteMethod.enclClass)
- }
-
- def isCandidate = (
- isClosureClass(receiver)
- || concreteMethod.isEffectivelyFinalOrNotOverridden
- || receiver.isEffectivelyFinalOrNotOverridden
- )
-
- def isApply = concreteMethod.name == nme.apply
-
- def isCountable = !(
- isClosureClass(receiver)
- || isApply
- || isMonadicMethod(concreteMethod)
- || receiver.enclosingPackage == definitions.RuntimePackage
- ) // only count non-closures
-
- debuglog("Treating " + i
- + "\n\treceiver: " + receiver
- + "\n\ticodes.available: " + isAvailable
- + "\n\tconcreteMethod.isEffectivelyFinalOrNotOverridden: " + concreteMethod.isEffectivelyFinalOrNotOverridden)
-
- if (!isCandidate) warnNoInline("it can be overridden")
- else if (!isAvailable) warnNoInline("bytecode unavailable")
- else lookupIMethod(concreteMethod, receiver) filter (callee => callee.hasCode || warnNoInline("callee has no code")) exists { callee =>
- val inc = new IMethodInfo(callee)
- val pair = new CallerCalleeInfo(caller, inc, fresh, inlinedMethodCount)
-
- if (inc.hasHandlers && (stackLength == -1)) {
- // no inlining is done, yet don't warn about it, stackLength == -1 indicates we're trying to inlineWithoutTFA.
- // Shortly, a TFA will be computed and an error message reported if indeed inlining not possible.
- false
- }
- else {
- val isSafe = pair isStampedForInlining stackLength match {
- case DontInlineHere(msg) => warnNoInline(msg)
- case NeverSafeToInline => false
- case InlineableAtThisCaller => true
- case FeasibleInline(required, toPublicize) =>
- for (f <- toPublicize) {
- inlineLog("access", f, "making public")
- f setFlag Flags.notPRIVATE
- f setFlag Flags.notPROTECTED
- }
- // only add to `knownSafe` after all `toPublicize` fields actually made public.
- if (required == NonPublicRefs.Public)
- tfa.knownSafe += inc.sym
-
- true
- }
- isSafe && {
- retry = true
- if (isCountable) count += 1
- pair.doInline(bb, i)
- if (!pair.isInlineForced || inc.isMonadic) caller.inlinedCalls += 1
- inlinedMethodCount(inc.sym) += 1
-
- // Remove the caller from the cache (this inlining might have changed its calls-private relation).
- usesNonPublics -= m
- recentTFAs -= m.symbol
- true
- }
- }
- }
- }
-
- /* Pre-inlining consists in invoking the usual inlining subroutine with (receiver class, concrete method) pairs as input
- * where both method and receiver are final, which implies that the receiver computed via TFA will always match `concreteMethod.owner`.
- *
- * As with any invocation of `analyzeInc()` the inlining outcome is based on heuristics which favor inlining an isMonadicMethod before other methods.
- * That's why preInline() is invoked twice: any inlinings downplayed by the heuristics during the first round get an opportunity to rank higher during the second.
- *
- * As a whole, both `preInline()` invocations amount to priming the inlining process,
- * so that the first TFA that is run afterwards is able to gain more information as compared to a cold-start.
- */
- /*val totalPreInlines = */ { // Val name commented out to emphasize it is never used
- val firstRound = preInline(isFirstRound = true)
- if(firstRound == 0) 0 else (firstRound + preInline(isFirstRound = false))
- }
- staleOut.clear()
- splicedBlocks.clear()
- staleIn.clear()
-
- do {
- retry = false
- debuglog(analyzeMessage)
-
- /* it's important not to inline in unreachable basic blocks. linearizedBlocks() returns only reachable ones. */
- tfa.callerLin = caller.m.linearizedBlocks()
- /* TODO Do we really want to inline inside exception handlers?
- * Seems counterproductive (the larger the method the less likely it will be JITed).
- * The alternative would be `linearizer.linearizeAt(caller.m, caller.m.startBlock)`.
- * And, we would cut down on TFA iterations, too.
- * See also comment on the same topic in TypeFlowAnalysis. */
-
- tfa.reinit(m, staleOut.toList, splicedBlocks, staleIn)
- tfa.run
-
- staleOut.clear()
- splicedBlocks.clear()
- staleIn.clear()
-
- import scala.util.control.Breaks._
- for(bb <- tfa.callerLin; if tfa.preCandidates(bb)) {
- val cms = bb.toList collect { case cm : CALL_METHOD => cm }
- breakable {
- for (cm <- cms; if tfa.remainingCALLs.isDefinedAt(cm)) {
- val analysis.CallsiteInfo(_, receiver, stackLength, concreteMethod) = tfa.remainingCALLs(cm)
- if (analyzeInc(cm, bb, receiver, stackLength, concreteMethod)) {
- break()
- }
- }
- }
- }
-
- /* As part of inlining, some instructions are moved to a new block.
- * In detail: the instructions moved to a new block originally appeared after a (by now inlined) callsite.
- * Their new home is an `afterBlock` created by `doInline()` to that effect.
- * Each block in staleIn is one such `afterBlock`.
- *
- * Some of those instructions may be CALL_METHOD possibly tracked in `remainingCALLs`
- * (with an entry still noting the old containing block). However, that causes no problem:
- *
- * (1) such callsites won't be analyzed for inlining by `analyzeInc()` (*in this iteration*)
- * because of the `break` that abandons the original basic block where it was contained.
- *
- * (2) Additionally, its new containing block won't be visited either (*in this iteration*)
- * because the new blocks don't show up in the linearization computed before inlinings started:
- * `for(bb <- tfa.callerLin; if tfa.preCandidates(bb)) {`
- *
- * For a next iteration, the new home of any instructions that have moved
- * will be tracked properly in `remainingCALLs` after `MTFAGrowable.reinit()` puts on radar their new homes.
- *
- */
- if(retry) {
- for(afterBlock <- staleIn) {
- val justCALLsAfter = afterBlock.toList collect { case c : opcodes.CALL_METHOD => c }
- for(ia <- justCALLsAfter) { tfa.remainingCALLs.remove(ia) }
- }
- }
-
- /*
- if(splicedBlocks.nonEmpty) { // TODO explore (saves time but leads to slightly different inlining decisions)
- // opportunistically perform straightforward inlinings before the next typeflow round
- val savedRetry = retry
- val savedStaleOut = staleOut.toSet; staleOut.clear()
- val savedStaleIn = staleIn.toSet ; staleIn.clear()
- val howmany = inlineWithoutTFA(splicedBlocks, tfa.knownBeforehand)
- splicedBlocks ++= staleIn
- staleOut.clear(); staleOut ++= savedStaleOut;
- staleIn.clear(); staleIn ++= savedStaleIn;
- retry = savedRetry
- }
- */
-
- if (tfa.stat)
- log(m.symbol.fullName + " iterations: " + tfa.iterations + " (size: " + caller.length + ")")
- }
- while (retry && count < MAX_INLINE_RETRY)
-
- for(inlFail <- tfa.warnIfInlineFails) {
- warn(inlFail.pos, "At the end of the day, could not inline @inline-marked method " + inlFail.method.unexpandedName.decode)
- }
-
- m.normalize()
- if (sizeBeforeInlining > 0) {
- val instrAfterInlining = m.code.instructionCount
- val inlinings = caller.inlinedCalls
- if (inlinings > 0) {
- val s1 = s"instructions $instrBeforeInlining -> $instrAfterInlining"
- val s2 = if (sizeBeforeInlining == m.code.blockCount) "" else s", blocks $sizeBeforeInlining -> ${m.code.blockCount}"
- val callees = inlinedMethodCount.toList map { case (k, v) => k.fullNameString + ( if (v == 1) "" else "/" + v ) }
-
- inlineLog("inlined", m.symbol.fullName, callees.sorted.mkString(inlinings + " inlined: ", ", ", ""))
- inlineLog("<<tldr>>", m.symbol.fullName, s"${m.symbol.nameString}: $s1$s2")
- }
- }
- }
-
- private def isHigherOrderMethod(sym: Symbol) = (
- sym.isMethod
- && enteringExplicitOuter(sym.info.paramTypes exists isFunctionType) // was "at erasurePhase.prev"
- )
-
- /** Should method 'sym' being called in 'receiver' be loaded from disk? */
- def shouldLoadImplFor(sym: Symbol, receiver: Symbol): Boolean = {
- def alwaysLoad = (receiver.enclosingPackage == RuntimePackage) || (receiver == PredefModule.moduleClass)
- def loadCondition = sym.isEffectivelyFinalOrNotOverridden && isMonadicMethod(sym) && isHigherOrderMethod(sym)
-
- val res = hasInline(sym) || alwaysLoad || loadCondition
- debuglog("shouldLoadImplFor: " + receiver + "." + sym + ": " + res)
- res
- }
-
- class IMethodInfo(val m: IMethod) {
- override def toString = m.toString
-
- val sym = m.symbol
- def owner = sym.owner
- def paramTypes = sym.info.paramTypes
- def minimumStack = paramTypes.length + 1
-
- def isBridge = sym.isBridge
- val isInClosure = isClosureClass(owner)
- val isHigherOrder = isHigherOrderMethod(sym)
- def isMonadic = isMonadicMethod(sym)
-
- def handlers = m.exh
- def blocks = m.blocks
- def locals = m.locals
- def length = blocks.length
- def openBlocks = blocks filterNot (_.closed)
- def instructions = m.code.instructions
-
- def isSmall = (length <= SMALL_METHOD_SIZE) && blocks(0).length < 10
- def isLarge = length > MAX_INLINE_SIZE
- def isRecursive = m.recursive
- def hasHandlers = handlers.nonEmpty || m.bytecodeHasEHs
-
- def isSynchronized = sym.hasFlag(Flags.SYNCHRONIZED)
- def hasNonFinalizerHandler = handlers exists {
- case _: Finalizer => true
- case _ => false
- }
-
- // the number of inlined calls in 'm', used by 'isScoreOK'
- var inlinedCalls = 0
-
- def addLocals(ls: List[Local]) = m.locals ++= ls
- def addLocal(l: Local) = addLocals(List(l))
- def addHandlers(exhs: List[ExceptionHandler]) = m.exh = exhs ::: m.exh
-
- /**
- * This method inspects the callee's instructions, finding out the most restrictive accessibility implied by them.
- *
- * Rather than giving up upon encountering an access to a private field `p`, it provisorily admits `p` as "can-be-made-public", provided:
- * - `p` is being compiled as part of this compilation run, and
- * - `p` is synthetic or param-accessor.
- *
- * This method is side-effect free, in particular it lets the invoker decide
- * whether the accessibility of the `toBecomePublic` fields should be changed or not.
- */
- def accessRequirements: AccessReq = {
-
- var toBecomePublic: List[Symbol] = Nil
-
- def check(sym: Symbol, cond: Boolean) =
- if (cond) Private
- else if (sym.isProtected) Protected
- else Public
-
- def canMakePublic(f: Symbol): Boolean =
- (m.sourceFile ne NoSourceFile) &&
- (f.isSynthetic || f.isParamAccessor) &&
- { toBecomePublic = f :: toBecomePublic; true }
-
- /* A safety check to consider as private, for the purposes of inlining, a public field that:
- * (1) is defined in an external library, and
- * (2) can be presumed synthetic (due to a dollar sign in its name).
- * Such field was made public by `doMakePublic()` and we don't want to rely on that,
- * because under other compilation conditions (ie no -optimize) that won't be the case anymore.
- *
- * This allows aggressive intra-library inlining (making public if needed)
- * that does not break inter-library scenarios (see comment for `Inliners`).
- *
- * TODO handle more robustly the case of a trait var changed at the source-level from public to private[this]
- * (eg by having ICodeReader use unpickler, see SI-5442).
-
- DISABLED
-
- def potentiallyPublicized(f: Symbol): Boolean = {
- (m.sourceFile eq NoSourceFile) && f.name.containsChar('$')
- }
- */
-
-
- def isPrivateForInlining(sym: Symbol): Boolean = {
- if (sym.isJavaDefined) {
- def check(sym: Symbol) = !(sym.isPublic || sym.isProtected)
- check(sym) || check(sym.owner) // SI-7582 Must check the enclosing class *and* the symbol for Java.
- }
- else sym.isPrivate // Scala never emits package-private bytecode
- }
-
- def checkField(f: Symbol) = check(f, isPrivateForInlining(f) && !canMakePublic(f))
- def checkSuper(n: Symbol) = check(n, isPrivateForInlining(n) || !n.isClassConstructor)
- def checkMethod(n: Symbol) = check(n, isPrivateForInlining(n))
-
- def getAccess(i: Instruction) = i match {
- case CALL_METHOD(n, SuperCall(_)) => checkSuper(n)
- case CALL_METHOD(n, _) => checkMethod(n)
- case LOAD_FIELD(f, _) => checkField(f)
- case STORE_FIELD(f, _) => checkField(f)
- case _ => Public
- }
-
- var seen = Public
- val iter = instructions.iterator
- while((seen ne Private) && iter.hasNext) {
- val i = iter.next()
- getAccess(i) match {
- case Private =>
- inlineLog("access", s"instruction $i requires private access", "pos=" + i.pos)
- toBecomePublic = Nil
- seen = Private
- case Protected => seen = Protected
- case _ => ()
- }
- }
-
- AccessReq(seen, toBecomePublic)
- }
-
- }
-
- /**
- * Classifies a pair (caller, callee) into one of four categories:
- *
- * (a) inlining should be performed, classified in turn into:
- * (a.1) `InlineableAtThisCaller`: unconditionally at this caller
- * (a.2) `FeasibleInline`: it only remains for certain access requirements to be met (see `IMethodInfo.accessRequirements()`)
- *
- * (b) inlining shouldn't be performed, classified in turn into:
- * (b.1) `DontInlineHere`: indicates that this particular occurrence of the callee at the caller shouldn't be inlined.
- * - Nothing is said about the outcome for other callers, or for other occurrences of the callee for the same caller.
- * - In particular inlining might be possible, but heuristics gave a low score for it.
- * (b.2) `NeverSafeToInline`: the callee can't be inlined anywhere, irrespective of caller.
- *
- * The classification above is computed by `isStampedForInlining()` based on which `analyzeInc()` goes on to:
- * - either log the reason for failure --- case (b) ---,
- * - or perform inlining --- case (a) ---.
- */
- sealed abstract class InlineSafetyInfo
- case object NeverSafeToInline extends InlineSafetyInfo
- case object InlineableAtThisCaller extends InlineSafetyInfo
- case class DontInlineHere(msg: String) extends InlineSafetyInfo
- case class FeasibleInline(accessNeeded: NonPublicRefs.Value, toBecomePublic: List[Symbol]) extends InlineSafetyInfo
-
- case class AccessReq(
- accessNeeded: NonPublicRefs.Value,
- toBecomePublic: List[Symbol]
- )
-
- final class CallerCalleeInfo(val caller: IMethodInfo, val inc: IMethodInfo, fresh: mutable.Map[String, Int], inlinedMethodCount: scala.collection.Map[Symbol, Int]) {
-
- assert(!caller.isBridge && inc.m.hasCode,
- "A guard in Inliner.analyzeClass() should have prevented from getting here.")
-
- def isLargeSum = caller.length + inc.length - 1 > SMALL_METHOD_SIZE
-
- private def freshName(s: String): TermName = {
- fresh(s) += 1
- newTermName(s + fresh(s))
- }
-
- private def isKnownToInlineSafely: Boolean = { tfa.knownSafe(inc.sym) }
-
- val isInlineForced = hasInline(inc.sym)
- val isInlineForbidden = hasNoInline(inc.sym)
- assert(!(isInlineForced && isInlineForbidden), "method ("+inc.m+") marked both @inline and @noinline.")
-
- /** Inline 'inc' into 'caller' at the given block and instruction.
- * The instruction must be a CALL_METHOD.
- */
- def doInline(block: BasicBlock, instr: CALL_METHOD) {
-
- staleOut += block
-
- tfa.remainingCALLs.remove(instr) // this bookkeeping is done here and not in MTFAGrowable.reinit due to (1st) convenience and (2nd) necessity.
- tfa.isOnWatchlist.remove(instr) // ditto
- tfa.warnIfInlineFails.remove(instr)
-
- val targetPos = instr.pos
-
- def blockEmit(i: Instruction) = block.emit(i, targetPos)
- def newLocal(baseName: String, kind: TypeKind) =
- new Local(caller.sym.newVariable(freshName(baseName), targetPos) setInfo kind.toType, kind, false)
-
- val (hasRETURN, a) = getRecentTFA(inc.m, isInlineForced)
-
- /* The exception handlers that are active at the current block. */
- val activeHandlers = caller.handlers filter (_ covered block)
-
- /* Map 'original' blocks to the ones inlined in the caller. */
- val inlinedBlock = mutable.Map[BasicBlock, BasicBlock]()
-
- val varsInScope = mutable.HashSet[Local]() ++= block.varsInScope
-
- /* Side effects varsInScope when it sees SCOPE_ENTERs. */
- def instrBeforeFilter(i: Instruction): Boolean = {
- i match { case SCOPE_ENTER(l) => varsInScope += l ; case _ => () }
- i ne instr
- }
- val instrBefore = block.toList takeWhile instrBeforeFilter
- val instrAfter = block.toList drop (instrBefore.length + 1)
-
- assert(!instrAfter.isEmpty, "CALL_METHOD cannot be the last instruction in block!")
-
- // store the '$this' into the special local
- val inlinedThis = newLocal("$inlThis", REFERENCE(ObjectClass))
-
- /* buffer for the returned value */
- val retVal = inc.m.returnType match {
- case UNIT => null
- case x => newLocal("$retVal", x)
- }
-
- val inlinedLocals = mutable.HashMap.empty[Local, Local]
-
- /* Add a new block in the current context. */
- def newBlock() = {
- val b = caller.m.code.newBlock()
- activeHandlers foreach (_ addCoveredBlock b)
- if (retVal ne null) b.varsInScope += retVal
- b.varsInScope += inlinedThis
- b.varsInScope ++= varsInScope
- b
- }
-
- def translateExh(e: ExceptionHandler) = {
- val handler: ExceptionHandler = e.dup
- handler.covered = handler.covered map inlinedBlock
- handler setStartBlock inlinedBlock(e.startBlock)
- handler
- }
-
- /* alfa-rename `l` in caller's context. */
- def dupLocal(l: Local): Local = {
- val sym = caller.sym.newVariable(freshName(l.sym.name.toString), l.sym.pos)
- // sym.setInfo(l.sym.tpe)
- val dupped = new Local(sym, l.kind, false)
- inlinedLocals(l) = dupped
- dupped
- }
-
- val afterBlock = newBlock()
-
- /* Map from nw.init instructions to their matching NEW call */
- val pending: mutable.Map[Instruction, NEW] = new mutable.HashMap
-
- /* Map an instruction from the callee to one suitable for the caller. */
- def map(i: Instruction): Instruction = {
- def assertLocal(l: Local) = {
- assert(caller.locals contains l, "Could not find local '" + l + "' in locals, nor in inlinedLocals: " + inlinedLocals)
- i
- }
- def isInlined(l: Local) = inlinedLocals isDefinedAt l
-
- val newInstr = i match {
- case THIS(clasz) => LOAD_LOCAL(inlinedThis)
- case STORE_THIS(_) => STORE_LOCAL(inlinedThis)
- case JUMP(whereto) => JUMP(inlinedBlock(whereto))
- case CJUMP(succ, fail, cond, kind) => CJUMP(inlinedBlock(succ), inlinedBlock(fail), cond, kind)
- case CZJUMP(succ, fail, cond, kind) => CZJUMP(inlinedBlock(succ), inlinedBlock(fail), cond, kind)
- case SWITCH(tags, labels) => SWITCH(tags, labels map inlinedBlock)
- case RETURN(_) => JUMP(afterBlock)
- case LOAD_LOCAL(l) if isInlined(l) => LOAD_LOCAL(inlinedLocals(l))
- case STORE_LOCAL(l) if isInlined(l) => STORE_LOCAL(inlinedLocals(l))
- case LOAD_LOCAL(l) => assertLocal(l)
- case STORE_LOCAL(l) => assertLocal(l)
- case SCOPE_ENTER(l) if isInlined(l) => SCOPE_ENTER(inlinedLocals(l))
- case SCOPE_EXIT(l) if isInlined(l) => SCOPE_EXIT(inlinedLocals(l))
-
- case nw @ NEW(sym) =>
- val r = NEW(sym)
- pending(nw.init) = r
- r
-
- case CALL_METHOD(meth, Static(true)) if meth.isClassConstructor =>
- CALL_METHOD(meth, Static(onInstance = true))
-
- case _ => i.clone()
- }
- // check any pending NEW's
- pending remove i foreach (_.init = newInstr.asInstanceOf[CALL_METHOD])
- newInstr
- }
-
- caller addLocals (inc.locals map dupLocal)
- caller addLocal inlinedThis
-
- if (retVal ne null)
- caller addLocal retVal
-
- inc.m foreachBlock { b =>
- inlinedBlock += (b -> newBlock())
- inlinedBlock(b).varsInScope ++= (b.varsInScope map inlinedLocals)
- }
-
- // re-emit the instructions before the call
- block.open()
- block.clear()
- block emit instrBefore
-
- // store the arguments into special locals
- inc.m.params.reverse foreach (p => blockEmit(STORE_LOCAL(inlinedLocals(p))))
- blockEmit(STORE_LOCAL(inlinedThis))
-
- // jump to the start block of the callee
- blockEmit(JUMP(inlinedBlock(inc.m.startBlock)))
- block.close()
-
- // duplicate the other blocks in the callee
- val calleeLin = inc.m.linearizedBlocks()
- calleeLin foreach { bb =>
- var info = if(hasRETURN) (a in bb) else null
- def emitInlined(i: Instruction) = inlinedBlock(bb).emit(i, targetPos)
- def emitDrops(toDrop: Int) = info.stack.types drop toDrop foreach (t => emitInlined(DROP(t)))
-
- for (i <- bb) {
- i match {
- case RETURN(UNIT) => emitDrops(0)
- case RETURN(kind) =>
- if (info.stack.length > 1) {
- emitInlined(STORE_LOCAL(retVal))
- emitDrops(1)
- emitInlined(LOAD_LOCAL(retVal))
- }
- case _ => ()
- }
- emitInlined(map(i))
- info = if(hasRETURN) a.interpret(info, i) else null
- }
- inlinedBlock(bb).close()
- }
-
- afterBlock emit instrAfter
- afterBlock.close()
-
- staleIn += afterBlock
- splicedBlocks ++= (calleeLin map inlinedBlock)
-
- // add exception handlers of the callee
- caller addHandlers (inc.handlers map translateExh)
- assert(pending.isEmpty, "Pending NEW elements: " + pending)
- if (settings.debug) icodes.checkValid(caller.m)
- }
-
- def isStampedForInlining(stackLength: Int): InlineSafetyInfo = {
-
- if(tfa.blackballed(inc.sym)) { return NeverSafeToInline }
-
- if(!isKnownToInlineSafely) {
-
- if(inc.openBlocks.nonEmpty) {
- val msg = ("Encountered " + inc.openBlocks.size + " open block(s) in isSafeToInline: this indicates a bug in the optimizer!\n" +
- " caller = " + caller.m + ", callee = " + inc.m)
- warn(inc.sym.pos, msg)
- tfa.knownNever += inc.sym
- return DontInlineHere("Open blocks in " + inc.m)
- }
-
- val reasonWhyNever: String = {
- var rs: List[String] = Nil
- if(inc.isRecursive) { rs ::= "is recursive" }
- if(isInlineForbidden) { rs ::= "is annotated @noinline" }
- if(inc.isSynchronized) { rs ::= "is synchronized method" }
- if(inc.m.bytecodeHasEHs) { rs ::= "bytecode contains exception handlers / finally clause" } // SI-6188
- if(inc.m.bytecodeHasInvokeDynamic) { rs ::= "bytecode contains invoke dynamic" }
- if(rs.isEmpty) null else rs.mkString("", ", and ", "")
- }
-
- if(reasonWhyNever != null) {
- tfa.knownNever += inc.sym
- inlineLog("never", inc.sym, reasonWhyNever)
- // next time around NeverSafeToInline is returned, thus skipping (duplicate) msg, this is intended.
- return DontInlineHere(inc.m + " " + reasonWhyNever)
- }
-
- if(sameSymbols) { // TODO but this also amounts to recursive, ie should lead to adding to tfa.knownNever, right?
- tfa.knownUnsafe += inc.sym
- return DontInlineHere("sameSymbols (ie caller == callee)")
- }
-
- }
-
- /*
- * From here on, two main categories of checks remain, (a) and (b) below:
- * (a.1) either the scoring heuristics give green light; or
- * (a.2) forced as candidate due to @inline.
- * After that, safety proper is checked:
- * (b.1) the callee does not contain calls to private methods when called from another class
- * (b.2) the callee is not going to be inlined into a position with non-empty stack,
- * while having a top-level finalizer (see liftedTry problem)
- * As a result of (b), some synthetic private members can be chosen to become public.
- */
-
- val score = inlinerScore
- val scoreStr = if (score > 0) "+" + score else "" + score
- val what = if (score > 0) "ok to" else "don't"
- inlineLog(scoreStr, inc.m.symbol, s"$what inline into ${ownedName(caller.m.symbol)}")
-
- if (!isInlineForced && score <= 0) {
- // During inlining retry, a previous caller-callee pair that scored low may pass.
- // Thus, adding the callee to tfa.knownUnsafe isn't warranted.
- return DontInlineHere(s"inliner heuristic")
- }
-
- if(inc.hasHandlers && (stackLength > inc.minimumStack)) {
- return DontInlineHere("callee contains exception handlers / finally clause, and is invoked with non-empty operand stack") // SI-6157
- }
-
- if(isKnownToInlineSafely) { return InlineableAtThisCaller }
-
- if(stackLength > inc.minimumStack && inc.hasNonFinalizerHandler) {
- val msg = "method " + inc.sym + " is used on a non-empty stack with finalizer."
- debuglog(msg)
- // FYI: not reason enough to add inc.sym to tfa.knownUnsafe (because at other callsite in this caller, inlining might be ok)
- return DontInlineHere(msg)
- }
-
- val accReq = inc.accessRequirements
- if(!canAccess(accReq.accessNeeded)) {
- tfa.knownUnsafe += inc.sym
- val msg = "access level required by callee not matched by caller"
- inlineLog("fail", inc.sym, msg)
- return DontInlineHere(msg)
- }
-
- FeasibleInline(accReq.accessNeeded, accReq.toBecomePublic)
-
- }
-
- def canAccess(level: NonPublicRefs.Value) = level match {
- case Private => caller.owner == inc.owner
- case Protected => caller.owner.tpe <:< inc.owner.tpe
- case Public => true
- }
- private def sameSymbols = caller.sym == inc.sym
-
- /** Gives green light for inlining (which may still be vetoed later). Heuristics:
- * - it's bad to make the caller larger (> SMALL_METHOD_SIZE) if it was small
- * - it's bad to inline large methods
- * - it's good to inline higher order functions
- * - it's good to inline closures functions.
- * - it's bad (useless) to inline inside bridge methods
- */
- def inlinerScore: Int = {
- var score = 0
-
- // better not inline inside closures, but hope that the closure itself is repeatedly inlined
- if (caller.isInClosure) score -= 2
- else if (caller.inlinedCalls < 1) score -= 1 // only monadic methods can trigger the first inline
-
- if (inc.isSmall) score += 1
- // if (inc.hasClosureParam) score += 2
- if (inc.isLarge) score -= 1
- if (caller.isSmall && isLargeSum) {
- score -= 1
- debuglog(s"inliner score decreased to $score because small caller $caller would become large")
- }
-
- if (inc.isMonadic) score += 3
- else if (inc.isHigherOrder) score += 1
-
- if (inc.isInClosure) score += 2
- if (inlinedMethodCount(inc.sym) > 2) score -= 2
- score
- }
- }
-
- def lookupIMethod(meth: Symbol, receiver: Symbol): Option[IMethod] = {
- def tryParent(sym: Symbol) = icodes icode sym flatMap (_ lookupMethod meth)
-
- (receiver.info.baseClasses.iterator map tryParent find (_.isDefined)).flatten
- }
- } /* class Inliner */
-} /* class Inliners */
diff --git a/src/compiler/scala/tools/nsc/classpath/AggregateFlatClassPath.scala b/src/compiler/scala/tools/nsc/classpath/AggregateClassPath.scala
index 3f06264e3c..a1af3413ea 100644
--- a/src/compiler/scala/tools/nsc/classpath/AggregateFlatClassPath.scala
+++ b/src/compiler/scala/tools/nsc/classpath/AggregateClassPath.scala
@@ -6,22 +6,23 @@ package scala.tools.nsc.classpath
import java.net.URL
import scala.annotation.tailrec
import scala.collection.mutable.ArrayBuffer
+import scala.reflect.internal.FatalError
import scala.reflect.io.AbstractFile
import scala.tools.nsc.util.ClassPath
import scala.tools.nsc.util.ClassRepresentation
/**
* A classpath unifying multiple class- and sourcepath entries.
- * Flat classpath can obtain entries for classes and sources independently
+ * The Classpath can obtain entries for classes and sources independently
* so it tries to do operations quite optimally - iterating only these collections
* which are needed in the given moment and only as far as it's necessary.
+ *
* @param aggregates classpath instances containing entries which this class processes
*/
-case class AggregateFlatClassPath(aggregates: Seq[FlatClassPath]) extends FlatClassPath {
-
+case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath {
override def findClassFile(className: String): Option[AbstractFile] = {
@tailrec
- def find(aggregates: Seq[FlatClassPath]): Option[AbstractFile] =
+ def find(aggregates: Seq[ClassPath]): Option[AbstractFile] =
if (aggregates.nonEmpty) {
val classFile = aggregates.head.findClassFile(className)
if (classFile.isDefined) classFile
@@ -31,22 +32,27 @@ case class AggregateFlatClassPath(aggregates: Seq[FlatClassPath]) extends FlatCl
find(aggregates)
}
- override def findClass(className: String): Option[ClassRepresentation[AbstractFile]] = {
- val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className)
-
+ override def findClass(className: String): Option[ClassRepresentation] = {
@tailrec
- def findEntry[T <: ClassRepClassPathEntry](aggregates: Seq[FlatClassPath], getEntries: FlatClassPath => Seq[T]): Option[T] =
+ def findEntry(aggregates: Seq[ClassPath], isSource: Boolean): Option[ClassRepresentation] =
if (aggregates.nonEmpty) {
- val entry = getEntries(aggregates.head)
- .find(_.name == simpleClassName)
+ val entry = aggregates.head.findClass(className) match {
+ case s @ Some(_: SourceFileEntry) if isSource => s
+ case s @ Some(_: ClassFileEntry) if !isSource => s
+ case _ => None
+ }
if (entry.isDefined) entry
- else findEntry(aggregates.tail, getEntries)
+ else findEntry(aggregates.tail, isSource)
} else None
- val classEntry = findEntry(aggregates, classesGetter(pkg))
- val sourceEntry = findEntry(aggregates, sourcesGetter(pkg))
+ val classEntry = findEntry(aggregates, isSource = false)
+ val sourceEntry = findEntry(aggregates, isSource = true)
- mergeClassesAndSources(classEntry.toList, sourceEntry.toList).headOption
+ (classEntry, sourceEntry) match {
+ case (Some(c: ClassFileEntry), Some(s: SourceFileEntry)) => Some(ClassAndSourceFilesEntry(c.file, s.file))
+ case (c @ Some(_), _) => c
+ case (_, s) => s
+ }
}
override def asURLs: Seq[URL] = aggregates.flatMap(_.asURLs)
@@ -61,16 +67,25 @@ case class AggregateFlatClassPath(aggregates: Seq[FlatClassPath]) extends FlatCl
}
override private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] =
- getDistinctEntries(classesGetter(inPackage))
+ getDistinctEntries(_.classes(inPackage))
override private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] =
- getDistinctEntries(sourcesGetter(inPackage))
-
- override private[nsc] def list(inPackage: String): FlatClassPathEntries = {
- val (packages, classesAndSources) = aggregates.map(_.list(inPackage)).unzip
+ getDistinctEntries(_.sources(inPackage))
+
+ override private[nsc] def list(inPackage: String): ClassPathEntries = {
+ val (packages, classesAndSources) = aggregates.map { cp =>
+ try {
+ cp.list(inPackage)
+ } catch {
+ case ex: java.io.IOException =>
+ val e = new FatalError(ex.getMessage)
+ e.initCause(ex)
+ throw e
+ }
+ }.unzip
val distinctPackages = packages.flatten.distinct
val distinctClassesAndSources = mergeClassesAndSources(classesAndSources: _*)
- FlatClassPathEntries(distinctPackages, distinctClassesAndSources)
+ ClassPathEntries(distinctPackages, distinctClassesAndSources)
}
/**
@@ -78,11 +93,11 @@ case class AggregateFlatClassPath(aggregates: Seq[FlatClassPath]) extends FlatCl
* creates an entry containing both of them. If there would be more than one class or source
* entries for the same class it always would use the first entry of each type found on a classpath.
*/
- private def mergeClassesAndSources(entries: Seq[ClassRepClassPathEntry]*): Seq[ClassRepClassPathEntry] = {
+ private def mergeClassesAndSources(entries: Seq[ClassRepresentation]*): Seq[ClassRepresentation] = {
// based on the implementation from MergedClassPath
var count = 0
val indices = collection.mutable.HashMap[String, Int]()
- val mergedEntries = new ArrayBuffer[ClassRepClassPathEntry](1024)
+ val mergedEntries = new ArrayBuffer[ClassRepresentation](1024)
for {
partOfEntries <- entries
@@ -107,7 +122,7 @@ case class AggregateFlatClassPath(aggregates: Seq[FlatClassPath]) extends FlatCl
mergedEntries.toIndexedSeq
}
- private def getDistinctEntries[EntryType <: ClassRepClassPathEntry](getEntries: FlatClassPath => Seq[EntryType]): Seq[EntryType] = {
+ private def getDistinctEntries[EntryType <: ClassRepresentation](getEntries: ClassPath => Seq[EntryType]): Seq[EntryType] = {
val seenNames = collection.mutable.HashSet[String]()
val entriesBuffer = new ArrayBuffer[EntryType](1024)
for {
@@ -119,7 +134,16 @@ case class AggregateFlatClassPath(aggregates: Seq[FlatClassPath]) extends FlatCl
}
entriesBuffer.toIndexedSeq
}
+}
- private def classesGetter(pkg: String) = (cp: FlatClassPath) => cp.classes(pkg)
- private def sourcesGetter(pkg: String) = (cp: FlatClassPath) => cp.sources(pkg)
+object AggregateClassPath {
+ def createAggregate(parts: ClassPath*): ClassPath = {
+ val elems = new ArrayBuffer[ClassPath]()
+ parts foreach {
+ case AggregateClassPath(ps) => elems ++= ps
+ case p => elems += p
+ }
+ if (elems.size == 1) elems.head
+ else AggregateClassPath(elems.toIndexedSeq)
+ }
}
diff --git a/src/compiler/scala/tools/nsc/classpath/ClassPath.scala b/src/compiler/scala/tools/nsc/classpath/ClassPath.scala
new file mode 100644
index 0000000000..08bd98b1d8
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/classpath/ClassPath.scala
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2014 Contributor. All rights reserved.
+ */
+package scala.tools.nsc.classpath
+
+import scala.reflect.io.AbstractFile
+import scala.tools.nsc.util.ClassRepresentation
+
+case class ClassPathEntries(packages: Seq[PackageEntry], classesAndSources: Seq[ClassRepresentation])
+
+object ClassPathEntries {
+ import scala.language.implicitConversions
+ // to have working unzip method
+ implicit def entry2Tuple(entry: ClassPathEntries): (Seq[PackageEntry], Seq[ClassRepresentation]) = (entry.packages, entry.classesAndSources)
+}
+
+trait ClassFileEntry extends ClassRepresentation {
+ def file: AbstractFile
+}
+
+trait SourceFileEntry extends ClassRepresentation {
+ def file: AbstractFile
+}
+
+trait PackageEntry {
+ def name: String
+}
+
+private[nsc] case class ClassFileEntryImpl(file: AbstractFile) extends ClassFileEntry {
+ override def name = FileUtils.stripClassExtension(file.name) // class name
+
+ override def binary: Option[AbstractFile] = Some(file)
+ override def source: Option[AbstractFile] = None
+}
+
+private[nsc] case class SourceFileEntryImpl(file: AbstractFile) extends SourceFileEntry {
+ override def name = FileUtils.stripSourceExtension(file.name)
+
+ override def binary: Option[AbstractFile] = None
+ override def source: Option[AbstractFile] = Some(file)
+}
+
+private[nsc] case class ClassAndSourceFilesEntry(classFile: AbstractFile, srcFile: AbstractFile) extends ClassRepresentation {
+ override def name = FileUtils.stripClassExtension(classFile.name)
+
+ override def binary: Option[AbstractFile] = Some(classFile)
+ override def source: Option[AbstractFile] = Some(srcFile)
+}
+
+private[nsc] case class PackageEntryImpl(name: String) extends PackageEntry
+
+private[nsc] trait NoSourcePaths {
+ def asSourcePathString: String = ""
+ private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] = Seq.empty
+}
+
+private[nsc] trait NoClassPaths {
+ def findClassFile(className: String): Option[AbstractFile] = None
+ private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = Seq.empty
+}
diff --git a/src/compiler/scala/tools/nsc/classpath/ClassPathFactory.scala b/src/compiler/scala/tools/nsc/classpath/ClassPathFactory.scala
index 9bf4e3f779..80c5ec8828 100644
--- a/src/compiler/scala/tools/nsc/classpath/ClassPathFactory.scala
+++ b/src/compiler/scala/tools/nsc/classpath/ClassPathFactory.scala
@@ -3,53 +3,80 @@
*/
package scala.tools.nsc.classpath
-import scala.reflect.io.AbstractFile
+import scala.reflect.io.{AbstractFile, VirtualDirectory}
+import scala.reflect.io.Path.string2path
+import scala.tools.nsc.Settings
+import FileUtils.AbstractFileOps
import scala.tools.nsc.util.ClassPath
/**
- * A trait that contains factory methods for classpath elements of type T.
- *
- * The logic has been abstracted from ClassPath#ClassPathContext so it's possible
- * to have common trait that supports both recursive and flat classpath representations.
- *
- * Therefore, we expect that T will be either ClassPath[U] or FlatClassPath.
+ * Provides factory methods for classpath. When creating classpath instances for a given path,
+ * it uses proper type of classpath depending on a types of particular files containing sources or classes.
*/
-trait ClassPathFactory[T] {
-
+class ClassPathFactory(settings: Settings) {
/**
- * Create a new classpath based on the abstract file.
- */
- def newClassPath(file: AbstractFile): T
+ * Create a new classpath based on the abstract file.
+ */
+ def newClassPath(file: AbstractFile): ClassPath = ClassPathFactory.newClassPath(file, settings)
/**
- * Creators for sub classpaths which preserve this context.
- */
- def sourcesInPath(path: String): List[T]
+ * Creators for sub classpaths which preserve this context.
+ */
+ def sourcesInPath(path: String): List[ClassPath] =
+ for {
+ file <- expandPath(path, expandStar = false)
+ dir <- Option(AbstractFile getDirectory file)
+ } yield createSourcePath(dir)
+
- def expandPath(path: String, expandStar: Boolean = true): List[String] = ClassPath.expandPath(path, expandStar)
+ def expandPath(path: String, expandStar: Boolean = true): List[String] = scala.tools.nsc.util.ClassPath.expandPath(path, expandStar)
- def expandDir(extdir: String): List[String] = ClassPath.expandDir(extdir)
+ def expandDir(extdir: String): List[String] = scala.tools.nsc.util.ClassPath.expandDir(extdir)
- def contentsOfDirsInPath(path: String): List[T] =
+ def contentsOfDirsInPath(path: String): List[ClassPath] =
for {
dir <- expandPath(path, expandStar = false)
name <- expandDir(dir)
entry <- Option(AbstractFile.getDirectory(name))
} yield newClassPath(entry)
- def classesInExpandedPath(path: String): IndexedSeq[T] =
+ def classesInExpandedPath(path: String): IndexedSeq[ClassPath] =
classesInPathImpl(path, expand = true).toIndexedSeq
def classesInPath(path: String) = classesInPathImpl(path, expand = false)
def classesInManifest(useManifestClassPath: Boolean) =
- if (useManifestClassPath) ClassPath.manifests.map(url => newClassPath(AbstractFile getResources url))
+ if (useManifestClassPath) scala.tools.nsc.util.ClassPath.manifests.map(url => newClassPath(AbstractFile getResources url))
else Nil
// Internal
protected def classesInPathImpl(path: String, expand: Boolean) =
for {
file <- expandPath(path, expand)
- dir <- Option(AbstractFile.getDirectory(file))
+ dir <- {
+ def asImage = if (file.endsWith(".jimage")) Some(AbstractFile.getFile(file)) else None
+ Option(AbstractFile.getDirectory(file)).orElse(asImage)
+ }
} yield newClassPath(dir)
+
+ private def createSourcePath(file: AbstractFile): ClassPath =
+ if (file.isJarOrZip)
+ ZipAndJarSourcePathFactory.create(file, settings)
+ else if (file.isDirectory)
+ new DirectorySourcePath(file.file)
+ else
+ sys.error(s"Unsupported sourcepath element: $file")
+}
+
+object ClassPathFactory {
+ def newClassPath(file: AbstractFile, settings: Settings): ClassPath = file match {
+ case vd: VirtualDirectory => VirtualDirectoryClassPath(vd)
+ case _ =>
+ if (file.isJarOrZip)
+ ZipAndJarClassPathFactory.create(file, settings)
+ else if (file.isDirectory)
+ new DirectoryClassPath(file.file)
+ else
+ sys.error(s"Unsupported classpath element: $file")
+ }
}
diff --git a/src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala b/src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala
new file mode 100644
index 0000000000..fbd59eb04a
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2014 Contributor. All rights reserved.
+ */
+package scala.tools.nsc.classpath
+
+import java.io.File
+import java.net.{URI, URL}
+import java.nio.file.{FileSystems, Files, SimpleFileVisitor}
+import java.util.function.IntFunction
+import java.util
+import java.util.Comparator
+
+import scala.reflect.io.{AbstractFile, PlainFile, PlainNioFile}
+import scala.tools.nsc.util.{ClassPath, ClassRepresentation}
+import FileUtils._
+import scala.collection.JavaConverters._
+
+/**
+ * A trait allowing to look for classpath entries in directories. It provides common logic for
+ * classes handling class and source files.
+ * It makes use of the fact that in the case of nested directories it's easy to find a file
+ * when we have a name of a package.
+ * It abstracts over the file representation to work with both JFile and AbstractFile.
+ */
+trait DirectoryLookup[FileEntryType <: ClassRepresentation] extends ClassPath {
+ type F
+
+ val dir: F
+
+ protected def emptyFiles: Array[F] // avoids reifying ClassTag[F]
+ protected def getSubDir(dirName: String): Option[F]
+ protected def listChildren(dir: F, filter: Option[F => Boolean] = None): Array[F]
+ protected def getName(f: F): String
+ protected def toAbstractFile(f: F): AbstractFile
+ protected def isPackage(f: F): Boolean
+
+ protected def createFileEntry(file: AbstractFile): FileEntryType
+ protected def isMatchingFile(f: F): Boolean
+
+ private def getDirectory(forPackage: String): Option[F] = {
+ if (forPackage == ClassPath.RootPackage) {
+ Some(dir)
+ } else {
+ val packageDirName = FileUtils.dirPath(forPackage)
+ getSubDir(packageDirName)
+ }
+ }
+
+ private[nsc] def packages(inPackage: String): Seq[PackageEntry] = {
+ val dirForPackage = getDirectory(inPackage)
+ val nestedDirs: Array[F] = dirForPackage match {
+ case None => emptyFiles
+ case Some(directory) => listChildren(directory, Some(isPackage))
+ }
+ val prefix = PackageNameUtils.packagePrefix(inPackage)
+ nestedDirs.map(f => PackageEntryImpl(prefix + getName(f)))
+ }
+
+ protected def files(inPackage: String): Seq[FileEntryType] = {
+ val dirForPackage = getDirectory(inPackage)
+ val files: Array[F] = dirForPackage match {
+ case None => emptyFiles
+ case Some(directory) => listChildren(directory, Some(isMatchingFile))
+ }
+ files.map(f => createFileEntry(toAbstractFile(f)))
+ }
+
+ private[nsc] def list(inPackage: String): ClassPathEntries = {
+ val dirForPackage = getDirectory(inPackage)
+ val files: Array[F] = dirForPackage match {
+ case None => emptyFiles
+ case Some(directory) => listChildren(directory)
+ }
+ val packagePrefix = PackageNameUtils.packagePrefix(inPackage)
+ val packageBuf = collection.mutable.ArrayBuffer.empty[PackageEntry]
+ val fileBuf = collection.mutable.ArrayBuffer.empty[FileEntryType]
+ for (file <- files) {
+ if (isPackage(file))
+ packageBuf += PackageEntryImpl(packagePrefix + getName(file))
+ else if (isMatchingFile(file))
+ fileBuf += createFileEntry(toAbstractFile(file))
+ }
+ ClassPathEntries(packageBuf, fileBuf)
+ }
+}
+
+trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends DirectoryLookup[FileEntryType] {
+ type F = File
+
+ protected def emptyFiles: Array[File] = Array.empty
+ protected def getSubDir(packageDirName: String): Option[File] = {
+ val packageDir = new File(dir, packageDirName)
+ if (packageDir.exists && packageDir.isDirectory) Some(packageDir)
+ else None
+ }
+ protected def listChildren(dir: File, filter: Option[File => Boolean]): Array[File] = {
+ val listing = filter match {
+ case Some(f) => dir.listFiles(mkFileFilter(f))
+ case None => dir.listFiles()
+ }
+
+ // Sort by file name for stable order of directory .class entries in package scope.
+ // This gives stable results ordering of base type sequences for unrelated classes
+ // with the same base type depth.
+ //
+ // Notably, this will stably infer`Product with Serializable`
+ // as the type of `case class C(); case class D(); List(C(), D()).head`, rather than the opposite order.
+ // On Mac, the HFS performs this sorting transparently, but on Linux the order is unspecified.
+ //
+ // Note this behaviour can be enabled in javac with `javac -XDsortfiles`, but that's only
+ // intended to improve determinism of the compiler for compiler hackers.
+ util.Arrays.sort(listing, (o1: File, o2: File) => o1.getName.compareTo(o2.getName))
+ listing
+ }
+ protected def getName(f: File): String = f.getName
+ protected def toAbstractFile(f: File): AbstractFile = new PlainFile(new scala.reflect.io.File(f))
+ protected def isPackage(f: File): Boolean = f.isPackage
+
+ assert(dir != null, "Directory file in DirectoryFileLookup cannot be null")
+
+ def asURLs: Seq[URL] = Seq(dir.toURI.toURL)
+ def asClassPathStrings: Seq[String] = Seq(dir.getPath)
+}
+
+object JrtClassPath {
+ import java.nio.file._, java.net.URI
+ def apply(): Option[ClassPath] = {
+ try {
+ val fs = FileSystems.getFileSystem(URI.create("jrt:/"))
+ Some(new JrtClassPath(fs))
+ } catch {
+ case _: ProviderNotFoundException | _: FileSystemNotFoundException =>
+ None
+ }
+ }
+}
+
+/**
+ * Implementation `ClassPath` based on the JDK 9 encapsulated runtime modules (JEP-220)
+ *
+ * https://bugs.openjdk.java.net/browse/JDK-8066492 is the most up to date reference
+ * for the structure of the jrt:// filesystem.
+ *
+ * The implementation assumes that no classes exist in the empty package.
+ */
+final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with NoSourcePaths {
+ import java.nio.file.Path, java.nio.file._
+ type F = Path
+ private val dir: Path = fs.getPath("/packages")
+
+ // e.g. "java.lang" -> Seq("/modules/java.base")
+ private val packageToModuleBases: Map[String, Seq[Path]] = {
+ val ps = Files.newDirectoryStream(dir).iterator().asScala
+ def lookup(pack: Path): Seq[Path] = {
+ Files.list(pack).iterator().asScala.map(l => if (Files.isSymbolicLink(l)) Files.readSymbolicLink(l) else l).toList
+ }
+ ps.map(p => (p.toString.stripPrefix("/packages/"), lookup(p))).toMap
+ }
+
+ override private[nsc] def packages(inPackage: String): Seq[PackageEntry] = {
+ def matches(packageDottedName: String) =
+ if (packageDottedName.contains("."))
+ packageOf(packageDottedName) == inPackage
+ else inPackage == ""
+ packageToModuleBases.keysIterator.filter(matches).map(PackageEntryImpl(_)).toVector
+ }
+ private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = {
+ if (inPackage == "") Nil
+ else {
+ packageToModuleBases.getOrElse(inPackage, Nil).flatMap(x =>
+ Files.list(x.resolve(inPackage.replace('.', '/'))).iterator().asScala.filter(_.getFileName.toString.endsWith(".class"))).map(x =>
+ ClassFileEntryImpl(new PlainNioFile(x))).toVector
+ }
+ }
+
+ override private[nsc] def list(inPackage: String): ClassPathEntries =
+ if (inPackage == "") ClassPathEntries(packages(inPackage), Nil)
+ else ClassPathEntries(packages(inPackage), classes(inPackage))
+
+ def asURLs: Seq[URL] = Seq(dir.toUri.toURL)
+ // We don't yet have a scheme to represent the JDK modules in our `-classpath`.
+ // java models them as entries in the new "module path", we'll probably need to follow this.
+ def asClassPathStrings: Seq[String] = Nil
+
+ def findClassFile(className: String): Option[AbstractFile] = {
+ if (!className.contains(".")) None
+ else {
+ val inPackage = packageOf(className)
+ packageToModuleBases.getOrElse(inPackage, Nil).iterator.flatMap{x =>
+ val file = x.resolve(className.replace('.', '/') + ".class")
+ if (Files.exists(file)) new scala.reflect.io.PlainNioFile(file) :: Nil else Nil
+ }.take(1).toList.headOption
+ }
+ }
+ private def packageOf(dottedClassName: String): String =
+ dottedClassName.substring(0, dottedClassName.lastIndexOf("."))
+}
+
+case class DirectoryClassPath(dir: File) extends JFileDirectoryLookup[ClassFileEntryImpl] with NoSourcePaths {
+ override def findClass(className: String): Option[ClassRepresentation] = findClassFile(className) map ClassFileEntryImpl
+
+ def findClassFile(className: String): Option[AbstractFile] = {
+ val relativePath = FileUtils.dirPath(className)
+ val classFile = new File(s"$dir/$relativePath.class")
+ if (classFile.exists) {
+ val wrappedClassFile = new scala.reflect.io.File(classFile)
+ val abstractClassFile = new PlainFile(wrappedClassFile)
+ Some(abstractClassFile)
+ } else None
+ }
+
+ protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file)
+ protected def isMatchingFile(f: File): Boolean = f.isClass
+
+ private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage)
+}
+
+case class DirectorySourcePath(dir: File) extends JFileDirectoryLookup[SourceFileEntryImpl] with NoClassPaths {
+ def asSourcePathString: String = asClassPathString
+
+ protected def createFileEntry(file: AbstractFile): SourceFileEntryImpl = SourceFileEntryImpl(file)
+ protected def isMatchingFile(f: File): Boolean = endsScalaOrJava(f.getName)
+
+ override def findClass(className: String): Option[ClassRepresentation] = findSourceFile(className) map SourceFileEntryImpl
+
+ private def findSourceFile(className: String): Option[AbstractFile] = {
+ val relativePath = FileUtils.dirPath(className)
+ val sourceFile = Stream("scala", "java")
+ .map(ext => new File(s"$dir/$relativePath.$ext"))
+ .collectFirst { case file if file.exists() => file }
+
+ sourceFile.map { file =>
+ val wrappedSourceFile = new scala.reflect.io.File(file)
+ val abstractSourceFile = new PlainFile(wrappedSourceFile)
+ abstractSourceFile
+ }
+ }
+
+ private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage)
+}
diff --git a/src/compiler/scala/tools/nsc/classpath/DirectoryFlatClassPath.scala b/src/compiler/scala/tools/nsc/classpath/DirectoryFlatClassPath.scala
deleted file mode 100644
index 81d2f7320f..0000000000
--- a/src/compiler/scala/tools/nsc/classpath/DirectoryFlatClassPath.scala
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (c) 2014 Contributor. All rights reserved.
- */
-package scala.tools.nsc.classpath
-
-import java.io.File
-import java.io.FileFilter
-import java.net.URL
-import scala.reflect.io.AbstractFile
-import scala.reflect.io.PlainFile
-import scala.tools.nsc.util.ClassRepresentation
-import FileUtils._
-
-/**
- * A trait allowing to look for classpath entries of given type in directories.
- * It provides common logic for classes handling class and source files.
- * It makes use of the fact that in the case of nested directories it's easy to find a file
- * when we have a name of a package.
- */
-trait DirectoryFileLookup[FileEntryType <: ClassRepClassPathEntry] extends FlatClassPath {
- val dir: File
- assert(dir != null, "Directory file in DirectoryFileLookup cannot be null")
-
- override def asURLs: Seq[URL] = Seq(dir.toURI.toURL)
- override def asClassPathStrings: Seq[String] = Seq(dir.getPath)
-
- import FlatClassPath.RootPackage
- private def getDirectory(forPackage: String): Option[File] = {
- if (forPackage == RootPackage) {
- Some(dir)
- } else {
- val packageDirName = FileUtils.dirPath(forPackage)
- val packageDir = new File(dir, packageDirName)
- if (packageDir.exists && packageDir.isDirectory) {
- Some(packageDir)
- } else None
- }
- }
-
- override private[nsc] def packages(inPackage: String): Seq[PackageEntry] = {
- val dirForPackage = getDirectory(inPackage)
- val nestedDirs: Array[File] = dirForPackage match {
- case None => Array.empty
- case Some(directory) => directory.listFiles(DirectoryFileLookup.packageDirectoryFileFilter)
- }
- val prefix = PackageNameUtils.packagePrefix(inPackage)
- val entries = nestedDirs map { file =>
- PackageEntryImpl(prefix + file.getName)
- }
- entries
- }
-
- protected def files(inPackage: String): Seq[FileEntryType] = {
- val dirForPackage = getDirectory(inPackage)
- val files: Array[File] = dirForPackage match {
- case None => Array.empty
- case Some(directory) => directory.listFiles(fileFilter)
- }
- val entries = files map { file =>
- val wrappedFile = new scala.reflect.io.File(file)
- createFileEntry(new PlainFile(wrappedFile))
- }
- entries
- }
-
- override private[nsc] def list(inPackage: String): FlatClassPathEntries = {
- val dirForPackage = getDirectory(inPackage)
- val files: Array[File] = dirForPackage match {
- case None => Array.empty
- case Some(directory) => directory.listFiles()
- }
- val packagePrefix = PackageNameUtils.packagePrefix(inPackage)
- val packageBuf = collection.mutable.ArrayBuffer.empty[PackageEntry]
- val fileBuf = collection.mutable.ArrayBuffer.empty[FileEntryType]
- for (file <- files) {
- if (file.isPackage) {
- val pkgEntry = PackageEntryImpl(packagePrefix + file.getName)
- packageBuf += pkgEntry
- } else if (fileFilter.accept(file)) {
- val wrappedFile = new scala.reflect.io.File(file)
- val abstractFile = new PlainFile(wrappedFile)
- fileBuf += createFileEntry(abstractFile)
- }
- }
- FlatClassPathEntries(packageBuf, fileBuf)
- }
-
- protected def createFileEntry(file: AbstractFile): FileEntryType
- protected def fileFilter: FileFilter
-}
-
-object DirectoryFileLookup {
-
- private[classpath] object packageDirectoryFileFilter extends FileFilter {
- override def accept(pathname: File): Boolean = pathname.isPackage
- }
-}
-
-case class DirectoryFlatClassPath(dir: File)
- extends DirectoryFileLookup[ClassFileEntryImpl]
- with NoSourcePaths {
-
- override def findClass(className: String): Option[ClassRepresentation[AbstractFile]] = findClassFile(className) map ClassFileEntryImpl
-
- override def findClassFile(className: String): Option[AbstractFile] = {
- val relativePath = FileUtils.dirPath(className)
- val classFile = new File(s"$dir/$relativePath.class")
- if (classFile.exists) {
- val wrappedClassFile = new scala.reflect.io.File(classFile)
- val abstractClassFile = new PlainFile(wrappedClassFile)
- Some(abstractClassFile)
- } else None
- }
-
- override protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file)
- override protected def fileFilter: FileFilter = DirectoryFlatClassPath.classFileFilter
-
- override private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage)
-}
-
-object DirectoryFlatClassPath {
-
- private val classFileFilter = new FileFilter {
- override def accept(pathname: File): Boolean = pathname.isClass
- }
-}
-
-case class DirectoryFlatSourcePath(dir: File)
- extends DirectoryFileLookup[SourceFileEntryImpl]
- with NoClassPaths {
-
- override def asSourcePathString: String = asClassPathString
-
- override protected def createFileEntry(file: AbstractFile): SourceFileEntryImpl = SourceFileEntryImpl(file)
- override protected def fileFilter: FileFilter = DirectoryFlatSourcePath.sourceFileFilter
-
- override def findClass(className: String): Option[ClassRepresentation[AbstractFile]] = {
- findSourceFile(className) map SourceFileEntryImpl
- }
-
- private def findSourceFile(className: String): Option[AbstractFile] = {
- val relativePath = FileUtils.dirPath(className)
- val sourceFile = Stream("scala", "java")
- .map(ext => new File(s"$dir/$relativePath.$ext"))
- .collectFirst { case file if file.exists() => file }
-
- sourceFile.map { file =>
- val wrappedSourceFile = new scala.reflect.io.File(file)
- val abstractSourceFile = new PlainFile(wrappedSourceFile)
- abstractSourceFile
- }
- }
-
- override private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage)
-}
-
-object DirectoryFlatSourcePath {
-
- private val sourceFileFilter = new FileFilter {
- override def accept(pathname: File): Boolean = endsScalaOrJava(pathname.getName)
- }
-}
diff --git a/src/compiler/scala/tools/nsc/classpath/FileUtils.scala b/src/compiler/scala/tools/nsc/classpath/FileUtils.scala
index ee2528e15c..2ade83c6f9 100644
--- a/src/compiler/scala/tools/nsc/classpath/FileUtils.scala
+++ b/src/compiler/scala/tools/nsc/classpath/FileUtils.scala
@@ -3,7 +3,7 @@
*/
package scala.tools.nsc.classpath
-import java.io.{ File => JFile }
+import java.io.{File => JFile, FileFilter}
import java.net.URL
import scala.reflect.internal.FatalError
import scala.reflect.io.AbstractFile
@@ -63,6 +63,10 @@ object FileUtils {
// probably it should match a pattern like [a-z_]{1}[a-z0-9_]* but it cannot be changed
// because then some tests in partest don't pass
- private def mayBeValidPackage(dirName: String): Boolean =
+ def mayBeValidPackage(dirName: String): Boolean =
(dirName != "META-INF") && (dirName != "") && (dirName.charAt(0) != '.')
+
+ def mkFileFilter(f: JFile => Boolean) = new FileFilter {
+ def accept(pathname: JFile): Boolean = f(pathname)
+ }
}
diff --git a/src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala b/src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala
deleted file mode 100644
index cb201617d2..0000000000
--- a/src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (c) 2014 Contributor. All rights reserved.
- */
-package scala.tools.nsc.classpath
-
-import scala.reflect.io.AbstractFile
-import scala.tools.nsc.util.{ ClassFileLookup, ClassPath, ClassRepresentation }
-
-/**
- * A base trait for the particular flat classpath representation implementations.
- *
- * We call this variant of a classpath representation flat because it's possible to
- * query the whole classpath using just single instance extending this trait.
- *
- * This is an alternative design compared to scala.tools.nsc.util.ClassPath
- */
-trait FlatClassPath extends ClassFileLookup[AbstractFile] {
- /** Empty string represents root package */
- private[nsc] def packages(inPackage: String): Seq[PackageEntry]
- private[nsc] def classes(inPackage: String): Seq[ClassFileEntry]
- private[nsc] def sources(inPackage: String): Seq[SourceFileEntry]
-
- /** Allows to get entries for packages and classes merged with sources possibly in one pass. */
- private[nsc] def list(inPackage: String): FlatClassPathEntries
-
- // A default implementation which should be overridden, if we can create the more efficient
- // solution for a given type of FlatClassPath
- override def findClass(className: String): Option[ClassRepresentation[AbstractFile]] = {
- val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className)
-
- val foundClassFromClassFiles = classes(pkg)
- .find(_.name == simpleClassName)
-
- def findClassInSources = sources(pkg)
- .find(_.name == simpleClassName)
-
- foundClassFromClassFiles orElse findClassInSources
- }
-
- override def asClassPathString: String = ClassPath.join(asClassPathStrings: _*)
- def asClassPathStrings: Seq[String]
-}
-
-object FlatClassPath {
- val RootPackage = ""
-}
-
-case class FlatClassPathEntries(packages: Seq[PackageEntry], classesAndSources: Seq[ClassRepClassPathEntry])
-
-object FlatClassPathEntries {
- import scala.language.implicitConversions
- // to have working unzip method
- implicit def entry2Tuple(entry: FlatClassPathEntries) = (entry.packages, entry.classesAndSources)
-}
-
-sealed trait ClassRepClassPathEntry extends ClassRepresentation[AbstractFile]
-
-trait ClassFileEntry extends ClassRepClassPathEntry {
- def file: AbstractFile
-}
-
-trait SourceFileEntry extends ClassRepClassPathEntry {
- def file: AbstractFile
-}
-
-trait PackageEntry {
- def name: String
-}
-
-private[nsc] case class ClassFileEntryImpl(file: AbstractFile) extends ClassFileEntry {
- override def name = FileUtils.stripClassExtension(file.name) // class name
-
- override def binary: Option[AbstractFile] = Some(file)
- override def source: Option[AbstractFile] = None
-}
-
-private[nsc] case class SourceFileEntryImpl(file: AbstractFile) extends SourceFileEntry {
- override def name = FileUtils.stripSourceExtension(file.name)
-
- override def binary: Option[AbstractFile] = None
- override def source: Option[AbstractFile] = Some(file)
-}
-
-private[nsc] case class ClassAndSourceFilesEntry(classFile: AbstractFile, srcFile: AbstractFile) extends ClassRepClassPathEntry {
- override def name = FileUtils.stripClassExtension(classFile.name)
-
- override def binary: Option[AbstractFile] = Some(classFile)
- override def source: Option[AbstractFile] = Some(srcFile)
-}
-
-private[nsc] case class PackageEntryImpl(name: String) extends PackageEntry
-
-private[nsc] trait NoSourcePaths {
- def asSourcePathString: String = ""
- private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] = Seq.empty
-}
-
-private[nsc] trait NoClassPaths {
- def findClassFile(className: String): Option[AbstractFile] = None
- private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = Seq.empty
-}
diff --git a/src/compiler/scala/tools/nsc/classpath/FlatClassPathFactory.scala b/src/compiler/scala/tools/nsc/classpath/FlatClassPathFactory.scala
deleted file mode 100644
index 7f67381d4d..0000000000
--- a/src/compiler/scala/tools/nsc/classpath/FlatClassPathFactory.scala
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (c) 2014 Contributor. All rights reserved.
- */
-package scala.tools.nsc.classpath
-
-import scala.tools.nsc.Settings
-import scala.tools.nsc.io.AbstractFile
-import scala.tools.nsc.util.ClassPath
-import FileUtils.AbstractFileOps
-
-/**
- * Provides factory methods for flat classpath. When creating classpath instances for a given path,
- * it uses proper type of classpath depending on a types of particular files containing sources or classes.
- */
-class FlatClassPathFactory(settings: Settings) extends ClassPathFactory[FlatClassPath] {
-
- override def newClassPath(file: AbstractFile): FlatClassPath =
- if (file.isJarOrZip)
- ZipAndJarFlatClassPathFactory.create(file, settings)
- else if (file.isDirectory)
- new DirectoryFlatClassPath(file.file)
- else
- sys.error(s"Unsupported classpath element: $file")
-
- override def sourcesInPath(path: String): List[FlatClassPath] =
- for {
- file <- expandPath(path, expandStar = false)
- dir <- Option(AbstractFile getDirectory file)
- } yield createSourcePath(dir)
-
- private def createSourcePath(file: AbstractFile): FlatClassPath =
- if (file.isJarOrZip)
- ZipAndJarFlatSourcePathFactory.create(file, settings)
- else if (file.isDirectory)
- new DirectoryFlatSourcePath(file.file)
- else
- sys.error(s"Unsupported sourcepath element: $file")
-}
diff --git a/src/compiler/scala/tools/nsc/classpath/PackageNameUtils.scala b/src/compiler/scala/tools/nsc/classpath/PackageNameUtils.scala
index c907d565d2..39b0d78135 100644
--- a/src/compiler/scala/tools/nsc/classpath/PackageNameUtils.scala
+++ b/src/compiler/scala/tools/nsc/classpath/PackageNameUtils.scala
@@ -3,7 +3,7 @@
*/
package scala.tools.nsc.classpath
-import scala.tools.nsc.classpath.FlatClassPath.RootPackage
+import scala.tools.nsc.util.ClassPath.RootPackage
/**
* Common methods related to package names represented as String
diff --git a/src/compiler/scala/tools/nsc/classpath/VirtualDirectoryClassPath.scala b/src/compiler/scala/tools/nsc/classpath/VirtualDirectoryClassPath.scala
new file mode 100644
index 0000000000..6fefaf0da0
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/classpath/VirtualDirectoryClassPath.scala
@@ -0,0 +1,40 @@
+package scala.tools.nsc.classpath
+
+import scala.tools.nsc.util.ClassRepresentation
+import scala.reflect.io.{AbstractFile, Path, PlainFile, VirtualDirectory}
+import FileUtils._
+import java.net.URL
+
+import scala.reflect.internal.util.AbstractFileClassLoader
+import scala.tools.nsc.util.ClassPath
+
+case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath with DirectoryLookup[ClassFileEntryImpl] with NoSourcePaths {
+ type F = AbstractFile
+
+ protected def emptyFiles: Array[AbstractFile] = Array.empty
+ protected def getSubDir(packageDirName: String): Option[AbstractFile] =
+ Option(AbstractFileClassLoader.lookupPath(dir)(packageDirName.split('/'), directory = true))
+ protected def listChildren(dir: AbstractFile, filter: Option[AbstractFile => Boolean] = None): Array[F] = filter match {
+ case Some(f) => dir.iterator.filter(f).toArray
+ case _ => dir.toArray
+ }
+ def getName(f: AbstractFile): String = f.name
+ def toAbstractFile(f: AbstractFile): AbstractFile = f
+ def isPackage(f: AbstractFile): Boolean = f.isPackage
+
+ // mimic the behavior of the old nsc.util.DirectoryClassPath
+ def asURLs: Seq[URL] = Seq(new URL(dir.name))
+ def asClassPathStrings: Seq[String] = Seq(dir.path)
+
+ override def findClass(className: String): Option[ClassRepresentation] = findClassFile(className) map ClassFileEntryImpl
+
+ def findClassFile(className: String): Option[AbstractFile] = {
+ val relativePath = FileUtils.dirPath(className) + ".class"
+ Option(AbstractFileClassLoader.lookupPath(dir)(relativePath split '/', directory = false))
+ }
+
+ private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage)
+
+ protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file)
+ protected def isMatchingFile(f: AbstractFile): Boolean = f.isClass
+}
diff --git a/src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala b/src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala
index 85c7c3c843..fe74e5f874 100644
--- a/src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala
+++ b/src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala
@@ -6,7 +6,8 @@ package scala.tools.nsc.classpath
import java.io.File
import java.net.URL
import scala.annotation.tailrec
-import scala.reflect.io.{ AbstractFile, FileZipArchive, ManifestResources }
+import scala.reflect.io.{AbstractFile, FileZipArchive, ManifestResources}
+import scala.tools.nsc.util.ClassPath
import scala.tools.nsc.Settings
import FileUtils._
@@ -19,17 +20,16 @@ import FileUtils._
* when there are a lot of projects having a lot of common dependencies.
*/
sealed trait ZipAndJarFileLookupFactory {
+ private val cache = collection.mutable.Map.empty[AbstractFile, ClassPath]
- private val cache = collection.mutable.Map.empty[AbstractFile, FlatClassPath]
-
- def create(zipFile: AbstractFile, settings: Settings): FlatClassPath = {
+ def create(zipFile: AbstractFile, settings: Settings): ClassPath = {
if (settings.YdisableFlatCpCaching) createForZipFile(zipFile)
else createUsingCache(zipFile, settings)
}
- protected def createForZipFile(zipFile: AbstractFile): FlatClassPath
+ protected def createForZipFile(zipFile: AbstractFile): ClassPath
- private def createUsingCache(zipFile: AbstractFile, settings: Settings): FlatClassPath = cache.synchronized {
+ private def createUsingCache(zipFile: AbstractFile, settings: Settings): ClassPath = cache.synchronized {
def newClassPathInstance = {
if (settings.verbose || settings.Ylogcp)
println(s"$zipFile is not yet in the classpath cache")
@@ -40,12 +40,11 @@ sealed trait ZipAndJarFileLookupFactory {
}
/**
- * Manages creation of flat classpath for class files placed in zip and jar files.
+ * Manages creation of classpath for class files placed in zip and jar files.
* It should be the only way of creating them as it provides caching.
*/
-object ZipAndJarFlatClassPathFactory extends ZipAndJarFileLookupFactory {
-
- private case class ZipArchiveFlatClassPath(zipFile: File)
+object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory {
+ private case class ZipArchiveClassPath(zipFile: File)
extends ZipArchiveFileLookup[ClassFileEntryImpl]
with NoSourcePaths {
@@ -67,10 +66,7 @@ object ZipAndJarFlatClassPathFactory extends ZipAndJarFileLookupFactory {
* with a particularly prepared scala-library.jar. It should have all classes listed in the manifest like e.g. this entry:
* Name: scala/Function2$mcFJD$sp.class
*/
- private case class ManifestResourcesFlatClassPath(file: ManifestResources)
- extends FlatClassPath
- with NoSourcePaths {
-
+ private case class ManifestResourcesClassPath(file: ManifestResources) extends ClassPath with NoSourcePaths {
override def findClassFile(className: String): Option[AbstractFile] = {
val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className)
classes(pkg).find(_.name == simpleClassName).map(_.file)
@@ -80,8 +76,8 @@ object ZipAndJarFlatClassPathFactory extends ZipAndJarFileLookupFactory {
override def asURLs: Seq[URL] = file.toURLs()
- import ManifestResourcesFlatClassPath.PackageFileInfo
- import ManifestResourcesFlatClassPath.PackageInfo
+ import ManifestResourcesClassPath.PackageFileInfo
+ import ManifestResourcesClassPath.PackageInfo
/**
* A cache mapping package name to abstract file for package directory and subpackages of given package.
@@ -119,8 +115,8 @@ object ZipAndJarFlatClassPathFactory extends ZipAndJarFileLookupFactory {
}
val subpackages = getSubpackages(file)
- packages.put(FlatClassPath.RootPackage, PackageFileInfo(file, subpackages))
- traverse(FlatClassPath.RootPackage, subpackages, collection.mutable.Queue())
+ packages.put(ClassPath.RootPackage, PackageFileInfo(file, subpackages))
+ traverse(ClassPath.RootPackage, subpackages, collection.mutable.Queue())
packages
}
@@ -137,21 +133,21 @@ object ZipAndJarFlatClassPathFactory extends ZipAndJarFileLookupFactory {
(for (file <- pkg if file.isClass) yield ClassFileEntryImpl(file))(collection.breakOut)
}
- override private[nsc] def list(inPackage: String): FlatClassPathEntries = FlatClassPathEntries(packages(inPackage), classes(inPackage))
+ override private[nsc] def list(inPackage: String): ClassPathEntries = ClassPathEntries(packages(inPackage), classes(inPackage))
}
- private object ManifestResourcesFlatClassPath {
+ private object ManifestResourcesClassPath {
case class PackageFileInfo(packageFile: AbstractFile, subpackages: Seq[AbstractFile])
case class PackageInfo(packageName: String, subpackages: List[AbstractFile])
}
- override protected def createForZipFile(zipFile: AbstractFile): FlatClassPath =
+ override protected def createForZipFile(zipFile: AbstractFile): ClassPath =
if (zipFile.file == null) createWithoutUnderlyingFile(zipFile)
- else ZipArchiveFlatClassPath(zipFile.file)
+ else ZipArchiveClassPath(zipFile.file)
private def createWithoutUnderlyingFile(zipFile: AbstractFile) = zipFile match {
case manifestRes: ManifestResources =>
- ManifestResourcesFlatClassPath(manifestRes)
+ ManifestResourcesClassPath(manifestRes)
case _ =>
val errorMsg = s"Abstract files which don't have an underlying file and are not ManifestResources are not supported. There was $zipFile"
throw new IllegalArgumentException(errorMsg)
@@ -159,12 +155,11 @@ object ZipAndJarFlatClassPathFactory extends ZipAndJarFileLookupFactory {
}
/**
- * Manages creation of flat classpath for source files placed in zip and jar files.
+ * Manages creation of classpath for source files placed in zip and jar files.
* It should be the only way of creating them as it provides caching.
*/
-object ZipAndJarFlatSourcePathFactory extends ZipAndJarFileLookupFactory {
-
- private case class ZipArchiveFlatSourcePath(zipFile: File)
+object ZipAndJarSourcePathFactory extends ZipAndJarFileLookupFactory {
+ private case class ZipArchiveSourcePath(zipFile: File)
extends ZipArchiveFileLookup[SourceFileEntryImpl]
with NoClassPaths {
@@ -176,5 +171,5 @@ object ZipAndJarFlatSourcePathFactory extends ZipAndJarFileLookupFactory {
override protected def isRequiredFileType(file: AbstractFile): Boolean = file.isScalaOrJavaSource
}
- override protected def createForZipFile(zipFile: AbstractFile): FlatClassPath = ZipArchiveFlatSourcePath(zipFile.file)
+ override protected def createForZipFile(zipFile: AbstractFile): ClassPath = ZipArchiveSourcePath(zipFile.file)
}
diff --git a/src/compiler/scala/tools/nsc/classpath/ZipArchiveFileLookup.scala b/src/compiler/scala/tools/nsc/classpath/ZipArchiveFileLookup.scala
index 1d0de57779..9c147cf8cc 100644
--- a/src/compiler/scala/tools/nsc/classpath/ZipArchiveFileLookup.scala
+++ b/src/compiler/scala/tools/nsc/classpath/ZipArchiveFileLookup.scala
@@ -9,13 +9,14 @@ import scala.collection.Seq
import scala.reflect.io.AbstractFile
import scala.reflect.io.FileZipArchive
import FileUtils.AbstractFileOps
+import scala.tools.nsc.util.{ClassPath, ClassRepresentation}
/**
* A trait allowing to look for classpath entries of given type in zip and jar files.
* It provides common logic for classes handling class and source files.
* It's aware of things like e.g. META-INF directory which is correctly skipped.
*/
-trait ZipArchiveFileLookup[FileEntryType <: ClassRepClassPathEntry] extends FlatClassPath {
+trait ZipArchiveFileLookup[FileEntryType <: ClassRepresentation] extends ClassPath {
val zipFile: File
assert(zipFile != null, "Zip file in ZipArchiveFileLookup cannot be null")
@@ -39,7 +40,7 @@ trait ZipArchiveFileLookup[FileEntryType <: ClassRepClassPathEntry] extends Flat
entry <- dirEntry.iterator if isRequiredFileType(entry)
} yield createFileEntry(entry)
- override private[nsc] def list(inPackage: String): FlatClassPathEntries = {
+ override private[nsc] def list(inPackage: String): ClassPathEntries = {
val foundDirEntry = findDirEntry(inPackage)
foundDirEntry map { dirEntry =>
@@ -53,11 +54,11 @@ trait ZipArchiveFileLookup[FileEntryType <: ClassRepClassPathEntry] extends Flat
else if (isRequiredFileType(entry))
fileBuf += createFileEntry(entry)
}
- FlatClassPathEntries(pkgBuf, fileBuf)
- } getOrElse FlatClassPathEntries(Seq.empty, Seq.empty)
+ ClassPathEntries(pkgBuf, fileBuf)
+ } getOrElse ClassPathEntries(Seq.empty, Seq.empty)
}
- private def findDirEntry(pkg: String) = {
+ private def findDirEntry(pkg: String): Option[archive.DirEntry] = {
val dirName = s"${FileUtils.dirPath(pkg)}/"
archive.allDirs.get(dirName)
}
diff --git a/src/compiler/scala/tools/nsc/io/Jar.scala b/src/compiler/scala/tools/nsc/io/Jar.scala
index efb026cdff..779f546f69 100644
--- a/src/compiler/scala/tools/nsc/io/Jar.scala
+++ b/src/compiler/scala/tools/nsc/io/Jar.scala
@@ -6,11 +6,12 @@
package scala.tools.nsc
package io
-import java.io.{ InputStream, OutputStream, IOException, FileNotFoundException, FileInputStream, DataOutputStream }
+import scala.language.postfixOps
+
+import java.io.{ InputStream, OutputStream, DataOutputStream }
import java.util.jar._
import scala.collection.JavaConverters._
import Attributes.Name
-import scala.language.{ implicitConversions, postfixOps }
// Attributes.Name instances:
//
diff --git a/src/compiler/scala/tools/nsc/io/Socket.scala b/src/compiler/scala/tools/nsc/io/Socket.scala
index a803e4121a..a17517da2e 100644
--- a/src/compiler/scala/tools/nsc/io/Socket.scala
+++ b/src/compiler/scala/tools/nsc/io/Socket.scala
@@ -8,7 +8,7 @@ package io
import java.io.{ IOException, InputStreamReader, BufferedReader, PrintWriter, Closeable }
import java.io.{ BufferedOutputStream, BufferedReader }
-import java.net.{ ServerSocket, SocketException, SocketTimeoutException, InetAddress, Socket => JSocket }
+import java.net.{ InetAddress, Socket => JSocket }
import scala.io.Codec
/** A skeletal only-as-much-as-I-need Socket wrapper.
diff --git a/src/compiler/scala/tools/nsc/io/SourceReader.scala b/src/compiler/scala/tools/nsc/io/SourceReader.scala
index 3220c2e2b2..b84c509a32 100644
--- a/src/compiler/scala/tools/nsc/io/SourceReader.scala
+++ b/src/compiler/scala/tools/nsc/io/SourceReader.scala
@@ -7,7 +7,7 @@
package scala.tools.nsc
package io
-import java.io.{ FileInputStream, InputStream, IOException }
+import java.io.{ FileInputStream, IOException }
import java.nio.{ByteBuffer, CharBuffer}
import java.nio.channels.{ ReadableByteChannel, Channels }
import java.nio.charset.{CharsetDecoder, CoderResult}
diff --git a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala
index 876247510b..3ef75679ee 100644
--- a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala
+++ b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala
@@ -27,7 +27,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
def freshName(prefix: String): Name = freshTermName(prefix)
def freshTermName(prefix: String): TermName = unit.freshTermName(prefix)
def freshTypeName(prefix: String): TypeName = unit.freshTypeName(prefix)
- def deprecationWarning(off: Int, msg: String) = currentRun.reporting.deprecationWarning(off, msg)
+ def deprecationWarning(off: Int, msg: String, since: String) = currentRun.reporting.deprecationWarning(off, msg, since)
implicit def i2p(offset : Int) : Position = Position.offset(unit.source, offset)
def warning(pos : Int, msg : String) : Unit = reporter.warning(pos, msg)
def syntaxError(pos: Int, msg: String) : Unit = reporter.error(pos, msg)
@@ -111,17 +111,14 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
def arrayOf(tpt: Tree) =
AppliedTypeTree(scalaDot(tpnme.Array), List(tpt))
- def blankExpr = Ident(nme.WILDCARD)
+ def blankExpr = EmptyTree
def makePackaging(pkg: RefTree, stats: List[Tree]): PackageDef =
atPos(pkg.pos) { PackageDef(pkg, stats) }
def makeTemplate(parents: List[Tree], stats: List[Tree]) =
- Template(
- parents,
- noSelfType,
- if (treeInfo.firstConstructor(stats) == EmptyTree) makeConstructor(List()) :: stats
- else stats)
+ Template(parents, noSelfType, if (treeInfo.firstConstructor(stats) == EmptyTree)
+ makeConstructor(Nil) :: stats else stats)
def makeSyntheticParam(count: Int, tpt: Tree): ValDef =
makeParam(nme.syntheticParamName(count), tpt)
@@ -135,6 +132,11 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
DefDef(Modifiers(Flags.JAVA), nme.CONSTRUCTOR, List(), List(vparams), TypeTree(), blankExpr)
}
+ /** A hook for joining the comment associated with a definition.
+ * Overridden by scaladoc.
+ */
+ def joinComment(trees: => List[Tree]): List[Tree] = trees
+
// ------------- general parsing ---------------------------
/** skip parent or brace enclosed sequence of things */
@@ -264,7 +266,8 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
}
}
- def typ(): Tree =
+ def typ(): Tree = {
+ annotations()
optArrayBrackets {
if (in.token == FINAL) in.nextToken()
if (in.token == IDENTIFIER) {
@@ -273,7 +276,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
// SelectFromTypeTree otherwise. See #3567.
// Select nodes can be later
// converted in the typechecker to SelectFromTypeTree if the class
- // turns out to be an instance ionner class instead of a static inner class.
+ // turns out to be an instance inner class instead of a static inner class.
def typeSelect(t: Tree, name: Name) = t match {
case Ident(_) | Select(_, _) => Select(t, name)
case _ => SelectFromTypeTree(t, name.toTypeName)
@@ -287,6 +290,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
basicType()
}
}
+ }
def typeArgs(t: Tree): Tree = {
val wildcards = new ListBuffer[TypeDef]
@@ -404,6 +408,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
def typeParam(): TypeDef =
atPos(in.currentPos) {
+ annotations()
val name = identForType()
val hi = if (in.token == EXTENDS) { in.nextToken() ; bound() } else EmptyTree
TypeDef(Modifiers(Flags.JAVA | Flags.DEFERRED | Flags.PARAM), name, Nil, TypeBoundsTree(EmptyTree, hi))
@@ -509,7 +514,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
EmptyTree
}
}
- // for abstract methods (of classes), the `DEFERRED` flag is alredy set.
+ // for abstract methods (of classes), the `DEFERRED` flag is already set.
// here we also set it for interface methods that are not static and not default.
if (!isConcreteInterfaceMethod) mods1 |= Flags.DEFERRED
List {
@@ -630,26 +635,8 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
Import(Ident(cdef.name.toTermName), ImportSelector.wildList)
}
- // Importing the companion object members cannot be done uncritically: see
- // ticket #2377 wherein a class contains two static inner classes, each of which
- // has a static inner class called "Builder" - this results in an ambiguity error
- // when each performs the import in the enclosing class's scope.
- //
- // To address this I moved the import Companion._ inside the class, as the first
- // statement. This should work without compromising the enclosing scope, but may (?)
- // end up suffering from the same issues it does in scala - specifically that this
- // leaves auxiliary constructors unable to access members of the companion object
- // as unqualified identifiers.
- def addCompanionObject(statics: List[Tree], cdef: ClassDef): List[Tree] = {
- def implWithImport(importStmt: Tree) = deriveTemplate(cdef.impl)(importStmt :: _)
- // if there are no statics we can use the original cdef, but we always
- // create the companion so import A._ is not an error (see ticket #1700)
- val cdefNew =
- if (statics.isEmpty) cdef
- else deriveClassDef(cdef)(_ => implWithImport(importCompanionObject(cdef)))
-
- List(makeCompanionObject(cdefNew, statics), cdefNew)
- }
+ def addCompanionObject(statics: List[Tree], cdef: ClassDef): List[Tree] =
+ List(makeCompanionObject(cdef, statics), cdef)
def importDecl(): List[Tree] = {
accept(IMPORT)
@@ -756,8 +743,15 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
in.nextToken()
} else {
if (in.token == ENUM || definesInterface(in.token)) mods |= Flags.STATIC
- val decls = memberDecl(mods, parentToken)
- (if (mods.hasStaticFlag || inInterface && !(decls exists (_.isInstanceOf[DefDef])))
+ val decls = joinComment(memberDecl(mods, parentToken))
+
+ def isDefDef(tree: Tree): Boolean = tree match {
+ case _: DefDef => true
+ case DocDef(_, defn) => isDefDef(defn)
+ case _ => false
+ }
+
+ (if (mods.hasStaticFlag || inInterface && !(decls exists isDefDef))
statics
else
members) ++= decls
@@ -838,16 +832,10 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
val superclazz =
AppliedTypeTree(javaLangDot(tpnme.Enum), List(enumType))
val finalFlag = if (enumIsFinal) Flags.FINAL else 0l
- val abstractFlag = {
- // javac adds `ACC_ABSTRACT` to enum classes with deferred members
- val hasAbstractMember = body exists {
- case d: DefDef => d.mods.isDeferred
- case _ => false
- }
- if (hasAbstractMember) Flags.ABSTRACT else 0l
- }
addCompanionObject(consts ::: statics ::: predefs, atPos(pos) {
- ClassDef(mods | Flags.JAVA_ENUM | finalFlag | abstractFlag, name, List(),
+ // Marking the enum class SEALED | ABSTRACT enables exhaustiveness checking. See also ClassfileParser.
+ // This is a bit of a hack and requires excluding the ABSTRACT flag in the backend, see method javaClassfileFlags.
+ ClassDef(mods | Flags.JAVA_ENUM | Flags.SEALED | Flags.ABSTRACT | finalFlag, name, List(),
makeTemplate(superclazz :: interfaces, body))
})
}
@@ -874,10 +862,10 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
}
def typeDecl(mods: Modifiers): List[Tree] = in.token match {
- case ENUM => enumDecl(mods)
- case INTERFACE => interfaceDecl(mods)
+ case ENUM => joinComment(enumDecl(mods))
+ case INTERFACE => joinComment(interfaceDecl(mods))
case AT => annotationDecl(mods)
- case CLASS => classDecl(mods)
+ case CLASS => joinComment(classDecl(mods))
case _ => in.nextToken(); syntaxError("illegal start of type declaration", skipIt = true); List(errorTypeTree)
}
diff --git a/src/compiler/scala/tools/nsc/javac/JavaScanners.scala b/src/compiler/scala/tools/nsc/javac/JavaScanners.scala
index 94c9d07939..af9b63c8ae 100644
--- a/src/compiler/scala/tools/nsc/javac/JavaScanners.scala
+++ b/src/compiler/scala/tools/nsc/javac/JavaScanners.scala
@@ -217,7 +217,7 @@ trait JavaScanners extends ast.parser.ScannersCommon {
*
* @author Martin Odersky
*/
- abstract class JavaScanner extends AbstractJavaScanner with JavaTokenData with Cloneable with ScannerCommon {
+ abstract class JavaScanner extends AbstractJavaScanner with JavaTokenData with Cloneable with ScannerCommon with DocScanner {
override def intVal = super.intVal// todo: needed?
override def floatVal = super.floatVal
def currentPos: Position = g2p(pos - 1)
@@ -579,23 +579,36 @@ trait JavaScanners extends ast.parser.ScannersCommon {
}
}
- protected def skipComment(): Boolean = {
- @tailrec def skipLineComment(): Unit = in.ch match {
- case CR | LF | SU =>
- case _ => in.next; skipLineComment()
- }
- @tailrec def skipJavaComment(): Unit = in.ch match {
- case SU => incompleteInputError("unclosed comment")
- case '*' => in.next; if (in.ch == '/') in.next else skipJavaComment()
- case _ => in.next; skipJavaComment()
- }
+ final protected def putCommentChar(): Unit = { processCommentChar(); in.next() }
+
+ @tailrec final protected def skipBlockComment(isDoc: Boolean): Unit = {
+ if (isDoc) beginDocComment("/*") // the second '*' is the current character
+
in.ch match {
- case '/' => in.next ; skipLineComment() ; true
- case '*' => in.next ; skipJavaComment() ; true
- case _ => false
+ case SU => incompleteInputError("unclosed comment")
+ case '*' => putCommentChar() ; if (in.ch == '/') putCommentChar() else skipBlockComment(isDoc)
+ case _ => putCommentChar() ; skipBlockComment(isDoc)
}
}
+ @tailrec final protected def skipLineComment(): Unit = in.ch match {
+ case CR | LF | SU =>
+ case _ => putCommentChar() ; skipLineComment()
+ }
+
+ final protected def skipComment(): Boolean = in.ch match {
+ case '/' => putCommentChar() ; skipLineComment() ; finishDocComment() ; true
+ case '*' =>
+ putCommentChar()
+ in.ch match {
+ case '*' => skipBlockComment(isDoc = true)
+ case _ => skipBlockComment(isDoc = false)
+ }
+ finishDocComment()
+ true
+ case _ => false
+ }
+
// Identifiers ---------------------------------------------------------------
private def getIdentRest() {
@@ -862,9 +875,9 @@ trait JavaScanners extends ast.parser.ScannersCommon {
class JavaUnitScanner(unit: CompilationUnit) extends JavaScanner {
in = new JavaCharArrayReader(unit.source.content, !settings.nouescape.value, syntaxError)
init()
- def error (pos: Int, msg: String) = reporter.error(pos, msg)
+ def error(pos: Int, msg: String) = reporter.error(pos, msg)
def incompleteInputError(pos: Int, msg: String) = currentRun.parsing.incompleteInputError(pos, msg)
- def deprecationWarning(pos: Int, msg: String) = currentRun.reporting.deprecationWarning(pos, msg)
+ def deprecationWarning(pos: Int, msg: String, since: String) = currentRun.reporting.deprecationWarning(pos, msg, since)
implicit def g2p(pos: Int): Position = Position.offset(unit.source, pos)
}
}
diff --git a/src/compiler/scala/tools/nsc/plugins/Plugin.scala b/src/compiler/scala/tools/nsc/plugins/Plugin.scala
index dd17750cd4..ed1675e4cc 100644
--- a/src/compiler/scala/tools/nsc/plugins/Plugin.scala
+++ b/src/compiler/scala/tools/nsc/plugins/Plugin.scala
@@ -6,14 +6,12 @@
package scala.tools.nsc
package plugins
-import scala.tools.nsc.io.{ Jar }
+import scala.tools.nsc.io.Jar
import scala.reflect.internal.util.ScalaClassLoader
import scala.reflect.io.{ Directory, File, Path }
import java.io.InputStream
-import java.util.zip.ZipException
import scala.collection.mutable
-import mutable.ListBuffer
import scala.util.{ Try, Success, Failure }
/** Information about a plugin loaded from a jar file.
@@ -66,7 +64,7 @@ abstract class Plugin {
true
}
- @deprecated("use Plugin#init instead", since="2.11")
+ @deprecated("use Plugin#init instead", since="2.11.0")
def processOptions(options: List[String], error: String => Unit): Unit = {
if (!options.isEmpty) error(s"Error: $name takes no options")
}
diff --git a/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala b/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala
index 5bf611a7b0..224de97734 100644
--- a/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala
+++ b/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala
@@ -7,83 +7,88 @@ package scala
package tools.nsc
package reporters
-import java.io.{ BufferedReader, IOException, PrintWriter }
-import scala.reflect.internal.util._
-import StringOps._
+import java.io.{BufferedReader, PrintWriter}
+import scala.reflect.internal.util.{Position, StringOps}
+import Position.formatMessage
+import StringOps.{countElementsAsString => countAs, trimAllTrailingSpace => trimTrailing}
-/**
- * This class implements a Reporter that displays messages on a text console.
+/** This class implements a Reporter that displays messages on a text console.
*/
-class ConsoleReporter(val settings: Settings, reader: BufferedReader, writer: PrintWriter) extends AbstractReporter {
- def this(settings: Settings) = this(settings, Console.in, new PrintWriter(Console.err, true))
+class ConsoleReporter(val settings: Settings, reader: BufferedReader, writer: PrintWriter, echoWriter: PrintWriter) extends AbstractReporter {
+ def this(settings: Settings) = this(settings, Console.in, new PrintWriter(Console.err, true), new PrintWriter(Console.out, true))
+ def this(settings: Settings, reader: BufferedReader, writer: PrintWriter) =
+ this(settings, reader, writer, writer)
/** Whether a short file name should be displayed before errors */
var shortname: Boolean = false
/** maximal number of error messages to be printed */
+ @deprecated("configured by settings.maxerrs", since="2.12.2")
final val ERROR_LIMIT = 100
private def label(severity: Severity): String = severity match {
case ERROR => "error"
case WARNING => "warning"
- case INFO => null
+ case INFO => ""
}
- protected def clabel(severity: Severity): String = {
- val label0 = label(severity)
- if (label0 eq null) "" else label0 + ": "
+ protected def clabel(severity: Severity): String = label(severity) match {
+ case "" => ""
+ case s => s"$s: "
}
- /** Returns the number of errors issued totally as a string.
- */
- private def getCountString(severity: Severity): String =
- StringOps.countElementsAsString((severity).count, label(severity))
-
/** Prints the message. */
- def printMessage(msg: String) {
- writer print trimAllTrailingSpace(msg) + "\n"
+ def printMessage(msg: String): Unit = {
+ writer.println(trimTrailing(msg))
writer.flush()
}
- /** Prints the message with the given position indication. */
- def printMessage(posIn: Position, msg: String) {
- printMessage(Position.formatMessage(posIn, msg, shortname))
- }
- def print(pos: Position, msg: String, severity: Severity) {
- printMessage(pos, clabel(severity) + msg)
+ /** Prints the message to the echoWriter, which is usually stdout. */
+ override def echo(msg: String): Unit = {
+ echoWriter.println(trimTrailing(msg))
+ echoWriter.flush()
}
- /** Prints the column marker of the given position.
- */
- def printColumnMarker(pos: Position) =
- if (pos.isDefined) { printMessage(" " * (pos.column - 1) + "^") }
+ /** Prints the message with the given position indication. */
+ def printMessage(posIn: Position, msg: String): Unit = printMessage(formatMessage(posIn, msg, shortname))
- /** Prints the number of errors and warnings if their are non-zero. */
- def printSummary() {
- if (WARNING.count > 0) printMessage(getCountString(WARNING) + " found")
- if ( ERROR.count > 0) printMessage(getCountString(ERROR ) + " found")
- }
+ def print(pos: Position, msg: String, severity: Severity): Unit = printMessage(pos, s"${clabel(severity)}${msg}")
- def display(pos: Position, msg: String, severity: Severity) {
- if (severity != ERROR || severity.count <= ERROR_LIMIT)
- print(pos, msg, severity)
+ /** Prints the column marker of the given position. */
+ def printColumnMarker(pos: Position): Unit = if (pos.isDefined) printMessage(" " * (pos.column - 1) + "^")
+
+ /** Prints the number of warnings and errors if there are any. */
+ def printSummary(): Unit =
+ for (k <- List(WARNING, ERROR) if k.count > 0) printMessage(s"${countAs(k.count, label(k))} found")
+
+ def display(pos: Position, msg: String, severity: Severity): Unit = {
+ val ok = severity match {
+ case ERROR => ERROR.count <= settings.maxerrs.value
+ case WARNING => WARNING.count <= settings.maxwarns.value
+ case _ => true
+ }
+ if (ok) print(pos, msg, severity)
}
def displayPrompt(): Unit = {
- writer.print("\na)bort, s)tack, r)esume: ")
+ writer.println()
+ writer.print("a)bort, s)tack, r)esume: ")
writer.flush()
if (reader != null) {
- val response = reader.read().asInstanceOf[Char].toLower
- if (response == 'a' || response == 's') {
- (new Exception).printStackTrace()
- if (response == 'a')
- sys exit 1
-
- writer.print("\n")
- writer.flush()
+ reader.read match {
+ case 'a' | 'A' =>
+ new Throwable().printStackTrace(writer)
+ System.exit(1)
+ case 's' | 'S' =>
+ new Throwable().printStackTrace(writer)
+ writer.println()
+ writer.flush()
+ case _ =>
}
}
}
- override def flush() { writer.flush() }
+ override def flush() = writer.flush()
+
+ override def finish() = printSummary()
}
diff --git a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala
index 6b339b2a6d..9d643825f6 100644
--- a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala
@@ -30,7 +30,8 @@ trait AbsScalaSettings {
type OutputSetting <: Setting
def BooleanSetting(name: String, descr: String): BooleanSetting
- def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String): ChoiceSetting
+ def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String, choicesHelp: List[String] = Nil): ChoiceSetting
+ def ChoiceSettingForcedDefault(name: String, helpArg: String, descr: String, choices: List[String], default: String, choicesHelp: List[String] = Nil): ChoiceSetting
def IntSetting(name: String, descr: String, default: Int, range: Option[(Int, Int)], parser: String => Option[Int]): IntSetting
def MultiStringSetting(name: String, helpArg: String, descr: String): MultiStringSetting
def MultiChoiceSetting[E <: MultiChoiceEnumeration](name: String, helpArg: String, descr: String, domain: E, default: Option[List[String]]): MultiChoiceSetting[E]
diff --git a/src/compiler/scala/tools/nsc/settings/AbsSettings.scala b/src/compiler/scala/tools/nsc/settings/AbsSettings.scala
index 060a24d8d4..08fa56d8e9 100644
--- a/src/compiler/scala/tools/nsc/settings/AbsSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/AbsSettings.scala
@@ -88,6 +88,12 @@ trait AbsSettings extends scala.reflect.internal.settings.AbsSettings {
/** Issue error and return */
def errorAndValue[T](msg: String, x: T): T = { errorFn(msg) ; x }
+ /** If this method returns true, print the [[help]] message and exit. */
+ def isHelping: Boolean = false
+
+ /** The help message to be printed if [[isHelping]]. */
+ def help: String = ""
+
/** After correct Setting has been selected, tryToSet is called with the
* remainder of the command line. It consumes any applicable arguments and
* returns the unconsumed ones.
diff --git a/src/compiler/scala/tools/nsc/settings/FscSettings.scala b/src/compiler/scala/tools/nsc/settings/FscSettings.scala
index fffbb4333f..d6013e0b00 100644
--- a/src/compiler/scala/tools/nsc/settings/FscSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/FscSettings.scala
@@ -37,9 +37,7 @@ class FscSettings(error: String => Unit) extends Settings(error) {
/** If a setting (other than a PathSetting) represents a path or paths.
* For use in absolutization.
*/
- private def holdsPath = Set[Settings#Setting](
- d, dependencyfile, pluginsDir, Ygenjavap
- )
+ private def holdsPath = Set[Settings#Setting](d, dependencyfile, pluginsDir)
override def processArguments(arguments: List[String], processAll: Boolean): (Boolean, List[String]) = {
val (r, args) = super.processArguments(arguments, processAll)
diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
index 11cde935f2..40aabb0df1 100644
--- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
@@ -219,8 +219,15 @@ class MutableSettings(val errorFn: String => Unit)
}
def BooleanSetting(name: String, descr: String) = add(new BooleanSetting(name, descr))
- def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String) =
- add(new ChoiceSetting(name, helpArg, descr, choices, default))
+ def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String, choicesHelp: List[String]) =
+ add(new ChoiceSetting(name, helpArg, descr, choices, default, choicesHelp))
+ def ChoiceSettingForcedDefault(name: String, helpArg: String, descr: String, choices: List[String], default: String, choicesHelp: List[String]) =
+ ChoiceSetting(name, helpArg, descr, choices, default, choicesHelp).withPostSetHook(sett =>
+ if (sett.value != default) {
+ sett.withDeprecationMessage(s"${name}:${sett.value} is deprecated, forcing use of $default")
+ sett.value = default
+ }
+ )
def IntSetting(name: String, descr: String, default: Int, range: Option[(Int, Int)], parser: String => Option[Int]) =
add(new IntSetting(name, descr, default, range, parser))
def MultiStringSetting(name: String, arg: String, descr: String) = add(new MultiStringSetting(name, arg, descr))
@@ -254,8 +261,8 @@ class MutableSettings(val errorFn: String => Unit)
*/
private var singleOutDir: Option[AbstractFile] = None
- /** Add a destination directory for sources found under srcdir.
- * Both directories should exits.
+ /** Add a destination directory for sources found under `srcDir`.
+ * Both directories should exist.
*/
def add(srcDir: String, outDir: String): Unit = // used in ide?
add(checkDir(AbstractFile.getDirectory(srcDir), srcDir),
@@ -409,9 +416,9 @@ class MutableSettings(val errorFn: String => Unit)
// Helper to generate a textual explanation of valid inputs
private def getValidText: String = (min, max) match {
case (IntMin, IntMax) => "can be any integer"
- case (IntMin, x) => "must be less than or equal to "+x
- case (x, IntMax) => "must be greater than or equal to "+x
- case _ => "must be between %d and %d".format(min, max)
+ case (IntMin, x) => f"must be less than or equal to $x%d"
+ case (x, IntMax) => f"must be greater than or equal to $x%d"
+ case _ => f"must be between $min%d and $max%d"
}
// Ensure that the default value is actually valid
@@ -424,7 +431,7 @@ class MutableSettings(val errorFn: String => Unit)
}
}
- def errorMsg() = errorFn("invalid setting for -"+name+" "+getValidText)
+ def errorMsg() = errorFn(s"invalid setting for $name $getValidText")
def tryToSet(args: List[String]) =
if (args.isEmpty) errorAndValue("missing argument", None)
@@ -437,7 +444,7 @@ class MutableSettings(val errorFn: String => Unit)
if (value == default) Nil
else List(name, value.toString)
- withHelpSyntax(name + " <n>")
+ withHelpSyntax(s"$name <n>")
}
/** A setting represented by a boolean flag (false, unless set) */
@@ -620,7 +627,7 @@ class MutableSettings(val errorFn: String => Unit)
descr: String,
val domain: E,
val default: Option[List[String]]
- ) extends Setting(name, s"$descr: `_' for all, `$name:help' to list") with Clearable {
+ ) extends Setting(name, s"$descr: `_' for all, `$name:help' to list choices.") with Clearable {
withHelpSyntax(s"$name:<_,$helpArg,-$helpArg>")
@@ -741,9 +748,9 @@ class MutableSettings(val errorFn: String => Unit)
def contains(choice: domain.Value): Boolean = value contains choice
- def isHelping: Boolean = sawHelp
+ override def isHelping: Boolean = sawHelp
- def help: String = {
+ override def help: String = {
val describe: ((String, String)) => String = {
val choiceWidth = choices.map(_.length).max + 1
val formatStr = s" %-${choiceWidth}s %s"
@@ -809,18 +816,33 @@ class MutableSettings(val errorFn: String => Unit)
helpArg: String,
descr: String,
override val choices: List[String],
- val default: String)
- extends Setting(name, descr + choices.mkString(" (", ",", ") default:" + default)) {
+ val default: String,
+ val choicesHelp: List[String])
+ extends Setting(name,
+ if (choicesHelp.isEmpty) s"$descr Choices: ${choices.mkString("(", ",", ")")}, default: $default."
+ else s"$descr Default: `$default', `help' to list choices.") {
type T = String
protected var v: T = default
def indexOfChoice: Int = choices indexOf value
- private def usageErrorMessage = f"Usage: $name:<$helpArg>%n where <$helpArg> choices are ${choices mkString ", "} (default: $default)%n"
+ private def choicesHelpMessage = if (choicesHelp.isEmpty) "" else {
+ val choiceLength = choices.map(_.length).max + 1
+ val formatStr = s" %-${choiceLength}s %s%n"
+ choices.zipAll(choicesHelp, "", "").map({
+ case (choice, desc) => formatStr.format(choice, desc)
+ }).mkString("")
+ }
+ private def usageErrorMessage = f"Usage: $name:<$helpArg> where <$helpArg> choices are ${choices mkString ", "} (default: $default).%n$choicesHelpMessage"
+
+ private var sawHelp = false
+ override def isHelping = sawHelp
+ override def help = usageErrorMessage
def tryToSet(args: List[String]) = errorAndValue(usageErrorMessage, None)
override def tryToSetColon(args: List[String]) = args match {
case Nil => errorAndValue(usageErrorMessage, None)
+ case List("help") => sawHelp = true; Some(Nil)
case List(x) if choices contains x => value = x ; Some(Nil)
case List(x) => errorAndValue("'" + x + "' is not a valid choice for '" + name + "'", None)
case xs => errorAndValue("'" + name + "' does not accept multiple arguments.", None)
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
index 8e5c064e1f..cce9a5b3a8 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -9,10 +9,11 @@ package tools
package nsc
package settings
+import scala.language.existentials
+
import scala.annotation.elidable
import scala.tools.util.PathResolver.Defaults
import scala.collection.mutable
-import scala.language.{implicitConversions, existentials}
trait ScalaSettings extends AbsScalaSettings
with StandardScalaSettings
@@ -22,33 +23,23 @@ trait ScalaSettings extends AbsScalaSettings
/** Set of settings */
protected[scala] lazy val allSettings = mutable.HashSet[Setting]()
- /** Against my better judgment, giving in to martin here and allowing
- * CLASSPATH to be used automatically. So for the user-specified part
- * of the classpath:
- *
- * - If -classpath or -cp is given, it is that
- * - Otherwise, if CLASSPATH is set, it is that
- * - If neither of those, then "." is used.
+ /** The user class path, specified by `-classpath` or `-cp`,
+ * defaults to the value of CLASSPATH env var if it is set, as in Java,
+ * or else to `"."` for the current user directory.
*/
protected def defaultClasspath = sys.env.getOrElse("CLASSPATH", ".")
/** Enabled under -Xexperimental. */
- protected def experimentalSettings = List[BooleanSetting](YmethodInfer, overrideObjects, overrideVars)
+ protected def experimentalSettings = List[BooleanSetting](YpartialUnification)
/** Enabled under -Xfuture. */
protected def futureSettings = List[BooleanSetting]()
- /** Enabled under -optimise. */
- def optimiseSettings = List[BooleanSetting](inline, inlineHandlers, Xcloselim, Xdce, YconstOptimization)
-
/** If any of these settings is enabled, the compiler should print a message and exit. */
def infoSettings = List[Setting](version, help, Xhelp, Yhelp, showPlugins, showPhases, genPhaseGraph)
- /** Any -multichoice:help? Nicer if any option could report that it had help to offer. */
- private def multihelp = allSettings exists { case s: MultiChoiceSetting[_] => s.isHelping case _ => false }
-
- /** Is an info setting set? */
- def isInfo = (infoSettings exists (_.isSetByUser)) || multihelp
+ /** Is an info setting set? Any -option:help? */
+ def isInfo = infoSettings.exists(_.isSetByUser) || allSettings.exists(_.isHelping)
/** Disable a setting */
def disable(s: Setting) = allSettings -= s
@@ -93,7 +84,10 @@ trait ScalaSettings extends AbsScalaSettings
* though this helper.
*/
def isScala211: Boolean = source.value >= ScalaVersion("2.11.0")
- def isScala212: Boolean = source.value >= ScalaVersion("2.12.0")
+ private[this] val version212 = ScalaVersion("2.12.0")
+ def isScala212: Boolean = source.value >= version212
+ private[this] val version213 = ScalaVersion("2.13.0")
+ def isScala213: Boolean = source.value >= version213
/**
* -X "Advanced" settings
@@ -113,17 +107,18 @@ trait ScalaSettings extends AbsScalaSettings
val logFreeTerms = BooleanSetting ("-Xlog-free-terms", "Print a message when reification creates a free term.")
val logFreeTypes = BooleanSetting ("-Xlog-free-types", "Print a message when reification resorts to generating a free type.")
val maxClassfileName = IntSetting ("-Xmax-classfile-name", "Maximum filename length for generated classes", 255, Some((72, 255)), _ => None)
+ val maxerrs = IntSetting ("-Xmaxerrs", "Maximum errors to print", 100, None, _ => None)
+ val maxwarns = IntSetting ("-Xmaxwarns", "Maximum warnings to print", 100, None, _ => None)
val Xmigration = ScalaVersionSetting ("-Xmigration", "version", "Warn about constructs whose behavior may have changed since version.", initial = NoScalaVersion, default = Some(AnyScalaVersion))
val nouescape = BooleanSetting ("-Xno-uescape", "Disable handling of \\u unicode escapes.")
val Xnojline = BooleanSetting ("-Xnojline", "Do not use JLine for editing.")
- val Xverify = BooleanSetting ("-Xverify", "Verify generic signatures in generated bytecode (asm backend only.)")
+ val Xverify = BooleanSetting ("-Xverify", "Verify generic signatures in generated bytecode.")
val plugin = MultiStringSetting ("-Xplugin", "paths", "Load a plugin from each classpath.")
val disable = MultiStringSetting ("-Xplugin-disable", "plugin", "Disable plugins by name.")
val showPlugins = BooleanSetting ("-Xplugin-list", "Print a synopsis of loaded plugins.")
val require = MultiStringSetting ("-Xplugin-require", "plugin", "Abort if a named plugin is not loaded.")
val pluginsDir = StringSetting ("-Xpluginsdir", "path", "Path to search for plugin archives.", Defaults.scalaPluginPath)
val Xprint = PhasesSetting ("-Xprint", "Print out program after")
- val writeICode = PhasesSetting ("-Xprint-icode", "Log internal icode to *.icode files after", "icode")
val Xprintpos = BooleanSetting ("-Xprint-pos", "Print tree positions, as offsets.")
val printtypes = BooleanSetting ("-Xprint-types", "Print tree types (debugging option).")
val prompt = BooleanSetting ("-Xprompt", "Display a prompt after each error (debugging option).")
@@ -134,21 +129,38 @@ trait ScalaSettings extends AbsScalaSettings
val Xshowobj = StringSetting ("-Xshow-object", "object", "Show internal representation of object.", "")
val showPhases = BooleanSetting ("-Xshow-phases", "Print a synopsis of compiler phases.")
val sourceReader = StringSetting ("-Xsource-reader", "classname", "Specify a custom method for reading source files.", "")
+ val reporter = StringSetting ("-Xreporter", "classname", "Specify a custom reporter for compiler messages.", "scala.tools.nsc.reporters.ConsoleReporter")
val strictInference = BooleanSetting ("-Xstrict-inference", "Don't infer known-unsound types")
- val source = ScalaVersionSetting ("-Xsource", "version", "Treat compiler input as Scala source for the specified version, see SI-8126.", initial = ScalaVersion("2.11"))
+ val source = ScalaVersionSetting ("-Xsource", "version", "Treat compiler input as Scala source for the specified version, see SI-8126.", initial = ScalaVersion("2.12"))
val XnoPatmatAnalysis = BooleanSetting ("-Xno-patmat-analysis", "Don't perform exhaustivity/unreachability analysis. Also, ignore @switch annotation.")
val XfullLubs = BooleanSetting ("-Xfull-lubs", "Retains pre 2.10 behavior of less aggressive truncation of least upper bounds.")
+ val XmixinForceForwarders = ChoiceSetting(
+ name = "-Xmixin-force-forwarders",
+ helpArg = "mode",
+ descr = "Generate forwarder methods in classes inhering concrete methods from traits.",
+ choices = List("true", "junit", "false"),
+ default = "true",
+ choicesHelp = List(
+ "Always generate mixin forwarders.",
+ "Generate mixin forwarders for JUnit-annotated methods (JUnit 4 does not support default methods).",
+ "Only generate mixin forwarders required for program correctness."))
+
+ object mixinForwarderChoices {
+ def isTruthy = XmixinForceForwarders.value == "true"
+ def isAtLeastJunit = isTruthy || XmixinForceForwarders.value == "junit"
+ }
+
// XML parsing options
object XxmlSettings extends MultiChoiceEnumeration {
val coalescing = Choice("coalescing", "Convert PCData to Text and coalesce sibling nodes")
- def isCoalescing = (Xxml contains coalescing) || (!isScala212 && !Xxml.isSetByUser)
+ def isCoalescing = Xxml contains coalescing
}
val Xxml = MultiChoiceSetting(
name = "-Xxml",
helpArg = "property",
- descr = "Configure XML parsing",
+ descr = "Configure XML parsing.",
domain = XxmlSettings
)
@@ -171,19 +183,10 @@ trait ScalaSettings extends AbsScalaSettings
val browse = PhasesSetting ("-Ybrowse", "Browse the abstract syntax tree after")
val check = PhasesSetting ("-Ycheck", "Check the tree at the end of")
val Yshow = PhasesSetting ("-Yshow", "(Requires -Xshow-class or -Xshow-object) Show after")
- val Xcloselim = BooleanSetting ("-Yclosure-elim", "Perform closure elimination.")
- val YconstOptimization = BooleanSetting ("-Yconst-opt", "Perform optimization with constant values.")
val Ycompacttrees = BooleanSetting ("-Ycompact-trees", "Use compact tree printer when displaying trees.")
val noCompletion = BooleanSetting ("-Yno-completion", "Disable tab-completion in the REPL.")
- val completion = ChoiceSetting ("-Ycompletion", "provider", "Select tab-completion in the REPL.", List("pc","adhoc","none"), "pc")
- val Xdce = BooleanSetting ("-Ydead-code", "Perform dead code elimination.")
val debug = BooleanSetting ("-Ydebug", "Increase the quantity of debugging output.")
- //val doc = BooleanSetting ("-Ydoc", "Generate documentation")
- val termConflict = ChoiceSetting ("-Yresolve-term-conflict", "strategy", "Resolve term conflicts", List("package", "object", "error"), "error")
- val inline = BooleanSetting ("-Yinline", "Perform inlining when possible.")
- val inlineHandlers = BooleanSetting ("-Yinline-handlers", "Perform exception handler inlining when possible.")
- val YinlinerWarnings= BooleanSetting ("-Yinline-warnings", "Emit inlining warnings. (Normally suppressed due to high volume)")
- val Xlinearizer = ChoiceSetting ("-Ylinearizer", "which", "Linearizer to use", List("normal", "dfs", "rpo", "dump"), "rpo")
+ val termConflict = ChoiceSetting ("-Yresolve-term-conflict", "strategy", "Resolve term conflicts.", List("package", "object", "error"), "error")
val log = PhasesSetting ("-Ylog", "Log operations during")
val Ylogcp = BooleanSetting ("-Ylog-classpath", "Output information about what classpath is being applied.")
val Ynogenericsig = BooleanSetting ("-Yno-generic-signatures", "Suppress generation of generic signatures for Java.")
@@ -200,7 +203,6 @@ trait ScalaSettings extends AbsScalaSettings
val Yshowsymkinds = BooleanSetting ("-Yshow-symkinds", "Print abbreviated symbol kinds next to symbol names.")
val Yshowsymowners = BooleanSetting ("-Yshow-symowners", "Print owner identifiers next to symbol names.")
val skip = PhasesSetting ("-Yskip", "Skip")
- val Ygenjavap = StringSetting ("-Ygen-javap", "dir", "Generate a parallel output directory of .javap files.", "")
val Ygenasmp = StringSetting ("-Ygen-asmp", "dir", "Generate a parallel output directory of .asmp files (ie ASM Textifier output).", "")
val Ydumpclasses = StringSetting ("-Ydump-classes", "dir", "Dump the generated bytecode to .class files (useful for reflective compilation that utilizes in-memory classloaders).", "")
val stopAfter = PhasesSetting ("-Ystop-after", "Stop after") withAbbreviation ("-stop") // backward compat
@@ -208,112 +210,119 @@ trait ScalaSettings extends AbsScalaSettings
val Yrangepos = BooleanSetting ("-Yrangepos", "Use range positions for syntax trees.")
val Ymemberpos = StringSetting ("-Yshow-member-pos", "output style", "Show start and end positions of members", "") withPostSetHook (_ => Yrangepos.value = true)
val Yreifycopypaste = BooleanSetting ("-Yreify-copypaste", "Dump the reified trees in copypasteable representation.")
- val Ymacroexpand = ChoiceSetting ("-Ymacro-expand", "policy", "Control expansion of macros, useful for scaladoc and presentation compiler", List(MacroExpand.Normal, MacroExpand.None, MacroExpand.Discard), MacroExpand.Normal)
+ val Ymacroexpand = ChoiceSetting ("-Ymacro-expand", "policy", "Control expansion of macros, useful for scaladoc and presentation compiler.", List(MacroExpand.Normal, MacroExpand.None, MacroExpand.Discard), MacroExpand.Normal)
val Ymacronoexpand = BooleanSetting ("-Ymacro-no-expand", "Don't expand macros. Might be useful for scaladoc and presentation compiler, but will crash anything which uses macros and gets past typer.") withDeprecationMessage(s"Use ${Ymacroexpand.name}:${MacroExpand.None}") withPostSetHook(_ => Ymacroexpand.value = MacroExpand.None)
val Yreplsync = BooleanSetting ("-Yrepl-sync", "Do not use asynchronous code for repl startup")
val Yreplclassbased = BooleanSetting ("-Yrepl-class-based", "Use classes to wrap REPL snippets instead of objects")
val Yreploutdir = StringSetting ("-Yrepl-outdir", "path", "Write repl-generated classfiles to given output directory (use \"\" to generate a temporary dir)" , "")
val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overridden methods.")
- val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition.").withDeprecationMessage(removalIn212)
- val inferByName = BooleanSetting ("-Yinfer-by-name", "Allow inference of by-name types. This is a temporary option to ease transition. See SI-7899.").withDeprecationMessage(removalIn212)
- val YclasspathImpl = ChoiceSetting ("-YclasspathImpl", "implementation", "Choose classpath scanning method.", List(ClassPathRepresentationType.Recursive, ClassPathRepresentationType.Flat), ClassPathRepresentationType.Recursive)
val YdisableFlatCpCaching = BooleanSetting ("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.")
val YpartialUnification = BooleanSetting ("-Ypartial-unification", "Enable partial unification in type constructor inference")
+ val Yvirtpatmat = BooleanSetting ("-Yvirtpatmat", "Enable pattern matcher virtualization")
- val YvirtClasses = false // too embryonic to even expose as a -Y //BooleanSetting ("-Yvirtual-classes", "Support virtual classes")
- val YdisableUnreachablePrevention = BooleanSetting("-Ydisable-unreachable-prevention", "Disable the prevention of unreachable blocks in code generation.")
- val YnoLoadImplClass = BooleanSetting ("-Yno-load-impl-class", "Do not load $class.class files.")
+ val exposeEmptyPackage = BooleanSetting ("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly()
+ val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "method")
- val exposeEmptyPackage = BooleanSetting("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly()
- // 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.")
+ object optChoices extends MultiChoiceEnumeration {
+ val unreachableCode = Choice("unreachable-code", "Eliminate unreachable code, exception handlers guarding no instructions, redundant metadata (debug information, line numbers).")
val simplifyJumps = Choice("simplify-jumps", "Simplify branching instructions, eliminate unnecessary ones.")
- 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 copyPropagation = Choice("copy-propagation", "Eliminate redundant local variables and unused values (including closures). Enables unreachable-code.")
+ val redundantCasts = Choice("redundant-casts", "Eliminate redundant casts using a type propagation analysis.")
+ val boxUnbox = Choice("box-unbox", "Eliminate box-unbox pairs within the same method (also tuples, xRefs, value class instances). Enables unreachable-code.")
val nullnessTracking = Choice("nullness-tracking", "Track nullness / non-nullness of local variables and apply optimizations.")
- val closureElimination = Choice("closure-elimination" , "Rewrite closure invocations to the implementation method and eliminate closures.")
- 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 closureInvocations = Choice("closure-invocations" , "Rewrite closure invocations to the implementation method.")
+ val inlineProject = Choice("inline-project", "Inline only methods defined in the files being compiled. Enables unreachable-code.")
+ val inlineGlobal = Choice("inline-global", "Inline methods from any source, including classfiles on the compile classpath. Enables unreachable-code.")
- val lNone = Choice("l:none", "Don't enable any optimizations.")
+ // note: unlike the other optimizer levels, "l:none" appears up in the `opt.value` set because it's not an expanding option (expandsTo is empty)
+ val lNone = Choice("l:none", "Disable optimizations. Takes precedence: `-opt:l:none,+box-unbox` / `-opt:l:none -opt:box-unbox` don't enable box-unbox.")
private val defaultChoices = List(unreachableCode)
- val lDefault = Choice("l:default", "Enable default optimizations: "+ defaultChoices.mkString(","), expandsTo = defaultChoices)
+ val lDefault = Choice("l:default", "Enable default optimizations: "+ defaultChoices.mkString("", ",", "."), expandsTo = defaultChoices)
- private val methodChoices = List(unreachableCode, simplifyJumps, emptyLineNumbers, emptyLabels, compactLocals, nullnessTracking, closureElimination)
- val lMethod = Choice("l:method", "Enable intra-method optimizations: "+ methodChoices.mkString(","), expandsTo = methodChoices)
+ private val methodChoices = List(unreachableCode, simplifyJumps, compactLocals, copyPropagation, redundantCasts, boxUnbox, nullnessTracking, closureInvocations)
+ val lMethod = Choice("l:method", "Enable intra-method optimizations: "+ methodChoices.mkString("", ",", "."), expandsTo = methodChoices)
private val projectChoices = List(lMethod, inlineProject)
- val lProject = Choice("l:project", "Enable cross-method optimizations within the current project: "+ projectChoices.mkString(","), expandsTo = projectChoices)
+ val lProject = Choice("l:project", "Enable cross-method optimizations within the current project: "+ projectChoices.mkString("", ",", "."), expandsTo = projectChoices)
private val classpathChoices = List(lProject, inlineGlobal)
- val lClasspath = Choice("l:classpath", "Enable cross-method optimizations across the entire classpath: "+ classpathChoices.mkString(","), expandsTo = classpathChoices)
+ val lClasspath = Choice("l:classpath", "Enable cross-method optimizations across the entire classpath: "+ classpathChoices.mkString("", ",", "."), expandsTo = classpathChoices)
}
- val Yopt = MultiChoiceSetting(
- name = "-Yopt",
+ // We don't use the `default` parameter of `MultiChoiceSetting`: it specifies the default values
+ // when `-opt` is passed without explicit choices. When `-opt` is not explicitly specified, the
+ // set `opt.value` is empty.
+ val opt = MultiChoiceSetting(
+ name = "-opt",
helpArg = "optimization",
descr = "Enable optimizations",
- domain = YoptChoices)
+ domain = optChoices)
- def YoptNone = Yopt.isSetByUser && Yopt.value.isEmpty
- def YoptUnreachableCode = !Yopt.isSetByUser || Yopt.contains(YoptChoices.unreachableCode)
- def YoptSimplifyJumps = Yopt.contains(YoptChoices.simplifyJumps)
- def YoptEmptyLineNumbers = Yopt.contains(YoptChoices.emptyLineNumbers)
- def YoptEmptyLabels = Yopt.contains(YoptChoices.emptyLabels)
- def YoptCompactLocals = Yopt.contains(YoptChoices.compactLocals)
- def YoptNullnessTracking = Yopt.contains(YoptChoices.nullnessTracking)
- def YoptClosureElimination = Yopt.contains(YoptChoices.closureElimination)
+ private def optEnabled(choice: optChoices.Choice) = {
+ !opt.contains(optChoices.lNone) && {
+ opt.contains(choice) ||
+ !opt.isSetByUser && optChoices.lDefault.expandsTo.contains(choice)
+ }
+ }
- def YoptInlineProject = Yopt.contains(YoptChoices.inlineProject)
- def YoptInlineGlobal = Yopt.contains(YoptChoices.inlineGlobal)
- def YoptInlinerEnabled = YoptInlineProject || YoptInlineGlobal
+ def optNone = opt.contains(optChoices.lNone)
+ def optUnreachableCode = optEnabled(optChoices.unreachableCode)
+ def optSimplifyJumps = optEnabled(optChoices.simplifyJumps)
+ def optCompactLocals = optEnabled(optChoices.compactLocals)
+ def optCopyPropagation = optEnabled(optChoices.copyPropagation)
+ def optRedundantCasts = optEnabled(optChoices.redundantCasts)
+ def optBoxUnbox = optEnabled(optChoices.boxUnbox)
+ def optNullnessTracking = optEnabled(optChoices.nullnessTracking)
+ def optClosureInvocations = optEnabled(optChoices.closureInvocations)
- def YoptBuildCallGraph = YoptInlinerEnabled || YoptClosureElimination
- def YoptAddToBytecodeRepository = YoptInlinerEnabled || YoptClosureElimination
+ def optInlineProject = optEnabled(optChoices.inlineProject)
+ def optInlineGlobal = optEnabled(optChoices.inlineGlobal)
+ def optInlinerEnabled = optInlineProject || optInlineGlobal
+
+ def optBuildCallGraph = optInlinerEnabled || optClosureInvocations
+ def optAddToBytecodeRepository = optBuildCallGraph || optInlinerEnabled || optClosureInvocations
val YoptInlineHeuristics = ChoiceSetting(
name = "-Yopt-inline-heuristics",
helpArg = "strategy",
descr = "Set the heuristics for inlining decisions.",
- choices = List("at-inline-annotated", "everything"),
- default = "at-inline-annotated")
+ choices = List("at-inline-annotated", "everything", "default"),
+ default = "default")
- object YoptWarningsChoices extends MultiChoiceEnumeration {
+ object optWarningsChoices 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 anyInlineFailed = Choice("any-inline-failed" , "A detailed warning for every callsite that was chosen for inlining by the heuristics, but 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",
+ val optWarnings = MultiChoiceSetting(
+ name = "-opt-warnings",
helpArg = "warning",
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
- })
+ domain = optWarningsChoices,
+ default = Some(List(optWarningsChoices.atInlineFailed.name)))
+
+ def optWarningsSummaryOnly = optWarnings.value subsetOf Set(optWarningsChoices.none, optWarningsChoices.atInlineFailedSummary)
- def YoptWarningEmitAtInlineFailed =
- !YoptWarnings.isSetByUser ||
- YoptWarnings.contains(YoptWarningsChoices.atInlineFailedSummary) ||
- YoptWarnings.contains(YoptWarningsChoices.atInlineFailed)
+ def optWarningEmitAtInlineFailed =
+ !optWarnings.isSetByUser ||
+ optWarnings.contains(optWarningsChoices.atInlineFailedSummary) ||
+ optWarnings.contains(optWarningsChoices.atInlineFailed) ||
+ optWarnings.contains(optWarningsChoices.anyInlineFailed)
- def YoptWarningNoInlineMixed = YoptWarnings.contains(YoptWarningsChoices.noInlineMixed)
- def YoptWarningNoInlineMissingBytecode = YoptWarnings.contains(YoptWarningsChoices.noInlineMissingBytecode)
- def YoptWarningNoInlineMissingScalaInlineInfoAttr = YoptWarnings.contains(YoptWarningsChoices.noInlineMissingScalaInlineInfoAttr)
+ def optWarningNoInlineMixed = optWarnings.contains(optWarningsChoices.noInlineMixed)
+ def optWarningNoInlineMissingBytecode = optWarnings.contains(optWarningsChoices.noInlineMissingBytecode)
+ def optWarningNoInlineMissingScalaInlineInfoAttr = optWarnings.contains(optWarningsChoices.noInlineMissingScalaInlineInfoAttr)
- 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."
+ val YoptTrace = StringSetting("-Yopt-trace", "package/Class.method", "Trace the optimizer progress for methods; `_` to print all, prefix match to select.", "")
+
+ val YoptLogInline = StringSetting("-Yopt-log-inline", "package/Class.method", "Print a summary of inliner activity; `_` to print all, prefix match to select.", "")
object YstatisticsPhases extends MultiChoiceEnumeration { val parser, typer, patmat, erasure, cleanup, jvm = Value }
val Ystatistics = {
@@ -344,22 +353,15 @@ trait ScalaSettings extends AbsScalaSettings
str => Some(if(str.equalsIgnoreCase("off")) Int.MaxValue else str.toInt))
val Yquasiquotedebug = BooleanSetting("-Yquasiquote-debug", "Trace quasiquote-related activities.")
- // TODO 2.12 Remove
- val Yinferdebug = BooleanSetting("-Yinfer-debug", "Trace type inference and implicit search.") withDeprecationMessage("Use -Ytyper-debug") enabling(List(Ytyperdebug))
-
/** Groups of Settings.
*/
val future = BooleanSetting("-Xfuture", "Turn on future language features.") enablingIfNotSetByUser futureSettings
- val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying optimisations to the program") withAbbreviation "-optimize" enablingIfNotSetByUser optimiseSettings
- val nooptimise = BooleanSetting("-Ynooptimise", "Clears all the flags set by -optimise. Useful for testing optimizations in isolation.") withAbbreviation "-Ynooptimize" disabling optimise::optimiseSettings
+ val optimise = BooleanSetting("-optimise", "Compiler flag for the optimizer in Scala 2.11")
+ .withAbbreviation("-optimize")
+ .withDeprecationMessage("In 2.12, -optimise enables -opt:l:classpath. Check -opt:help for using the Scala 2.12 optimizer.")
+ .withPostSetHook(_ => opt.tryToSet(List(optChoices.lClasspath.name)))
val Xexperimental = BooleanSetting("-Xexperimental", "Enable experimental extensions.") enablingIfNotSetByUser experimentalSettings
- /**
- * Settings motivated by GenBCode
- */
- val Ybackend = ChoiceSetting ("-Ybackend", "choice of bytecode emitter", "Choice of bytecode emitter.",
- List("GenASM", "GenBCode"),
- "GenASM")
// Feature extensions
val XmacroSettings = MultiStringSetting("-Xmacro-settings", "option", "Custom settings for macros.")
@@ -383,16 +385,24 @@ trait ScalaSettings extends AbsScalaSettings
/** Test whether this is scaladoc we're looking at */
def isScaladoc = false
- def isBCodeActive = Ybackend.value == "GenBCode"
-
object MacroExpand {
val None = "none"
val Normal = "normal"
val Discard = "discard"
}
-}
-object ClassPathRepresentationType {
- val Flat = "flat"
- val Recursive = "recursive"
+ def conflictWarning: Option[String] = {
+ // See cd878232b5 for an example how to warn about conflicting settings
+
+ /*
+ def checkSomeConflict: Option[String] = ...
+
+ List(/* checkSomeConflict, ... */).flatten match {
+ case Nil => None
+ case warnings => Some("Conflicting compiler settings were detected. Some settings will be ignored.\n" + warnings.mkString("\n"))
+ }
+ */
+
+ None
+ }
}
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala b/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala
index d7901730a4..c38de753c8 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala
@@ -68,45 +68,37 @@ case object AnyScalaVersion extends ScalaVersion {
* Factory methods for producing ScalaVersions
*/
object ScalaVersion {
- private val dot = "\\."
- private val dash = "\\-"
- private def not(s:String) = s"[^${s}]"
- private val R = s"((${not(dot)}*)(${dot}(${not(dot)}*)(${dot}(${not(dash)}*)(${dash}(.*))?)?)?)".r
-
- def apply(versionString : String, errorHandler: String => Unit): ScalaVersion = {
- def errorAndValue() = {
- errorHandler(
- s"There was a problem parsing ${versionString}. " +
- "Versions should be in the form major[.minor[.revision]] " +
- "where each part is a positive number, as in 2.10.1. " +
- "The minor and revision parts are optional."
- )
- AnyScalaVersion
- }
+ private val dot = """\."""
+ private val dash = "-"
+ private val vchar = """\d""" //"[^-+.]"
+ private val vpat = s"(?s)($vchar+)(?:$dot($vchar+)(?:$dot($vchar+)(?:$dash(.*))?)?)?".r
+ private val rcpat = """(?i)rc(\d*)""".r
+ private val mspat = """(?i)m(\d*)""".r
+
+ def apply(versionString: String, errorHandler: String => Unit): ScalaVersion = {
+ def error() = errorHandler(
+ s"Bad version (${versionString}) not major[.minor[.revision[-suffix]]]"
+ )
def toInt(s: String) = s match {
case null | "" => 0
- case _ => s.toInt
+ case _ => s.toInt
}
- def isInt(s: String) = util.Try(toInt(s)).isSuccess
-
def toBuild(s: String) = s match {
case null | "FINAL" => Final
- case s if (s.toUpperCase.startsWith("RC") && isInt(s.substring(2))) => RC(toInt(s.substring(2)))
- case s if (s.toUpperCase.startsWith("M") && isInt(s.substring(1))) => Milestone(toInt(s.substring(1)))
- case _ => Development(s)
+ case rcpat(i) => RC(toInt(i))
+ case mspat(i) => Milestone(toInt(i))
+ case _ /* | "" */ => Development(s)
}
- try versionString match {
+ versionString match {
case "none" => NoScalaVersion
- case "any" => AnyScalaVersion
- case R(_, majorS, _, minorS, _, revS, _, buildS) =>
+ case "" => NoScalaVersion
+ case "any" => AnyScalaVersion
+ case vpat(majorS, minorS, revS, buildS) =>
SpecificScalaVersion(toInt(majorS), toInt(minorS), toInt(revS), toBuild(buildS))
- case _ =>
- errorAndValue()
- } catch {
- case e: NumberFormatException => errorAndValue()
+ case _ => error() ; AnyScalaVersion
}
}
diff --git a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala
index d42c0dd730..f197a4930d 100644
--- a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala
@@ -38,8 +38,8 @@ trait StandardScalaSettings {
val nowarn = BooleanSetting ("-nowarn", "Generate no warnings.")
val optimise: BooleanSetting // depends on post hook which mutates other settings
val print = BooleanSetting ("-print", "Print program with Scala-specific features removed.")
- val target = ChoiceSetting ("-target", "target", "Target platform for object files. All JVM 1.5 targets are deprecated.",
- List("jvm-1.5", "jvm-1.6", "jvm-1.7", "jvm-1.8"), "jvm-1.6")
+ val target = ChoiceSettingForcedDefault ("-target", "target", "Target platform for object files. All JVM 1.5 - 1.7 targets are deprecated.",
+ List("jvm-1.5", "jvm-1.6", "jvm-1.7", "jvm-1.8"), "jvm-1.8")
val unchecked = BooleanSetting ("-unchecked", "Enable additional warnings where generated code depends on assumptions.")
val uniqid = BooleanSetting ("-uniqid", "Uniquely tag all identifiers in debugging output.")
val usejavacp = BooleanSetting ("-usejavacp", "Utilize the java.class.path in classpath resolution.")
diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala
index f570037760..329a6aadd7 100644
--- a/src/compiler/scala/tools/nsc/settings/Warnings.scala
+++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala
@@ -7,8 +7,6 @@ package scala.tools
package nsc
package settings
-import language.existentials
-
/** Settings influencing the printing of warnings.
*/
trait Warnings {
@@ -17,15 +15,42 @@ trait Warnings {
// Warning semantics.
val fatalWarnings = BooleanSetting("-Xfatal-warnings", "Fail the compilation if there are any warnings.")
- // Non-lint warnings
+ // Non-lint warnings.
val warnDeadCode = BooleanSetting("-Ywarn-dead-code", "Warn when dead code is identified.")
val warnValueDiscard = BooleanSetting("-Ywarn-value-discard", "Warn when non-Unit expression results are unused.")
val warnNumericWiden = BooleanSetting("-Ywarn-numeric-widen", "Warn when numerics are widened.")
- // SI-7712, SI-7707 warnUnused not quite ready for prime-time
- val warnUnused = BooleanSetting("-Ywarn-unused", "Warn when local and private vals, vars, defs, and types are unused.")
- // currently considered too noisy for general use
- val warnUnusedImport = BooleanSetting("-Ywarn-unused-import", "Warn when imports are unused.")
+
+ object UnusedWarnings extends MultiChoiceEnumeration {
+ val Imports = Choice("imports", "Warn if an import selector is not referenced.")
+ val PatVars = Choice("patvars", "Warn if a variable bound in a pattern is unused.")
+ val Privates = Choice("privates", "Warn if a private member is unused.")
+ val Locals = Choice("locals", "Warn if a local definition is unused.")
+ val Params = Choice("params", "Warn if a value parameter is unused.")
+ val Implicits = Choice("implicits", "Warn if an implicit parameter is unused.")
+ }
+
+ // The -Ywarn-unused warning group.
+ val warnUnused = MultiChoiceSetting(
+ name = "-Ywarn-unused",
+ helpArg = "warning",
+ descr = "Enable or disable specific `unused' warnings",
+ domain = UnusedWarnings,
+ default = Some(List("_"))
+ )
+
+ def warnUnusedImport = warnUnused contains UnusedWarnings.Imports
+ def warnUnusedPatVars = warnUnused contains UnusedWarnings.PatVars
+ def warnUnusedPrivates = warnUnused contains UnusedWarnings.Privates
+ def warnUnusedLocals = warnUnused contains UnusedWarnings.Locals
+ def warnUnusedParams = warnUnused contains UnusedWarnings.Params
+ def warnUnusedImplicits = warnUnused contains UnusedWarnings.Implicits
+
+ BooleanSetting("-Ywarn-unused-import", "Warn when imports are unused.") withPostSetHook { s =>
+ warnUnused.add(s"${if (s) "" else "-"}imports")
+ } //withDeprecationMessage s"Enable -Ywarn-unused:imports"
+
+ val warnExtraImplicit = BooleanSetting("-Ywarn-extra-implicit", "Warn when more than one implicit parameter section is defined.")
// Experimental lint warnings that are turned off, but which could be turned on programmatically.
// They are not activated by -Xlint and can't be enabled on the command line because they are not
@@ -59,6 +84,8 @@ trait Warnings {
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.")
+ val Constant = LintWarning("constant", "Evaluation of a constant arithmetic expression results in an error.")
+ val Unused = LintWarning("unused", "Enable -Ywarn-unused:imports,privates,locals,implicits.")
def allLintWarnings = values.toSeq.asInstanceOf[Seq[LintWarning]]
}
@@ -80,6 +107,8 @@ trait Warnings {
def warnPackageObjectClasses = lint contains PackageObjectClasses
def warnUnsoundMatch = lint contains UnsoundMatch
def warnStarsAlign = lint contains StarsAlign
+ def warnConstant = lint contains Constant
+ def lintUnused = lint contains Unused
// Lint warnings that are currently -Y, but deprecated in that usage
@deprecated("Use warnAdaptedArgs", since="2.11.2")
@@ -99,7 +128,11 @@ trait Warnings {
helpArg = "warning",
descr = "Enable or disable specific warnings",
domain = LintWarnings,
- default = Some(List("_")))
+ default = Some(List("_"))
+ ).withPostSetHook { s =>
+ val unused = List("imports", "privates", "locals", "implicits")
+ if (s contains Unused) unused.foreach(warnUnused.add)
+ }
allLintWarnings foreach {
case w if w.yAliased =>
diff --git a/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala b/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala
index d3c7ba4d76..3ac283b9a4 100644
--- a/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala
+++ b/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala
@@ -52,7 +52,7 @@ abstract class BrowsingLoaders extends GlobalSymbolLoaders {
}
/** Browse the top-level of given abstract file `src` and enter
- * eny encountered top-level classes and modules in `root`
+ * any encountered top-level classes and modules in `root`
*/
def browseTopLevel(root: Symbol, src: AbstractFile) {
diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala
index 4f5589fd7c..dd44366692 100644
--- a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala
+++ b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala
@@ -10,10 +10,8 @@ import classfile.ClassfileParser
import java.io.IOException
import scala.reflect.internal.MissingRequirementError
import scala.reflect.internal.util.Statistics
-import scala.reflect.io.{ AbstractFile, NoAbstractFile }
-import scala.tools.nsc.classpath.FlatClassPath
-import scala.tools.nsc.settings.ClassPathRepresentationType
-import scala.tools.nsc.util.{ ClassPath, ClassRepresentation }
+import scala.reflect.io.{AbstractFile, NoAbstractFile}
+import scala.tools.nsc.util.{ClassPath, ClassRepresentation}
/** This class ...
*
@@ -54,20 +52,28 @@ abstract class SymbolLoaders {
})
}
+ def newClass(owner: Symbol, name: String): ClassSymbol = owner.newClass(newTypeName(name))
+
/** Enter class with given `name` into scope of `root`
* and give them `completer` as type.
*/
- def enterClass(owner: Symbol, name: String, completer: SymbolLoader): Symbol = {
- val clazz = owner.newClass(newTypeName(name))
+ def enterClass(owner: Symbol, name: String, completer: SymbolLoader): Symbol =
+ enterClass(owner, newClass(owner, name), completer)
+
+ def enterClass(owner: Symbol, clazz: ClassSymbol, completer: SymbolLoader): Symbol = {
clazz setInfo completer
enterIfNew(owner, clazz, completer)
}
+ def newModule(owner: Symbol, name: String): ModuleSymbol = owner.newModule(newTermName(name))
+
/** Enter module with given `name` into scope of `root`
* and give them `completer` as type.
*/
- def enterModule(owner: Symbol, name: String, completer: SymbolLoader): Symbol = {
- val module = owner.newModule(newTermName(name))
+ def enterModule(owner: Symbol, name: String, completer: SymbolLoader): Symbol =
+ enterModule(owner, newModule(owner, name), completer)
+
+ def enterModule(owner: Symbol, module: ModuleSymbol, completer: SymbolLoader): Symbol = {
module setInfo completer
module.moduleClass setInfo moduleClassLoader
enterIfNew(owner, module, completer)
@@ -115,9 +121,17 @@ abstract class SymbolLoaders {
/** Enter class and module with given `name` into scope of `root`
* and give them `completer` as type.
*/
- def enterClassAndModule(root: Symbol, name: String, completer: SymbolLoader) {
- val clazz = enterClass(root, name, completer)
- val module = enterModule(root, name, completer)
+ def enterClassAndModule(root: Symbol, name: String, getCompleter: (ClassSymbol, ModuleSymbol) => SymbolLoader) {
+ val clazz0 = newClass(root, name)
+ val module0 = newModule(root, name)
+ val completer = getCompleter(clazz0, module0)
+ // enterClass/Module may return an existing symbol instead of the ones we created above
+ // this may happen when there's both sources and binaries on the classpath, but the class
+ // name is different from the file name, so the classpath can't match the binary and source
+ // representation. `companionModule/Class` prefers the source version, so we should be careful
+ // to reuse the symbols returned below.
+ val clazz = enterClass(root, clazz0, completer)
+ val module = enterModule(root, module0, completer)
if (!clazz.isAnonymousClass) {
// Diagnostic for SI-7147
def msg: String = {
@@ -138,7 +152,7 @@ abstract class SymbolLoaders {
* (overridden in interactive.Global).
*/
def enterToplevelsFromSource(root: Symbol, name: String, src: AbstractFile) {
- enterClassAndModule(root, name, new SourcefileLoader(src))
+ enterClassAndModule(root, name, (_, _) => new SourcefileLoader(src))
}
/** The package objects of scala and scala.reflect should always
@@ -154,7 +168,7 @@ abstract class SymbolLoaders {
/** Initialize toplevel class and module symbols in `owner` from class path representation `classRep`
*/
- def initializeFromClassPath(owner: Symbol, classRep: ClassRepresentation[AbstractFile]) {
+ def initializeFromClassPath(owner: Symbol, classRep: ClassRepresentation) {
((classRep.binary, classRep.source) : @unchecked) match {
case (Some(bin), Some(src))
if platform.needCompile(bin, src) && !binaryOnly(owner, classRep.name) =>
@@ -164,17 +178,10 @@ abstract class SymbolLoaders {
if (settings.verbose) inform("[symloader] no class, picked up source file for " + src.path)
enterToplevelsFromSource(owner, classRep.name, src)
case (Some(bin), _) =>
- enterClassAndModule(owner, classRep.name, newClassLoader(bin))
+ enterClassAndModule(owner, classRep.name, new ClassfileLoader(bin, _, _))
}
}
- /** Create a new loader from a binary classfile.
- * This is intended as a hook allowing to support loading symbols from
- * files other than .class files.
- */
- protected def newClassLoader(bin: AbstractFile): SymbolLoader =
- new ClassfileLoader(bin)
-
/**
* A lazy type that completes itself by calling parameter doComplete.
* Any linked modules/classes or module classes are also initialized.
@@ -247,41 +254,11 @@ abstract class SymbolLoaders {
}
/**
- * Load contents of a package
- */
- class PackageLoader(classpath: ClassPath[AbstractFile]) extends SymbolLoader with FlagAgnosticCompleter {
- protected def description = s"package loader ${classpath.name}"
-
- protected def doComplete(root: Symbol) {
- assert(root.isPackageClass, root)
- // Time travel to a phase before refchecks avoids an initialization issue. `openPackageModule`
- // creates a module symbol and invokes invokes `companionModule` while the `infos` field is
- // still null. This calls `isModuleNotMethod`, which forces the `info` if run after refchecks.
- enteringPhase(phaseBeforeRefchecks) {
- root.setInfo(new PackageClassInfoType(newScope, root))
-
- if (!root.isRoot) {
- for (classRep <- classpath.classes) {
- initializeFromClassPath(root, classRep)
- }
- }
- if (!root.isEmptyPackageClass) {
- for (pkg <- classpath.packages) {
- enterPackage(root, pkg.name, new PackageLoader(pkg))
- }
-
- openPackageModule(root)
- }
- }
- }
- }
-
- /**
* Loads contents of a package
*/
- class PackageLoaderUsingFlatClassPath(packageName: String, classPath: FlatClassPath) extends SymbolLoader with FlagAgnosticCompleter {
+ class PackageLoader(packageName: String, classPath: ClassPath) extends SymbolLoader with FlagAgnosticCompleter {
protected def description = {
- val shownPackageName = if (packageName == FlatClassPath.RootPackage) "<root package>" else packageName
+ val shownPackageName = if (packageName == ClassPath.RootPackage) "<root package>" else packageName
s"package loader $shownPackageName"
}
@@ -298,9 +275,9 @@ abstract class SymbolLoaders {
val fullName = pkg.name
val name =
- if (packageName == FlatClassPath.RootPackage) fullName
+ if (packageName == ClassPath.RootPackage) fullName
else fullName.substring(packageName.length + 1)
- val packageLoader = new PackageLoaderUsingFlatClassPath(fullName, classPath)
+ val packageLoader = new PackageLoader(fullName, classPath)
enterPackage(root, name, packageLoader)
}
@@ -309,7 +286,7 @@ abstract class SymbolLoaders {
}
}
- class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader with FlagAssigningCompleter {
+ class ClassfileLoader(val classfile: AbstractFile, clazz: ClassSymbol, module: ModuleSymbol) extends SymbolLoader with FlagAssigningCompleter {
private object classfileParser extends {
val symbolTable: SymbolLoaders.this.symbolTable.type = SymbolLoaders.this.symbolTable
} with ClassfileParser {
@@ -329,23 +306,14 @@ abstract class SymbolLoaders {
val loaders = SymbolLoaders.this.asInstanceOf[SymbolLoadersRefined]
- override def classFileLookup: util.ClassFileLookup[AbstractFile] = settings.YclasspathImpl.value match {
- case ClassPathRepresentationType.Recursive => platform.classPath
- case ClassPathRepresentationType.Flat => platform.flatClassPath
- }
+ override def classPath: ClassPath = platform.classPath
}
protected def description = "class file "+ classfile.toString
protected def doComplete(root: Symbol) {
val start = if (Statistics.canEnable) Statistics.startTimer(classReadNanos) else null
-
- // Running the classfile parser after refchecks can lead to "illegal class file dependency"
- // errors. More concretely, the classfile parser calls "sym.companionModule", which calls
- // "isModuleNotMethod" on the companion. After refchecks, this method forces the info, which
- // may run the classfile parser. This produces the error.
- enteringPhase(phaseBeforeRefchecks)(classfileParser.parse(classfile, root))
-
+ classfileParser.parse(classfile, clazz, module)
if (root.associatedFile eq NoAbstractFile) {
root match {
// In fact, the ModuleSymbol forwards its setter to the module class
diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
index 809effe18b..f146419a73 100644
--- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
+++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
@@ -8,16 +8,18 @@ package tools.nsc
package symtab
package classfile
-import java.io.{ File, IOException }
+import java.io.{ByteArrayInputStream, DataInputStream, File, IOException}
import java.lang.Integer.toHexString
-import scala.collection.{ mutable, immutable }
-import scala.collection.mutable.{ ListBuffer, ArrayBuffer }
+
+import scala.collection.{immutable, mutable}
+import scala.collection.mutable.{ArrayBuffer, ListBuffer}
import scala.annotation.switch
-import scala.reflect.internal.{ JavaAccFlags }
-import scala.reflect.internal.pickling.{PickleBuffer, ByteCodecs}
+import scala.reflect.internal.JavaAccFlags
+import scala.reflect.internal.pickling.{ByteCodecs, PickleBuffer}
import scala.reflect.io.NoAbstractFile
+import scala.tools.nsc.util.ClassPath
import scala.tools.nsc.io.AbstractFile
-import scala.tools.nsc.util.ClassFileLookup
+import scala.util.control.NonFatal
/** This abstract class implements a class file parser.
*
@@ -43,8 +45,8 @@ abstract class ClassfileParser {
*/
protected def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol
- /** The way of the class file lookup used by the compiler. */
- def classFileLookup: ClassFileLookup[AbstractFile]
+ /** The compiler classpath. */
+ def classPath: ClassPath
import definitions._
import scala.reflect.internal.ClassfileConstants._
@@ -53,18 +55,18 @@ abstract class ClassfileParser {
protected type ThisConstantPool <: ConstantPool
protected def newConstantPool: ThisConstantPool
- protected var file: AbstractFile = _ // the class file
- protected var in: AbstractFileReader = _ // the class file reader
- protected var clazz: Symbol = _ // the class symbol containing dynamic members
- protected var staticModule: Symbol = _ // the module symbol containing static members
- protected var instanceScope: Scope = _ // the scope of all instance definitions
- protected var staticScope: Scope = _ // the scope of all static definitions
- protected var pool: ThisConstantPool = _ // the classfile's constant pool
- protected var isScala: Boolean = _ // does class file describe a scala class?
- protected var isScalaAnnot: Boolean = _ // does class file describe a scala class with its pickled info in an annotation?
- protected var isScalaRaw: Boolean = _ // this class file is a scala class with no pickled info
- protected var busy: Symbol = _ // lock to detect recursive reads
- protected var currentClass: Name = _ // JVM name of the current class
+ protected var file: AbstractFile = _ // the class file
+ protected var in: AbstractFileReader = _ // the class file reader
+ protected var clazz: ClassSymbol = _ // the class symbol containing dynamic members
+ protected var staticModule: ModuleSymbol = _ // the module symbol containing static members
+ protected var instanceScope: Scope = _ // the scope of all instance definitions
+ protected var staticScope: Scope = _ // the scope of all static definitions
+ protected var pool: ThisConstantPool = _ // the classfile's constant pool
+ protected var isScala: Boolean = _ // does class file describe a scala class?
+ protected var isScalaAnnot: Boolean = _ // does class file describe a scala class with its pickled info in an annotation?
+ protected var isScalaRaw: Boolean = _ // this class file is a scala class with no pickled info
+ protected var busy: Symbol = _ // lock to detect recursive reads
+ protected var currentClass: Name = _ // JVM name of the current class
protected var classTParams = Map[Name,Symbol]()
protected var srcfile0 : Option[AbstractFile] = None
protected def moduleClass: Symbol = staticModule.moduleClass
@@ -132,17 +134,21 @@ abstract class ClassfileParser {
finally loaders.parentsLevel -= 1
}
- def parse(file: AbstractFile, root: Symbol): Unit = {
- debuglog("[class] >> " + root.fullName)
-
+ /**
+ * `clazz` and `module` are the class and module symbols corresponding to the classfile being
+ * parsed. Note that the ClassfileLoader unconditionally creates both of these symbols, they may
+ * may get invalidated later on (.exists).
+ *
+ * Note that using `companionModule` / `companionClass` does not always work to navigate between
+ * those two symbols, namely when they are shadowed by a type / value in the a package object
+ * (scala-dev#248).
+ */
+ def parse(file: AbstractFile, clazz: ClassSymbol, module: ModuleSymbol): Unit = {
this.file = file
- pushBusy(root) {
+ pushBusy(clazz) {
this.in = new AbstractFileReader(file)
- this.clazz = if (root.isModule) root.companionClass else root
- // WARNING! do no use clazz.companionModule to find staticModule.
- // In a situation where root can be defined, but its companionClass not,
- // this would give incorrect results (see SI-5031 in separate compilation scenario)
- this.staticModule = if (root.isModule) root else root.companionModule
+ this.clazz = clazz
+ this.staticModule = module
this.isScala = false
parseHeader()
@@ -206,10 +212,14 @@ abstract class ClassfileParser {
case name: Name => name
case _ =>
val start = firstExpecting(index, CONSTANT_UTF8)
- recordAtIndex(newTermName(in.buf, start + 2, in.getChar(start).toInt), index)
+ val len = in.getChar(start).toInt
+ recordAtIndex(TermName(fromMUTF8(in.buf, start, len + 2)), index)
}
)
+ private def fromMUTF8(bytes: Array[Byte], offset: Int, len: Int): String =
+ new DataInputStream(new ByteArrayInputStream(bytes, offset, len)).readUTF
+
/** Return the name found at given index in the constant pool, with '/' replaced by '.'. */
def getExternalName(index: Int): Name = {
if (index <= 0 || len <= index)
@@ -271,7 +281,7 @@ abstract class ClassfileParser {
* arrays are considered to be class types, they might
* appear as entries in 'newarray' or 'cast' opcodes.
*/
- def getClassOrArrayType(index: Int): Type = (
+ def getClassOrArrayType(index: Int): Type = {
if (index <= 0 || len <= index) errorBadIndex(index)
else values(index) match {
case tp: Type => tp
@@ -283,7 +293,7 @@ abstract class ClassfileParser {
case _ => recordAtIndex(classNameToSymbol(name), index).tpe_*
}
}
- )
+ }
def getType(index: Int): Type = getType(null, index)
def getType(sym: Symbol, index: Int): Type = sigToType(sym, getExternalName(index))
@@ -356,63 +366,43 @@ abstract class ClassfileParser {
abort(s"bad constant pool tag ${in.buf(start)} at byte $start")
}
- private def loadClassSymbol(name: Name): Symbol = {
- val file = classFileLookup findClassFile name.toString getOrElse {
- // SI-5593 Scaladoc's current strategy is to visit all packages in search of user code that can be documented
- // therefore, it will rummage through the classpath triggering errors whenever it encounters package objects
- // that are not in their correct place (see bug for details)
-
- // TODO More consistency with use of stub symbols in `Unpickler`
- // - better owner than `NoSymbol`
- // - remove eager warning
- val msg = s"Class $name not found - continuing with a stub."
- if ((!settings.isScaladoc) && (settings.verbose || settings.developer)) warning(msg)
- return NoSymbol.newStubSymbol(name.toTypeName, msg)
- }
- val completer = new loaders.ClassfileLoader(file)
- var owner: Symbol = rootMirror.RootClass
- var sym: Symbol = NoSymbol
- var ss: Name = null
- var start = 0
- var end = name indexOf '.'
-
- while (end > 0) {
- ss = name.subName(start, end)
- sym = owner.info.decls lookup ss
- if (sym == NoSymbol) {
- sym = owner.newPackage(ss.toTermName) setInfo completer
- sym.moduleClass setInfo completer
- owner.info.decls enter sym
- }
- owner = sym.moduleClass
- start = end + 1
- end = name.indexOf('.', start)
- }
- ss = name.subName(0, start)
- owner.info.decls lookup ss orElse {
- sym = owner.newClass(ss.toTypeName) setInfoAndEnter completer
- debuglog("loaded "+sym+" from file "+file)
- sym
- }
+ def stubClassSymbol(name: Name): Symbol = {
+ // SI-5593 Scaladoc's current strategy is to visit all packages in search of user code that can be documented
+ // therefore, it will rummage through the classpath triggering errors whenever it encounters package objects
+ // that are not in their correct place (see bug for details)
+
+ // TODO More consistency with use of stub symbols in `Unpickler`
+ // - better owner than `NoSymbol`
+ // - remove eager warning
+ val msg = s"Class $name not found - continuing with a stub."
+ if ((!settings.isScaladoc) && (settings.verbose || settings.developer)) warning(msg)
+ NoSymbol.newStubSymbol(name.toTypeName, msg)
}
- /** FIXME - we shouldn't be doing ad hoc lookups in the empty package.
- * The method called "getClassByName" should either return the class or not.
- */
- private def lookupClass(name: Name) = (
+ private def lookupClass(name: Name) = try {
if (name containsChar '.')
- rootMirror getClassByName name // see tickets #2464, #3756
+ rootMirror getClassByName name
else
+ // FIXME - we shouldn't be doing ad hoc lookups in the empty package, getClassByName should return the class
definitions.getMember(rootMirror.EmptyPackageClass, name.toTypeName)
- )
+ } catch {
+ // The handler
+ // - prevents crashes with deficient InnerClassAttributes (SI-2464, 0ce0ad5)
+ // - was referenced in the bugfix commit for SI-3756 (4fb0d53), not sure why
+ // - covers the case when a type alias in a package object shadows a class symbol,
+ // getClassByName throws a MissingRequirementError (scala-dev#248)
+ case _: FatalError =>
+ // getClassByName can throw a MissingRequirementError (which extends FatalError)
+ // definitions.getMember can throw a FatalError, for example in pos/t5165b
+ stubClassSymbol(name)
+ }
/** Return the class symbol of the given name. */
def classNameToSymbol(name: Name): Symbol = {
if (innerClasses contains name)
innerClasses innerSymbol name
else
- try lookupClass(name)
- catch { case _: FatalError => loadClassSymbol(name) }
+ lookupClass(name)
}
def parseClass() {
@@ -441,13 +431,10 @@ abstract class ClassfileParser {
}
val isTopLevel = !(currentClass containsChar '$') // Java class name; *don't* try to to use Scala name decoding (SI-7532)
-
- val c = if (isTopLevel) pool.getClassSymbol(nameIdx) else clazz
if (isTopLevel) {
- if (c != clazz) {
- if ((clazz eq NoSymbol) && (c ne NoSymbol)) clazz = c
- else mismatchError(c)
- }
+ val c = pool.getClassSymbol(nameIdx)
+ // scala-dev#248: when a type alias (in a package object) shadows a class symbol, getClassSymbol returns a stub
+ if (!c.isInstanceOf[StubSymbol] && c != clazz) mismatchError(c)
}
addEnclosingTParams(clazz)
@@ -542,7 +529,7 @@ abstract class ClassfileParser {
devWarning(s"no linked class for java enum $sym in ${sym.owner}. A referencing class file might be missing an InnerClasses entry.")
case linked =>
if (!linked.isSealed)
- // Marking the enum class SEALED | ABSTRACT enables exhaustiveness checking.
+ // Marking the enum class SEALED | ABSTRACT enables exhaustiveness checking. See also JavaParsers.
// This is a bit of a hack and requires excluding the ABSTRACT flag in the backend, see method javaClassfileFlags.
linked setFlag (SEALED | ABSTRACT)
linked addChild sym
@@ -566,6 +553,7 @@ abstract class ClassfileParser {
val name = readName()
val sym = ownerForFlags(jflags).newMethod(name.toTermName, NoPosition, sflags)
var info = pool.getType(sym, u2)
+ var removedOuterParameter = false
if (name == nme.CONSTRUCTOR)
info match {
case MethodType(params, restpe) =>
@@ -580,6 +568,7 @@ abstract class ClassfileParser {
* ClassfileParser for 1 executes, and clazz.owner is the package.
*/
assert(params.head.tpe.typeSymbol == clazz.owner || clazz.owner.hasPackageFlag, params.head.tpe.typeSymbol + ": " + clazz.owner)
+ removedOuterParameter = true
params.tail
case _ =>
params
@@ -599,7 +588,7 @@ abstract class ClassfileParser {
// parsed from SignatureATTR
sym setInfo info
propagatePackageBoundary(jflags, sym)
- parseAttributes(sym, info)
+ parseAttributes(sym, info, removedOuterParameter)
if (jflags.isVarargs)
sym modifyInfo arrayToRepeated
@@ -782,7 +771,7 @@ abstract class ClassfileParser {
GenPolyType(ownTypeParams, tpe)
} // sigToType
- def parseAttributes(sym: Symbol, symtype: Type) {
+ def parseAttributes(sym: Symbol, symtype: Type, removedOuterParameter: Boolean = false) {
def convertTo(c: Constant, pt: Type): Constant = {
if (pt.typeSymbol == BooleanClass && c.tag == IntTag)
Constant(c.value != 0)
@@ -815,6 +804,31 @@ abstract class ClassfileParser {
val c1 = convertTo(c, symtype)
if (c1 ne null) sym.setInfo(ConstantType(c1))
else devWarning(s"failure to convert $c to $symtype")
+ case tpnme.MethodParametersATTR =>
+ def readParamNames(): Unit = {
+ import scala.tools.asm.Opcodes.ACC_SYNTHETIC
+ val paramCount = u1
+ var i = 0
+ if (removedOuterParameter && i < paramCount) {
+ in.skip(4)
+ i += 1
+ }
+ var remainingParams = sym.paramss.head // Java only has exactly one parameter list
+ while (i < paramCount) {
+ val name = pool.getName(u2)
+ val access = u2
+ if (remainingParams.nonEmpty) {
+ val param = remainingParams.head
+ remainingParams = remainingParams.tail
+ if ((access & ACC_SYNTHETIC) != ACC_SYNTHETIC) { // name not synthetic
+ param.name = name.encode
+ param.resetFlag(SYNTHETIC)
+ }
+ }
+ i += 1
+ }
+ }
+ readParamNames()
case tpnme.ScalaSignatureATTR =>
if (!isScalaAnnot) {
devWarning(s"symbol ${sym.fullName} has pickled signature in attribute")
@@ -830,16 +844,19 @@ abstract class ClassfileParser {
// Java annotations on classes / methods / fields with RetentionPolicy.RUNTIME
case tpnme.RuntimeAnnotationATTR =>
if (isScalaAnnot || !isScala) {
- val scalaSigAnnot = parseAnnotations(attrLen)
- if (isScalaAnnot)
- scalaSigAnnot match {
- case Some(san: AnnotationInfo) =>
- val bytes =
- san.assocs.find({ _._1 == nme.bytes }).get._2.asInstanceOf[ScalaSigBytes].bytes
- unpickler.unpickle(bytes, 0, clazz, staticModule, in.file.name)
- case None =>
- throw new RuntimeException("Scala class file does not contain Scala annotation")
- }
+ // For Scala classfiles we are only interested in the scala signature annotations. Other
+ // annotations should be skipped (the pickle contains the symbol's annotations).
+ // Skipping them also prevents some spurious warnings / errors related to SI-7014,
+ // SI-7551, pos/5165b
+ val scalaSigAnnot = parseAnnotations(onlyScalaSig = isScalaAnnot)
+ if (isScalaAnnot) scalaSigAnnot match {
+ case Some(san: AnnotationInfo) =>
+ val bytes =
+ san.assocs.find({ _._1 == nme.bytes }).get._2.asInstanceOf[ScalaSigBytes].bytes
+ unpickler.unpickle(bytes, 0, clazz, staticModule, in.file.name)
+ case None =>
+ throw new RuntimeException("Scala class file does not contain Scala annotation")
+ }
debuglog("[class] << " + sym.fullName + sym.annotationsString)
}
else
@@ -873,6 +890,24 @@ abstract class ClassfileParser {
}
}
+ def skipAnnotArg(): Unit = {
+ u1 match {
+ case STRING_TAG | BOOL_TAG | BYTE_TAG | CHAR_TAG | SHORT_TAG |
+ INT_TAG | LONG_TAG | FLOAT_TAG | DOUBLE_TAG | CLASS_TAG =>
+ in.skip(2)
+
+ case ENUM_TAG =>
+ in.skip(4)
+
+ case ARRAY_TAG =>
+ val num = u2
+ for (i <- 0 until num) skipAnnotArg()
+
+ case ANNOTATION_TAG =>
+ parseAnnotation(u2, onlyScalaSig = true)
+ }
+ }
+
def parseAnnotArg: Option[ClassfileAnnotArg] = {
val tag = u1
val index = u2
@@ -906,7 +941,7 @@ abstract class ClassfileParser {
if (hasError) None
else Some(ArrayAnnotArg(arr.toArray))
case ANNOTATION_TAG =>
- parseAnnotation(index) map (NestedAnnotArg(_))
+ parseAnnotation(index, onlyScalaSig = false) map (NestedAnnotArg(_))
}
}
@@ -933,7 +968,7 @@ abstract class ClassfileParser {
/* Parse and return a single annotation. If it is malformed,
* return None.
*/
- def parseAnnotation(attrNameIndex: Int): Option[AnnotationInfo] = try {
+ def parseAnnotation(attrNameIndex: Int, onlyScalaSig: Boolean): Option[AnnotationInfo] = try {
val attrType = pool.getType(attrNameIndex)
val nargs = u2
val nvpairs = new ListBuffer[(Name, ClassfileAnnotArg)]
@@ -954,18 +989,17 @@ abstract class ClassfileParser {
case None => hasError = true
}
else
- parseAnnotArg match {
+ if (onlyScalaSig) skipAnnotArg()
+ else parseAnnotArg match {
case Some(c) => nvpairs += ((name, c))
case None => hasError = true
}
}
if (hasError) None
else Some(AnnotationInfo(attrType, List(), nvpairs.toList))
- }
- catch {
- case f: FatalError => throw f // don't eat fatal errors, they mean a class was not found
- case ex: java.lang.Error => throw ex
- case ex: Throwable =>
+ } catch {
+ case f: FatalError => throw f // don't eat fatal errors, they mean a class was not found
+ case NonFatal(ex) =>
// We want to be robust when annotations are unavailable, so the very least
// we can do is warn the user about the exception
// There was a reference to ticket 1135, but that is outdated: a reference to a class not on
@@ -974,7 +1008,6 @@ abstract class ClassfileParser {
// and that should never be swallowed silently.
warning(s"Caught: $ex while parsing annotations in ${in.file}")
if (settings.debug) ex.printStackTrace()
-
None // ignore malformed annotations
}
@@ -996,19 +1029,18 @@ abstract class ClassfileParser {
/* Parse a sequence of annotations and attaches them to the
* current symbol sym, except for the ScalaSignature annotation that it returns, if it is available. */
- def parseAnnotations(len: Int): Option[AnnotationInfo] = {
+ def parseAnnotations(onlyScalaSig: Boolean): Option[AnnotationInfo] = {
val nAttr = u2
var scalaSigAnnot: Option[AnnotationInfo] = None
- for (n <- 0 until nAttr)
- parseAnnotation(u2) match {
- case Some(scalaSig) if (scalaSig.atp == ScalaSignatureAnnotation.tpe) =>
- scalaSigAnnot = Some(scalaSig)
- case Some(scalaSig) if (scalaSig.atp == ScalaLongSignatureAnnotation.tpe) =>
- scalaSigAnnot = Some(scalaSig)
- case Some(annot) =>
- sym.addAnnotation(annot)
- case None =>
- }
+ for (n <- 0 until nAttr) parseAnnotation(u2, onlyScalaSig) match {
+ case Some(scalaSig) if scalaSig.atp == ScalaSignatureAnnotation.tpe =>
+ scalaSigAnnot = Some(scalaSig)
+ case Some(scalaSig) if scalaSig.atp == ScalaLongSignatureAnnotation.tpe =>
+ scalaSigAnnot = Some(scalaSig)
+ case Some(annot) =>
+ sym.addAnnotation(annot)
+ case None =>
+ }
scalaSigAnnot
}
@@ -1025,7 +1057,6 @@ abstract class ClassfileParser {
def enterClassAndModule(entry: InnerClassEntry, file: AbstractFile) {
def jflags = entry.jflags
- val completer = new loaders.ClassfileLoader(file)
val name = entry.originalName
val sflags = jflags.toScalaFlags
val owner = ownerForFlags(jflags)
@@ -1039,8 +1070,11 @@ abstract class ClassfileParser {
val (innerClass, innerModule) = if (file == NoAbstractFile) {
(newStub(name.toTypeName), newStub(name.toTermName))
} else {
- val cls = owner.newClass(name.toTypeName, NoPosition, sflags) setInfo completer
- val mod = owner.newModule(name.toTermName, NoPosition, sflags) setInfo completer
+ val cls = owner.newClass(name.toTypeName, NoPosition, sflags)
+ val mod = owner.newModule(name.toTermName, NoPosition, sflags)
+ val completer = new loaders.ClassfileLoader(file, cls, mod)
+ cls setInfo completer
+ mod setInfo completer
mod.moduleClass setInfo loaders.moduleClassLoader
List(cls, mod.moduleClass) foreach (_.associatedFile = file)
(cls, mod)
@@ -1064,7 +1098,7 @@ abstract class ClassfileParser {
for (entry <- innerClasses.entries) {
// create a new class member for immediate inner classes
if (entry.outerName == currentClass) {
- val file = classFileLookup.findClassFile(entry.externalName.toString)
+ val file = classPath.findClassFile(entry.externalName.toString)
enterClassAndModule(entry, file.getOrElse(NoAbstractFile))
}
}
@@ -1083,8 +1117,6 @@ abstract class ClassfileParser {
val attrName = readTypeName()
val attrLen = u4
attrName match {
- case tpnme.SignatureATTR =>
- in.skip(attrLen)
case tpnme.ScalaSignatureATTR =>
isScala = true
val pbuf = new PickleBuffer(in.buf, in.bp, in.bp + attrLen)
@@ -1151,10 +1183,10 @@ abstract class ClassfileParser {
private def innerSymbol(entry: InnerClassEntry): Symbol = {
val name = entry.originalName.toTypeName
val enclosing = entry.enclosing
- val member = (
+ val member = {
if (enclosing == clazz) entry.scope lookup name
else lookupMemberAtTyperPhaseIfPossible(enclosing, name)
- )
+ }
def newStub = {
enclosing
.newStubSymbol(name, s"Unable to locate class corresponding to inner class entry for $name in owner ${entry.outerName}")
diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala
deleted file mode 100644
index 7f18565cdf..0000000000
--- a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala
+++ /dev/null
@@ -1,1130 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Iulian Dragos
- */
-
-package scala
-package tools.nsc
-package symtab
-package classfile
-
-import scala.collection.{ mutable, immutable }
-import mutable.ListBuffer
-import ClassfileConstants._
-import scala.reflect.internal.JavaAccFlags
-
-/** ICode reader from Java bytecode.
- *
- * @author Iulian Dragos
- * @version 1.0
- */
-abstract class ICodeReader extends ClassfileParser {
- val global: Global
- val symbolTable: global.type
- val loaders: global.loaders.type
- import global._
- import icodes._
-
- var instanceCode: IClass = null // the ICode class for the current symbol
- var staticCode: IClass = null // the ICode class static members
- var method: IMethod = NoIMethod // the current IMethod
- var isScalaModule = false
-
- override protected type ThisConstantPool = ICodeConstantPool
- override protected def newConstantPool = new ICodeConstantPool
-
- /** Try to force the chain of enclosing classes for the given name. Otherwise
- * flatten would not lift classes that were not referenced in the source code.
- */
- def forceMangledName(name: Name, module: Boolean): Symbol = {
- val parts = name.decode.toString.split(Array('.', '$'))
- var sym: Symbol = rootMirror.RootClass
-
- // was "at flatten.prev"
- enteringFlatten {
- for (part0 <- parts; if !(part0 == ""); part = newTermName(part0)) {
- val sym1 = enteringIcode {
- sym.linkedClassOfClass.info
- sym.info.decl(part.encode)
- }//.suchThat(module == _.isModule)
-
- sym = sym1 orElse sym.info.decl(part.encode.toTypeName)
- }
- }
- sym
- }
-
- protected class ICodeConstantPool extends ConstantPool {
- /** Return the symbol of the class member at `index`.
- * The following special cases exist:
- * - If the member refers to special `MODULE$` static field, return
- * the symbol of the corresponding module.
- * - If the member is a field, and is not found with the given name,
- * another try is made by appending `nme.LOCAL_SUFFIX_STRING`
- * - If no symbol is found in the right tpe, a new try is made in the
- * companion class, in case the owner is an implementation class.
- */
- def getMemberSymbol(index: Int, static: Boolean): Symbol = {
- if (index <= 0 || len <= index) errorBadIndex(index)
- var f = values(index).asInstanceOf[Symbol]
- if (f eq null) {
- val start = starts(index)
- val first = in.buf(start).toInt
- if (first != CONSTANT_FIELDREF &&
- 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.unexpandedName)
- val (name0, tpe0) = getNameAndType(in.getChar(start + 3).toInt, ownerTpe)
- debuglog("getMemberSymbol: name and tpe: " + name0 + ": " + tpe0)
-
- forceMangledName(tpe0.typeSymbol.name, module = false)
- val (name, tpe) = getNameAndType(in.getChar(start + 3).toInt, ownerTpe)
- if (name == nme.MODULE_INSTANCE_FIELD) {
- val index = in.getChar(start + 1).toInt
- val name = getExternalName(in.getChar(starts(index).toInt + 1).toInt)
- //assert(name.endsWith("$"), "Not a module class: " + name)
- f = forceMangledName(name dropRight 1, module = true)
- if (f == NoSymbol)
- f = rootMirror.getModuleByName(name dropRight 1)
- } else {
- val origName = nme.unexpandedName(name)
- val owner = if (static) ownerTpe.typeSymbol.linkedClassOfClass else ownerTpe.typeSymbol
- f = owner.info.findMember(origName, 0, 0, stableOnly = false).suchThat(_.tpe.widen =:= tpe)
- if (f == NoSymbol)
- f = owner.info.findMember(newTermName(origName + nme.LOCAL_SUFFIX_STRING), 0, 0, stableOnly = false).suchThat(_.tpe =:= tpe)
- if (f == NoSymbol) {
- // if it's an impl class, try to find it's static member inside the class
- if (ownerTpe.typeSymbol.isImplClass) {
- f = ownerTpe.findMember(origName, 0, 0, stableOnly = false).suchThat(_.tpe =:= tpe)
- } else {
- log("Couldn't find " + name + ": " + tpe + " inside: \n" + ownerTpe)
- f = tpe match {
- case MethodType(_, _) => owner.newMethod(name.toTermName, owner.pos)
- case _ => owner.newVariable(name.toTermName, owner.pos)
- }
- f setInfo tpe
- log("created fake member " + f.fullName)
- }
- }
- }
- assert(f != NoSymbol,
- s"could not find $name: $tpe in $ownerTpe" + (
- if (settings.debug.value) ownerTpe.members.mkString(", members are:\n ", "\n ", "") else ""
- )
- )
- values(index) = f
- }
- f
- }
- }
-
- /** Read back bytecode for the given class symbol. It returns
- * two IClass objects, one for static members and one
- * for non-static members.
- */
- def readClass(cls: Symbol): (IClass, IClass) = {
- cls.info // ensure accurate type information
-
- isScalaModule = cls.isModule && !cls.isJavaDefined
- log("ICodeReader reading " + cls)
- val name = cls.javaClassName
-
- classFileLookup.findClassFile(name) match {
- case Some(classFile) => parse(classFile, cls)
- case _ => MissingRequirementError.notFound("Could not find bytecode for " + cls)
- }
-
- (staticCode, instanceCode)
- }
-
- override def parseClass() {
- this.instanceCode = new IClass(clazz)
- this.staticCode = new IClass(staticModule)
-
- u2
- pool getClassSymbol u2
- parseInnerClasses()
-
- in.skip(2) // super class
- in.skip(2 * u2) // interfaces
- val fieldCount = u2
- for (i <- 0 until fieldCount) parseField()
- val methodCount = u2
- for (i <- 0 until methodCount) parseMethod()
- instanceCode.methods = instanceCode.methods.reverse
- staticCode.methods = staticCode.methods.reverse
- }
-
- override def parseField() {
- val (jflags, sym) = parseMember(field = true)
- getCode(jflags) addField new IField(sym)
- skipAttributes()
- }
-
- private def parseMember(field: Boolean): (JavaAccFlags, Symbol) = {
- val jflags = JavaAccFlags(u2)
- val name = pool getName u2
- /* If we're parsing a scala module, the owner of members is always
- * the module symbol.
- */
- val owner = (
- if (isScalaModule) staticModule
- else if (jflags.isStatic) moduleClass
- else clazz
- )
- val dummySym = owner.newMethod(name.toTermName, owner.pos, jflags.toScalaFlags)
-
- try {
- val ch = u2
- val tpe = pool.getType(dummySym, ch)
-
- if ("<clinit>" == name.toString)
- (jflags, NoSymbol)
- else {
- var sym = owner.info.findMember(name, 0, 0, stableOnly = false).suchThat(old => sameType(old.tpe, tpe))
- if (sym == NoSymbol)
- sym = owner.info.findMember(newTermName(name + nme.LOCAL_SUFFIX_STRING), 0, 0, stableOnly = false).suchThat(_.tpe =:= tpe)
- if (sym == NoSymbol) {
- sym = if (field) owner.newValue(name.toTermName, owner.pos, jflags.toScalaFlags) else dummySym
- sym setInfoAndEnter tpe
- log(s"ICodeReader could not locate ${name.decode} in $owner. Created ${sym.defString}.")
- }
- (jflags, sym)
- }
- } catch {
- case e: MissingRequirementError =>
- (jflags, NoSymbol)
- }
- }
-
- /** Checks if `tp1` is the same type as `tp2`, modulo implicit methods.
- * We don't care about the distinction between implicit and explicit
- * methods as this point, and we can't get back the information from
- * bytecode anyway.
- */
- private def sameType(tp1: Type, tp2: Type): Boolean = (tp1, tp2) match {
- case (mt1 @ MethodType(args1, resTpe1), mt2 @ MethodType(args2, resTpe2)) if mt1.isImplicit || mt2.isImplicit =>
- MethodType(args1, resTpe1) =:= MethodType(args2, resTpe2)
- case _ =>
- tp1 =:= tp2
- }
-
- override def parseMethod() {
- val (jflags, sym) = parseMember(field = false)
- val beginning = in.bp
- try {
- if (sym != NoSymbol) {
- this.method = new IMethod(sym)
- this.method.returnType = toTypeKind(sym.tpe.resultType)
- getCode(jflags).addMethod(this.method)
- if (jflags.isNative)
- this.method.native = true
- val attributeCount = u2
- for (i <- 0 until attributeCount) parseAttribute()
- } else {
- debuglog("Skipping non-existent method.")
- skipAttributes()
- }
- } catch {
- case e: MissingRequirementError =>
- in.bp = beginning; skipAttributes()
- debuglog("Skipping non-existent method. " + e.msg)
- }
- }
-
- def parseAttribute() {
- val attrName = pool.getName(u2).toTypeName
- val attrLen = u4
- attrName match {
- case tpnme.CodeATTR =>
- parseByteCode()
- case _ =>
- in.skip(attrLen)
- }
- }
-
- override def classNameToSymbol(name: Name) = {
- val sym = if (name == fulltpnme.RuntimeNothing)
- definitions.NothingClass
- else if (name == fulltpnme.RuntimeNull)
- definitions.NullClass
- else if (nme.isImplClassName(name)) {
- val iface = rootMirror.getClassByName(tpnme.interfaceName(name))
- log("forcing " + iface.owner + " at phase: " + phase + " impl: " + iface.implClass)
- iface.owner.info // force the mixin type-transformer
- rootMirror.getClassByName(name)
- }
- else if (nme.isModuleName(name)) {
- val strippedName = name.dropModule
- forceMangledName(newTermName(strippedName.decode), module = true) orElse rootMirror.getModuleByName(strippedName)
- }
- else {
- forceMangledName(name, module = false)
- exitingFlatten(rootMirror.getClassByName(name.toTypeName))
- }
- if (sym.isModule)
- sym.moduleClass
- else
- sym
- }
-
-
- var maxStack: Int = _
- var maxLocals: Int = _
- val JVM = ClassfileConstants // shorter, uppercase alias for use in case patterns
-
- def toUnsignedByte(b: Byte): Int = b.toInt & 0xff
- var pc = 0
-
- /** Parse java bytecode into ICode */
- def parseByteCode() {
- maxStack = u2
- maxLocals = u2
- val codeLength = u4
- val code = new LinearCode
-
- def parseInstruction() {
- import opcodes._
- import code._
- var size = 1 // instruction size
-
- /* Parse 16 bit jump target. */
- def parseJumpTarget = {
- size += 2
- val offset = u2.toShort
- val target = pc + offset
- assert(target >= 0 && target < codeLength, "Illegal jump target: " + target)
- target
- }
-
- /* Parse 32 bit jump target. */
- def parseJumpTargetW: Int = {
- size += 4
- val offset = u4
- val target = pc + offset
- assert(target >= 0 && target < codeLength, "Illegal jump target: " + target + "pc: " + pc + " offset: " + offset)
- target
- }
-
- u1 match {
- case JVM.nop => parseInstruction()
- case JVM.aconst_null => code emit CONSTANT(Constant(null))
- case JVM.iconst_m1 => code emit CONSTANT(Constant(-1))
- case JVM.iconst_0 => code emit CONSTANT(Constant(0))
- case JVM.iconst_1 => code emit CONSTANT(Constant(1))
- case JVM.iconst_2 => code emit CONSTANT(Constant(2))
- case JVM.iconst_3 => code emit CONSTANT(Constant(3))
- case JVM.iconst_4 => code emit CONSTANT(Constant(4))
- case JVM.iconst_5 => code emit CONSTANT(Constant(5))
-
- case JVM.lconst_0 => code emit CONSTANT(Constant(0l))
- case JVM.lconst_1 => code emit CONSTANT(Constant(1l))
- case JVM.fconst_0 => code emit CONSTANT(Constant(0.0f))
- case JVM.fconst_1 => code emit CONSTANT(Constant(1.0f))
- case JVM.fconst_2 => code emit CONSTANT(Constant(2.0f))
- case JVM.dconst_0 => code emit CONSTANT(Constant(0.0))
- case JVM.dconst_1 => code emit CONSTANT(Constant(1.0))
-
- case JVM.bipush => code.emit(CONSTANT(Constant(s1))); size += 1
- case JVM.sipush => code.emit(CONSTANT(Constant(s2))); size += 2
- case JVM.ldc => code.emit(CONSTANT(pool.getConstant(u1))); size += 1
- case JVM.ldc_w => code.emit(CONSTANT(pool.getConstant(u2))); size += 2
- case JVM.ldc2_w => code.emit(CONSTANT(pool.getConstant(u2))); size += 2
- case JVM.iload => code.emit(LOAD_LOCAL(code.getLocal(u1, INT))); size += 1
- case JVM.lload => code.emit(LOAD_LOCAL(code.getLocal(u1, LONG))); size += 1
- case JVM.fload => code.emit(LOAD_LOCAL(code.getLocal(u1, FLOAT))); size += 1
- case JVM.dload => code.emit(LOAD_LOCAL(code.getLocal(u1, DOUBLE))); size += 1
- case JVM.aload =>
- val local = u1.toInt; size += 1
- if (local == 0 && !method.isStatic)
- code.emit(THIS(method.symbol.owner))
- else
- code.emit(LOAD_LOCAL(code.getLocal(local, ObjectReference)))
-
- case JVM.iload_0 => code.emit(LOAD_LOCAL(code.getLocal(0, INT)))
- case JVM.iload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, INT)))
- case JVM.iload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, INT)))
- case JVM.iload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, INT)))
- case JVM.lload_0 => code.emit(LOAD_LOCAL(code.getLocal(0, LONG)))
- case JVM.lload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, LONG)))
- case JVM.lload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, LONG)))
- case JVM.lload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, LONG)))
- case JVM.fload_0 => code.emit(LOAD_LOCAL(code.getLocal(0, FLOAT)))
- case JVM.fload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, FLOAT)))
- case JVM.fload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, FLOAT)))
- case JVM.fload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, FLOAT)))
- case JVM.dload_0 => code.emit(LOAD_LOCAL(code.getLocal(0, DOUBLE)))
- case JVM.dload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, DOUBLE)))
- case JVM.dload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, DOUBLE)))
- case JVM.dload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, DOUBLE)))
- case JVM.aload_0 =>
- if (!method.isStatic)
- code.emit(THIS(method.symbol.owner))
- else
- code.emit(LOAD_LOCAL(code.getLocal(0, ObjectReference)))
- case JVM.aload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, ObjectReference)))
- case JVM.aload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, ObjectReference)))
- case JVM.aload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, ObjectReference)))
-
- case JVM.iaload => code.emit(LOAD_ARRAY_ITEM(INT))
- case JVM.laload => code.emit(LOAD_ARRAY_ITEM(LONG))
- case JVM.faload => code.emit(LOAD_ARRAY_ITEM(FLOAT))
- case JVM.daload => code.emit(LOAD_ARRAY_ITEM(DOUBLE))
- case JVM.aaload => code.emit(LOAD_ARRAY_ITEM(ObjectReference))
- case JVM.baload => code.emit(LOAD_ARRAY_ITEM(BYTE))
- case JVM.caload => code.emit(LOAD_ARRAY_ITEM(CHAR))
- case JVM.saload => code.emit(LOAD_ARRAY_ITEM(SHORT))
-
- case JVM.istore => code.emit(STORE_LOCAL(code.getLocal(u1, INT))); size += 1
- case JVM.lstore => code.emit(STORE_LOCAL(code.getLocal(u1, LONG))); size += 1
- case JVM.fstore => code.emit(STORE_LOCAL(code.getLocal(u1, FLOAT))); size += 1
- case JVM.dstore => code.emit(STORE_LOCAL(code.getLocal(u1, DOUBLE))); size += 1
- case JVM.astore => code.emit(STORE_LOCAL(code.getLocal(u1, ObjectReference))); size += 1
- case JVM.istore_0 => code.emit(STORE_LOCAL(code.getLocal(0, INT)))
- case JVM.istore_1 => code.emit(STORE_LOCAL(code.getLocal(1, INT)))
- case JVM.istore_2 => code.emit(STORE_LOCAL(code.getLocal(2, INT)))
- case JVM.istore_3 => code.emit(STORE_LOCAL(code.getLocal(3, INT)))
- case JVM.lstore_0 => code.emit(STORE_LOCAL(code.getLocal(0, LONG)))
- case JVM.lstore_1 => code.emit(STORE_LOCAL(code.getLocal(1, LONG)))
- case JVM.lstore_2 => code.emit(STORE_LOCAL(code.getLocal(2, LONG)))
- case JVM.lstore_3 => code.emit(STORE_LOCAL(code.getLocal(3, LONG)))
- case JVM.fstore_0 => code.emit(STORE_LOCAL(code.getLocal(0, FLOAT)))
- case JVM.fstore_1 => code.emit(STORE_LOCAL(code.getLocal(1, FLOAT)))
- case JVM.fstore_2 => code.emit(STORE_LOCAL(code.getLocal(2, FLOAT)))
- case JVM.fstore_3 => code.emit(STORE_LOCAL(code.getLocal(3, FLOAT)))
- case JVM.dstore_0 => code.emit(STORE_LOCAL(code.getLocal(0, DOUBLE)))
- case JVM.dstore_1 => code.emit(STORE_LOCAL(code.getLocal(1, DOUBLE)))
- case JVM.dstore_2 => code.emit(STORE_LOCAL(code.getLocal(2, DOUBLE)))
- case JVM.dstore_3 => code.emit(STORE_LOCAL(code.getLocal(3, DOUBLE)))
- case JVM.astore_0 =>
- if (method.isStatic)
- code.emit(STORE_LOCAL(code.getLocal(0, ObjectReference)))
- else
- code.emit(STORE_THIS(ObjectReference))
- case JVM.astore_1 => code.emit(STORE_LOCAL(code.getLocal(1, ObjectReference)))
- case JVM.astore_2 => code.emit(STORE_LOCAL(code.getLocal(2, ObjectReference)))
- case JVM.astore_3 => code.emit(STORE_LOCAL(code.getLocal(3, ObjectReference)))
- case JVM.iastore => code.emit(STORE_ARRAY_ITEM(INT))
- case JVM.lastore => code.emit(STORE_ARRAY_ITEM(LONG))
- case JVM.fastore => code.emit(STORE_ARRAY_ITEM(FLOAT))
- case JVM.dastore => code.emit(STORE_ARRAY_ITEM(DOUBLE))
- case JVM.aastore => code.emit(STORE_ARRAY_ITEM(ObjectReference))
- case JVM.bastore => code.emit(STORE_ARRAY_ITEM(BYTE))
- case JVM.castore => code.emit(STORE_ARRAY_ITEM(CHAR))
- case JVM.sastore => code.emit(STORE_ARRAY_ITEM(SHORT))
-
- case JVM.pop => code.emit(DROP(INT)) // any 1-word type would do
- case JVM.pop2 => code.emit(DROP(LONG)) // any 2-word type would do
- case JVM.dup => code.emit(DUP(ObjectReference)) // TODO: Is the kind inside DUP ever needed?
- case JVM.dup_x1 => code.emit(DUP_X1) // sys.error("Unsupported JVM bytecode: dup_x1")
- case JVM.dup_x2 => code.emit(DUP_X2) // sys.error("Unsupported JVM bytecode: dup_x2")
- case JVM.dup2 => code.emit(DUP(LONG)) // TODO: Is the kind inside DUP ever needed?
- case JVM.dup2_x1 => code.emit(DUP2_X1) // sys.error("Unsupported JVM bytecode: dup2_x1")
- case JVM.dup2_x2 => code.emit(DUP2_X2) // sys.error("Unsupported JVM bytecode: dup2_x2")
- case JVM.swap => sys.error("Unsupported JVM bytecode: swap")
-
- case JVM.iadd => code.emit(CALL_PRIMITIVE(Arithmetic(ADD, INT)))
- case JVM.ladd => code.emit(CALL_PRIMITIVE(Arithmetic(ADD, LONG)))
- case JVM.fadd => code.emit(CALL_PRIMITIVE(Arithmetic(ADD, FLOAT)))
- case JVM.dadd => code.emit(CALL_PRIMITIVE(Arithmetic(ADD, DOUBLE)))
- case JVM.isub => code.emit(CALL_PRIMITIVE(Arithmetic(SUB, INT)))
- case JVM.lsub => code.emit(CALL_PRIMITIVE(Arithmetic(SUB, LONG)))
- case JVM.fsub => code.emit(CALL_PRIMITIVE(Arithmetic(SUB, FLOAT)))
- case JVM.dsub => code.emit(CALL_PRIMITIVE(Arithmetic(SUB, DOUBLE)))
- case JVM.imul => code.emit(CALL_PRIMITIVE(Arithmetic(MUL, INT)))
- case JVM.lmul => code.emit(CALL_PRIMITIVE(Arithmetic(MUL, LONG)))
- case JVM.fmul => code.emit(CALL_PRIMITIVE(Arithmetic(MUL, FLOAT)))
- case JVM.dmul => code.emit(CALL_PRIMITIVE(Arithmetic(MUL, DOUBLE)))
- case JVM.idiv => code.emit(CALL_PRIMITIVE(Arithmetic(DIV, INT)))
- case JVM.ldiv => code.emit(CALL_PRIMITIVE(Arithmetic(DIV, LONG)))
- case JVM.fdiv => code.emit(CALL_PRIMITIVE(Arithmetic(DIV, FLOAT)))
- case JVM.ddiv => code.emit(CALL_PRIMITIVE(Arithmetic(DIV, DOUBLE)))
- case JVM.irem => code.emit(CALL_PRIMITIVE(Arithmetic(REM, INT)))
- case JVM.lrem => code.emit(CALL_PRIMITIVE(Arithmetic(REM, LONG)))
- case JVM.frem => code.emit(CALL_PRIMITIVE(Arithmetic(REM, FLOAT)))
- case JVM.drem => code.emit(CALL_PRIMITIVE(Arithmetic(REM, DOUBLE)))
-
- case JVM.ineg => code.emit(CALL_PRIMITIVE(Negation(INT)))
- case JVM.lneg => code.emit(CALL_PRIMITIVE(Negation(LONG)))
- case JVM.fneg => code.emit(CALL_PRIMITIVE(Negation(FLOAT)))
- case JVM.dneg => code.emit(CALL_PRIMITIVE(Negation(DOUBLE)))
-
- case JVM.ishl => code.emit(CALL_PRIMITIVE(Shift(LSL, INT)))
- case JVM.lshl => code.emit(CALL_PRIMITIVE(Shift(LSL, LONG)))
- case JVM.ishr => code.emit(CALL_PRIMITIVE(Shift(ASR, INT)))
- case JVM.lshr => code.emit(CALL_PRIMITIVE(Shift(ASR, LONG)))
- case JVM.iushr => code.emit(CALL_PRIMITIVE(Shift(LSR, INT)))
- case JVM.lushr => code.emit(CALL_PRIMITIVE(Shift(LSR, LONG)))
- case JVM.iand => code.emit(CALL_PRIMITIVE(Logical(AND, INT)))
- case JVM.land => code.emit(CALL_PRIMITIVE(Logical(AND, LONG)))
- case JVM.ior => code.emit(CALL_PRIMITIVE(Logical(OR, INT)))
- case JVM.lor => code.emit(CALL_PRIMITIVE(Logical(OR, LONG)))
- case JVM.ixor => code.emit(CALL_PRIMITIVE(Logical(XOR, INT)))
- case JVM.lxor => code.emit(CALL_PRIMITIVE(Logical(XOR, LONG)))
- case JVM.iinc =>
- size += 2
- val local = code.getLocal(u1, INT)
- code.emit(LOAD_LOCAL(local))
- code.emit(CONSTANT(Constant(s1)))
- code.emit(CALL_PRIMITIVE(Arithmetic(ADD, INT)))
- code.emit(STORE_LOCAL(local))
-
- case JVM.i2l => code.emit(CALL_PRIMITIVE(Conversion(INT, LONG)))
- case JVM.i2f => code.emit(CALL_PRIMITIVE(Conversion(INT, FLOAT)))
- case JVM.i2d => code.emit(CALL_PRIMITIVE(Conversion(INT, DOUBLE)))
- case JVM.l2i => code.emit(CALL_PRIMITIVE(Conversion(LONG, INT)))
- case JVM.l2f => code.emit(CALL_PRIMITIVE(Conversion(LONG, FLOAT)))
- case JVM.l2d => code.emit(CALL_PRIMITIVE(Conversion(LONG, DOUBLE)))
- case JVM.f2i => code.emit(CALL_PRIMITIVE(Conversion(FLOAT, INT)))
- case JVM.f2l => code.emit(CALL_PRIMITIVE(Conversion(FLOAT, LONG)))
- case JVM.f2d => code.emit(CALL_PRIMITIVE(Conversion(FLOAT, DOUBLE)))
- case JVM.d2i => code.emit(CALL_PRIMITIVE(Conversion(DOUBLE, INT)))
- case JVM.d2l => code.emit(CALL_PRIMITIVE(Conversion(DOUBLE, LONG)))
- case JVM.d2f => code.emit(CALL_PRIMITIVE(Conversion(DOUBLE, FLOAT)))
- case JVM.i2b => code.emit(CALL_PRIMITIVE(Conversion(INT, BYTE)))
- case JVM.i2c => code.emit(CALL_PRIMITIVE(Conversion(INT, CHAR)))
- case JVM.i2s => code.emit(CALL_PRIMITIVE(Conversion(INT, SHORT)))
-
- case JVM.lcmp => code.emit(CALL_PRIMITIVE(Comparison(CMP, LONG)))
- case JVM.fcmpl => code.emit(CALL_PRIMITIVE(Comparison(CMPL, FLOAT)))
- case JVM.fcmpg => code.emit(CALL_PRIMITIVE(Comparison(CMPG, FLOAT)))
- case JVM.dcmpl => code.emit(CALL_PRIMITIVE(Comparison(CMPL, DOUBLE)))
- case JVM.dcmpg => code.emit(CALL_PRIMITIVE(Comparison(CMPG, DOUBLE)))
-
- case JVM.ifeq => code.emit(LCZJUMP(parseJumpTarget, pc + size, EQ, INT))
- case JVM.ifne => code.emit(LCZJUMP(parseJumpTarget, pc + size, NE, INT))
- case JVM.iflt => code.emit(LCZJUMP(parseJumpTarget, pc + size, LT, INT))
- case JVM.ifge => code.emit(LCZJUMP(parseJumpTarget, pc + size, GE, INT))
- case JVM.ifgt => code.emit(LCZJUMP(parseJumpTarget, pc + size, GT, INT))
- case JVM.ifle => code.emit(LCZJUMP(parseJumpTarget, pc + size, LE, INT))
-
- case JVM.if_icmpeq => code.emit(LCJUMP(parseJumpTarget, pc + size, EQ, INT))
- case JVM.if_icmpne => code.emit(LCJUMP(parseJumpTarget, pc + size, NE, INT))
- case JVM.if_icmplt => code.emit(LCJUMP(parseJumpTarget, pc + size, LT, INT))
- case JVM.if_icmpge => code.emit(LCJUMP(parseJumpTarget, pc + size, GE, INT))
- case JVM.if_icmpgt => code.emit(LCJUMP(parseJumpTarget, pc + size, GT, INT))
- case JVM.if_icmple => code.emit(LCJUMP(parseJumpTarget, pc + size, LE, INT))
- case JVM.if_acmpeq => code.emit(LCJUMP(parseJumpTarget, pc + size, EQ, ObjectReference))
- case JVM.if_acmpne => code.emit(LCJUMP(parseJumpTarget, pc + size, NE, ObjectReference))
-
- case JVM.goto => emit(LJUMP(parseJumpTarget))
- case JVM.jsr => sys.error("Cannot handle jsr/ret")
- case JVM.ret => sys.error("Cannot handle jsr/ret")
- case JVM.tableswitch =>
- val padding = if ((pc + size) % 4 != 0) 4 - ((pc + size) % 4) else 0
- size += padding
- in.bp += padding
- assert((pc + size % 4) != 0, pc)
-/* var byte1 = u1; size += 1;
- while (byte1 == 0) { byte1 = u1; size += 1; }
- val default = byte1 << 24 | u1 << 16 | u1 << 8 | u1;
- size = size + 3
- */
- val default = pc + u4; size += 4
- val low = u4
- val high = u4
- size += 8
- assert(low <= high, "Value low not <= high for tableswitch.")
-
- val tags = List.tabulate(high - low + 1)(n => List(low + n))
- val targets = for (_ <- tags) yield parseJumpTargetW
- code.emit(LSWITCH(tags, targets ::: List(default)))
-
- case JVM.lookupswitch =>
- val padding = if ((pc + size) % 4 != 0) 4 - ((pc + size) % 4) else 0
- size += padding
- in.bp += padding
- assert((pc + size % 4) != 0, pc)
- val default = pc + u4; size += 4
- val npairs = u4; size += 4
- var tags: List[List[Int]] = Nil
- var targets: List[Int] = Nil
- var i = 0
- while (i < npairs) {
- tags = List(u4) :: tags; size += 4
- targets = parseJumpTargetW :: targets; // parseJumpTargetW updates 'size' itself
- i += 1
- }
- targets = default :: targets
- code.emit(LSWITCH(tags.reverse, targets.reverse))
-
- case JVM.ireturn => code.emit(RETURN(INT))
- case JVM.lreturn => code.emit(RETURN(LONG))
- case JVM.freturn => code.emit(RETURN(FLOAT))
- case JVM.dreturn => code.emit(RETURN(DOUBLE))
- case JVM.areturn => code.emit(RETURN(ObjectReference))
- case JVM.return_ => code.emit(RETURN(UNIT))
-
- case JVM.getstatic =>
- val field = pool.getMemberSymbol(u2, static = true); size += 2
- if (field.hasModuleFlag)
- code emit LOAD_MODULE(field)
- else
- code emit LOAD_FIELD(field, isStatic = true)
- case JVM.putstatic =>
- val field = pool.getMemberSymbol(u2, static = true); size += 2
- code.emit(STORE_FIELD(field, isStatic = true))
- case JVM.getfield =>
- val field = pool.getMemberSymbol(u2, static = false); size += 2
- code.emit(LOAD_FIELD(field, isStatic = false))
- case JVM.putfield =>
- val field = pool.getMemberSymbol(u2, static = false); size += 2
- code.emit(STORE_FIELD(field, isStatic = false))
-
- case JVM.invokevirtual =>
- val m = pool.getMemberSymbol(u2, static = false); size += 2
- code.emit(CALL_METHOD(m, Dynamic))
- method.updateRecursive(m)
- case JVM.invokeinterface =>
- val m = pool.getMemberSymbol(u2, static = false); size += 4
- in.skip(2)
- code.emit(CALL_METHOD(m, Dynamic))
- // invokeinterface can't be recursive
- case JVM.invokespecial =>
- val m = pool.getMemberSymbol(u2, static = false); size += 2
- val style = if (m.name == nme.CONSTRUCTOR || m.isPrivate) Static(onInstance = true)
- else SuperCall(m.owner.name)
- code.emit(CALL_METHOD(m, style))
- method.updateRecursive(m)
- case JVM.invokestatic =>
- val m = pool.getMemberSymbol(u2, static = true); size += 2
- if (isBox(m))
- code.emit(BOX(toTypeKind(m.info.paramTypes.head)))
- else if (isUnbox(m))
- code.emit(UNBOX(toTypeKind(m.info.resultType)))
- else {
- code.emit(CALL_METHOD(m, Static(onInstance = false)))
- method.updateRecursive(m)
- }
- case JVM.invokedynamic =>
- // TODO, this is just a place holder. A real implementation must parse the class constant entry
- debuglog("Found JVM invokedynamic instruction, inserting place holder ICode INVOKE_DYNAMIC.")
- containsInvokeDynamic = true
- val poolEntry = in.nextChar.toInt
- in.skip(2)
- code.emit(INVOKE_DYNAMIC(poolEntry))
-
- case JVM.new_ =>
- code.emit(NEW(REFERENCE(pool.getClassSymbol(u2))))
- size += 2
- case JVM.newarray =>
- val kind = u1 match {
- case T_BOOLEAN => BOOL
- case T_CHAR => CHAR
- case T_FLOAT => FLOAT
- case T_DOUBLE => DOUBLE
- case T_BYTE => BYTE
- case T_SHORT => SHORT
- case T_INT => INT
- case T_LONG => LONG
- }
- size += 1
- code.emit(CREATE_ARRAY(kind, 1))
-
- case JVM.anewarray =>
- val tpe = pool.getClassOrArrayType(u2); size += 2
- code.emit(CREATE_ARRAY(toTypeKind(tpe), 1))
-
- case JVM.arraylength => code.emit(CALL_PRIMITIVE(ArrayLength(ObjectReference))); // the kind does not matter
- case JVM.athrow => code.emit(THROW(definitions.ThrowableClass))
- case JVM.checkcast =>
- code.emit(CHECK_CAST(toTypeKind(pool.getClassOrArrayType(u2)))); size += 2
- case JVM.instanceof =>
- code.emit(IS_INSTANCE(toTypeKind(pool.getClassOrArrayType(u2)))); size += 2
- case JVM.monitorenter => code.emit(MONITOR_ENTER())
- case JVM.monitorexit => code.emit(MONITOR_EXIT())
- case JVM.wide =>
- size += 1
- u1 match {
- case JVM.iload => code.emit(LOAD_LOCAL(code.getLocal(u2, INT))); size += 2
- case JVM.lload => code.emit(LOAD_LOCAL(code.getLocal(u2, LONG))); size += 2
- case JVM.fload => code.emit(LOAD_LOCAL(code.getLocal(u2, FLOAT))); size += 2
- case JVM.dload => code.emit(LOAD_LOCAL(code.getLocal(u2, DOUBLE))); size += 2
- case JVM.aload => code.emit(LOAD_LOCAL(code.getLocal(u2, ObjectReference))); size += 2
- case JVM.istore => code.emit(STORE_LOCAL(code.getLocal(u2, INT))); size += 2
- case JVM.lstore => code.emit(STORE_LOCAL(code.getLocal(u2, LONG))); size += 2
- case JVM.fstore => code.emit(STORE_LOCAL(code.getLocal(u2, FLOAT))); size += 2
- case JVM.dstore => code.emit(STORE_LOCAL(code.getLocal(u2, DOUBLE))); size += 2
- case JVM.astore => code.emit(STORE_LOCAL(code.getLocal(u2, ObjectReference))); size += 2
- case JVM.ret => sys.error("Cannot handle jsr/ret")
- case JVM.iinc =>
- size += 4
- val local = code.getLocal(u2, INT)
- code.emit(CONSTANT(Constant(u2)))
- code.emit(CALL_PRIMITIVE(Arithmetic(ADD, INT)))
- code.emit(STORE_LOCAL(local))
- case _ => sys.error("Invalid 'wide' operand")
- }
-
- case JVM.multianewarray =>
- size += 3
- val tpe = toTypeKind(pool getClassOrArrayType u2)
- val dim = u1
-// assert(dim == 1, "Cannot handle multidimensional arrays yet.")
- code emit CREATE_ARRAY(tpe, dim)
-
- case JVM.ifnull => code emit LCZJUMP(parseJumpTarget, pc + size, EQ, ObjectReference)
- case JVM.ifnonnull => code emit LCZJUMP(parseJumpTarget, pc + size, NE, ObjectReference)
- case JVM.goto_w => code emit LJUMP(parseJumpTargetW)
- case JVM.jsr_w => sys.error("Cannot handle jsr/ret")
-
-// case _ => sys.error("Unknown bytecode")
- }
- pc += size
- }
-
- // add parameters
- var idx = if (method.isStatic) 0 else 1
- for (t <- method.symbol.tpe.paramTypes) {
- val kind = toTypeKind(t)
- this.method addParam code.enterParam(idx, kind)
- val width = if (kind.isWideType) 2 else 1
- idx += width
- }
-
- pc = 0
- while (pc < codeLength) parseInstruction()
-
- val exceptionEntries = u2.toInt
- code.containsEHs = (exceptionEntries != 0)
- var i = 0
- while (i < exceptionEntries) {
- // skip start end PC
- in.skip(4)
- // read the handler PC
- code.jmpTargets += u2
- // skip the exception type
- in.skip(2)
- i += 1
- }
- skipAttributes()
-
- code.toBasicBlock
- assert(method.hasCode, method)
- // reverse parameters, as they were prepended during code generation
- method.params = method.params.reverse
-
- if (code.containsDUPX)
- code.resolveDups()
-
- if (code.containsNEW)
- code.resolveNEWs()
- }
-
- /** Note: these methods are different from the methods of the same name found
- * in Definitions. These test whether a symbol represents one of the boxTo/unboxTo
- * methods found in BoxesRunTime. The others test whether a symbol represents a
- * synthetic method from one of the fake companion classes of the primitive types,
- * such as Int.box(5).
- */
- def isBox(m: Symbol): Boolean =
- (m.owner == definitions.BoxesRunTimeClass
- && m.name.startsWith("boxTo"))
-
- def isUnbox(m: Symbol): Boolean =
- (m.owner == definitions.BoxesRunTimeClass
- && m.name.startsWith("unboxTo"))
-
- /** Return the icode class that should include members with the given flags.
- * There are two possible classes, the static part and the instance part.
- */
- def getCode(flags: JavaAccFlags): IClass =
- if (isScalaModule || flags.isStatic) staticCode else instanceCode
-
- class LinearCode {
- val instrs: ListBuffer[(Int, Instruction)] = new ListBuffer
- val jmpTargets: mutable.Set[Int] = perRunCaches.newSet[Int]()
- val locals: mutable.Map[Int, List[(Local, TypeKind)]] = perRunCaches.newMap()
-
- var containsDUPX = false
- var containsNEW = false
- var containsEHs = false
- var containsInvokeDynamic = false
-
- def emit(i: Instruction) {
- instrs += ((pc, i))
- if (i.isInstanceOf[DupX])
- containsDUPX = true
- if (i.isInstanceOf[opcodes.NEW])
- containsNEW = true
- }
-
- /** Break this linear code in basic block representation
- * As a side effect, it sets the `code` field of the current
- */
- def toBasicBlock: Code = {
- import opcodes._
-
- val code = new Code(method)
- method.setCode(code)
- method.bytecodeHasEHs = containsEHs
- method.bytecodeHasInvokeDynamic = containsInvokeDynamic
- var bb = code.startBlock
-
- def makeBasicBlocks: mutable.Map[Int, BasicBlock] =
- mutable.Map(jmpTargets.toSeq map (_ -> code.newBlock): _*)
-
- val blocks = makeBasicBlocks
- var otherBlock: BasicBlock = NoBasicBlock
-
- for ((pc, instr) <- instrs.iterator) {
-// Console.println("> " + pc + ": " + instr);
- if (jmpTargets(pc)) {
- otherBlock = blocks(pc)
- if (!bb.closed && otherBlock != bb) {
- bb.emit(JUMP(otherBlock))
- bb.close()
-// Console.println("\t> closing bb: " + bb)
- }
- bb = otherBlock
-// Console.println("\t> entering bb: " + bb)
- }
-
- if (bb.closed) {
- // the basic block is closed, i.e. the previous instruction was a jump, return or throw,
- // but the next instruction is not a jump target. this means that the next instruction is
- // dead code. we can therefore advance until the next jump target.
- debuglog(s"ICode reader skipping dead instruction $instr in classfile $instanceCode")
- } else {
- instr match {
- case LJUMP(target) =>
- otherBlock = blocks(target)
- bb.emitOnly(JUMP(otherBlock))
-
- case LCJUMP(success, failure, cond, kind) =>
- otherBlock = blocks(success)
- val failBlock = blocks(failure)
- bb.emitOnly(CJUMP(otherBlock, failBlock, cond, kind))
-
- case LCZJUMP(success, failure, cond, kind) =>
- otherBlock = blocks(success)
- val failBlock = blocks(failure)
- bb.emitOnly(CZJUMP(otherBlock, failBlock, cond, kind))
-
- case LSWITCH(tags, targets) =>
- bb.emitOnly(SWITCH(tags, targets map blocks))
-
- case RETURN(_) =>
- bb emitOnly instr
-
- case THROW(clasz) =>
- bb emitOnly instr
-
- case _ =>
- bb emit instr
- }
- }
- }
-
- method.code
- }
-
- def resolveDups() {
- import opcodes._
-
- val tfa = new analysis.MethodTFA() {
- import analysis._
-
- /** Abstract interpretation for one instruction. */
- override def mutatingInterpret(out: typeFlowLattice.Elem, i: Instruction): typeFlowLattice.Elem = {
- val stack = out.stack
- import stack.push
- i match {
- case DUP_X1 =>
- val (one, two) = stack.pop2
- push(one); push(two); push(one)
-
- case DUP_X2 =>
- val (one, two, three) = stack.pop3
- push(one); push(three); push(two); push(one)
-
- case DUP2_X1 =>
- val (one, two) = stack.pop2
- if (one.isWideType) {
- push(one); push(two); push(one)
- } else {
- val three = stack.pop
- push(two); push(one); push(three); push(two); push(one)
- }
-
- case DUP2_X2 =>
- val (one, two) = stack.pop2
- if (one.isWideType && two.isWideType) {
- push(one); push(two); push(one)
- } else if (one.isWideType) {
- val three = stack.pop
- assert(!three.isWideType, "Impossible")
- push(one); push(three); push(two); push(one)
- } else {
- val three = stack.pop
- if (three.isWideType) {
- push(two); push(one); push(one); push(three); push(two); push(one)
- } else {
- val four = stack.pop
- push(two); push(one); push(four); push(one); push(three); push(two); push(one)
- }
- }
-
- case _ =>
- super.mutatingInterpret(out, i)
- }
- out
- }
- }
-
-// method.dump
- tfa.init(method)
- tfa.run()
- for (bb <- linearizer.linearize(method)) {
- var info = tfa.in(bb)
- for (i <- bb.toList) {
- i match {
- case DUP_X1 =>
- val one = info.stack.types(0)
- val two = info.stack.types(1)
- assert(!one.isWideType, "DUP_X1 expects values of size 1 on top of stack " + info.stack)
- val tmp1 = freshLocal(one)
- val tmp2 = freshLocal(two)
- bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
- STORE_LOCAL(tmp2),
- LOAD_LOCAL(tmp1),
- LOAD_LOCAL(tmp2),
- LOAD_LOCAL(tmp1)))
-
- case DUP_X2 =>
- val one = info.stack.types(0)
- val two = info.stack.types(1)
- assert (!one.isWideType, "DUP_X2 expects values of size 1 on top of stack " + info.stack)
- val tmp1 = freshLocal(one)
- val tmp2 = freshLocal(two)
- if (two.isWideType)
- bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
- STORE_LOCAL(tmp2),
- LOAD_LOCAL(tmp1),
- LOAD_LOCAL(tmp2),
- LOAD_LOCAL(tmp1)))
- else {
- val tmp3 = freshLocal(info.stack.types(2))
- bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
- STORE_LOCAL(tmp2),
- STORE_LOCAL(tmp3),
- LOAD_LOCAL(tmp1),
- LOAD_LOCAL(tmp3),
- LOAD_LOCAL(tmp2),
- LOAD_LOCAL(tmp1)))
- }
-
- case DUP2_X1 =>
- val one = info.stack.types(0)
- val two = info.stack.types(1)
- val tmp1 = freshLocal(one)
- val tmp2 = freshLocal(two)
- if (one.isWideType) {
- assert(!two.isWideType, "Impossible")
- bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
- STORE_LOCAL(tmp2),
- LOAD_LOCAL(tmp1),
- LOAD_LOCAL(tmp2),
- LOAD_LOCAL(tmp1)))
- } else {
- val tmp3 = freshLocal(info.stack.types(2))
- bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
- STORE_LOCAL(tmp2),
- STORE_LOCAL(tmp3),
- LOAD_LOCAL(tmp1),
- LOAD_LOCAL(tmp3),
- LOAD_LOCAL(tmp2),
- LOAD_LOCAL(tmp1)))
- }
-
- case DUP2_X2 =>
- val one = info.stack.types(0)
- val two = info.stack.types(1)
- val tmp1 = freshLocal(one)
- val tmp2 = freshLocal(two)
- if (one.isWideType && two.isWideType) {
- bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
- STORE_LOCAL(tmp2),
- LOAD_LOCAL(tmp1),
- LOAD_LOCAL(tmp2),
- LOAD_LOCAL(tmp1)))
- } else if (one.isWideType) {
- val three = info.stack.types(2)
- assert(!two.isWideType && !three.isWideType, "Impossible")
- val tmp3 = freshLocal(three)
- bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
- STORE_LOCAL(tmp2),
- STORE_LOCAL(tmp3),
- LOAD_LOCAL(tmp1),
- LOAD_LOCAL(tmp3),
- LOAD_LOCAL(tmp2),
- LOAD_LOCAL(tmp1)))
- } else {
- val three = info.stack.types(2)
- val tmp3 = freshLocal(three)
- if (three.isWideType) {
- bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
- STORE_LOCAL(tmp2),
- STORE_LOCAL(tmp3),
- LOAD_LOCAL(tmp2),
- LOAD_LOCAL(tmp1),
- LOAD_LOCAL(tmp3),
- LOAD_LOCAL(tmp2),
- LOAD_LOCAL(tmp1)))
- } else {
- val four = info.stack.types(3)
- val tmp4 = freshLocal(three)
- assert(!four.isWideType, "Impossible")
- bb.replaceInstruction(i, List(STORE_LOCAL(tmp1),
- STORE_LOCAL(tmp2),
- STORE_LOCAL(tmp3),
- STORE_LOCAL(tmp4),
- LOAD_LOCAL(tmp2),
- LOAD_LOCAL(tmp1),
- LOAD_LOCAL(tmp4),
- LOAD_LOCAL(tmp3),
- LOAD_LOCAL(tmp2),
- LOAD_LOCAL(tmp1)))
- }
- }
- case _ =>
- }
- info = tfa.interpret(info, i)
- }
- }
- }
-
- /** Recover def-use chains for NEW and initializers. */
- def resolveNEWs() {
- import opcodes._
- val rdef = new reachingDefinitions.ReachingDefinitionsAnalysis
- rdef.init(method)
- rdef.run()
-
- for (bb <- method.code.blocks ; (i, idx) <- bb.toList.zipWithIndex) i match {
- case cm @ CALL_METHOD(m, Static(true)) if m.isClassConstructor =>
- def loop(bb0: BasicBlock, idx0: Int, depth: Int): Unit = {
- rdef.findDefs(bb0, idx0, 1, depth) match {
- case ((bb1, idx1)) :: _ =>
- bb1(idx1) match {
- case _: DUP => loop(bb1, idx1, 0)
- case x: NEW => x.init = cm
- case _: THIS => () // super constructor call
- case producer => dumpMethodAndAbort(method, "producer: " + producer)
- }
- case _ => ()
- }
- }
- loop(bb, idx, m.info.paramTypes.length)
-
- case _ => ()
- }
- }
-
- /** Return the local at given index, with the given type. */
- def getLocal(idx: Char, kind: TypeKind): Local = getLocal(idx.toInt, kind)
- def getLocal(idx: Int, kind: TypeKind): Local = {
- assert(idx < maxLocals, "Index too large for local variable.")
-
- def checkValidIndex() {
- locals.get(idx - 1) match {
- case Some(others) if others exists (_._2.isWideType) =>
- global.globalError("Illegal index: " + idx + " points in the middle of another local")
- case _ => ()
- }
- kind match {
- case LONG | DOUBLE if (locals.isDefinedAt(idx + 1)) =>
- global.globalError("Illegal index: " + idx + " overlaps " + locals(idx + 1) + "\nlocals: " + locals)
- case _ => ()
- }
- }
-
- locals.get(idx) match {
- case Some(ls) =>
- val l = ls find { loc => loc._2 isAssignabledTo kind }
- l match {
- case Some((loc, _)) => loc
- case None =>
- val l = freshLocal(kind)
- locals(idx) = (l, kind) :: locals(idx)
- log("Expected kind " + kind + " for local " + idx +
- " but only " + ls + " found. Added new local.")
- l
- }
- case None =>
- checkValidIndex()
- val l = freshLocal(idx, kind, isArg = false)
- debuglog("Added new local for idx " + idx + ": " + kind)
- locals += (idx -> List((l, kind)))
- l
- }
- }
-
- override def toString(): String = instrs.toList.mkString("", "\n", "")
-
- /** Return a fresh Local variable for the given index.
- */
- private def freshLocal(idx: Int, kind: TypeKind, isArg: Boolean) = {
- val sym = method.symbol.newVariable(newTermName("loc" + idx)).setInfo(kind.toType)
- val l = new Local(sym, kind, isArg)
- method.addLocal(l)
- l
- }
-
- private var count = 0
-
- /** Invent a new local, with a new index value outside the range of
- * the original method. */
- def freshLocal(kind: TypeKind): Local = {
- count += 1
- freshLocal(maxLocals + count, kind, isArg = false)
- }
-
- /** add a method param with the given index. */
- def enterParam(idx: Int, kind: TypeKind) = {
- val sym = method.symbol.newVariable(newTermName("par" + idx)).setInfo(kind.toType)
- val l = new Local(sym, kind, true)
- assert(!locals.isDefinedAt(idx), locals(idx))
- locals += (idx -> List((l, kind)))
- l
- }
-
- /** Base class for branch instructions that take addresses. */
- abstract class LazyJump(pc: Int) extends Instruction {
- override def toString() = "LazyJump " + pc
- jmpTargets += pc
- }
-
- case class LJUMP(pc: Int) extends LazyJump(pc)
-
- case class LCJUMP(success: Int, failure: Int, cond: TestOp, kind: TypeKind)
- extends LazyJump(success) {
- override def toString(): String = "LCJUMP (" + kind + ") " + success + " : " + failure
-
- jmpTargets += failure
- }
-
- case class LCZJUMP(success: Int, failure: Int, cond: TestOp, kind: TypeKind)
- extends LazyJump(success) {
- override def toString(): String = "LCZJUMP (" + kind + ") " + success + " : " + failure
-
- jmpTargets += failure
- }
-
- case class LSWITCH(tags: List[List[Int]], targets: List[Int]) extends LazyJump(targets.head) {
- override def toString(): String = "LSWITCH (tags: " + tags + ") targets: " + targets
-
- jmpTargets ++= targets.tail
- }
-
- /** Duplicate and exchange pseudo-instruction. Should be later
- * replaced by proper ICode */
- abstract class DupX extends Instruction
-
- case object DUP_X1 extends DupX
- case object DUP_X2 extends DupX
- case object DUP2_X1 extends DupX
- case object DUP2_X2 extends DupX
- }
-}
diff --git a/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala b/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala
new file mode 100644
index 0000000000..e027b065ac
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala
@@ -0,0 +1,403 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2013 LAMP/EPFL and Lightbend, Inc
+ */
+
+package scala.tools.nsc
+package transform
+
+import symtab._
+import Flags._
+import scala.collection.mutable
+
+trait AccessorSynthesis extends Transform with ast.TreeDSL {
+ import global._
+ import definitions._
+ import CODE._
+
+ val EmptyThicket = EmptyTree
+ def Thicket(trees: List[Tree]) = if (trees.isEmpty) EmptyTree else Block(trees, EmptyTree)
+ def mustExplodeThicket(tree: Tree): Boolean =
+ tree match {
+ case EmptyTree => true
+ case Block(_, EmptyTree) => true
+ case _ => false
+ }
+ def explodeThicket(tree: Tree): List[Tree] = tree match {
+ case EmptyTree => Nil
+ case Block(thicket, EmptyTree) => thicket
+ case stat => stat :: Nil
+ }
+
+
+ trait AccessorTreeSynthesis {
+ protected def typedPos(pos: Position)(tree: Tree): Tree
+
+ // used while we still need to synthesize some accessors in mixins: paramaccessors and presupers
+ class UncheckedAccessorSynth(protected val clazz: Symbol){
+ protected val _newDefs = mutable.ListBuffer[Tree]()
+
+ def newDefs = _newDefs.toList
+
+ /** Add tree at given position as new definition */
+ protected def addDef(tree: ValOrDefDef): Unit = _newDefs += typedPos(position(tree.symbol))(tree)
+
+ /** The position of given symbol, or, if this is undefined,
+ * the position of the current class.
+ */
+ private def position(sym: Symbol) = if (sym.pos == NoPosition) clazz.pos else sym.pos
+
+ /** Add new method definition.
+ *
+ * @param sym The method symbol.
+ * @param rhs The method body.
+ */
+ def addDefDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(DefDef(sym, rhs))
+ def addValDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(ValDef(sym, rhs))
+
+ /** Complete `stats` with init checks and bitmaps,
+ * removing any abstract method definitions in `stats` that are
+ * matched by some symbol defined by a tree previously passed to `addDef`.
+ */
+ def implementWithNewDefs(stats: List[Tree]): List[Tree] = {
+ val newDefs = _newDefs.toList
+ val newSyms = newDefs map (_.symbol)
+ def isNotDuplicate(tree: Tree) = tree match {
+ case DefDef(_, _, _, _, _, _) =>
+ val sym = tree.symbol
+ !(sym.isDeferred &&
+ (newSyms exists (nsym => nsym.name == sym.name && (nsym.tpe matches sym.tpe))))
+ case _ => true
+ }
+ if (newDefs.isEmpty) stats
+ else newDefs ::: (stats filter isNotDuplicate)
+ }
+
+ def accessorBody(sym: Symbol) =
+ if (sym.isSetter) setterBody(sym, sym.getterIn(clazz)) else getterBody(sym)
+
+ protected def getterBody(getter: Symbol): Tree = {
+ assert(getter.isGetter)
+ assert(getter.hasFlag(PARAMACCESSOR))
+
+ fieldAccess(getter)
+ }
+
+ protected def setterBody(setter: Symbol, getter: Symbol): Tree = {
+ assert(getter.hasFlag(PARAMACCESSOR), s"missing implementation for non-paramaccessor $setter in $clazz")
+
+ Assign(fieldAccess(setter), Ident(setter.firstParam))
+ }
+
+ private def fieldAccess(accessor: Symbol) =
+ Select(This(clazz), accessor.accessed)
+
+ }
+ }
+
+ case class BitmapInfo(symbol: Symbol, mask: Literal) {
+ def storageClass: ClassSymbol = symbol.info.typeSymbol.asClass
+ }
+
+
+ // TODO: better way to communicate from info transform to tree transform?
+ private[this] val _bitmapInfo = perRunCaches.newMap[Symbol, BitmapInfo]
+ private[this] val _slowPathFor = perRunCaches.newMap[Symbol, Symbol]()
+
+ def checkedAccessorSymbolSynth(clz: Symbol) =
+ if (settings.checkInit) new CheckInitAccessorSymbolSynth { val clazz = clz }
+ else new CheckedAccessorSymbolSynth { val clazz = clz }
+
+ // base trait, with enough functionality for lazy vals -- CheckInitAccessorSymbolSynth adds logic for -Xcheckinit
+ trait CheckedAccessorSymbolSynth {
+ protected val clazz: Symbol
+
+ protected def defaultPos = clazz.pos.focus
+ protected def isTrait = clazz.isTrait
+ protected def hasTransientAnnot(field: Symbol) = field.accessedOrSelf hasAnnotation TransientAttr
+
+ def needsBitmap(sym: Symbol): Boolean = !(isTrait || sym.isDeferred) && sym.isMethod && sym.isLazy && !sym.isSpecialized
+
+
+ /** Examines the symbol and returns a name indicating what brand of
+ * bitmap it requires. The possibilities are the BITMAP_* vals
+ * defined in StdNames. If it needs no bitmap, nme.NO_NAME.
+ *
+ * bitmaps for checkinit fields are not inherited
+ */
+ protected def bitmapCategory(sym: Symbol): Name = {
+ // ensure that nested objects are transformed TODO: still needed?
+ sym.initialize
+
+ import nme._
+
+ if (needsBitmap(sym) && sym.isLazy)
+ if (hasTransientAnnot(sym)) BITMAP_TRANSIENT else BITMAP_NORMAL
+ else NO_NAME
+ }
+
+
+ def bitmapFor(sym: Symbol): BitmapInfo = _bitmapInfo(sym)
+ protected def hasBitmap(sym: Symbol): Boolean = _bitmapInfo isDefinedAt sym
+
+
+ /** Fill the map from fields to bitmap infos.
+ *
+ * Instead of field symbols, the map keeps their getter symbols. This makes code generation easier later.
+ */
+ def computeBitmapInfos(decls: List[Symbol]): List[Symbol] = {
+ def doCategory(fields: List[Symbol], category: Name) = {
+ val nbFields = fields.length // we know it's > 0
+ val (bitmapClass, bitmapCapacity) =
+ if (nbFields == 1) (BooleanClass, 1)
+ else if (nbFields <= 8) (ByteClass, 8)
+ else if (nbFields <= 32) (IntClass, 32)
+ else (LongClass, 64)
+
+ // 0-based index of highest bit, divided by bits per bitmap
+ // note that this is only ever > 0 when bitmapClass == LongClass
+ val maxBitmapNumber = (nbFields - 1) / bitmapCapacity
+
+ // transient fields get their own category
+ val isTransientCategory = fields.head hasAnnotation TransientAttr
+
+ val bitmapSyms =
+ (0 to maxBitmapNumber).toArray map { bitmapNumber =>
+ val bitmapSym = (
+ clazz.newVariable(nme.newBitmapName(category, bitmapNumber).toTermName, defaultPos)
+ setInfo bitmapClass.tpe
+ setFlag PrivateLocal | NEEDS_TREES
+ )
+
+ bitmapSym addAnnotation VolatileAttr
+
+ if (isTransientCategory) bitmapSym addAnnotation TransientAttr
+
+ bitmapSym
+ }
+
+ fields.zipWithIndex foreach { case (f, idx) =>
+ val bitmapIdx = idx / bitmapCapacity
+ val offsetInBitmap = idx % bitmapCapacity
+ val mask =
+ if (bitmapClass == LongClass) Constant(1L << offsetInBitmap)
+ else Constant(1 << offsetInBitmap)
+
+ _bitmapInfo(f) = BitmapInfo(bitmapSyms(bitmapIdx), Literal(mask))
+ }
+
+ bitmapSyms
+ }
+
+ decls groupBy bitmapCategory flatMap {
+ case (category, fields) if category != nme.NO_NAME && fields.nonEmpty => doCategory(fields, category)
+ case _ => Nil
+ } toList
+ }
+
+ def slowPathFor(lzyVal: Symbol): Symbol = _slowPathFor(lzyVal)
+
+ def newSlowPathSymbol(lzyVal: Symbol): Symbol = {
+ val pos = if (lzyVal.pos != NoPosition) lzyVal.pos else defaultPos // TODO: is the else branch ever taken?
+ val sym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), pos, PRIVATE) setInfo MethodType(Nil, lzyVal.tpe.resultType)
+ _slowPathFor(lzyVal) = sym
+ sym
+ }
+
+ }
+
+ trait CheckInitAccessorSymbolSynth extends CheckedAccessorSymbolSynth {
+ /** Does this field require an initialized bit?
+ * Note: fields of classes inheriting DelayedInit are not checked.
+ * This is because they are neither initialized in the constructor
+ * nor do they have a setter (not if they are vals anyway). The usual
+ * logic for setting bitmaps does therefore not work for such fields.
+ * That's why they are excluded.
+ * Note: The `checkinit` option does not check if transient fields are initialized.
+ */
+ protected def needsInitFlag(sym: Symbol): Boolean =
+ sym.isGetter &&
+ !( sym.isInitializedToDefault
+ || isConstantType(sym.info.finalResultType) // SI-4742
+ || sym.hasFlag(PARAMACCESSOR | SPECIALIZED | LAZY)
+ || sym.accessed.hasFlag(PRESUPER)
+ || sym.isOuterAccessor
+ || (sym.owner isSubClass DelayedInitClass)
+ || (sym.accessed hasAnnotation TransientAttr))
+
+ /** Examines the symbol and returns a name indicating what brand of
+ * bitmap it requires. The possibilities are the BITMAP_* vals
+ * defined in StdNames. If it needs no bitmap, nme.NO_NAME.
+ *
+ * bitmaps for checkinit fields are not inherited
+ */
+ override protected def bitmapCategory(sym: Symbol): Name = {
+ import nme._
+
+ super.bitmapCategory(sym) match {
+ case NO_NAME if needsInitFlag(sym) && !sym.isDeferred =>
+ if (hasTransientAnnot(sym)) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT
+ case category => category
+ }
+ }
+
+ override def needsBitmap(sym: Symbol): Boolean = super.needsBitmap(sym) || !(isTrait || sym.isDeferred) && needsInitFlag(sym)
+ }
+
+
+ // synthesize trees based on info gathered during info transform
+ // (which are known to have been run because the tree transform runs afterOwnPhase)
+ // since we can't easily share all info via symbols and flags, we have two maps above
+ // (they are persisted even between phases because the -Xcheckinit logic runs during constructors)
+ // TODO: can we use attachments instead of _bitmapInfo and _slowPathFor?
+ trait CheckedAccessorTreeSynthesis extends AccessorTreeSynthesis {
+
+ // note: we deal in getters here, not field symbols
+ trait SynthCheckedAccessorsTreesInClass extends CheckedAccessorSymbolSynth {
+ def isUnitGetter(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass
+ def thisRef = gen.mkAttributedThis(clazz)
+
+ /** Return an (untyped) tree of the form 'clazz.this.bitmapSym & mask (==|!=) 0', the
+ * precise comparison operator depending on the value of 'equalToZero'.
+ */
+ def mkTest(field: Symbol, equalToZero: Boolean = true): Tree = {
+ val bitmap = bitmapFor(field)
+ val bitmapTree = thisRef DOT bitmap.symbol
+
+ if (bitmap.storageClass == BooleanClass) {
+ if (equalToZero) NOT(bitmapTree) else bitmapTree
+ } else {
+ val lhs = bitmapTree GEN_&(bitmap.mask, bitmap.storageClass)
+ if (equalToZero) lhs GEN_==(ZERO, bitmap.storageClass)
+ else lhs GEN_!=(ZERO, bitmap.storageClass)
+ }
+ }
+
+ /** Return an (untyped) tree of the form 'Clazz.this.bmp = Clazz.this.bmp | mask'. */
+ def mkSetFlag(valSym: Symbol): Tree = {
+ val bitmap = bitmapFor(valSym)
+ def x = thisRef DOT bitmap.symbol
+
+ Assign(x,
+ if (bitmap.storageClass == BooleanClass) TRUE
+ else {
+ val or = Apply(Select(x, getMember(bitmap.storageClass, nme.OR)), List(bitmap.mask))
+ // NOTE: bitwise or (`|`) on two bytes yields and Int (TODO: why was this not a problem when this ran during mixins?)
+ // TODO: need this to make it type check -- is there another way??
+ if (bitmap.storageClass != LongClass) Apply(Select(or, newTermName("to" + bitmap.storageClass.name)), Nil)
+ else or
+ }
+ )
+ }
+ }
+
+ class SynthLazyAccessorsIn(protected val clazz: Symbol) extends SynthCheckedAccessorsTreesInClass {
+ /**
+ * The compute method (slow path) looks like:
+ *
+ * ```
+ * def l$compute() = {
+ * synchronized(this) {
+ * if ((bitmap$n & MASK) == 0) {
+ * init // l$ = <rhs>
+ * bitmap$n = bimap$n | MASK
+ * }
+ * }
+ * ...
+ * this.f1 = null
+ * ...
+ * this.fn = null
+ * l$
+ * }
+ * ```
+ *
+ * `bitmap$n` is a byte, int or long value acting as a bitmap of initialized values.
+ * The kind of the bitmap determines how many bit indicators for lazy vals are stored in it.
+ * For Int bitmap it is 32 and then 'n' in the above code is: (offset / 32),
+ * the MASK is (1 << (offset % 32)).
+ *
+ * If the class contains only a single lazy val then the bitmap is
+ * represented as a Boolean and the condition checking is a simple bool test.
+ *
+ * Private fields used only in this initializer are subsequently set to null.
+ *
+ * For performance reasons the double-checked locking is split into two parts,
+ * the first (fast) path checks the bitmap without synchronizing, and if that
+ * fails it initializes the lazy val within the synchronization block (slow path).
+ *
+ * This way the inliner should optimize the fast path because the method body is small enough.
+ */
+ def expandLazyClassMember(lazyVar: global.Symbol, lazyAccessor: global.Symbol, transformedRhs: global.Tree): Tree = {
+ val slowPathSym = slowPathFor(lazyAccessor)
+ val rhsAtSlowDef = transformedRhs.changeOwner(lazyAccessor -> slowPathSym)
+
+ val isUnit = isUnitGetter(lazyAccessor)
+ val selectVar = if (isUnit) UNIT else Select(thisRef, lazyVar)
+ val storeRes = if (isUnit) rhsAtSlowDef else Assign(selectVar, fields.castHack(rhsAtSlowDef, lazyVar.info))
+
+ def needsInit = mkTest(lazyAccessor)
+ val doInit = Block(List(storeRes), mkSetFlag(lazyAccessor))
+ // the slow part of double-checked locking (TODO: is this the most efficient pattern? https://github.come/scala/scala-dev/issues/204)
+ val slowPathRhs = Block(gen.mkSynchronized(thisRef)(If(needsInit, doInit, EmptyTree)) :: Nil, selectVar)
+
+ // The lazy accessor delegates to the compute method if needed, otherwise just accesses the var (it was initialized previously)
+ // `if ((bitmap&n & MASK) == 0) this.l$compute() else l$`
+ val accessorRhs = If(needsInit, Apply(Select(thisRef, slowPathSym), Nil), selectVar)
+
+ afterOwnPhase { // so that we can assign to vals
+ Thicket(List((DefDef(slowPathSym, slowPathRhs)), DefDef(lazyAccessor, accessorRhs)) map typedPos(lazyAccessor.pos.focus))
+ }
+ }
+ }
+
+ class SynthInitCheckedAccessorsIn(protected val clazz: Symbol) extends SynthCheckedAccessorsTreesInClass with CheckInitAccessorSymbolSynth {
+ private object addInitBitsTransformer extends Transformer {
+ private def checkedGetter(lhs: Tree)(pos: Position) = {
+ val getter = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter)
+ if (hasBitmap(getter) && needsInitFlag(getter)) {
+ debuglog("adding checked getter for: " + getter + " " + lhs.symbol.flagString)
+ List(typedPos(pos)(mkSetFlag(getter)))
+ }
+ else Nil
+ }
+ override def transformStats(stats: List[Tree], exprOwner: Symbol) = {
+ // !!! Ident(self) is never referenced, is it supposed to be confirming
+ // that self is anything in particular?
+ super.transformStats(
+ stats flatMap {
+ case stat@Assign(lhs@Select(This(_), _), rhs) => stat :: checkedGetter(lhs)(stat.pos.focus)
+ // remove initialization for default values -- TODO is this case ever hit? constructors does not generate Assigns with EmptyTree for the rhs AFAICT
+ case Apply(lhs@Select(Ident(self), _), EmptyTree.asList) if lhs.symbol.isSetter => Nil
+ case stat => List(stat)
+ },
+ exprOwner
+ )
+ }
+ }
+
+ /** Make getters check the initialized bit, and the class constructor & setters are changed to set the initialized bits. */
+ def wrapRhsWithInitChecks(sym: Symbol)(rhs: Tree): Tree = {
+ // Add statements to the body of a constructor to set the 'init' bit for each field initialized in the constructor
+ if (sym.isConstructor) addInitBitsTransformer transform rhs
+ else if (isTrait || rhs == EmptyTree) rhs
+ else if (needsInitFlag(sym)) // getter
+ mkCheckedAccessorRhs(if (isUnitGetter(sym)) UNIT else rhs, rhs.pos, sym)
+ else if (sym.isSetter) {
+ val getter = sym.getterIn(clazz)
+ if (needsInitFlag(getter)) Block(List(rhs, typedPos(rhs.pos.focus)(mkSetFlag(getter))), UNIT)
+ else rhs
+ }
+ else rhs
+ }
+
+ private def mkCheckedAccessorRhs(retVal: Tree, pos: Position, getter: Symbol): Tree = {
+ val msg = s"Uninitialized field: ${clazz.sourceFile}: ${pos.line}"
+ val result =
+ IF(mkTest(getter, equalToZero = false)).
+ THEN(retVal).
+ ELSE(Throw(NewFromConstructor(UninitializedFieldConstructor, LIT(msg))))
+
+ typedPos(pos)(BLOCK(result, retVal))
+ }
+ }
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala
deleted file mode 100644
index 82e7c76409..0000000000
--- a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala
+++ /dev/null
@@ -1,376 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package transform
-
-import symtab._
-import Flags._
-import scala.tools.nsc.util.ClassPath
-
-abstract class AddInterfaces extends InfoTransform { self: Erasure =>
- import global._ // the global environment
- import definitions._ // standard classes and methods
-
- /** The phase sets lateINTERFACE for non-interface traits that now
- * become interfaces. It sets lateDEFERRED for formerly concrete
- * methods in such traits.
- */
- override def phaseNewFlags: Long = lateDEFERRED | lateINTERFACE
-
- /** A lazily constructed map that associates every non-interface trait with
- * its implementation class.
- */
- private val implClassMap = perRunCaches.newMap[Symbol, Symbol]()
-
- /** A lazily constructed map that associates every concrete method in a non-interface
- * trait that's currently compiled with its corresponding method in the trait's
- * implementation class.
- */
- private val implMethodMap = perRunCaches.newMap[Symbol, Symbol]()
-
- override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = {
- implClassMap.clear()
- implMethodMap.clear()
- super.newPhase(prev)
- }
-
- /** Is given trait member symbol a member of the trait's interface
- * after this transform is performed?
- */
- private def isInterfaceMember(sym: Symbol) = (
- sym.isType || {
- sym.info // initialize to set lateMETHOD flag if necessary
-
- ( sym.isMethod
- && !sym.isLabel
- && !sym.isPrivate
- && (!(sym hasFlag BRIDGE) || sym.hasBridgeAnnotation) // count @bridge annotated classes as interface members
- && !sym.isConstructor
- && !sym.isImplOnly
- )
- }
- )
-
- /** Does symbol need an implementation method? */
- def needsImplMethod(sym: Symbol) = (
- sym.isMethod
- && isInterfaceMember(sym)
- && (!sym.hasFlag(DEFERRED | SUPERACCESSOR) || (sym hasFlag lateDEFERRED))
- )
-
- def implClassPhase = currentRun.erasurePhase.next
-
- private def newImplClass(iface: Symbol): Symbol = {
- val inClass = iface.owner.isClass
- val implName = tpnme.implClassName(iface.name)
- val implFlags = (iface.flags & ~(INTERFACE | lateINTERFACE)) | IMPLCLASS
-
- val impl0 = {
- if (!inClass) NoSymbol
- else {
- val typeInfo = iface.owner.info
- typeInfo.decl(implName) match {
- case NoSymbol => NoSymbol
- case implSym =>
- // Unlink a pre-existing symbol only if the implementation class is
- // visible on the compilation classpath. In general this is true under
- // -optimise and not otherwise, but the classpath can use arbitrary
- // logic so the classpath must be queried.
- // TODO this is not taken into account by flat classpath yet
- classPath match {
- case cp: ClassPath[_] if !cp.context.isValidName(implName + ".class") =>
- log(s"not unlinking $iface's existing implClass ${implSym.name} because it is not on the classpath.")
- implSym
- case _ =>
- typeInfo.decls unlink implSym
- NoSymbol
- }
- }
- }
- }
-
- val impl = impl0 orElse {
- val impl = iface.owner.newImplClass(implName, iface.pos, implFlags)
- if (iface.thisSym != iface) {
- impl.typeOfThis = iface.typeOfThis
- impl.thisSym setName iface.thisSym.name
- }
- impl.associatedFile = iface.sourceFile
- if (inClass)
- iface.owner.info.decls enter impl
-
- impl
- }
- if (currentRun compiles iface)
- currentRun.symSource(impl) = iface.sourceFile
-
- implClassMap(iface) = impl
- impl setInfo new LazyImplClassType(iface)
- }
-
- /** Return the implementation class of a trait; create a new one if one does not yet exist */
- def implClass(iface: Symbol): Symbol = {
- iface.info
-
- implClassMap.getOrElse(iface, enteringPhase(implClassPhase) {
- if (iface.implClass eq NoSymbol)
- debuglog(s"${iface.fullLocationString} has no implClass yet, creating it now.")
- else
- log(s"${iface.fullLocationString} impl class is ${iface.implClass.nameString}")
-
- newImplClass(iface)
- })
- }
-
- /** A lazy type to set the info of an implementation class
- * The parents of an implementation class for trait iface are:
- *
- * - superclass: Object
- * - mixin classes: mixin classes of iface where every non-interface
- * trait is mapped to its implementation class, followed by iface itself.
- *
- * The declarations of a mixin class are:
- * - for every interface member of iface: its implementation method, if one is needed
- * - every former member of iface that is implementation only
- */
- private class LazyImplClassType(iface: Symbol) extends LazyType with FlagAgnosticCompleter {
- /** Compute the decls of implementation class implClass,
- * given the decls ifaceDecls of its interface.
- */
- private def implDecls(implClass: Symbol, ifaceDecls: Scope): Scope = {
- debuglog("LazyImplClassType calculating decls for " + implClass)
-
- val decls = newScope
- if ((ifaceDecls lookup nme.MIXIN_CONSTRUCTOR) == NoSymbol) {
- log("Adding mixin constructor to " + implClass)
-
- decls enter (
- implClass.newMethod(nme.MIXIN_CONSTRUCTOR, implClass.pos)
- setInfo MethodType(Nil, UnitTpe)
- )
- }
-
- for (sym <- ifaceDecls) {
- if (isInterfaceMember(sym)) {
- if (needsImplMethod(sym)) {
- val clone = sym.cloneSymbol(implClass).resetFlag(lateDEFERRED)
- if (currentRun.compiles(implClass)) implMethodMap(sym) = clone
- decls enter clone
- sym setFlag lateDEFERRED
- if (!sym.isSpecialized)
- log(s"Cloned ${sym.name} from ${sym.owner} into implClass ${implClass.fullName}")
- }
- }
- else {
- log(s"Destructively modifying owner of $sym from ${sym.owner} to $implClass")
- sym.owner = implClass
- // note: OK to destructively modify the owner here,
- // because symbol will not be accessible from outside the sourcefile.
- // mixin constructors are corrected separately; see TermSymbol.owner
- decls enter sym
- }
- }
-
- decls
- }
-
- override def complete(implSym: Symbol) {
- debuglog("LazyImplClassType completing " + implSym)
-
- /* If `tp` refers to a non-interface trait, return a
- * reference to its implementation class. Otherwise return `tp`.
- */
- def mixinToImplClass(tp: Type): Type = AddInterfaces.this.erasure(implSym) {
- tp match { //@MATN: no normalize needed (comes after erasure)
- case TypeRef(pre, sym, _) if sym.needsImplClass =>
- typeRef(pre, implClass(sym), Nil)
- case _ =>
- tp
- }
- }
- def implType(tp: Type): Type = tp match {
- case ClassInfoType(parents, decls, _) =>
- assert(phase == implClassPhase, tp)
- // Impl class parents: Object first, matching interface last.
- val implParents = ObjectTpe +: (parents.tail map mixinToImplClass filter (_.typeSymbol != ObjectClass)) :+ iface.tpe
- ClassInfoType(implParents, implDecls(implSym, decls), implSym)
- case PolyType(_, restpe) =>
- implType(restpe)
- }
- implSym setInfo implType(enteringErasure(iface.info))
- }
-
- override def load(clazz: Symbol) { complete(clazz) }
- }
-
- def transformMixinInfo(tp: Type): Type = tp match {
- case ClassInfoType(parents, decls, clazz) if clazz.isPackageClass || !clazz.isJavaDefined =>
- if (clazz.needsImplClass)
- implClass(clazz setFlag lateINTERFACE) // generate an impl class
-
- val parents1 = parents match {
- case Nil => Nil
- case hd :: tl =>
- assert(!hd.typeSymbol.isTrait, clazz)
- if (clazz.isTrait) ObjectTpe :: tl
- else parents
- }
- val decls1 = scopeTransform(clazz)(
- decls filter (sym =>
- if (clazz.isInterface) isInterfaceMember(sym)
- else sym.isClass || sym.isTerm
- )
- )
- ClassInfoType(parents1, decls1, clazz)
- case _ =>
- tp
- }
-
-// Tree transformation --------------------------------------------------------------
-
- private class ChangeOwnerAndReturnTraverser(oldowner: Symbol, newowner: Symbol)
- extends ChangeOwnerTraverser(oldowner, newowner) {
- override def traverse(tree: Tree) {
- tree match {
- case _: Return => change(tree.symbol)
- case _ =>
- }
- super.traverse(tree)
- }
- }
-
- private def createMemberDef(tree: Tree, isForInterface: Boolean)(create: Tree => Tree) = {
- val isInterfaceTree = tree.isDef && isInterfaceMember(tree.symbol)
- if (isInterfaceTree && needsImplMethod(tree.symbol))
- create(tree)
- else if (isInterfaceTree == isForInterface)
- tree
- else
- EmptyTree
- }
- private def implMemberDef(tree: Tree): Tree = createMemberDef(tree, false)(implMethodDef)
- private def ifaceMemberDef(tree: Tree): Tree = createMemberDef(tree, true)(t => DefDef(t.symbol, EmptyTree))
-
- private def ifaceTemplate(templ: Template): Template =
- treeCopy.Template(templ, templ.parents, noSelfType, templ.body map ifaceMemberDef)
-
- /** Transforms the member tree containing the implementation
- * into a member of the impl class.
- */
- private def implMethodDef(tree: Tree): Tree = {
- val impl = implMethodMap.getOrElse(tree.symbol, abort("implMethod missing for " + tree.symbol))
-
- val newTree = if (impl.isErroneous) tree else { // e.g. res/t687
- // SI-5167: Ensure that the tree that we are grafting refers the parameter symbols from the
- // new method symbol `impl`, rather than the symbols of the original method signature in
- // the trait. `tree setSymbol impl` does *not* suffice!
- val DefDef(_, _, _, vparamss, _, _) = tree
- val oldSyms = vparamss.flatten.map(_.symbol)
- val newSyms = impl.info.paramss.flatten
- assert(oldSyms.length == newSyms.length, (oldSyms, impl, impl.info))
- tree.substituteSymbols(oldSyms, newSyms)
- }
- new ChangeOwnerAndReturnTraverser(newTree.symbol, impl)(newTree setSymbol impl)
- }
-
- /** Add mixin constructor definition
- * def $init$(): Unit = ()
- * to `stats` unless there is already one.
- */
- private def addMixinConstructorDef(clazz: Symbol, stats: List[Tree]): List[Tree] =
- if (treeInfo.firstConstructor(stats) != EmptyTree) stats
- else DefDef(clazz.primaryConstructor, Block(List(), Literal(Constant(())))) :: stats
-
- private def implTemplate(clazz: Symbol, templ: Template): Template = atPos(templ.pos) {
- val templ1 = (
- Template(templ.parents, noSelfType, addMixinConstructorDef(clazz, templ.body map implMemberDef))
- setSymbol clazz.newLocalDummy(templ.pos)
- )
- templ1.changeOwner(templ.symbol.owner -> clazz, templ.symbol -> templ1.symbol)
- templ1
- }
-
- def implClassDefs(trees: List[Tree]): List[Tree] = {
- trees collect {
- case cd: ClassDef if cd.symbol.needsImplClass =>
- val clazz = implClass(cd.symbol).initialize
- ClassDef(clazz, implTemplate(clazz, cd.impl))
- }
- }
-
- /** Add calls to supermixin constructors
- * `super[mix].$init$()`
- * to tree, which is assumed to be the body of a constructor of class clazz.
- */
- private def addMixinConstructorCalls(tree: Tree, clazz: Symbol): Tree = {
- def mixinConstructorCall(impl: Symbol): Tree = atPos(tree.pos) {
- Apply(Select(This(clazz), impl.primaryConstructor), List())
- }
- val mixinConstructorCalls: List[Tree] = {
- for (mc <- clazz.mixinClasses.reverse
- if mc.hasFlag(lateINTERFACE))
- yield mixinConstructorCall(implClass(mc))
- }
- tree match {
- case Block(Nil, expr) =>
- // AnyVal constructor - have to provide a real body so the
- // jvm doesn't throw a VerifyError. But we can't add the
- // body until now, because the typer knows that Any has no
- // constructor and won't accept a call to super.init.
- assert((clazz isSubClass AnyValClass) || clazz.info.parents.isEmpty, clazz)
- Block(List(Apply(gen.mkSuperInitCall, Nil)), expr)
-
- case Block(stats, expr) =>
- // needs `hasSymbolField` check because `supercall` could be a block (named / default args)
- val (presuper, supercall :: rest) = stats span (t => t.hasSymbolWhich(_ hasFlag PRESUPER))
- treeCopy.Block(tree, presuper ::: (supercall :: mixinConstructorCalls ::: rest), expr)
- }
- }
-
- protected val mixinTransformer = new Transformer {
- override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] =
- (super.transformStats(stats, exprOwner) :::
- super.transformStats(implClassDefs(stats), exprOwner))
- override def transform(tree: Tree): Tree = {
- val sym = tree.symbol
- val tree1 = tree match {
- case ClassDef(mods, _, _, impl) if sym.needsImplClass =>
- implClass(sym).initialize // to force lateDEFERRED flags
- copyClassDef(tree)(mods = mods | INTERFACE, impl = ifaceTemplate(impl))
- case DefDef(_,_,_,_,_,_) if sym.isClassConstructor && sym.isPrimaryConstructor && sym.owner != ArrayClass =>
- deriveDefDef(tree)(addMixinConstructorCalls(_, sym.owner)) // (3)
- case Template(parents, self, body) =>
- val parents1 = sym.owner.info.parents map (t => TypeTree(t) setPos tree.pos)
- treeCopy.Template(tree, parents1, noSelfType, body)
- case This(_) if sym.needsImplClass =>
- val impl = implClass(sym)
- var owner = currentOwner
- while (owner != sym && owner != impl) owner = owner.owner;
- if (owner == impl) This(impl) setPos tree.pos
- else tree
- //TODO what about this commented out code?
-/* !!!
- case Super(qual, mix) =>
- val mix1 = mix
- if (mix == tpnme.EMPTY) mix
- else {
- val ps = enteringErasure {
- sym.info.parents dropWhile (p => p.symbol.name != mix)
- }
- assert(!ps.isEmpty, tree);
- if (ps.head.symbol.needsImplClass) implClass(ps.head.symbol).name
- else mix
- }
- if (sym.needsImplClass) Super(implClass(sym), mix1) setPos tree.pos
- else treeCopy.Super(tree, qual, mix1)
-*/
- case _ =>
- tree
- }
- super.transform(tree1)
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
index c29826551b..81df28bc87 100644
--- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala
+++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
@@ -21,16 +21,8 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
val phaseName: String = "cleanup"
/* used in GenBCode: collects ClassDef symbols owning a main(Array[String]) method */
- private var entryPoints: List[Symbol] = null
- def getEntryPoints: List[Symbol] = {
- assert(settings.isBCodeActive, "Candidate Java entry points are collected here only when GenBCode in use.")
- entryPoints sortBy ("" + _.fullName) // For predictably ordered error messages.
- }
-
- override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = {
- entryPoints = if (settings.isBCodeActive) Nil else null;
- super.newPhase(prev)
- }
+ private var entryPoints: List[Symbol] = Nil
+ def getEntryPoints: List[Symbol] = entryPoints sortBy ("" + _.fullName) // For predictably ordered error messages.
protected def newTransformer(unit: CompilationUnit): Transformer =
new CleanUpTransformer(unit)
@@ -49,7 +41,9 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
clearStatics()
val newBody = transformTrees(body)
val templ = deriveTemplate(tree)(_ => transformTrees(newStaticMembers.toList) ::: newBody)
- try addStaticInits(templ, newStaticInits, localTyper) // postprocess to include static ctors
+ try
+ if (newStaticInits.isEmpty) templ
+ else deriveTemplate(templ)(body => staticConstructor(body, localTyper, templ.pos)(newStaticInits.toList) :: body)
finally clearStatics()
}
private def mkTerm(prefix: String): TermName = unit.freshTermName(prefix)
@@ -85,24 +79,6 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
/* ### CREATING THE METHOD CACHE ### */
- def addStaticVariableToClass(forName: TermName, forType: Type, forInit: Tree, isFinal: Boolean): Symbol = {
- val flags = PRIVATE | STATIC | SYNTHETIC | (
- if (isFinal) FINAL else 0
- )
-
- val varSym = currentClass.newVariable(mkTerm("" + forName), ad.pos, flags.toLong) setInfoAndEnter forType
- if (!isFinal)
- varSym.addAnnotation(VolatileAttr)
-
- val varDef = typedPos(ValDef(varSym, forInit))
- newStaticMembers append transform(varDef)
-
- val varInit = typedPos( REF(varSym) === forInit )
- newStaticInits append transform(varInit)
-
- varSym
- }
-
def addStaticMethodToClass(forBody: (Symbol, Symbol) => Tree): Symbol = {
val methSym = currentClass.newMethod(mkTerm(nme.reflMethodName.toString), ad.pos, STATIC | SYNTHETIC)
val params = methSym.newSyntheticValueParams(List(ClassClass.tpe))
@@ -113,9 +89,6 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
methSym
}
- def fromTypesToClassArrayLiteral(paramTypes: List[Type]): Tree =
- ArrayValue(TypeTree(ClassClass.tpe), paramTypes map LIT)
-
def reflectiveMethodCache(method: String, paramTypes: List[Type]): Symbol = {
/* Implementation of the cache is as follows for method "def xyz(a: A, b: B)"
(SoftReference so that it does not interfere with classloader garbage collection,
@@ -126,7 +99,7 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
var reflPoly$Cache: SoftReference[scala.runtime.MethodCache] = new SoftReference(new EmptyMethodCache())
def reflMethod$Method(forReceiver: JClass[_]): JMethod = {
- var methodCache: MethodCache = reflPoly$Cache.find(forReceiver)
+ var methodCache: StructuralCallSite = indy[StructuralCallSite.bootstrap, "(LA;LB;)Ljava/lang/Object;]
if (methodCache eq null) {
methodCache = new EmptyMethodCache
reflPoly$Cache = new SoftReference(methodCache)
@@ -135,41 +108,32 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
if (method ne null)
return method
else {
- method = ScalaRunTime.ensureAccessible(forReceiver.getMethod("xyz", reflParams$Cache))
- reflPoly$Cache = new SoftReference(methodCache.add(forReceiver, method))
+ method = ScalaRunTime.ensureAccessible(forReceiver.getMethod("xyz", methodCache.parameterTypes()))
+ methodCache.add(forReceiver, method)
return method
}
}
- */
-
- val reflParamsCacheSym: Symbol =
- addStaticVariableToClass(nme.reflParamsCacheName, arrayType(ClassClass.tpe), fromTypesToClassArrayLiteral(paramTypes), true)
-
- def mkNewPolyCache = gen.mkSoftRef(NEW(TypeTree(EmptyMethodCacheClass.tpe)))
- val reflPolyCacheSym: Symbol = addStaticVariableToClass(nme.reflPolyCacheName, SoftReferenceClass.tpe, mkNewPolyCache, false)
- def getPolyCache = gen.mkCast(fn(REF(reflPolyCacheSym), nme.get), MethodCacheClass.tpe)
+ invokedynamic is used rather than a static field for the cache to support emitting bodies of methods
+ in Java 8 interfaces, which don't support private static fields.
+ */
addStaticMethodToClass((reflMethodSym, forReceiverSym) => {
- val methodCache = reflMethodSym.newVariable(mkTerm("methodCache"), ad.pos) setInfo MethodCacheClass.tpe
+ val methodCache = reflMethodSym.newVariable(mkTerm("methodCache"), ad.pos) setInfo StructuralCallSite.tpe
val methodSym = reflMethodSym.newVariable(mkTerm("method"), ad.pos) setInfo MethodClass.tpe
+ val dummyMethodType = MethodType(NoSymbol.newSyntheticValueParams(paramTypes), AnyTpe)
BLOCK(
- ValDef(methodCache, getPolyCache),
- IF (REF(methodCache) OBJ_EQ NULL) THEN BLOCK(
- REF(methodCache) === NEW(TypeTree(EmptyMethodCacheClass.tpe)),
- REF(reflPolyCacheSym) === gen.mkSoftRef(REF(methodCache))
- ) ENDIF,
-
- ValDef(methodSym, (REF(methodCache) DOT methodCache_find)(REF(forReceiverSym))),
+ ValDef(methodCache, ApplyDynamic(gen.mkAttributedIdent(StructuralCallSite_dummy), LIT(StructuralCallSite_bootstrap) :: LIT(dummyMethodType) :: Nil).setType(StructuralCallSite.tpe)),
+ ValDef(methodSym, (REF(methodCache) DOT StructuralCallSite_find)(REF(forReceiverSym))),
IF (REF(methodSym) OBJ_NE NULL) .
THEN (Return(REF(methodSym)))
ELSE {
- def methodSymRHS = ((REF(forReceiverSym) DOT Class_getMethod)(LIT(method), REF(reflParamsCacheSym)))
- def cacheRHS = ((REF(methodCache) DOT methodCache_add)(REF(forReceiverSym), REF(methodSym)))
+ def methodSymRHS = ((REF(forReceiverSym) DOT Class_getMethod)(LIT(method), (REF(methodCache) DOT StructuralCallSite_getParameterTypes)()))
+ def cacheAdd = ((REF(methodCache) DOT StructuralCallSite_add)(REF(forReceiverSym), REF(methodSym)))
BLOCK(
REF(methodSym) === (REF(currentRun.runDefinitions.ensureAccessibleMethod) APPLY (methodSymRHS)),
- REF(reflPolyCacheSym) === gen.mkSoftRef(cacheRHS),
+ cacheAdd,
Return(REF(methodSym))
)
}
@@ -369,6 +333,8 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
reporter.error(ad.pos, "Cannot resolve overload.")
(Nil, NoType)
}
+ case NoType =>
+ abort(ad.symbol.toString)
}
typedPos {
val sym = currentOwner.newValue(mkTerm("qual"), ad.pos) setInfo qual0.tpe
@@ -404,11 +370,7 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
}
override def transform(tree: Tree): Tree = tree match {
-
- case _: ClassDef
- if (entryPoints != null) &&
- genBCode.isJavaEntryPoint(tree.symbol, currentUnit)
- =>
+ case _: ClassDef if genBCode.isJavaEntryPoint(tree.symbol, currentUnit) =>
// collecting symbols for entry points here (as opposed to GenBCode where they are used)
// has the advantage of saving an additional pass over all ClassDefs.
entryPoints ::= tree.symbol
@@ -446,7 +408,7 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
* refinement, where the refinement defines a parameter based on a
* type variable. */
- case tree: ApplyDynamic =>
+ case tree: ApplyDynamic if tree.symbol.owner.isRefinementClass =>
transformApplyDynamic(tree)
/* Some cleanup transformations add members to templates (classes, traits, etc).
@@ -476,46 +438,15 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
/*
* This transformation should identify Scala symbol invocations in the tree and replace them
- * with references to a static member. Also, whenever a class has at least a single symbol invocation
- * somewhere in its methods, a new static member should be created and initialized for that symbol.
- * For instance, say we have a Scala class:
- *
- * class Cls {
- * def someSymbol1 = 'Symbolic1
- * def someSymbol2 = 'Symbolic2
- * def sameSymbol1 = 'Symbolic1
- * val someSymbol3 = 'Symbolic3
- * }
- *
- * After transformation, this class looks like this:
- *
- * class Cls {
- * private <static> var symbol$1: scala.Symbol
- * private <static> var symbol$2: scala.Symbol
- * private <static> var symbol$3: scala.Symbol
- * private val someSymbol3: scala.Symbol
- *
- * private <static> def <clinit> = {
- * symbol$1 = Symbol.apply("Symbolic1")
- * symbol$2 = Symbol.apply("Symbolic2")
- * }
- *
- * private def <init> = {
- * someSymbol3 = symbol$3
- * }
- *
- * def someSymbol1 = symbol$1
- * def someSymbol2 = symbol$2
- * def sameSymbol1 = symbol$1
- * val someSymbol3 = someSymbol3
- * }
+ * with references to a statically cached instance.
*
* The reasoning behind this transformation is the following. Symbols get interned - they are stored
* in a global map which is protected with a lock. The reason for this is making equality checks
* quicker. But calling Symbol.apply, although it does return a unique symbol, accesses a locked object,
* making symbol access slow. To solve this, the unique symbol from the global symbol map in Symbol
- * is accessed only once during class loading, and after that, the unique symbol is in the static
- * member. Hence, it is cheap to both reach the unique symbol and do equality checks on it.
+ * is accessed only once during class loading, and after that, the unique symbol is in the statically
+ * initialized call site returned by invokedynamic. Hence, it is cheap to both reach the unique symbol
+ * and do equality checks on it.
*
* And, finally, be advised - Scala's Symbol literal (scala.Symbol) and the Symbol class of the compiler
* have little in common.
@@ -523,15 +454,12 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
case Apply(fn @ Select(qual, _), (arg @ Literal(Constant(symname: String))) :: Nil)
if treeInfo.isQualifierSafeToElide(qual) && fn.symbol == Symbol_apply && !currentClass.isTrait =>
- def transformApply = {
- // add the symbol name to a map if it's not there already
- val rhs = gen.mkMethodCall(Symbol_apply, arg :: Nil)
- val staticFieldSym = getSymbolStaticField(tree.pos, symname, rhs, tree)
- // create a reference to a static field
- val ntree = typedWithPos(tree.pos)(REF(staticFieldSym))
- super.transform(ntree)
- }
- transformApply
+ super.transform(treeCopy.ApplyDynamic(tree, atPos(fn.pos)(Ident(SymbolLiteral_dummy).setType(SymbolLiteral_dummy.info)), LIT(SymbolLiteral_bootstrap) :: arg :: Nil))
+
+ // Drop the TypeApply, which was used in Erasure to make `synchronized { ... } ` erase like `...`
+ // (and to avoid boxing the argument to the polymorphic `synchronized` method).
+ case app@Apply(TypeApply(fun, _), args) if fun.symbol == Object_synchronized =>
+ super.transform(treeCopy.Apply(app, fun, args))
// Replaces `Array(Predef.wrapArray(ArrayValue(...).$asInstanceOf[...]), <tag>)`
// with just `ArrayValue(...).$asInstanceOf[...]`
@@ -548,32 +476,6 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
super.transform(tree)
}
- /* Returns the symbol and the tree for the symbol field interning a reference to a symbol 'synmname'.
- * If it doesn't exist, i.e. the symbol is encountered the first time,
- * it creates a new static field definition and initialization and returns it.
- */
- private def getSymbolStaticField(pos: Position, symname: String, rhs: Tree, tree: Tree): Symbol = {
- symbolsStoredAsStatic.getOrElseUpdate(symname, {
- val theTyper = typer.atOwner(tree, currentClass)
-
- // create a symbol for the static field
- val stfieldSym = (
- currentClass.newVariable(mkTerm("symbol$"), pos, PRIVATE | STATIC | SYNTHETIC | FINAL)
- setInfoAndEnter SymbolClass.tpe
- )
-
- // create field definition and initialization
- val stfieldDef = theTyper.typedPos(pos)(ValDef(stfieldSym, rhs))
- val stfieldInit = theTyper.typedPos(pos)(REF(stfieldSym) === rhs)
-
- // add field definition to new defs
- newStaticMembers append stfieldDef
- newStaticInits append stfieldInit
-
- stfieldSym
- })
- }
-
} // CleanUpTransformer
}
diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala
index b2aac587eb..231a3e4c64 100644
--- a/src/compiler/scala/tools/nsc/transform/Constructors.scala
+++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala
@@ -6,15 +6,14 @@
package scala.tools.nsc
package transform
-import scala.collection.{ mutable, immutable }
-import scala.collection.mutable.ListBuffer
+import scala.collection.mutable
import scala.reflect.internal.util.ListOfNil
import symtab.Flags._
/** This phase converts classes with parameters into Java-like classes with
* fields, which are assigned to from constructors.
*/
-abstract class Constructors extends Statics with Transform with ast.TreeDSL {
+abstract class Constructors extends Statics with Transform with TypingTransformers with ast.TreeDSL {
import global._
import definitions._
@@ -27,8 +26,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
private val guardedCtorStats: mutable.Map[Symbol, List[Tree]] = perRunCaches.newMap[Symbol, List[Tree]]()
private val ctorParams: mutable.Map[Symbol, List[Symbol]] = perRunCaches.newMap[Symbol, List[Symbol]]()
- class ConstructorTransformer(unit: CompilationUnit) extends Transformer {
-
+ class ConstructorTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {
/*
* Inspect for obvious out-of-order initialization; concrete, eager vals or vars, declared in this class,
* for which a reference to the member precedes its definition.
@@ -75,14 +73,17 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
override def transform(tree: Tree): Tree = {
tree match {
- case cd @ ClassDef(mods0, name0, tparams0, impl0) if !cd.symbol.isInterface && !isPrimitiveValueClass(cd.symbol) =>
+ case cd @ ClassDef(mods0, name0, tparams0, impl0) if !isPrimitiveValueClass(cd.symbol) && cd.symbol.primaryConstructor != NoSymbol =>
if(cd.symbol eq AnyValClass) {
cd
}
else {
checkUninitializedReads(cd)
val tplTransformer = new TemplateTransformer(unit, impl0)
- treeCopy.ClassDef(cd, mods0, name0, tparams0, tplTransformer.transformed)
+ tplTransformer.localTyper = this.localTyper
+ tplTransformer.atOwner(impl0, cd.symbol) {
+ treeCopy.ClassDef(cd, mods0, name0, tparams0, tplTransformer.transformed)
+ }
}
case _ =>
super.transform(tree)
@@ -121,15 +122,15 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
* What trees can be visited at this point?
* To recap, by the time the constructors phase runs, local definitions have been hoisted out of their original owner.
* Moreover, by the time elision is about to happen, the `intoConstructors` rewriting
- * of template-level statements has taken place (the resulting trees can be found in `constrStatBuf`).
+ * of template-level statements has taken place (the resulting trees can be found in `constructorStats`).
*
* That means:
*
- * - nested classes are to be found in `defBuf`
+ * - nested classes are to be found in `defs`
*
- * - value and method definitions are also in `defBuf` and none of them contains local methods or classes.
+ * - value and method definitions are also in `defs` and none of them contains local methods or classes.
*
- * - auxiliary constructors are to be found in `auxConstructorBuf`
+ * - auxiliary constructors are to be found in `auxConstructors`
*
* Coming back to the question which trees may contain accesses:
*
@@ -148,62 +149,56 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
* (the primary constructor) into a dedicated synthetic method that an anon-closure may invoke, as required by DelayedInit.
*
*/
- private trait OmittablesHelper { self: TemplateTransformer =>
-
- /*
- * Initially populated with all elision candidates.
- * Trees are traversed, and those candidates are removed which are actually needed.
- * After that, `omittables` doesn't shrink anymore: each symbol it contains can be unlinked from clazz.info.decls.
- */
- val omittables = mutable.Set.empty[Symbol]
-
- def populateOmittables() {
-
- omittables.clear()
-
- if(isDelayedInitSubclass) {
- return
- }
-
- def isParamCandidateForElision(sym: Symbol) = (sym.isParamAccessor && sym.isPrivateLocal)
- def isOuterCandidateForElision(sym: Symbol) = (sym.isOuterAccessor && sym.owner.isEffectivelyFinal && !sym.isOverridingSymbol)
-
- val paramCandidatesForElision: Set[ /*Field*/ Symbol] = (clazz.info.decls.toSet filter isParamCandidateForElision)
- val outerCandidatesForElision: Set[ /*Method*/ Symbol] = (clazz.info.decls.toSet filter isOuterCandidateForElision)
-
- omittables ++= paramCandidatesForElision
- omittables ++= outerCandidatesForElision
-
- val bodyOfOuterAccessor: Map[Symbol, DefDef] =
- defBuf.collect { case dd: DefDef if outerCandidatesForElision(dd.symbol) => dd.symbol -> dd }.toMap
+ private trait OmittablesHelper {
+ def computeOmittableAccessors(clazz: Symbol, defs: List[Tree], auxConstructors: List[Tree]): Set[Symbol] = {
+ val decls = clazz.info.decls.toSet
+ val isEffectivelyFinal = clazz.isEffectivelyFinal
+
+ // Initially populated with all elision candidates.
+ // Trees are traversed, and those candidates are removed which are actually needed.
+ // After that, `omittables` doesn't shrink anymore: each symbol it contains can be unlinked from clazz.info.decls.
+ //
+ // Note: elision of outer reference is based on a class-wise analysis, if a class might have subclasses,
+ // it doesn't work. For example, `LocalParent` retains the outer reference in:
+ //
+ // class Outer { def test = {class LocalParent; class LocalChild extends LocalParent } }
+ //
+ // See run/t9408.scala for related test cases.
+ def omittableParamAcc(sym: Symbol) = sym.isParamAccessor && sym.isPrivateLocal
+ def omittableOuterAcc(sym: Symbol) = isEffectivelyFinal && sym.isOuterAccessor && !sym.isOverridingSymbol
+ val omittables = mutable.Set.empty[Symbol] ++ (decls filter (sym => omittableParamAcc(sym) || omittableOuterAcc(sym))) // the closure only captures isEffectivelyFinal
// no point traversing further once omittables is empty, all candidates ruled out already.
object detectUsages extends Traverser {
- private def markUsage(sym: Symbol) {
- omittables -= debuglogResult("omittables -= ")(sym)
- // recursive call to mark as needed the field supporting the outer-accessor-method.
- bodyOfOuterAccessor get sym foreach (this traverse _.rhs)
- }
- override def traverse(tree: Tree): Unit = if (omittables.nonEmpty) {
- def sym = tree.symbol
- tree match {
- // don't mark as "needed" the field supporting this outer-accessor, ie not just yet.
- case _: DefDef if outerCandidatesForElision(sym) => ()
- case _: Select if omittables(sym) => markUsage(sym) ; super.traverse(tree)
- case _ => super.traverse(tree)
+ lazy val bodyOfOuterAccessor = defs.collect{ case dd: DefDef if omittableOuterAcc(dd.symbol) => dd.symbol -> dd.rhs }.toMap
+
+ override def traverse(tree: Tree): Unit =
+ if (omittables.nonEmpty) {
+ def sym = tree.symbol
+ tree match {
+ case _: DefDef if (sym.owner eq clazz) && omittableOuterAcc(sym) => // don't mark as "needed" the field supporting this outer-accessor (not just yet)
+ case _: Select if omittables(sym) => omittables -= sym // mark usage
+ bodyOfOuterAccessor get sym foreach traverse // recurse to mark as needed the field supporting the outer-accessor-method
+ super.traverse(tree)
+ case _ => super.traverse(tree)
+ }
}
- }
- def walk(xs: Seq[Tree]) = xs.iterator foreach traverse
- }
- if (omittables.nonEmpty) {
- detectUsages walk defBuf
- detectUsages walk auxConstructorBuf
}
- }
- def mustBeKept(sym: Symbol) = !omittables(sym)
+ if (omittables.nonEmpty)
+ (defs.iterator ++ auxConstructors.iterator) foreach detectUsages.traverse
+
+ omittables.toSet
+ }
} // OmittablesHelper
+ trait ConstructorTransformerBase {
+ def unit: CompilationUnit
+ def impl: Template
+ def clazz: Symbol
+ def localTyper: analyzer.Typer
+ }
+
/*
* TemplateTransformer rewrites DelayedInit subclasses.
* The list of statements that will end up in the primary constructor can be split into:
@@ -248,10 +243,8 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
* @return the DefDef for (c) above
*
* */
- private trait DelayedInitHelper { self: TemplateTransformer =>
-
+ private trait DelayedInitHelper extends ConstructorTransformerBase {
private def delayedEndpointDef(stats: List[Tree]): DefDef = {
-
val methodName = currentUnit.freshTermName("delayedEndpoint$" + clazz.fullNameAsName('$').toString + "$")
val methodSym = clazz.newMethod(methodName, impl.pos, SYNTHETIC | FINAL)
methodSym setInfoAndEnter MethodType(Nil, UnitTpe)
@@ -310,36 +303,30 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
satelliteClass.asInstanceOf[ClassDef]
}
- private def delayedInitCall(closure: Tree) = localTyper.typedPos(impl.pos) {
- gen.mkMethodCall(This(clazz), delayedInitMethod, Nil, List(New(closure.symbol.tpe, This(clazz))))
- }
+ /** For a DelayedInit subclass, wrap remainingConstrStats into a DelayedInit closure. */
+ def delayedInitDefsAndConstrStats(defs: List[Tree], remainingConstrStats: List[Tree]): (List[Tree], List[Tree]) = {
+ val delayedHook = delayedEndpointDef(remainingConstrStats)
+ val delayedHookSym = delayedHook.symbol.asInstanceOf[MethodSymbol]
- def rewriteDelayedInit() {
- /* XXX This is not correct: remainingConstrStats.nonEmpty excludes too much,
- * but excluding it includes too much. The constructor sequence being mimicked
- * needs to be reproduced with total fidelity.
- *
- * See test case files/run/bug4680.scala, the output of which is wrong in many
- * particulars.
- */
- val needsDelayedInit = (isDelayedInitSubclass && remainingConstrStats.nonEmpty)
-
- if (needsDelayedInit) {
- val delayedHook: DefDef = delayedEndpointDef(remainingConstrStats)
- defBuf += delayedHook
- val hookCallerClass = {
- // transform to make the closure-class' default constructor assign the outer instance to its param-accessor field.
- val drillDown = new ConstructorTransformer(unit)
- drillDown transform delayedInitClosure(delayedHook.symbol.asInstanceOf[MethodSymbol])
- }
- defBuf += hookCallerClass
- remainingConstrStats = delayedInitCall(hookCallerClass) :: Nil
+ // transform to make the closure-class' default constructor assign the outer instance to its param-accessor field.
+ val hookCallerClass = (new ConstructorTransformer(unit)) transform delayedInitClosure(delayedHookSym)
+ val delayedInitCall = localTyper.typedPos(impl.pos) {
+ gen.mkMethodCall(This(clazz), delayedInitMethod, Nil, List(New(hookCallerClass.symbol.tpe, This(clazz))))
}
+
+ (List(delayedHook, hookCallerClass), List(delayedInitCall))
}
} // DelayedInitHelper
- private trait GuardianOfCtorStmts { self: TemplateTransformer =>
+ private trait GuardianOfCtorStmts extends ConstructorTransformerBase {
+ def primaryConstrParams: List[Symbol]
+ def usesSpecializedField: Boolean
+
+ lazy val hasSpecializedFieldsSym = clazz.info.decl(nme.SPECIALIZED_INSTANCE)
+ // The constructor of a non-specialized class that has specialized subclasses
+ // should use `q"${hasSpecializedFieldsSym}()"` to guard the initialization of specialized fields.
+ lazy val guardSpecializedFieldInit = (hasSpecializedFieldsSym != NoSymbol) && !clazz.hasFlag(SPECIALIZED)
/* Return a single list of statements, merging the generic class constructor with the
* specialized stats. The original statements are retyped in the current class, and
@@ -347,7 +334,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
* `specializedStats` are replaced by the specialized assignment.
*/
private def mergeConstructors(genericClazz: Symbol, originalStats: List[Tree], specializedStats: List[Tree]): List[Tree] = {
- val specBuf = new ListBuffer[Tree]
+ val specBuf = new mutable.ListBuffer[Tree]
specBuf ++= specializedStats
def specializedAssignFor(sym: Symbol): Option[Tree] =
@@ -375,7 +362,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
}
log("merging: " + originalStats.mkString("\n") + "\nwith\n" + specializedStats.mkString("\n"))
- val res = for (s <- originalStats; stat = s.duplicate) yield {
+ for (s <- originalStats; stat = s.duplicate) yield {
log("merge: looking at " + stat)
val stat1 = stat match {
case Assign(sel @ Select(This(_), field), _) =>
@@ -388,9 +375,9 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
}
if (stat1 eq stat) {
- assert(ctorParams(genericClazz).length == constrInfo.constrParams.length)
+ assert(ctorParams(genericClazz).length == primaryConstrParams.length)
// this is just to make private fields public
- (new specializeTypes.ImplementationAdapter(ctorParams(genericClazz), constrInfo.constrParams, null, true))(stat1)
+ (new specializeTypes.ImplementationAdapter(ctorParams(genericClazz), primaryConstrParams, null, true))(stat1)
val stat2 = rewriteArrayUpdate(stat1)
// statements coming from the original class need retyping in the current context
@@ -405,9 +392,8 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
} else
stat1
}
- if (specBuf.nonEmpty)
- println("residual specialized constructor statements: " + specBuf)
- res
+// if (specBuf.nonEmpty)
+// println("residual specialized constructor statements: " + specBuf)
}
/* Add an 'if' around the statements coming after the super constructor. This
@@ -427,16 +413,16 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
// postfix = postfix.tail
// }
- if (shouldGuard && usesSpecializedField && stats.nonEmpty) {
+ if (guardSpecializedFieldInit && usesSpecializedField && stats.nonEmpty) {
// save them for duplication in the specialized subclass
guardedCtorStats(clazz) = stats
- ctorParams(clazz) = constrInfo.constrParams
+ ctorParams(clazz) = primaryConstrParams
val tree =
If(
Apply(
CODE.NOT (
- Apply(gen.mkAttributedRef(specializedFlag), List())),
+ Apply(gen.mkAttributedRef(hasSpecializedFieldsSym), List())),
List()),
Block(stats, Literal(Constant(()))),
EmptyTree)
@@ -459,39 +445,31 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
} // GuardianOfCtorStmts
private class TemplateTransformer(val unit: CompilationUnit, val impl: Template)
- extends StaticsTransformer
+ extends TypingTransformer(unit)
+ with StaticsTransformer
with DelayedInitHelper
with OmittablesHelper
- with GuardianOfCtorStmts {
-
- val clazz = impl.symbol.owner // the transformed class
- val stats = impl.body // the transformed template body
- val localTyper = typer.atOwner(impl, clazz)
-
- val specializedFlag: Symbol = clazz.info.decl(nme.SPECIALIZED_INSTANCE)
- val shouldGuard = (specializedFlag != NoSymbol) && !clazz.hasFlag(SPECIALIZED)
-
- val isDelayedInitSubclass = (clazz isSubClass DelayedInitClass)
-
- case class ConstrInfo(
- constr: DefDef, // The primary constructor
- constrParams: List[Symbol], // ... and its parameters
- constrBody: Block // ... and its body
- )
- // decompose primary constructor into the three entities above.
- val constrInfo: ConstrInfo = {
- val ddef = (stats find (_.symbol.isPrimaryConstructor))
- ddef match {
- case Some(ddef @ DefDef(_, _, _, List(vparams), _, rhs @ Block(_, _))) =>
- ConstrInfo(ddef, vparams map (_.symbol), rhs)
- case x =>
- abort("no constructor in template: impl = " + impl)
- }
+ with GuardianOfCtorStmts
+ with fields.CheckedAccessorTreeSynthesis
+ {
+ protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree)
+
+ val clazz = impl.symbol.owner // the transformed class
+
+ val isDelayedInitSubclass = clazz isSubClass DelayedInitClass
+
+ private val stats = impl.body // the transformed template body
+
+ // find and dissect primary constructor
+ private val (primaryConstr, _primaryConstrParams, primaryConstrBody) = stats collectFirst {
+ case dd@DefDef(_, _, _, vps :: Nil, _, rhs: Block) if dd.symbol.isPrimaryConstructor => (dd, vps map (_.symbol), rhs)
+ } getOrElse {
+ abort("no constructor in template: impl = " + impl)
}
- import constrInfo._
- // The parameter accessor fields which are members of the class
- val paramAccessors = clazz.constrParamAccessors
+
+ def primaryConstrParams = _primaryConstrParams
+ def usesSpecializedField = intoConstructor.usesSpecializedField
// The constructor parameter corresponding to an accessor
def parameter(acc: Symbol): Symbol = parameterNamed(acc.unexpandedName.getterName)
@@ -501,27 +479,26 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
def parameterNamed(name: Name): Symbol = {
def matchesName(param: Symbol) = param.name == name || param.name.startsWith(name + nme.NAME_JOIN_STRING)
- (constrParams filter matchesName) match {
- case Nil => abort(name + " not in " + constrParams)
+ primaryConstrParams filter matchesName match {
+ case Nil => abort(name + " not in " + primaryConstrParams)
case p :: _ => p
}
}
- /*
- * `usesSpecializedField` makes a difference in deciding whether constructor-statements
- * should be guarded in a `shouldGuard` class, ie in a class that's the generic super-class of
- * one or more specialized sub-classes.
- *
- * Given that `usesSpecializedField` isn't read for any other purpose than the one described above,
- * we skip setting `usesSpecializedField` in case the current class isn't `shouldGuard` to start with.
- * That way, trips to a map in `specializeTypes` are saved.
- */
- var usesSpecializedField: Boolean = false
-
// A transformer for expressions that go into the constructor
- private class IntoCtorTransformer extends Transformer {
-
- private def isParamRef(sym: Symbol) = (sym.isParamAccessor && sym.owner == clazz)
+ object intoConstructor extends Transformer {
+ /*
+ * `usesSpecializedField` makes a difference in deciding whether constructor-statements
+ * should be guarded in a `guardSpecializedFieldInit` class, ie in a class that's the generic super-class of
+ * one or more specialized sub-classes.
+ *
+ * Given that `usesSpecializedField` isn't read for any other purpose than the one described above,
+ * we skip setting `usesSpecializedField` in case the current class isn't `guardSpecializedFieldInit` to start with.
+ * That way, trips to a map in `specializeTypes` are saved.
+ */
+ var usesSpecializedField: Boolean = false
+
+ private def isParamRef(sym: Symbol) = sym.isParamAccessor && sym.owner == clazz
// Terminology: a stationary location is never written after being read.
private def isStationaryParamRef(sym: Symbol) = (
@@ -530,26 +507,27 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
!sym.isSetter
)
- private def possiblySpecialized(s: Symbol) = specializeTypes.specializedTypeVars(s).nonEmpty
-
/*
- * whether `sym` denotes a param-accessor (ie a field) that fulfills all of:
+ * whether `sym` denotes a param-accessor (ie in a class a PARAMACCESSOR field, or in a trait a method with same flag)
+ * that fulfills all of:
* (a) has stationary value, ie the same value provided via the corresponding ctor-arg; and
* (b) isn't subject to specialization. We might be processing statements for:
* (b.1) the constructor in the generic (super-)class; or
* (b.2) the constructor in the specialized (sub-)class.
* (c) isn't part of a DelayedInit subclass.
*/
- private def canBeSupplanted(sym: Symbol) = (!isDelayedInitSubclass && isStationaryParamRef(sym) && !possiblySpecialized(sym))
+ private def canBeSupplanted(sym: Symbol) = !isDelayedInitSubclass && isStationaryParamRef(sym) && !specializeTypes.possiblySpecialized(sym)
override def transform(tree: Tree): Tree = tree match {
-
case Apply(Select(This(_), _), List()) =>
// references to parameter accessor methods of own class become references to parameters
// outer accessors become references to $outer parameter
- if (canBeSupplanted(tree.symbol))
- gen.mkAttributedIdent(parameter(tree.symbol.accessed)) setPos tree.pos
- else if (tree.symbol.outerSource == clazz && !clazz.isImplClass)
+ // println(s"to param ref in $clazz for ${tree.symbol} ${tree.symbol.debugFlagString} / ${tree.symbol.outerSource} / ${canBeSupplanted(tree.symbol)}")
+ if (clazz.isTrait && !(tree.symbol hasAllFlags (ACCESSOR | PARAMACCESSOR)))
+ super.transform(tree)
+ else if (canBeSupplanted(tree.symbol))
+ gen.mkAttributedIdent(parameter(tree.symbol)) setPos tree.pos
+ else if (tree.symbol.outerSource == clazz && !isDelayedInitSubclass)
gen.mkAttributedIdent(parameterNamed(nme.OUTER)) setPos tree.pos
else
super.transform(tree)
@@ -558,8 +536,8 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
// references to parameter accessor field of own class become references to parameters
gen.mkAttributedIdent(parameter(tree.symbol)) setPos tree.pos
- case Select(_, _) if shouldGuard => // reasoning behind this guard in the docu of `usesSpecializedField`
- if (possiblySpecialized(tree.symbol)) {
+ case Select(_, _) if guardSpecializedFieldInit => // reasoning behind this guard in the docu of `usesSpecializedField`
+ if (specializeTypes.possiblySpecialized(tree.symbol)) {
usesSpecializedField = true
}
super.transform(tree)
@@ -568,23 +546,20 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
super.transform(tree)
}
+ // Move tree into constructor, take care of changing owner from `oldOwner` to `newOwner` (the primary constructor symbol)
+ def apply(oldOwner: Symbol, newOwner: Symbol)(tree: Tree) =
+ if (tree eq EmptyTree) tree
+ else transform(tree.changeOwner(oldOwner -> newOwner))
}
- private val intoConstructorTransformer = new IntoCtorTransformer
-
- // Move tree into constructor, take care of changing owner from `oldowner` to constructor symbol
- def intoConstructor(oldowner: Symbol, tree: Tree) =
- intoConstructorTransformer transform tree.changeOwner(oldowner -> constr.symbol)
-
- // Should tree be moved in front of super constructor call?
- def canBeMoved(tree: Tree) = tree match {
- case ValDef(mods, _, _, _) => (mods hasFlag PRESUPER | PARAMACCESSOR)
- case _ => false
- }
+ // Assign `rhs` to class field / trait setter `assignSym`
+ def mkAssign(assignSym: Symbol, rhs: Tree): Tree =
+ localTyper.typedPos(assignSym.pos) {
+ val qual = Select(This(clazz), assignSym)
+ if (assignSym.isSetter) Apply(qual, List(rhs))
+ else Assign(qual, rhs)
+ }
- // Create an assignment to class field `to` with rhs `from`
- def mkAssign(to: Symbol, from: Tree): Tree =
- localTyper.typedPos(to.pos) { Assign(Select(This(clazz), to), from) }
// Create code to copy parameter to parameter accessor field.
// If parameter is $outer, check that it is not null so that we NPE
@@ -594,139 +569,230 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
val result = mkAssign(to, Ident(from))
if (from.name != nme.OUTER ||
- from.tpe.typeSymbol.isPrimitiveValueClass) result
+ from.tpe.typeSymbol.isPrimitiveValueClass) result
else localTyper.typedPos(to.pos) {
// `throw null` has the same effect as `throw new NullPointerException`, see JVM spec on instruction `athrow`
- IF (from OBJ_EQ NULL) THEN Throw(gen.mkZero(ThrowableTpe)) ELSE result
+ IF(from OBJ_EQ NULL) THEN Throw(gen.mkZero(ThrowableTpe)) ELSE result
}
}
- // The list of definitions that go into class
- val defBuf = new ListBuffer[Tree]
-
- // The auxiliary constructors, separate from the defBuf since they should
- // follow the primary constructor
- val auxConstructorBuf = new ListBuffer[Tree]
-
- // The list of statements that go into the constructor after and including the superclass constructor call
- val constrStatBuf = new ListBuffer[Tree]
+ /** Triage definitions and statements in this template into the following categories.
+ * The primary constructor is treated separately, as it is assembled in part from these pieces.
+ *
+ * - `defs`: definitions that go into class
+ * - `auxConstrs`: auxiliary constructors, separate from the defs as they should follow the primary constructor
+ * - `constrPrefix`: early initializer statements that go into constructor before the superclass constructor call
+ * - `constrStats`: statements that go into the constructor after and including the superclass constructor call
+ * - `classInitStats`: statements that go into the class initializer
+ */
+ class Triage {
+ private val defBuf, auxConstructorBuf, constrPrefixBuf, constrStatBuf, classInitStatBuf = new mutable.ListBuffer[Tree]
+
+ triage()
+
+ val defs = defBuf.toList
+ val auxConstructors = auxConstructorBuf.toList
+ val constructorPrefix = constrPrefixBuf.toList
+ val constructorStats = constrStatBuf.toList
+ val classInitStats = classInitStatBuf.toList
+
+ private def triage() = {
+ // Constant typed vals are not memoized.
+ def memoizeValue(sym: Symbol) = !sym.info.resultType.isInstanceOf[ConstantType]
+
+ // The early initialized field definitions of the class (these are the class members)
+ val presupers = treeInfo.preSuperFields(stats)
+
+ // generate code to copy pre-initialized fields
+ for (stat <- primaryConstrBody.stats) {
+ constrStatBuf += stat
+ stat match {
+ case ValDef(mods, name, _, _) if mods.hasFlag(PRESUPER) => // TODO trait presupers
+ // stat is the constructor-local definition of the field value
+ val fields = presupers filter (_.getterName == name)
+ assert(fields.length == 1, s"expected exactly one field by name $name in $presupers of $clazz's early initializers")
+ val to = fields.head.symbol
+
+ if (memoizeValue(to)) constrStatBuf += mkAssign(to, Ident(stat.symbol))
+ case _ =>
+ }
+ }
- // The list of early initializer statements that go into constructor before the superclass constructor call
- val constrPrefixBuf = new ListBuffer[Tree]
+ val primaryConstrSym = primaryConstr.symbol
+
+ for (stat <- stats) {
+ val statSym = stat.symbol
+
+ // Move the RHS of a ValDef to the appropriate part of the ctor.
+ // If the val is an early initialized or a parameter accessor,
+ // it goes before the superclass constructor call, otherwise it goes after.
+ // A lazy val's effect is not moved to the constructor, as it is delayed.
+ // Returns `true` when a `ValDef` is needed.
+ def moveEffectToCtor(mods: Modifiers, rhs: Tree, assignSym: Symbol): Unit = {
+ val initializingRhs =
+ if ((assignSym eq NoSymbol) || statSym.isLazy) EmptyTree // not memoized, or effect delayed (for lazy val)
+ else if (!mods.hasStaticFlag) intoConstructor(statSym, primaryConstrSym)(rhs)
+ else rhs
+
+ if (initializingRhs ne EmptyTree) {
+ val initPhase =
+ if (mods hasFlag STATIC) classInitStatBuf
+ else if (mods hasFlag PRESUPER | PARAMACCESSOR) constrPrefixBuf
+ else constrStatBuf
+
+ initPhase += mkAssign(assignSym, initializingRhs)
+ }
+ }
- // The early initialized field definitions of the class (these are the class members)
- val presupers = treeInfo.preSuperFields(stats)
+ stat match {
+ // recurse on class definition, store in defBuf
+ case _: ClassDef =>
+ if (statSym.isInterface) defBuf += stat
+ else defBuf += new ConstructorTransformer(unit).transform(stat)
+
+ // primary constructor is already tracked as `primaryConstr`
+ // non-primary constructors go to auxConstructorBuf
+ case _: DefDef if statSym.isConstructor =>
+ if (statSym ne primaryConstrSym) auxConstructorBuf += stat
+
+ // If a val needs a field, an empty valdef goes into the template.
+ // Except for lazy and ConstantTyped vals, the field is initialized by an assignment in:
+ // - the class initializer (static),
+ // - the constructor, before the super call (early initialized or a parameter accessor),
+ // - the constructor, after the super call (regular val).
+ case vd: ValDef =>
+ if (vd.rhs eq EmptyTree) { defBuf += vd }
+ else {
+ val emitField = memoizeValue(statSym)
+
+ if (emitField) {
+ moveEffectToCtor(vd.mods, vd.rhs, statSym)
+ defBuf += deriveValDef(stat)(_ => EmptyTree)
+ }
+ }
- // The list of statements that go into the class initializer
- val classInitStatBuf = new ListBuffer[Tree]
+ case dd: DefDef =>
+ // either move the RHS to ctor (for getter of stored field) or just drop it (for corresponding setter)
+ def shouldMoveRHS =
+ clazz.isTrait && statSym.isAccessor && !statSym.isLazy && (statSym.isSetter || memoizeValue(statSym))
- // generate code to copy pre-initialized fields
- for (stat <- constrBody.stats) {
- constrStatBuf += stat
- stat match {
- case ValDef(mods, name, _, _) if (mods hasFlag PRESUPER) =>
- // stat is the constructor-local definition of the field value
- val fields = presupers filter (_.getterName == name)
- assert(fields.length == 1)
- val to = fields.head.symbol
- if (!to.tpe.isInstanceOf[ConstantType])
- constrStatBuf += mkAssign(to, Ident(stat.symbol))
- case _ =>
- }
- }
+ if ((dd.rhs eq EmptyTree) || !shouldMoveRHS) { defBuf += dd }
+ else {
+ if (statSym.isGetter) moveEffectToCtor(dd.mods, dd.rhs, statSym.asTerm.referenced orElse statSym.setterIn(clazz))
+ defBuf += deriveDefDef(stat)(_ => EmptyTree)
+ }
- // Triage all template definitions to go into defBuf/auxConstructorBuf, constrStatBuf, or constrPrefixBuf.
- for (stat <- stats) stat match {
- case DefDef(_,_,_,_,_,rhs) =>
- // methods with constant result type get literals as their body
- // all methods except the primary constructor go into template
- stat.symbol.tpe match {
- case MethodType(List(), tp @ ConstantType(c)) =>
- defBuf += deriveDefDef(stat)(Literal(c) setPos _.pos setType tp)
- case _ =>
- if (stat.symbol.isPrimaryConstructor) ()
- else if (stat.symbol.isConstructor) auxConstructorBuf += stat
- else defBuf += stat
- }
- case ValDef(mods, _, _, rhs) if !mods.hasStaticFlag =>
- // val defs with constant right-hand sides are eliminated.
- // for all other val defs, an empty valdef goes into the template and
- // the initializer goes as an assignment into the constructor
- // if the val def is an early initialized or a parameter accessor, it goes
- // before the superclass constructor call, otherwise it goes after.
- // Lazy vals don't get the assignment in the constructor.
- if (!stat.symbol.tpe.isInstanceOf[ConstantType]) {
- if (rhs != EmptyTree && !stat.symbol.isLazy) {
- val rhs1 = intoConstructor(stat.symbol, rhs)
- (if (canBeMoved(stat)) constrPrefixBuf else constrStatBuf) += mkAssign(
- stat.symbol, rhs1)
+ // all other statements go into the constructor
+ case _ =>
+ constrStatBuf += intoConstructor(impl.symbol, primaryConstrSym)(stat)
}
- defBuf += deriveValDef(stat)(_ => EmptyTree)
}
- case ValDef(_, _, _, rhs) =>
- // Add static initializer statements to classInitStatBuf and remove the rhs from the val def.
- classInitStatBuf += mkAssign(stat.symbol, rhs)
- defBuf += deriveValDef(stat)(_ => EmptyTree)
-
- case ClassDef(_, _, _, _) =>
- // classes are treated recursively, and left in the template
- defBuf += new ConstructorTransformer(unit).transform(stat)
- case _ =>
- // all other statements go into the constructor
- constrStatBuf += intoConstructor(impl.symbol, stat)
- }
-
- populateOmittables()
-
- // Initialize all parameters fields that must be kept.
- val paramInits = paramAccessors filter mustBeKept map { acc =>
- // Check for conflicting symbol amongst parents: see bug #1960.
- // It would be better to mangle the constructor parameter name since
- // it can only be used internally, but I think we need more robust name
- // mangling before we introduce more of it.
- val conflict = clazz.info.nonPrivateMember(acc.name) filter (s => s.isGetter && !s.isOuterField && s.enclClass.isTrait)
- if (conflict ne NoSymbol)
- reporter.error(acc.pos, "parameter '%s' requires field but conflicts with %s".format(acc.name, conflict.fullLocationString))
-
- copyParam(acc, parameter(acc))
- }
-
- /* Return a pair consisting of (all statements up to and including superclass and trait constr calls, rest) */
- def splitAtSuper(stats: List[Tree]) = {
- def isConstr(tree: Tree): Boolean = tree match {
- case Block(_, expr) => isConstr(expr) // SI-6481 account for named argument blocks
- case _ => (tree.symbol ne null) && tree.symbol.isConstructor
}
- val (pre, rest0) = stats span (!isConstr(_))
- val (supercalls, rest) = rest0 span (isConstr(_))
- (pre ::: supercalls, rest)
}
- val (uptoSuperStats, remainingConstrStats0) = splitAtSuper(constrStatBuf.toList)
- var remainingConstrStats = remainingConstrStats0
-
- rewriteDelayedInit()
+ def transformed = {
+ val triage = new Triage; import triage._
+
+ // omit unused outers
+ val omittableAccessor: Set[Symbol] =
+ if (isDelayedInitSubclass) Set.empty
+ else computeOmittableAccessors(clazz, defs, auxConstructors)
+
+ // TODO: this should omit fields for non-memoized (constant-typed, unit-typed vals need no storage --
+ // all the action is in the getter)
+ def omittableSym(sym: Symbol) = omittableAccessor(sym)
+ def omittableStat(stat: Tree) = omittableSym(stat.symbol)
+
+ // The parameter accessor fields which are members of the class
+ val paramAccessors =
+ if (clazz.isTrait) clazz.info.decls.toList.filter(sym => sym.hasAllFlags(STABLE | PARAMACCESSOR)) // since a trait does not have constructor parameters (yet), these can only come from lambdalift -- right?
+ else clazz.constrParamAccessors
+
+ // Initialize all parameters fields that must be kept.
+ val paramInits = paramAccessors filterNot omittableSym map { acc =>
+ // Check for conflicting field mixed in for a val/var defined in a parent trait (neg/t1960.scala).
+ // Since the fields phase has already mixed in fields, we can just look for
+ // an existing decl with the local variant of our paramaccessor's name.
+ //
+ // TODO: mangle the constructor parameter name (it can only be used internally), though we probably first need more robust name mangling
+
+ // sometimes acc is a field with a local name (when it's a val/var constructor param) --> exclude the `acc` itself when looking for conflicting decl
+ // sometimes it's not (just a constructor param) --> any conflicting decl is a problem
+ val conflict = clazz.info.decl(acc.name.localName).filter(sym => sym ne acc)
+ if (conflict ne NoSymbol) {
+ val orig = exitingTyper(clazz.info.nonPrivateMember(acc.name).filter(_ hasFlag ACCESSOR))
+ reporter.error(acc.pos, s"parameter '${acc.name}' requires field but conflicts with ${(orig orElse conflict).fullLocationString}")
+ }
- // Assemble final constructor
- defBuf += deriveDefDef(constr)(_ =>
- treeCopy.Block(
- constrBody,
- paramInits ::: constrPrefixBuf.toList ::: uptoSuperStats :::
- guardSpecializedInitializer(remainingConstrStats),
- constrBody.expr))
+ val accSetter =
+ if (clazz.isTrait) acc.setterIn(clazz, hasExpandedName = true)
+ else acc
- // Followed by any auxiliary constructors
- defBuf ++= auxConstructorBuf
+ copyParam(accSetter, parameter(acc))
+ }
- // Unlink all fields that can be dropped from class scope
- for (sym <- clazz.info.decls ; if !mustBeKept(sym))
- clazz.info.decls unlink sym
+ // Return a pair consisting of (all statements up to and including superclass and trait constr calls, rest)
+ def splitAtSuper(stats: List[Tree]) = {
+ def isConstr(tree: Tree): Boolean = tree match {
+ case Block(_, expr) => isConstr(expr) // SI-6481 account for named argument blocks
+ case _ => (tree.symbol ne null) && tree.symbol.isConstructor
+ }
+ val (pre, rest0) = stats span (!isConstr(_))
+ val (supercalls, rest) = rest0 span (isConstr(_))
+ (pre ::: supercalls, rest)
+ }
- // Eliminate all field definitions that can be dropped from template
- val templateWithoutOmittables: Template = deriveTemplate(impl)(_ => defBuf.toList filter (stat => mustBeKept(stat.symbol)))
- // Add the static initializers
- val transformed: Template = addStaticInits(templateWithoutOmittables, classInitStatBuf, localTyper)
+ val (uptoSuperStats, remainingConstrStats) = splitAtSuper(constructorStats)
+
+ /* TODO: XXX This condition (`isDelayedInitSubclass && remainingConstrStats.nonEmpty`) is not correct:
+ * remainingConstrStats.nonEmpty excludes too much,
+ * but excluding it includes too much. The constructor sequence being mimicked
+ * needs to be reproduced with total fidelity.
+ *
+ * See test case files/run/bug4680.scala, the output of which is wrong in many
+ * particulars.
+ */
+ val (delayedHookDefs, remainingConstrStatsDelayedInit) =
+ if (isDelayedInitSubclass && remainingConstrStats.nonEmpty) delayedInitDefsAndConstrStats(defs, remainingConstrStats)
+ else (Nil, remainingConstrStats)
+
+ // Assemble final constructor
+ val primaryConstructor = deriveDefDef(primaryConstr)(_ => {
+ treeCopy.Block(
+ primaryConstrBody,
+ paramInits ::: constructorPrefix ::: uptoSuperStats ::: guardSpecializedInitializer(remainingConstrStatsDelayedInit),
+ primaryConstrBody.expr)
+ })
+
+ if (omittableAccessor.exists(_.isOuterField) && !constructorStats.exists(_.exists { case i: Ident if i.symbol.isOuterParam => true; case _ => false}))
+ primaryConstructor.symbol.updateAttachment(OuterArgCanBeElided)
+
+ val constructors = primaryConstructor :: auxConstructors
+
+ // Unlink all fields that can be dropped from class scope
+ // Iterating on toList is cheaper (decls.filter does a toList anyway)
+ val decls = clazz.info.decls
+ decls.toList.filter(omittableSym).foreach(decls.unlink)
+
+ // Eliminate all field/accessor definitions that can be dropped from template
+ // We never eliminate delayed hooks or the constructors, so, only filter `defs`.
+ val prunedStats = (defs filterNot omittableStat) ::: delayedHookDefs ::: constructors
+
+ val statsWithInitChecks =
+ if (settings.checkInit) {
+ val addChecks = new SynthInitCheckedAccessorsIn(currentOwner)
+ prunedStats mapConserve {
+ case dd: DefDef => deriveDefDef(dd)(addChecks.wrapRhsWithInitChecks(dd.symbol))
+ case stat => stat
+ }
+ } else prunedStats
+ // Add the static initializers
+ if (classInitStats.isEmpty) deriveTemplate(impl)(_ => statsWithInitChecks)
+ else {
+ val staticCtor = staticConstructor(statsWithInitChecks, localTyper, impl.pos)(classInitStats)
+ deriveTemplate(impl)(_ => staticCtor :: statsWithInitChecks)
+ }
+ }
} // TemplateTransformer
-
}
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
index 8e323de623..034cf118d7 100644
--- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
+++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
@@ -4,35 +4,21 @@ package transform
import symtab._
import Flags._
import scala.collection._
-import scala.language.postfixOps
-import scala.reflect.internal.Symbols
-import scala.collection.mutable.LinkedHashMap
/**
- * This transformer is responsible for preparing lambdas for runtime, by either translating to anonymous classes
- * or to a tree that will be convereted to invokedynamic by the JVM 1.8+ backend.
- *
- * The main assumption it makes is that a lambda {args => body} has been turned into
- * {args => liftedBody()} where lifted body is a top level method that implements the body of the lambda.
- * Currently Uncurry is responsible for that transformation.
- *
- * From a lambda, Delambdafy will create:
- *
- * Under -target:jvm-1.7 and below:
- *
- * 1) a new top level class that
- a) has fields and a constructor taking the captured environment (including possibly the "this"
- * reference)
- * b) an apply method that calls the target method
- * c) if needed a bridge method for the apply method
- * 2) an instantiation of the newly created class which replaces the lambda
- *
- * Under -target:jvm-1.8 with GenBCode:
- *
- * 1) An application of the captured arguments to a fictional symbol representing the lambda factory.
- * This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`.
- * The captured arguments include `this` if `liftedBody` is unable to be made STATIC.
- */
+ * This transformer is responsible for preparing Function nodes for runtime,
+ * by translating to a tree that will be converted to an invokedynamic by the backend.
+ *
+ * The main assumption it makes is that a Function {args => body} has been turned into
+ * {args => liftedBody()} where lifted body is a top level method that implements the body of the function.
+ * Currently Uncurry is responsible for that transformation.
+ *
+ * From this shape of Function, Delambdafy will create:
+ *
+ * An application of the captured arguments to a fictional symbol representing the lambda factory.
+ * This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`.
+ * The captured arguments include `this` if `liftedBody` is unable to be made STATIC.
+ */
abstract class Delambdafy extends Transform with TypingTransformers with ast.TreeDSL with TypeAdaptingTransformer {
import global._
import definitions._
@@ -42,6 +28,19 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
/** the following two members override abstract members in Transform */
val phaseName: String = "delambdafy"
+ final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol, sam: Symbol, isSerializable: Boolean, addScalaSerializableMarker: Boolean)
+
+ /**
+ * Get the symbol of the target lifted lambda body method from a function. I.e. if
+ * the function is {args => anonfun(args)} then this method returns anonfun's symbol
+ */
+ private def targetMethod(fun: Function): Symbol = fun match {
+ case Function(_, Apply(target, _)) => target.symbol
+ case _ =>
+ // any other shape of Function is unexpected at this point
+ abort(s"could not understand function with tree $fun")
+ }
+
override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = {
if (settings.Ydelambdafy.value == "method") new Phase(prev)
else new SkipPhase(prev)
@@ -54,433 +53,250 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
protected def newTransformer(unit: CompilationUnit): Transformer =
new DelambdafyTransformer(unit)
- class DelambdafyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) with TypeAdapter {
- private val lambdaClassDefs = new mutable.LinkedHashMap[Symbol, List[Tree]] withDefaultValue Nil
-
-
- val typer = localTyper
-
- // we need to know which methods refer to the 'this' reference so that we can determine
- // which lambdas need access to it
- val thisReferringMethods: Set[Symbol] = {
- val thisReferringMethodsTraverser = new ThisReferringMethodsTraverser()
- thisReferringMethodsTraverser traverse unit.body
- val methodReferringMap = thisReferringMethodsTraverser.liftedMethodReferences
- val referrers = thisReferringMethodsTraverser.thisReferringMethods
- // recursively find methods that refer to 'this' directly or indirectly via references to other methods
- // for each method found add it to the referrers set
- def refersToThis(symbol: Symbol): Boolean = {
- if (referrers contains symbol) true
- else if (methodReferringMap(symbol) exists refersToThis) {
- // add it early to memoize
- debuglog(s"$symbol indirectly refers to 'this'")
- referrers += symbol
- true
- } else false
+ class DelambdafyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {
+ // we need to know which methods refer to the 'this' reference so that we can determine which lambdas need access to it
+ // TODO: this looks expensive, so I made it a lazy val. Can we make it more pay-as-you-go / optimize for common shapes?
+ private[this] lazy val methodReferencesThis: Set[Symbol] =
+ (new ThisReferringMethodsTraverser).methodReferencesThisIn(unit.body)
+
+ private def mkLambdaMetaFactoryCall(fun: Function, target: Symbol, functionalInterface: Symbol, samUserDefined: Symbol, isSpecialized: Boolean): Tree = {
+ val pos = fun.pos
+ def isSelfParam(p: Symbol) = p.isSynthetic && p.name == nme.SELF
+ val hasSelfParam = isSelfParam(target.firstParam)
+
+ val allCapturedArgRefs = {
+ // find which variables are free in the lambda because those are captures that need to be
+ // passed into the constructor of the anonymous function class
+ val captureArgs = FreeVarTraverser.freeVarsOf(fun).iterator.map(capture =>
+ gen.mkAttributedRef(capture) setPos pos
+ ).toList
+
+ if (!hasSelfParam) captureArgs.filterNot(arg => isSelfParam(arg.symbol))
+ else if (currentMethod.hasFlag(Flags.STATIC)) captureArgs
+ else (gen.mkAttributedThis(fun.symbol.enclClass) setPos pos) :: captureArgs
}
- methodReferringMap.keys foreach refersToThis
- referrers
- }
-
- // the result of the transformFunction method.
- sealed abstract class TransformedFunction
- // A class definition for the lambda, an expression instantiating the lambda class
- case class DelambdafyAnonClass(lambdaClassDef: ClassDef, newExpr: Tree) extends TransformedFunction
- case class InvokeDynamicLambda(tree: Apply) extends TransformedFunction
-
- private val boxingBridgeMethods = mutable.ArrayBuffer[Tree]()
-
- // here's the main entry point of the transform
- override def transform(tree: Tree): Tree = tree match {
- // the main thing we care about is lambdas
- case fun @ Function(_, _) =>
- transformFunction(fun) match {
- case DelambdafyAnonClass(lambdaClassDef, newExpr) =>
- // a lambda becomes a new class, an instantiation expression
- val pkg = lambdaClassDef.symbol.owner
-
- // we'll add the lambda class to the package later
- lambdaClassDefs(pkg) = lambdaClassDef :: lambdaClassDefs(pkg)
-
- super.transform(newExpr)
- case InvokeDynamicLambda(apply) =>
- // ... or an invokedynamic call
- super.transform(apply)
- }
- case Template(_, _, _) =>
- try {
- // during this call boxingBridgeMethods will be populated from the Function case
- val Template(parents, self, body) = super.transform(tree)
- Template(parents, self, body ++ boxingBridgeMethods)
- } finally boxingBridgeMethods.clear()
- case _ => super.transform(tree)
- }
- // this entry point is aimed at the statements in the compilation unit.
- // 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] = {
- // 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
-
- // turns a lambda into a new class def, a New expression instantiating that class
- private def transformFunction(originalFunction: Function): TransformedFunction = {
- val functionTpe = originalFunction.tpe
- val targs = functionTpe.typeArgs
- val formals :+ restpe = targs
- val oldClass = originalFunction.symbol.enclClass
+ // Create a symbol representing a fictional lambda factory method that accepts the captured
+ // arguments and returns the SAM type.
+ val msym = {
+ val meth = currentOwner.newMethod(nme.ANON_FUN_NAME, pos, ARTIFACT)
+ val capturedParams = meth.newSyntheticValueParams(allCapturedArgRefs.map(_.tpe))
+ meth.setInfo(MethodType(capturedParams, fun.tpe))
+ }
- // find which variables are free in the lambda because those are captures that need to be
- // passed into the constructor of the anonymous function class
- val captures = FreeVarTraverser.freeVarsOf(originalFunction)
+ // We then apply this symbol to the captures.
+ val apply = localTyper.typedPos(pos)(Apply(Ident(msym), allCapturedArgRefs))
- val target = targetMethod(originalFunction)
- target.makeNotPrivate(target.owner)
- if (!thisReferringMethods.contains(target))
- target setFlag STATIC
-
- val isStatic = target.hasFlag(STATIC)
-
- def createBoxingBridgeMethod(functionParamTypes: List[Type], functionResultType: Type): Tree = {
- // Note: we bail out of this method and return EmptyTree if we find there is no adaptation required.
- // If we need to improve performance, we could check the types first before creating the
- // method and parameter symbols.
- val methSym = oldClass.newMethod(target.name.append("$adapted").toTermName, target.pos, target.flags | FINAL | ARTIFACT)
- var neededAdaptation = false
- def boxedType(tpe: Type): Type = {
- if (isPrimitiveValueClass(tpe.typeSymbol)) {neededAdaptation = true; ObjectTpe}
- else if (enteringErasure(tpe.typeSymbol.isDerivedValueClass)) {neededAdaptation = true; ObjectTpe}
- else tpe
- }
- val targetParams: List[Symbol] = target.paramss.head
- val numCaptures = targetParams.length - functionParamTypes.length
- val (targetCaptureParams, targetFunctionParams) = targetParams.splitAt(numCaptures)
- val bridgeParams: List[Symbol] =
- targetCaptureParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName)) :::
- map2(targetFunctionParams, functionParamTypes)((param, tp) => methSym.newSyntheticValueParam(boxedType(tp), param.name.toTermName))
-
- val bridgeResultType: Type = {
- if (target.info.resultType == UnitTpe && functionResultType != UnitTpe) {
- neededAdaptation = true
- ObjectTpe
- } else
- boxedType(functionResultType)
- }
- val methodType = MethodType(bridgeParams, bridgeResultType)
- methSym setInfo methodType
- if (!neededAdaptation)
- EmptyTree
- else {
- val bridgeParamTrees = bridgeParams.map(ValDef(_))
-
- oldClass.info.decls enter methSym
-
- val body = localTyper.typedPos(originalFunction.pos) {
- val newTarget = Select(gen.mkAttributedThis(oldClass), target)
- val args: List[Tree] = mapWithIndex(bridgeParams) { (param, i) =>
- if (i < numCaptures) {
- gen.mkAttributedRef(param)
- } else {
- val functionParam = functionParamTypes(i - numCaptures)
- val targetParam = targetParams(i)
- if (enteringErasure(functionParam.typeSymbol.isDerivedValueClass)) {
- val casted = cast(gen.mkAttributedRef(param), functionParam)
- val unboxed = unbox(casted, ErasedValueType(functionParam.typeSymbol, targetParam.tpe)).modifyType(postErasure.elimErasedValueType)
- unboxed
- } else adaptToType(gen.mkAttributedRef(param), targetParam.tpe)
- }
- }
- gen.mkMethodCall(newTarget, args)
- }
- val body1 = if (enteringErasure(functionResultType.typeSymbol.isDerivedValueClass))
- adaptToType(box(body.setType(ErasedValueType(functionResultType.typeSymbol, body.tpe)), "boxing lambda target"), bridgeResultType)
- else adaptToType(body, bridgeResultType)
- val methDef0 = DefDef(methSym, List(bridgeParamTrees), body1)
- postErasure.newTransformer(unit).transform(methDef0).asInstanceOf[DefDef]
- }
+ // TODO: this is a bit gross
+ val sam = samUserDefined orElse {
+ if (isSpecialized) functionalInterface.info.decls.find(_.isDeferred).get
+ else functionalInterface.info.member(nme.apply)
}
- /**
- * Creates the apply method for the anonymous subclass of FunctionN
- */
- def createApplyMethod(newClass: Symbol, fun: Function, thisProxy: Symbol): DefDef = {
- val methSym = newClass.newMethod(nme.apply, fun.pos, FINAL | SYNTHETIC)
- val params = fun.vparams map (_.duplicate)
-
- val paramSyms = map2(formals, params) {
- (tp, vparam) => methSym.newSyntheticValueParam(tp, vparam.name)
- }
- params zip paramSyms foreach { case (valdef, sym) => valdef.symbol = sym }
- params foreach (_.symbol.owner = methSym)
- val methodType = MethodType(paramSyms, restpe)
- methSym setInfo methodType
+ // no need for adaptation when the implemented sam is of a specialized built-in function type
+ val lambdaTarget = if (isSpecialized) target else createBoxingBridgeMethodIfNeeded(fun, target, functionalInterface, sam)
+ val isSerializable = samUserDefined == NoSymbol || samUserDefined.owner.isNonBottomSubClass(definitions.JavaSerializableClass)
+ val addScalaSerializableMarker = samUserDefined == NoSymbol
- newClass.info.decls enter methSym
+ // The backend needs to know the target of the lambda and the functional interface in order
+ // to emit the invokedynamic instruction. We pass this information as tree attachment.
+ //
+ // see https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html
+ // instantiatedMethodType is derived from lambdaTarget's signature
+ // samMethodType is derived from samOf(functionalInterface)'s signature
+ apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, fun.vparams.length, functionalInterface, sam, isSerializable, addScalaSerializableMarker))
- val Apply(_, oldParams) = fun.body
- val qual = if (thisProxy.exists)
- Select(gen.mkAttributedThis(newClass), thisProxy)
- else
- gen.mkAttributedThis(oldClass) // sort of a lie, EmptyTree.<static method> would be more honest, but the backend chokes on that.
+ apply
+ }
- val body = localTyper typed Apply(Select(qual, target), oldParams)
- body.substituteSymbols(fun.vparams map (_.symbol), params map (_.symbol))
- body changeOwner (fun.symbol -> methSym)
- val methDef = DefDef(methSym, List(params), body)
+ private val boxingBridgeMethods = mutable.ArrayBuffer[Tree]()
- // Have to repack the type to avoid mismatches when existentials
- // appear in the result - see SI-4869.
- // TODO probably don't need packedType
- methDef.tpt setType localTyper.packedType(body, methSym)
- methDef
- }
+ private def reboxValueClass(tp: Type) = tp match {
+ case ErasedValueType(valueClazz, _) => TypeRef(NoPrefix, valueClazz, Nil)
+ case _ => tp
+ }
- /**
- * Creates the constructor on the newly created class. It will handle
- * initialization of members that represent the captured environment
- */
- def createConstructor(newClass: Symbol, members: List[ValDef]): DefDef = {
- val constrSym = newClass.newConstructor(originalFunction.pos, SYNTHETIC)
-
- val (paramSymbols, params, assigns) = (members map {member =>
- val paramSymbol = newClass.newVariable(member.symbol.name.toTermName, newClass.pos, 0)
- paramSymbol.setInfo(member.symbol.info)
- val paramVal = ValDef(paramSymbol)
- val paramIdent = Ident(paramSymbol)
- val assign = Assign(Select(gen.mkAttributedThis(newClass), member.symbol), paramIdent)
-
- (paramSymbol, paramVal, assign)
- }).unzip3
-
- val constrType = MethodType(paramSymbols, newClass.thisType)
- constrSym setInfoAndEnter constrType
-
- val body =
- Block(
- List(
- Apply(Select(Super(gen.mkAttributedThis(newClass), tpnme.EMPTY) setPos newClass.pos, nme.CONSTRUCTOR) setPos newClass.pos, Nil) setPos newClass.pos
- ) ++ assigns,
- Literal(Constant(())): Tree
- ) setPos newClass.pos
-
- (localTyper typed DefDef(constrSym, List(params), body) setPos newClass.pos).asInstanceOf[DefDef]
- }
+ // exclude primitives and value classes, which need special boxing
+ private def isReferenceType(tp: Type) = !tp.isInstanceOf[ErasedValueType] && {
+ val sym = tp.typeSymbol
+ !(isPrimitiveValueClass(sym) || sym.isDerivedValueClass)
+ }
- val pkg = oldClass.owner
-
- // Parent for anonymous class def
- val abstractFunctionErasedType = AbstractFunctionClass(formals.length).tpe
-
- // anonymous subclass of FunctionN with an apply method
- def makeAnonymousClass: ClassDef = {
- val parents = addSerializable(abstractFunctionErasedType)
- val funOwner = originalFunction.symbol.owner
-
- // TODO harmonize the naming of delambdafy anon-fun classes with those spun up by Uncurry
- // - make `anonClass.isAnonymousClass` true.
- // - use `newAnonymousClassSymbol` or push the required variations into a similar factory method
- // - reinstate the assertion in `Erasure.resolveAnonymousBridgeClash`
- val suffix = nme.DELAMBDAFY_LAMBDA_CLASS_NAME + "$" + (
- if (funOwner.isPrimaryConstructor) ""
- else "$" + funOwner.name + "$"
- )
- val oldClassPart = oldClass.name.decode
- // make sure the class name doesn't contain $anon, otherwise isAnonymousClass/Function may be true
- val name = unit.freshTypeName(s"$oldClassPart$suffix".replace("$anon", "$nestedInAnon"))
-
- val lambdaClass = pkg newClassSymbol(name, originalFunction.pos, FINAL | SYNTHETIC) addAnnotation SerialVersionUIDAnnotation
- lambdaClass.associatedFile = unit.source.file
- // 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)
-
- val captureProxies2 = new LinkedHashMap[Symbol, TermSymbol]
- captures foreach {capture =>
- val sym = lambdaClass.newVariable(unit.freshTermName(capture.name.toString + "$"), capture.pos, SYNTHETIC)
- sym setInfo capture.info
- captureProxies2 += ((capture, sym))
- }
+ // determine which lambda target to use with java's LMF -- create a new one if scala-specific boxing is required
+ def createBoxingBridgeMethodIfNeeded(fun: Function, target: Symbol, functionalInterface: Symbol, sam: Symbol): Symbol = {
+ val oldClass = fun.symbol.enclClass
+ val pos = fun.pos
+
+ // At erasure, there won't be any captured arguments (they are added in constructors)
+ val functionParamTypes = exitingErasure(target.info.paramTypes)
+ val functionResultType = exitingErasure(target.info.resultType)
+
+ val samParamTypes = exitingErasure(sam.info.paramTypes)
+ val samResultType = exitingErasure(sam.info.resultType)
+
+ /** How to satisfy the linking invariants of https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html
+ *
+ * Given samMethodType: (U1..Un)Ru and function type T1,..., Tn => Rt (the target method created by uncurry)
+ *
+ * Do we need a bridge, or can we use the original lambda target for implMethod: (<captured args> A1..An)Ra
+ * (We can ignore capture here.)
+ *
+ * If, for i=1..N:
+ * Ai =:= Ui || (Ai <:< Ui <:< AnyRef)
+ * Ru =:= void || (Ra =:= Ru || (Ra <:< AnyRef, Ru <:< AnyRef))
+ *
+ * We can use the target method as-is -- if not, we create a bridging one that uses the types closest
+ * to the target method that still meet the above requirements.
+ */
+ val resTpOk = (
+ samResultType =:= UnitTpe
+ || functionResultType =:= samResultType
+ || (isReferenceType(samResultType) && isReferenceType(functionResultType))) // yes, this is what the spec says -- no further correspondence required
+ if (resTpOk && (samParamTypes corresponds functionParamTypes){ (samParamTp, funParamTp) =>
+ funParamTp =:= samParamTp || (isReferenceType(funParamTp) && isReferenceType(samParamTp) && funParamTp <:< samParamTp) }) target
+ else {
+ // We have to construct a new lambda target that bridges to the one created by uncurry.
+ // The bridge must satisfy the above invariants, while also minimizing adaptation on our end.
+ // LMF will insert runtime casts according to the spec at the above link.
+
+ // we use the more precise type between samParamTp and funParamTp to minimize boxing in the bridge method
+ // we are constructing a method whose signature matches the sam's signature (because the original target did not)
+ // whenever a type in the sam's signature is (erases to) a primitive type, we must pick the sam's version,
+ // as we don't implement the logic regarding widening that's performed by LMF -- we require =:= for primitives
+ //
+ // We use the sam's type for the check whether we're dealing with a reference type, as it could be a generic type,
+ // which means the function's parameter -- even if it expects a value class -- will need to be
+ // boxed on the generic call to the sam method.
- // the Optional proxy that will hold a reference to the 'this'
- // object used by the lambda, if any. NoSymbol if there is no this proxy
- val thisProxy = {
- if (isStatic)
- NoSymbol
- else {
- val sym = lambdaClass.newVariable(nme.FAKE_LOCAL_THIS, originalFunction.pos, SYNTHETIC)
- sym.setInfo(oldClass.tpe)
- }
+ val bridgeParamTypes = map2(samParamTypes, functionParamTypes){ (samParamTp, funParamTp) =>
+ if (isReferenceType(samParamTp) && funParamTp <:< samParamTp) funParamTp
+ else samParamTp
}
- val decapturify = new DeCapturifyTransformer(captureProxies2, unit, oldClass, lambdaClass, originalFunction.symbol.pos, thisProxy)
-
- val decapturedFunction = decapturify.transform(originalFunction).asInstanceOf[Function]
+ val bridgeResultType =
+ if (resTpOk && isReferenceType(samResultType) && functionResultType <:< samResultType) functionResultType
+ else samResultType
+
+ val typeAdapter = new TypeAdapter { def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) }
+ import typeAdapter.{adaptToType, unboxValueClass}
+
+ val targetParams = target.paramss.head
+ val numCaptures = targetParams.length - functionParamTypes.length
+ val (targetCapturedParams, targetFunctionParams) = targetParams.splitAt(numCaptures)
+
+ val methSym = oldClass.newMethod(target.name.append("$adapted").toTermName, target.pos, target.flags | FINAL | ARTIFACT | STATIC)
+ val bridgeCapturedParams = targetCapturedParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName))
+ val bridgeFunctionParams =
+ map2(targetFunctionParams, bridgeParamTypes)((param, tp) => methSym.newSyntheticValueParam(tp, param.name.toTermName))
+
+ val bridgeParams = bridgeCapturedParams ::: bridgeFunctionParams
+
+ methSym setInfo MethodType(bridgeParams, bridgeResultType)
+ oldClass.info.decls enter methSym
+
+ val forwarderCall = localTyper.typedPos(pos) {
+ val capturedArgRefs = bridgeCapturedParams map gen.mkAttributedRef
+ val functionArgRefs =
+ map3(bridgeFunctionParams, functionParamTypes, targetParams.drop(numCaptures)) { (bridgeParam, functionParamTp, targetParam) =>
+ val bridgeParamRef = gen.mkAttributedRef(bridgeParam)
+ val targetParamTp = targetParam.tpe
+
+ // TODO: can we simplify this to something like `adaptToType(adaptToType(bridgeParamRef, functionParamTp), targetParamTp)`?
+ val unboxed =
+ functionParamTp match {
+ case ErasedValueType(clazz, underlying) =>
+ // when the original function expected an argument of value class type,
+ // the original target will expect the unboxed underlying value,
+ // whereas the bridge will receive the boxed value (since the sam's argument type did not match and we had to adapt)
+ localTyper.typed(unboxValueClass(bridgeParamRef, clazz, underlying), targetParamTp)
+ case _ => bridgeParamRef
+ }
+
+ adaptToType(unboxed, targetParamTp)
+ }
- val members = (optionSymbol(thisProxy).toList ++ (captureProxies2 map (_._2))) map {member =>
- lambdaClass.info.decls enter member
- ValDef(member, gen.mkZero(member.tpe)) setPos decapturedFunction.pos
+ gen.mkMethodCall(Select(gen.mkAttributedThis(oldClass), target), capturedArgRefs ::: functionArgRefs)
}
- // constructor
- val constr = createConstructor(lambdaClass, members)
-
- // apply method with same arguments and return type as original lambda.
- val applyMethodDef = createApplyMethod(lambdaClass, decapturedFunction, thisProxy)
-
- val bridgeMethod = createBridgeMethod(lambdaClass, originalFunction, applyMethodDef)
-
- def fulldef(sym: Symbol) =
- if (sym == NoSymbol) sym.toString
- else s"$sym: ${sym.tpe} in ${sym.owner}"
+ val bridge = postErasure.newTransformer(unit).transform(DefDef(methSym, List(bridgeParams.map(ValDef(_))),
+ adaptToType(forwarderCall setType functionResultType, bridgeResultType))).asInstanceOf[DefDef]
- bridgeMethod foreach (bm =>
- // TODO SI-6260 maybe just create the apply method with the signature (Object => Object) in all cases
- // rather than the method+bridge pair.
- if (bm.symbol.tpe =:= applyMethodDef.symbol.tpe)
- erasure.resolveAnonymousBridgeClash(applyMethodDef.symbol, bm.symbol)
- )
-
- val body = members ++ List(constr, applyMethodDef) ++ bridgeMethod
-
- // TODO if member fields are private this complains that they're not accessible
- localTyper.typedPos(decapturedFunction.pos)(ClassDef(lambdaClass, body)).asInstanceOf[ClassDef]
- }
-
- val allCaptureArgs: List[Tree] = {
- val thisArg = if (isStatic) Nil else (gen.mkAttributedThis(oldClass) setPos originalFunction.pos) :: Nil
- val captureArgs = captures.iterator.map(capture => gen.mkAttributedRef(capture) setPos originalFunction.pos).toList
- thisArg ::: captureArgs
- }
-
- val arity = originalFunction.vparams.length
-
- // Reconstruct the type of the function entering erasure.
- // We do this by taking the type after erasure, and re-boxing `ErasedValueType`.
- //
- // Unfortunately, the more obvious `enteringErasure(target.info)` doesn't work
- // as we would like, value classes in parameter position show up as the unboxed types.
- val (functionParamTypes, functionResultType) = exitingErasure {
- def boxed(tp: Type) = tp match {
- case ErasedValueType(valueClazz, _) => TypeRef(NoPrefix, valueClazz, Nil)
- case _ => tp
- }
- // We don't need to deeply map `boxedValueClassType` over the infos as `ErasedValueType`
- // will only appear directly as a parameter type in a method signature, as shown
- // https://gist.github.com/retronym/ba81dbd462282c504ff8
- val info = target.info
- val boxedParamTypes = info.paramTypes.takeRight(arity).map(boxed)
- (boxedParamTypes, boxed(info.resultType))
- }
- val functionType = definitions.functionType(functionParamTypes, functionResultType)
-
- val (functionalInterface, isSpecialized) = java8CompatFunctionalInterface(target, functionType)
- if (functionalInterface.exists) {
- // Create a symbol representing a fictional lambda factory method that accepts the captured
- // arguments and returns a Function.
- val msym = currentOwner.newMethod(nme.ANON_FUN_NAME, originalFunction.pos, ARTIFACT)
- val argTypes: List[Type] = allCaptureArgs.map(_.tpe)
- val params = msym.newSyntheticValueParams(argTypes)
- msym.setInfo(MethodType(params, functionType))
- val arity = originalFunction.vparams.length
-
- val lambdaTarget =
- if (isSpecialized)
- target
- else {
- createBoxingBridgeMethod(functionParamTypes, functionResultType) match {
- case EmptyTree =>
- target
- case bridge =>
- boxingBridgeMethods += bridge
- bridge.symbol
- }
- }
-
- // We then apply this symbol to the captures.
- val apply = localTyper.typedPos(originalFunction.pos)(Apply(Ident(msym), allCaptureArgs)).asInstanceOf[Apply]
-
- // The backend needs to know the target of the lambda and the functional interface in order
- // to emit the invokedynamic instruction. We pass this information as tree attachment.
- apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, arity, functionalInterface))
- InvokeDynamicLambda(apply)
- } else {
- val anonymousClassDef = makeAnonymousClass
- pkg.info.decls enter anonymousClassDef.symbol
- val newStat = Typed(New(anonymousClassDef.symbol, allCaptureArgs: _*), TypeTree(abstractFunctionErasedType))
- val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat)
- DelambdafyAnonClass(anonymousClassDef, typedNewStat)
+ boxingBridgeMethods += bridge
+ bridge.symbol
}
}
- /**
- * Creates a bridge method if needed. The bridge method forwards from apply(x1: Object, x2: Object...xn: Object): Object to
- * apply(x1: T1, x2: T2...xn: Tn): T0 using type adaptation on each input and output. The only time a bridge isn't needed
- * is when the original lambda is already erased to type Object, Object, Object... => Object
- */
- def createBridgeMethod(newClass:Symbol, originalFunction: Function, applyMethod: DefDef): Option[DefDef] = {
- val bridgeMethSym = newClass.newMethod(nme.apply, applyMethod.pos, FINAL | SYNTHETIC | BRIDGE)
- val originalParams = applyMethod.vparamss(0)
- val bridgeParams = originalParams map { originalParam =>
- val bridgeSym = bridgeMethSym.newSyntheticValueParam(ObjectTpe, originalParam.name)
- ValDef(bridgeSym)
- }
- val bridgeSyms = bridgeParams map (_.symbol)
+ private def transformFunction(originalFunction: Function): Tree = {
+ val target = targetMethod(originalFunction)
+ assert(target.hasFlag(Flags.STATIC))
+ target.setFlag(notPRIVATE)
- val methodType = MethodType(bridgeSyms, ObjectTpe)
- bridgeMethSym setInfo methodType
+ val funSym = originalFunction.tpe.typeSymbolDirect
+ // The functional interface that can be used to adapt the lambda target method `target` to the given function type.
+ val (functionalInterface, isSpecialized) =
+ if (!isFunctionSymbol(funSym)) (funSym, false)
+ else {
+ val specializedName =
+ specializeTypes.specializedFunctionName(funSym,
+ exitingErasure(target.info.paramTypes).map(reboxValueClass) :+ reboxValueClass(exitingErasure(target.info.resultType))).toTypeName
+
+ val isSpecialized = specializedName != funSym.name
+ val functionalInterface =
+ if (isSpecialized) {
+ // Unfortunately we still need to use custom functional interfaces for specialized functions so that the
+ // unboxed apply method is left abstract for us to implement.
+ currentRun.runDefinitions.Scala_Java8_CompatPackage.info.decl(specializedName.prepend("J"))
+ }
+ else FunctionClass(originalFunction.vparams.length)
- def adapt(tree: Tree, expectedTpe: Type): (Boolean, Tree) = {
- if (tree.tpe =:= expectedTpe) (false, tree)
- else (true, adaptToType(tree, expectedTpe))
- }
+ (functionalInterface, isSpecialized)
+ }
- def adaptAndPostErase(tree: Tree, pt: Type): (Boolean, Tree) = {
- val (needsAdapt, adaptedTree) = adapt(tree, pt)
- val trans = postErasure.newTransformer(unit)
- val postErasedTree = trans.atOwner(currentOwner)(trans.transform(adaptedTree)) // SI-8017 eliminates ErasedValueTypes
- (needsAdapt, postErasedTree)
- }
+ val sam = originalFunction.attachments.get[SAMFunction].map(_.sam).getOrElse(NoSymbol)
+ mkLambdaMetaFactoryCall(originalFunction, target, functionalInterface, sam, isSpecialized)
+ }
- enteringPhase(currentRun.posterasurePhase) {
- // e.g, in:
- // class C(val a: Int) extends AnyVal; (x: Int) => new C(x)
- //
- // This type is:
- // (x: Int)ErasedValueType(class C, Int)
- val liftedBodyDefTpe: MethodType = {
- val liftedBodySymbol = {
- val Apply(method, _) = originalFunction.body
- method.symbol
- }
- liftedBodySymbol.info.asInstanceOf[MethodType]
+ // here's the main entry point of the transform
+ override def transform(tree: Tree): Tree = tree match {
+ // the main thing we care about is lambdas
+ case fun: Function =>
+ super.transform(transformFunction(fun))
+ case Template(_, _, _) =>
+ def pretransform(tree: Tree): Tree = tree match {
+ case dd: DefDef if dd.symbol.isDelambdafyTarget =>
+ if (!dd.symbol.hasFlag(STATIC) && methodReferencesThis(dd.symbol)) {
+ gen.mkStatic(dd, dd.symbol.name, sym => sym)
+ } else {
+ dd.symbol.setFlag(STATIC)
+ dd
+ }
+ case t => t
}
- val (paramNeedsAdaptation, adaptedParams) = (bridgeSyms zip liftedBodyDefTpe.params map {case (bridgeSym, param) => adapt(Ident(bridgeSym) setType bridgeSym.tpe, param.tpe)}).unzip
- // SI-8017 Before, this code used `applyMethod.symbol.info.resultType`.
- // But that symbol doesn't have a type history that goes back before `delambdafy`,
- // so we just see a plain `Int`, rather than `ErasedValueType(C, Int)`.
- // This triggered primitive boxing, rather than value class boxing.
- val resTp = liftedBodyDefTpe.finalResultType
- val body = Apply(gen.mkAttributedSelect(gen.mkAttributedThis(newClass), applyMethod.symbol), adaptedParams) setType resTp
- val (needsReturnAdaptation, adaptedBody) = adaptAndPostErase(body, ObjectTpe)
-
- val needsBridge = (paramNeedsAdaptation contains true) || needsReturnAdaptation
- if (needsBridge) {
- val methDef = DefDef(bridgeMethSym, List(bridgeParams), adaptedBody)
- newClass.info.decls enter bridgeMethSym
- Some((localTyper typed methDef).asInstanceOf[DefDef])
- } else None
- }
+ try {
+ // during this call boxingBridgeMethods will be populated from the Function case
+ val Template(parents, self, body) = super.transform(deriveTemplate(tree)(_.mapConserve(pretransform)))
+ Template(parents, self, body ++ boxingBridgeMethods)
+ } finally boxingBridgeMethods.clear()
+ case dd: DefDef if dd.symbol.isLiftedMethod && !dd.symbol.isDelambdafyTarget =>
+ // SI-9390 emit lifted methods that don't require a `this` reference as STATIC
+ // delambdafy targets are excluded as they are made static by `transformFunction`.
+ if (!dd.symbol.hasFlag(STATIC) && !methodReferencesThis(dd.symbol)) {
+ dd.symbol.setFlag(STATIC)
+ dd.symbol.removeAttachment[mixer.NeedStaticImpl.type]
+ }
+ super.transform(tree)
+ case Apply(fun, outer :: rest) if shouldElideOuterArg(fun.symbol, outer) =>
+ val nullOuter = gen.mkZero(outer.tpe)
+ treeCopy.Apply(tree, transform(fun), nullOuter :: transformTrees(rest))
+ case _ => super.transform(tree)
}
} // DelambdafyTransformer
+ private def shouldElideOuterArg(fun: Symbol, outerArg: Tree): Boolean =
+ fun.isConstructor && treeInfo.isQualifierSafeToElide(outerArg) && fun.hasAttachment[OuterArgCanBeElided.type]
+
// A traverser that finds symbols used but not defined in the given Tree
// TODO freeVarTraverser in LambdaLift does a very similar task. With some
// analysis this could probably be unified with it
@@ -513,40 +329,45 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
}
}
- // A transformer that converts specified captured symbols into other symbols
- // TODO this transform could look more like ThisSubstituter and TreeSymSubstituter. It's not clear that it needs that level of sophistication since the types
- // at this point are always very simple flattened/erased types, but it would probably be more robust if it tried to take more complicated types into account
- class DeCapturifyTransformer(captureProxies: Map[Symbol, TermSymbol], unit: CompilationUnit, oldClass: Symbol, newClass:Symbol, pos: Position, thisProxy: Symbol) extends TypingTransformer(unit) {
- override def transform(tree: Tree) = tree match {
- case tree@This(encl) if tree.symbol == oldClass && thisProxy.exists =>
- gen mkAttributedSelect (gen mkAttributedThis newClass, thisProxy)
- case Ident(name) if (captureProxies contains tree.symbol) =>
- gen mkAttributedSelect (gen mkAttributedThis newClass, captureProxies(tree.symbol))
- case _ => super.transform(tree)
+ // finds all methods that reference 'this'
+ class ThisReferringMethodsTraverser extends Traverser {
+ // the set of methods that refer to this
+ private val thisReferringMethods = mutable.Set[Symbol]()
+
+ // the set of lifted lambda body methods that each method refers to
+ private val liftedMethodReferences = mutable.Map[Symbol, Set[Symbol]]().withDefault(_ => mutable.Set())
+
+ def methodReferencesThisIn(tree: Tree) = {
+ traverse(tree)
+ liftedMethodReferences.keys foreach refersToThis
+
+ thisReferringMethods
}
- }
- /**
- * Get the symbol of the target lifted lambda body method from a function. I.e. if
- * the function is {args => anonfun(args)} then this method returns anonfun's symbol
- */
- private def targetMethod(fun: Function): Symbol = fun match {
- case Function(_, Apply(target, _)) =>
- target.symbol
- case _ =>
- // any other shape of Function is unexpected at this point
- abort(s"could not understand function with tree $fun")
- }
+ // recursively find methods that refer to 'this' directly or indirectly via references to other methods
+ // for each method found add it to the referrers set
+ private def refersToThis(symbol: Symbol): Boolean = {
+ val seen = mutable.Set[Symbol]()
+ def loop(symbol: Symbol): Boolean = {
+ if (seen(symbol)) false
+ else {
+ seen += symbol
+ (thisReferringMethods contains symbol) ||
+ (liftedMethodReferences(symbol) exists loop) && {
+ // add it early to memoize
+ debuglog(s"$symbol indirectly refers to 'this'")
+ thisReferringMethods += symbol
+ true
+ }
+ }
+ }
+ loop(symbol)
+ }
- // finds all methods that reference 'this'
- class ThisReferringMethodsTraverser() extends Traverser {
private var currentMethod: Symbol = NoSymbol
- // the set of methods that refer to this
- val thisReferringMethods = mutable.Set[Symbol]()
- // the set of lifted lambda body methods that each method refers to
- val liftedMethodReferences = mutable.Map[Symbol, Set[Symbol]]().withDefault(_ => mutable.Set())
+
override def traverse(tree: Tree) = tree match {
- case DefDef(_, _, _, _, _, _) =>
+ case DefDef(_, _, _, _, _, _) if tree.symbol.isDelambdafyTarget || tree.symbol.isLiftedMethod =>
// we don't expect defs within defs. At this phase trees should be very flat
if (currentMethod.exists) devWarning("Found a def within a def at a phase where defs are expected to be flattened out.")
currentMethod = tree.symbol
@@ -557,37 +378,21 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
// They'll be of the form {(args...) => this.anonfun(args...)}
// but we do need to make note of the lifted body method in case it refers to 'this'
if (currentMethod.exists) liftedMethodReferences(currentMethod) += targetMethod(fun)
+ case Apply(sel @ Select(This(_), _), args) if sel.symbol.isLiftedMethod =>
+ if (currentMethod.exists) liftedMethodReferences(currentMethod) += sel.symbol
+ super.traverseTrees(args)
+ case Apply(fun, outer :: rest) if shouldElideOuterArg(fun.symbol, outer) =>
+ super.traverse(fun)
+ super.traverseTrees(rest)
case This(_) =>
if (currentMethod.exists && tree.symbol == currentMethod.enclClass) {
debuglog(s"$currentMethod directly refers to 'this'")
thisReferringMethods add currentMethod
}
+ case _: ClassDef if !tree.symbol.isTopLevel =>
+ case _: DefDef =>
case _ =>
super.traverse(tree)
}
}
-
- final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol)
-
- // The functional interface that can be used to adapt the lambda target method `target` to the
- // given function type. Returns `NoSymbol` if the compiler settings are unsuitable.
- private def java8CompatFunctionalInterface(target: Symbol, functionType: Type): (Symbol, Boolean) = {
- val canUseLambdaMetafactory: Boolean = {
- val isTarget18 = settings.target.value.contains("jvm-1.8")
- settings.isBCodeActive && isTarget18
- }
-
- val sym = functionType.typeSymbol
- val pack = currentRun.runDefinitions.Scala_Java8_CompatPackage
- val name1 = specializeTypes.specializedFunctionName(sym, functionType.typeArgs)
- val paramTps :+ restpe = functionType.typeArgs
- val arity = paramTps.length
- val isSpecialized = name1.toTypeName != sym.name
- val functionalInterface = if (!isSpecialized) {
- currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction(arity)
- } else {
- pack.info.decl(name1.toTypeName.prepend("J"))
- }
- (if (canUseLambdaMetafactory) functionalInterface else NoSymbol, isSpecialized)
- }
}
diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala
index 6b987f0089..e327a6658c 100644
--- a/src/compiler/scala/tools/nsc/transform/Erasure.scala
+++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala
@@ -12,7 +12,7 @@ import symtab._
import Flags._
import scala.reflect.internal.Mode._
-abstract class Erasure extends AddInterfaces
+abstract class Erasure extends InfoTransform
with scala.reflect.internal.transform.Erasure
with typechecker.Analyzer
with TypingTransformers
@@ -71,7 +71,9 @@ abstract class Erasure extends AddInterfaces
}
override protected def verifyJavaErasure = settings.Xverify || settings.debug
- def needsJavaSig(tp: Type) = !settings.Ynogenericsig && NeedsSigCollector.collect(tp)
+ def needsJavaSig(tp: Type, throwsArgs: List[Type]) = !settings.Ynogenericsig && {
+ NeedsSigCollector.collect(tp) || throwsArgs.exists(NeedsSigCollector.collect)
+ }
// only refer to type params that will actually make it into the sig, this excludes:
// * higher-order type parameters
@@ -187,18 +189,23 @@ abstract class Erasure extends AddInterfaces
/* Drop redundant types (ones which are implemented by some other parent) from the immediate parents.
* This is important on Android because there is otherwise an interface explosion.
+ * This is now restricted to Scala defined ancestors: a Java defined ancestor may need to be listed
+ * as an immediate parent to support an `invokespecial`.
*/
def minimizeParents(parents: List[Type]): List[Type] = if (parents.isEmpty) parents else {
- def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait
+ def isRedundantParent(sym: Symbol) = sym.isInterface || sym.isTrait
var rest = parents.tail
var leaves = collection.mutable.ListBuffer.empty[Type] += parents.head
while(rest.nonEmpty) {
val candidate = rest.head
- val nonLeaf = leaves exists { t => t.typeSymbol isSubClass candidate.typeSymbol }
- if(!nonLeaf) {
- leaves = leaves filterNot { t => isInterfaceOrTrait(t.typeSymbol) && (candidate.typeSymbol isSubClass t.typeSymbol) }
- leaves += candidate
+ if (candidate.typeSymbol.isJavaDefined && candidate.typeSymbol.isInterface) leaves += candidate
+ else {
+ val nonLeaf = leaves exists { t => t.typeSymbol isSubClass candidate.typeSymbol }
+ if (!nonLeaf) {
+ leaves = leaves filterNot { t => isRedundantParent(t.typeSymbol) && (candidate.typeSymbol isSubClass t.typeSymbol) }
+ leaves += candidate
+ }
}
rest = rest.tail
}
@@ -251,7 +258,7 @@ abstract class Erasure extends AddInterfaces
// Anything which could conceivably be a module (i.e. isn't known to be
// a type parameter or similar) must go through here or the signature is
// likely to end up with Foo<T>.Empty where it needs Foo<T>.Empty$.
- def fullNameInSig(sym: Symbol) = "L" + enteringIcode(sym.javaBinaryName)
+ def fullNameInSig(sym: Symbol) = "L" + enteringJVM(sym.javaBinaryNameString)
def jsig(tp0: Type, existentiallyBound: List[Symbol] = Nil, toplevel: Boolean = false, primitiveOK: Boolean = true): String = {
val tp = tp0.dealias
@@ -277,7 +284,7 @@ abstract class Erasure extends AddInterfaces
val preRebound = pre.baseType(sym.owner) // #2585
dotCleanup(
(
- if (needsJavaSig(preRebound)) {
+ if (needsJavaSig(preRebound, Nil)) {
val s = jsig(preRebound, existentiallyBound)
if (s.charAt(0) == 'L') s.substring(0, s.length - 1) + "." + sym.javaSimpleName
else fullNameInSig(sym)
@@ -352,8 +359,8 @@ abstract class Erasure extends AddInterfaces
buf append (if (restpe.typeSymbol == UnitClass || sym0.isConstructor) VOID_TAG.toString else jsig(restpe))
buf.toString
- case RefinedType(parent :: _, decls) =>
- boxedSig(parent)
+ case RefinedType(parents, decls) =>
+ jsig(intersectionDominator(parents), primitiveOK = primitiveOK)
case ClassInfoType(parents, _, _) =>
superSig(parents)
case AnnotatedType(_, atp) =>
@@ -367,8 +374,9 @@ abstract class Erasure extends AddInterfaces
else jsig(etp)
}
}
- if (needsJavaSig(info)) {
- try Some(jsig(info, toplevel = true))
+ val throwsArgs = sym0.annotations flatMap ThrownException.unapply
+ if (needsJavaSig(info, throwsArgs)) {
+ try Some(jsig(info, toplevel = true) + throwsArgs.map("^" + jsig(_, toplevel = true)).mkString(""))
catch { case ex: UnknownSig => None }
}
else None
@@ -376,16 +384,53 @@ abstract class Erasure extends AddInterfaces
class UnknownSig extends Exception
- /** The symbol's erased info. This is the type's erasure, except for the following symbols:
- *
- * - For $asInstanceOf : [T]T
- * - For $isInstanceOf : [T]scala#Boolean
- * - For class Array : [T]C where C is the erased classinfo of the Array class.
- * - For Array[T].<init> : {scala#Int)Array[T]
- * - For a type parameter : A type bounds type consisting of the erasures of its bounds.
- */
- override def transformInfo(sym: Symbol, tp: Type): Type =
- transformMixinInfo(super.transformInfo(sym, tp))
+ // TODO: move to constructors?
+ object mixinTransformer extends Transformer {
+ /** Add calls to supermixin constructors
+ * `super[mix].$init$()`
+ * to tree, which is assumed to be the body of a constructor of class clazz.
+ */
+ private def addMixinConstructorCalls(tree: Tree, clazz: Symbol): Tree = {
+ def mixinConstructorCall(mc: Symbol): Tree = atPos(tree.pos) {
+ Apply(SuperSelect(clazz, mc.primaryConstructor), Nil)
+ }
+ val mixinConstructorCalls: List[Tree] = {
+ for (mc <- clazz.mixinClasses.reverse
+ if mc.isTrait && mc.primaryConstructor != NoSymbol)
+ yield mixinConstructorCall(mc)
+ }
+ tree match {
+
+ case Block(Nil, expr) =>
+ // AnyVal constructor - have to provide a real body so the
+ // jvm doesn't throw a VerifyError. But we can't add the
+ // body until now, because the typer knows that Any has no
+ // constructor and won't accept a call to super.init.
+ assert((clazz isSubClass AnyValClass) || clazz.info.parents.isEmpty, clazz)
+ Block(List(Apply(gen.mkSuperInitCall, Nil)), expr)
+
+ case Block(stats, expr) =>
+ // needs `hasSymbolField` check because `supercall` could be a block (named / default args)
+ val (presuper, supercall :: rest) = stats span (t => t.hasSymbolWhich(_ hasFlag PRESUPER))
+ treeCopy.Block(tree, presuper ::: (supercall :: mixinConstructorCalls ::: rest), expr)
+ }
+ }
+
+ override def transform(tree: Tree): Tree = {
+ val sym = tree.symbol
+ val tree1 = tree match {
+ case DefDef(_,_,_,_,_,_) if sym.isClassConstructor && sym.isPrimaryConstructor && sym.owner != ArrayClass =>
+ deriveDefDef(tree)(addMixinConstructorCalls(_, sym.owner)) // (3)
+ case Template(parents, self, body) =>
+ val parents1 = sym.owner.info.parents map (t => TypeTree(t) setPos tree.pos)
+ treeCopy.Template(tree, parents1, noSelfType, body)
+ case _ =>
+ tree
+ }
+ super.transform(tree1)
+ }
+ }
+
val deconstMap = new TypeMap {
// For some reason classOf[Foo] creates ConstantType(Constant(tpe)) with an actual Type for tpe,
@@ -510,11 +555,11 @@ abstract class Erasure extends AddInterfaces
if (!bridgeNeeded)
return
- var newFlags = (member.flags | BRIDGE | ARTIFACT) & ~(ACCESSOR | DEFERRED | LAZY | lateDEFERRED)
+ var newFlags = (member.flags | BRIDGE | ARTIFACT) & ~(ACCESSOR | DEFERRED | LAZY)
// If `member` is a ModuleSymbol, the bridge should not also be a ModuleSymbol. Otherwise we
// end up with two module symbols with the same name in the same scope, which is surprising
// when implementing later phases.
- if (member.isModule) newFlags = (newFlags | METHOD) & ~(MODULE | lateMETHOD | STABLE)
+ if (member.isModule) newFlags = (newFlags | METHOD) & ~(MODULE | STABLE)
val bridge = other.cloneSymbolImpl(root, newFlags) setPos root.pos
debuglog("generating bridge from %s (%s): %s to %s: %s".format(
@@ -589,8 +634,9 @@ abstract class Erasure extends AddInterfaces
}
/** The modifier typer which retypes with erased types. */
- class Eraser(_context: Context) extends Typer(_context) with TypeAdapter {
- val typer = this.asInstanceOf[analyzer.Typer]
+ class Eraser(_context: Context) extends Typer(_context) {
+ val typeAdapter = new TypeAdapter { def typedPos(pos: Position)(tree: Tree): Tree = Eraser.this.typedPos(pos)(tree) }
+ import typeAdapter._
override protected def stabilize(tree: Tree, pre: Type, mode: Mode, pt: Type): Tree = tree
@@ -616,10 +662,8 @@ abstract class Erasure extends AddInterfaces
// !!! Make pending/run/t5866b.scala work. The fix might be here and/or in unbox1.
if (isPrimitiveValueType(targ.tpe) || isErasedValueType(targ.tpe)) {
val noNullCheckNeeded = targ.tpe match {
- case ErasedValueType(_, underlying) =>
- isPrimitiveValueClass(underlying.typeSymbol)
- case _ =>
- true
+ case ErasedValueType(_, underlying) => isPrimitiveValueType(underlying)
+ case _ => true
}
if (noNullCheckNeeded) unbox(qual1, targ.tpe)
else {
@@ -658,7 +702,7 @@ abstract class Erasure extends AddInterfaces
var qual1 = typedQualifier(qual)
if ((isPrimitiveValueType(qual1.tpe) && !isPrimitiveValueMember(tree.symbol)) ||
isErasedValueType(qual1.tpe))
- qual1 = box(qual1, "owner "+tree.symbol.owner)
+ qual1 = box(qual1)
else if (!isPrimitiveValueType(qual1.tpe) && isPrimitiveValueMember(tree.symbol))
qual1 = unbox(qual1, tree.symbol.owner.tpe)
@@ -667,13 +711,12 @@ abstract class Erasure extends AddInterfaces
if (isPrimitiveValueMember(tree.symbol) && !isPrimitiveValueType(qual1.tpe)) {
tree.symbol = NoSymbol
selectFrom(qual1)
- } else if (isMethodTypeWithEmptyParams(qual1.tpe)) {
+ } else if (isMethodTypeWithEmptyParams(qual1.tpe)) { // see also adaptToType in TypeAdapter
assert(qual1.symbol.isStable, qual1.symbol)
- val applied = Apply(qual1, List()) setPos qual1.pos setType qual1.tpe.resultType
- adaptMember(selectFrom(applied))
+ adaptMember(selectFrom(applyMethodWithEmptyParams(qual1)))
} else if (!(qual1.isInstanceOf[Super] || (qual1.tpe.typeSymbol isSubClass tree.symbol.owner))) {
assert(tree.symbol.owner != ArrayClass)
- selectFrom(cast(qual1, tree.symbol.owner.tpe))
+ selectFrom(cast(qual1, tree.symbol.owner.tpe.resultType))
} else {
selectFrom(qual1)
}
@@ -732,6 +775,12 @@ abstract class Erasure extends AddInterfaces
if (branch == EmptyTree) branch else adaptToType(branch, tree1.tpe)
tree1 match {
+ case fun: Function =>
+ fun.attachments.get[SAMFunction] match {
+ case Some(SAMFunction(samTp, _)) => fun setType specialScalaErasure(samTp)
+ case _ => fun
+ }
+
case If(cond, thenp, elsep) =>
treeCopy.If(tree1, cond, adaptBranch(thenp), adaptBranch(elsep))
case Match(selector, cases) =>
@@ -1019,24 +1068,20 @@ abstract class Erasure extends AddInterfaces
// erasure the ScalaRunTime.hash overload goes from Unit => Int to BoxedUnit => Int.
// This must be because some earlier transformation is being skipped on ##, but so
// far I don't know what. For null we now define null.## == 0.
+ def staticsCall(methodName: TermName): Tree = {
+ val newTree = gen.mkMethodCall(RuntimeStaticsModule, methodName, qual :: Nil)
+ global.typer.typed(newTree)
+ }
+
qual.tpe.typeSymbol match {
case UnitClass | NullClass => LIT(0)
case IntClass => qual
case s @ (ShortClass | ByteClass | CharClass) => numericConversion(qual, s)
case BooleanClass => If(qual, LIT(true.##), LIT(false.##))
- case _ =>
- // Since we are past typer, we need to avoid creating trees carrying
- // overloaded types. This logic is custom (and technically incomplete,
- // although serviceable) for def hash. What is really needed is for
- // the overloading logic presently hidden away in a few different
- // places to be properly exposed so we can just call "resolveOverload"
- // after typer. Until then:
- val alts = ScalaRunTimeModule.info.member(nme.hash_).alternatives
- def alt1 = alts find (_.info.paramTypes.head =:= qual.tpe)
- def alt2 = ScalaRunTimeModule.info.member(nme.hash_) suchThat (_.info.paramTypes.head.typeSymbol == AnyClass)
- val newTree = gen.mkRuntimeCall(nme.hash_, qual :: Nil) setSymbol (alt1 getOrElse alt2)
-
- global.typer.typed(newTree)
+ case LongClass => staticsCall(nme.longHash)
+ case FloatClass => staticsCall(nme.floatHash)
+ case DoubleClass => staticsCall(nme.doubleHash)
+ case _ => staticsCall(nme.anyHash)
}
} else if (isPrimitiveValueClass(qual.tpe.typeSymbol)) {
// Rewrite 5.getClass to ScalaRunTime.anyValClass(5)
@@ -1052,7 +1097,7 @@ abstract class Erasure extends AddInterfaces
// See SI-5568.
tree setSymbol Object_getClass
} else {
- 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")
+ devWarning(s"The symbol '${fn.symbol}' was intercepted but didn't match any cases, that means the intercepted methods set doesn't match the code")
tree
}
} else qual match {
@@ -1076,7 +1121,8 @@ abstract class Erasure extends AddInterfaces
case TypeApply(fun, args) if (fun.symbol.owner != AnyClass &&
fun.symbol != Object_asInstanceOf &&
- fun.symbol != Object_isInstanceOf) =>
+ fun.symbol != Object_isInstanceOf &&
+ fun.symbol != Object_synchronized) =>
// leave all other type tests/type casts, remove all other type applications
preErase(fun)
@@ -1115,7 +1161,6 @@ abstract class Erasure extends AddInterfaces
}
} else tree
case Template(parents, self, body) =>
- assert(!currentOwner.isImplClass)
//Console.println("checking no dble defs " + tree)//DEBUG
checkNoDoubleDefs(tree.symbol.owner)
treeCopy.Template(tree, parents, noSelfType, addBridges(body, currentOwner))
@@ -1125,7 +1170,7 @@ abstract class Erasure extends AddInterfaces
case Literal(ct) if ct.tag == ClazzTag
&& ct.typeValue.typeSymbol != definitions.UnitClass =>
- val erased = ct.typeValue match {
+ val erased = ct.typeValue.dealiasWiden match {
case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => scalaErasure.eraseNormalClassRef(tr)
case tpe => specialScalaErasure(tpe)
}
@@ -1154,14 +1199,24 @@ abstract class Erasure extends AddInterfaces
else {
val tree1 = preErase(tree)
tree1 match {
+ case TypeApply(fun, targs @ List(targ)) if (fun.symbol == Any_asInstanceOf || fun.symbol == Object_synchronized) && targ.tpe == UnitTpe =>
+ // SI-9066 prevent transforming `o.asInstanceOf[Unit]` to `o.asInstanceOf[BoxedUnit]`.
+ // adaptMember will then replace the call by a reference to BoxedUnit.UNIT.
+ treeCopy.TypeApply(tree1, transform(fun), targs).clearType()
case EmptyTree | TypeTree() =>
tree1 setType specialScalaErasure(tree1.tpe)
case ArrayValue(elemtpt, trees) =>
treeCopy.ArrayValue(
tree1, elemtpt setType specialScalaErasure.applyInArray(elemtpt.tpe), trees map transform).clearType()
case DefDef(_, _, _, _, tpt, _) =>
+ // TODO: move this in some post-processing transform in the fields phase?
+ if (fields.symbolAnnotationsTargetFieldAndGetter(tree.symbol))
+ fields.dropFieldAnnotationsFromGetter(tree.symbol)
+
try super.transform(tree1).clearType()
finally tpt setType specialErasure(tree1.symbol)(tree1.symbol.tpe).resultType
+ case ApplyDynamic(qual, Literal(Constant(bootstrapMethodRef: Symbol)) :: _) =>
+ tree
case _ =>
super.transform(tree1).clearType()
}
@@ -1192,5 +1247,41 @@ abstract class Erasure extends AddInterfaces
bridge.resetFlag(BRIDGE)
}
+ /** Does this symbol compile to the underlying platform's notion of an interface,
+ * without requiring compiler magic before it can be instantiated?
+ *
+ * More specifically, we're interested in whether LambdaMetaFactory can instantiate this type,
+ * assuming it has a single abstract method. In other words, if we were to mix this
+ * trait into a class, it should not result in any compiler-generated members having to be
+ * implemented in ("mixed in to") this class (except for the SAM).
+ *
+ * Thus, the type must erase to a java interface, either by virtue of being defined as one,
+ * or by being a trait that:
+ * - is static (explicitouter or lambdalift may add disqualifying members)
+ * - extends only other traits that compile to pure interfaces (except for Any)
+ * - has no val/var members
+ *
+ * TODO: can we speed this up using the INTERFACE flag, or set it correctly by construction?
+ */
+ final def compilesToPureInterface(tpSym: Symbol): Boolean = {
+ def ok(sym: Symbol) =
+ sym.isJavaInterface ||
+ sym.isTrait &&
+ // Unless sym.isStatic, even if the constructor is zero-argument now, it may acquire arguments in explicit outer or lambdalift.
+ // This is an impl restriction to simplify the decision of whether to expand the SAM during uncurry
+ // (when we don't yet know whether it will receive an outer pointer in explicit outer or whether lambda lift will add proxies for captures).
+ // When we delay sam expansion until after explicit outer & lambda lift, we could decide there whether
+ // to expand sam at compile time or use LMF, and this implementation restriction could be lifted.
+ sym.isStatic &&
+ // HACK: this is to rule out traits with an effectful initializer.
+ // The constructor only exists if the trait's template has statements.
+ // Sadly, we can't be more precise without access to the tree that defines the SAM's owner.
+ !sym.primaryConstructor.exists &&
+ (sym.isInterface || sym.info.decls.forall(mem => mem.isMethod || mem.isType)) // TODO OPT: && {sym setFlag INTERFACE; true})
+
+ // we still need to check our ancestors even if the INTERFACE flag is set, as it doesn't take inheritance into account
+ ok(tpSym) && tpSym.ancestors.forall(sym => (sym eq AnyClass) || (sym eq ObjectClass) || ok(sym))
+ }
+
private class TypeRefAttachment(val tpe: TypeRef)
}
diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala
index 540de2cfe1..8bdbf16e03 100644
--- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala
+++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala
@@ -8,10 +8,8 @@ package tools.nsc
package transform
import symtab._
-import Flags.{ CASE => _, _ }
-import scala.collection.mutable
+import Flags.{CASE => _, _}
import scala.collection.mutable.ListBuffer
-import scala.tools.nsc.settings.ScalaVersion
/** This class ...
*
@@ -69,8 +67,6 @@ abstract class ExplicitOuter extends InfoTransform
result
}
- private val innerClassConstructorParamName: TermName = newTermName("arg" + nme.OUTER)
-
class RemoveBindingsTransformer(toRemove: Set[Symbol]) extends Transformer {
override def transform(tree: Tree) = tree match {
case Bind(_, body) if toRemove(tree.symbol) => super.transform(body)
@@ -159,20 +155,18 @@ abstract class ExplicitOuter extends InfoTransform
* elides outer pointers.
*/
def transformInfo(sym: Symbol, tp: Type): Type = tp match {
- case MethodType(params, restpe1) =>
- val restpe = transformInfo(sym, restpe1)
- if (sym.owner.isTrait && ((sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isModule)) { // 5
- sym.makeNotPrivate(sym.owner)
- }
- if (sym.owner.isTrait && sym.isProtected) sym setFlag notPROTECTED // 6
- if (sym.isClassConstructor && isInner(sym.owner)) { // 1
- val p = sym.newValueParameter(innerClassConstructorParamName, sym.pos)
- .setInfo(sym.owner.outerClass.thisType)
- MethodType(p :: params, restpe)
- } else if (restpe ne restpe1)
- MethodType(params, restpe)
+ case MethodType(params, resTp) =>
+ val resTpTransformed = transformInfo(sym, resTp)
+
+ val paramsWithOuter =
+ if (sym.isClassConstructor && isInner(sym.owner)) // 1
+ sym.newValueParameter(nme.OUTER_ARG, sym.pos).setInfo(sym.owner.outerClass.thisType) :: params
+ else params
+
+ if ((resTpTransformed ne resTp) || (paramsWithOuter ne params)) MethodType(paramsWithOuter, resTpTransformed)
else tp
- case ClassInfoType(parents, decls, clazz) =>
+
+ case ClassInfoType(parents, decls, clazz) if !clazz.isJava =>
var decls1 = decls
if (isInner(clazz) && !clazz.isInterface) {
decls1 = decls.cloneScope
@@ -201,14 +195,6 @@ abstract class ExplicitOuter extends InfoTransform
if (restp eq restp1) tp else PolyType(tparams, restp1)
case _ =>
- // Local fields of traits need to be unconditionally unprivatized.
- // Reason: Those fields might need to be unprivatized if referenced by an inner class.
- // On the other hand, mixing in the trait into a separately compiled
- // 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.getterIn(sym.owner.toInterface) == NoSymbol))
- sym.makeNotPrivate(sym.owner)
tp
}
@@ -238,12 +224,17 @@ abstract class ExplicitOuter extends InfoTransform
* Will return `EmptyTree` if there is no outer accessor because of a premature self reference.
*/
private def outerSelect(base: Tree): Tree = {
- val baseSym = base.tpe.typeSymbol.toInterface
+ val baseSym = base.tpe.typeSymbol
val outerAcc = outerAccessor(baseSym)
- if (outerAcc == NoSymbol && baseSym.ownersIterator.exists(isUnderConstruction)) {
- // e.g neg/t6666.scala
- // The caller will report the error with more information.
- EmptyTree
+ if (outerAcc == NoSymbol) {
+ if (baseSym.ownersIterator.exists(isUnderConstruction)) {
+ // e.g neg/t6666.scala
+ // The caller will report the error with more information.
+ EmptyTree
+ } else {
+ globalError(currentOwner.pos, s"Internal error: unable to find the outer accessor symbol of $baseSym")
+ EmptyTree
+ }
} else {
val currentClass = this.currentClass //todo: !!! if this line is removed, we get a build failure that protected$currentClass need an override modifier
// outerFld is the $outer field of the current class, if the reference can
@@ -251,6 +242,7 @@ abstract class ExplicitOuter extends InfoTransform
// otherwise it is NoSymbol
val outerFld =
if (outerAcc.owner == currentClass &&
+ !outerAcc.owner.isTrait &&
base.tpe =:= currentClass.thisType &&
outerAcc.owner.isEffectivelyFinal)
outerField(currentClass) suchThat (_.owner == currentClass)
@@ -271,8 +263,7 @@ abstract class ExplicitOuter extends InfoTransform
*/
protected def outerPath(base: Tree, from: Symbol, to: Symbol): Tree = {
//Console.println("outerPath from "+from+" to "+to+" at "+base+":"+base.tpe)
- //assert(base.tpe.widen.baseType(from.toInterface) != NoType, ""+base.tpe.widen+" "+from.toInterface)//DEBUG
- if (from == to || from.isImplClass && from.toInterface == to) base
+ if (from == to) base
else outerPath(outerSelect(base), from.outerClass, to)
}
@@ -294,61 +285,41 @@ abstract class ExplicitOuter extends InfoTransform
}
}
- /** <p>
- * The phase performs the following transformations on terms:
- * </p>
- * <ol>
- * <li> <!-- 1 -->
- * <p>
- * An class which is not an interface and is not static gets an outer
- * accessor (@see outerDefs).
- * </p>
- * <p>
- * 1a. A class which is not a trait gets an outer field.
- * </p>
- * </li>
- * <li> <!-- 4 -->
- * A constructor of a non-trait inner class gets an outer parameter.
- * </li>
- * <li> <!-- 5 -->
- * A reference C.this where C refers to an
- * outer class is replaced by a selection
- * this.$outer$$C1 ... .$outer$$Cn (@see outerPath)
- * </li>
- * <li>
- * </li>
- * <li> <!-- 7 -->
- * A call to a constructor Q.<init>(args) or Q.$init$(args) where Q != this and
- * the constructor belongs to a non-static class is augmented by an outer argument.
- * E.g. Q.<init>(OUTER, args) where OUTER
- * is the qualifier corresponding to the singleton type Q.
- * </li>
- * <li>
- * A call to a constructor this.<init>(args) in a
- * secondary constructor is augmented to this.<init>(OUTER, args)
- * where OUTER is the last parameter of the secondary constructor.
- * </li>
- * <li> <!-- 9 -->
- * Remove private modifier from class members M
- * that are accessed from an inner class.
- * </li>
- * <li> <!-- 10 -->
- * Remove protected modifier from class members M
- * that are accessed without a super qualifier accessed from an inner
- * class or trait.
- * </li>
- * <li> <!-- 11 -->
- * Remove private and protected modifiers
- * from type symbols
- * </li>
- * <li> <!-- 12 -->
- * Remove private modifiers from members of traits
- * </li>
- * </ol>
- * <p>
- * Note: The whole transform is run in phase explicitOuter.next.
- * </p>
- */
+ /** The phase performs the following transformations (more or less...):
+ *
+ * (1) An class which is not an interface and is not static gets an outer accessor (@see outerDefs).
+ * (1a) A class which is not a trait gets an outer field.
+ *
+ * (4) A constructor of a non-trait inner class gets an outer parameter.
+ *
+ * (5) A reference C.this where C refers to an outer class is replaced by a selection
+ * `this.$outer$$C1 ... .$outer$$Cn` (@see outerPath)
+ *
+ * (7) A call to a constructor Q.(args) or Q.$init$(args) where Q != this and
+ * the constructor belongs to a non-static class is augmented by an outer argument.
+ * E.g. Q.(OUTER, args) where OUTER
+ * is the qualifier corresponding to the singleton type Q.
+ *
+ * (8) A call to a constructor this.(args) in a
+ * secondary constructor is augmented to this.(OUTER, args)
+ * where OUTER is the last parameter of the secondary constructor.
+ *
+ * (9) Remove private modifier from class members M that are accessed from an inner class.
+ *
+ * (10) Remove protected modifier from class members M that are accessed
+ * without a super qualifier accessed from an inner class or trait.
+ *
+ * (11) Remove private and protected modifiers from type symbols
+ *
+ * Note: The whole transform is run in phase explicitOuter.next.
+ *
+ * TODO: Make this doc reflect what's actually going on.
+ * Some of the deviations are motivated by separate compilation
+ * (name mangling based on usage is inherently unstable).
+ * Now that traits are compiled 1:1 to interfaces, they can have private members,
+ * so there's also less need to make trait members non-private
+ * (they still may need to be implemented in subclasses, though we could make those protected...).
+ */
class ExplicitOuterTransformer(unit: CompilationUnit) extends OuterPathTransformer(unit) {
transformer =>
@@ -397,7 +368,7 @@ abstract class ExplicitOuter extends InfoTransform
case Template(parents, self, decls) =>
val newDefs = new ListBuffer[Tree]
atOwner(tree, currentOwner) {
- if (!currentClass.isInterface || (currentClass hasFlag lateINTERFACE)) {
+ if (!currentClass.isInterface) {
if (isInner(currentClass)) {
if (hasOuterField(currentClass))
newDefs += outerFieldDef // (1a)
@@ -446,8 +417,10 @@ abstract class ExplicitOuter extends InfoTransform
//
// See SI-6552 for an example of why `sym.owner.enclMethod hasAnnotation ScalaInlineClass`
// is not suitable; if we make a method-local class non-private, it mangles outer pointer names.
- if (currentClass != sym.owner ||
- (closestEnclMethod(currentOwner) hasAnnotation ScalaInlineClass))
+ def enclMethodIsInline = closestEnclMethod(currentOwner) hasAnnotation ScalaInlineClass
+ // SI-8710 The extension method condition reflects our knowledge that a call to `new Meter(12).privateMethod`
+ // with later be rewritten (in erasure) to `Meter.privateMethod$extension(12)`.
+ if ((currentClass != sym.owner || enclMethodIsInline) && !sym.isMethodWithExtension)
sym.makeNotPrivate(sym.owner)
val qsym = qual.tpe.widen.typeSymbol
@@ -474,14 +447,15 @@ abstract class ExplicitOuter extends InfoTransform
// base.<outer>.eq(o) --> base.$outer().eq(o) if there's an accessor, else the whole tree becomes TRUE
// TODO remove the synthetic `<outer>` method from outerFor??
case Apply(eqsel@Select(eqapp@Apply(sel@Select(base, nme.OUTER_SYNTH), Nil), eq), args) =>
- val outerFor = sel.symbol.owner.toInterface // TODO: toInterface necessary?
+ val outerFor = sel.symbol.owner
val acc = outerAccessor(outerFor)
if (acc == NoSymbol ||
// since we can't fix SI-4440 properly (we must drop the outer accessors of final classes when there's no immediate reference to them in sight)
// at least don't crash... this duplicates maybeOmittable from constructors
(acc.owner.isEffectivelyFinal && !acc.isOverridingSymbol)) {
- currentRun.reporting.uncheckedWarning(tree.pos, "The outer reference in this type test cannot be checked at run time.")
+ if (!base.tpe.hasAnnotation(UncheckedClass))
+ currentRun.reporting.uncheckedWarning(tree.pos, "The outer reference in this type test cannot be checked at run time.")
transform(TRUE) // urgh... drop condition if there's no accessor (or if it may disappear after constructors)
} else {
// println("(base, acc)= "+(base, acc))
diff --git a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala
index 116047a2ad..f2237a0716 100644
--- a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala
+++ b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala
@@ -7,7 +7,7 @@ package transform
import symtab._
import Flags._
-import scala.collection.{ mutable, immutable }
+import scala.collection.mutable
/**
* Perform Step 1 in the inline classes SIP: Creates extension methods for all
@@ -192,8 +192,7 @@ abstract class ExtensionMethods extends Transform with TypingTransformers {
extensionDefs(currentOwner.companionModule) = new mutable.ListBuffer[Tree]
currentOwner.primaryConstructor.makeNotPrivate(NoSymbol)
// SI-7859 make param accessors accessible so the erasure can generate unbox operations.
- val paramAccessors = currentOwner.info.decls.filter(sym => sym.isParamAccessor && sym.isMethod)
- paramAccessors.foreach(_.makeNotPrivate(currentOwner))
+ currentOwner.info.decls.foreach(sym => if (sym.isParamAccessor && sym.isMethod) sym.makeNotPrivate(currentOwner))
super.transform(tree)
} else if (currentOwner.isStaticOwner) {
super.transform(tree)
@@ -208,7 +207,7 @@ abstract class ExtensionMethods extends Transform with TypingTransformers {
def makeExtensionMethodSymbol = {
val extensionName = extensionNames(origMeth).head.toTermName
val extensionMeth = (
- companion.moduleClass.newMethod(extensionName, tree.pos.focus, origMeth.flags & ~OVERRIDE & ~PROTECTED & ~LOCAL | FINAL)
+ companion.moduleClass.newMethod(extensionName, tree.pos.focus, origMeth.flags & ~OVERRIDE & ~PROTECTED & ~PRIVATE & ~LOCAL | FINAL)
setAnnotations origMeth.annotations
)
origMeth.removeAnnotation(TailrecClass) // it's on the extension method, now.
@@ -244,7 +243,10 @@ abstract class ExtensionMethods extends Transform with TypingTransformers {
// These three lines are assembling Foo.bar$extension[T1, T2, ...]($this)
// which leaves the actual argument application for extensionCall.
- val sel = Select(gen.mkAttributedRef(companion), extensionMeth)
+ // SI-9542 We form the selection here from the thisType of the companion's owner. This is motivated
+ // by the test case, and is a valid way to construct the reference because we know that this
+ // method is also enclosed by that owner.
+ val sel = Select(gen.mkAttributedRef(companion.owner.thisType, companion), extensionMeth)
val targs = origTpeParams map (_.tpeHK)
val callPrefix = gen.mkMethodCall(sel, targs, This(origThis) :: Nil)
diff --git a/src/compiler/scala/tools/nsc/transform/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala
new file mode 100644
index 0000000000..b2bf9fad3f
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/transform/Fields.scala
@@ -0,0 +1,787 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2013 LAMP/EPFL
+ * @author
+ */
+
+package scala.tools.nsc
+package transform
+
+import scala.annotation.tailrec
+import symtab.Flags._
+
+
+/** Synthesize accessors, fields (and bitmaps) for (lazy) vals and modules.
+ *
+ * During Namers, a `ValDef` that is `lazy`, deferred and/or defined in a trait carries its getter's symbol.
+ * The underlying field symbol does not exist until this phase.
+ *
+ * For `val`s defined in classes, we still emit a field immediately.
+ * TODO: uniformly assign getter symbol to all `ValDef`s, stop using `accessed`.
+ *
+ * This phase synthesizes accessors, fields and bitmaps (for lazy or init-checked vals under -Xcheckinit)
+ * in the first (closest in the subclassing lattice) subclass (not a trait) of a trait.
+ *
+ * For lazy vals and modules, we emit accessors that using double-checked locking (DCL) to balance thread safety
+ * and performance. For both lazy vals and modules, the a compute method contains the DCL's slow path.
+ *
+ * Local lazy vals do not receive bitmaps, but use a Lazy*Holder that has the volatile init bit and the computed value.
+ * See `mkLazyLocalDef`.
+ *
+ * Constructors will move the rhs to an assignment in the template body.
+ * Those statements then move to the template into the constructor,
+ * which means it will initialize the fields defined in this template (and execute the corresponding side effects).
+ * We need to maintain the connection between getter and rhs until after specialization so that it can duplicate vals.
+ *
+ * A ModuleDef is desugared to a ClassDef, an accessor (which reuses the module's term symbol)
+ * and a module var (unless the module is static and does not implement a member of a supertype, or we're in a trait).
+ *
+ * For subclasses of traits that define modules, a module var is mixed in, as well as the required module accessors.
+ *
+ * Phase ordering:
+ * - Runs after uncurry to deal with classes that implement SAM traits with ValDefs.
+ * - Runs before erasure (to get bridges), and thus before lambdalift/flatten, so that nested functions/definitions must be considered.
+ * - Lambdalift introduces new paramaccessors for captured vals, but runs too late in the pipeline, so
+ * mixins still synthesizes implementations for these accessors when a local trait that captures is subclassed.
+ *
+ *
+ * In the future, would like to get closer to dotty, which lifts a val's RHS (a similar thing is done for template-level statements)
+ * to a method `$_initialize_$1$x` instead of a block, which is used in the constructor to initialize the val.
+ * This makes for a nice unification of strict and lazy vals, in that the RHS is lifted to a method for both,
+ * with the corresponding compute method called at the appropriate time.)
+ *
+ * This only reduces the required number of methods per field declaration in traits,
+ * if we encode the name (and place in initialisation order) of the field
+ * in the name of its initializing method, to allow separate compilation.
+ * (The name mangling must include ordering, and thus complicate incremental compilation:
+ * ideally, we'd avoid renumbering unchanged methods, but that would result in
+ * different bytecode between clean recompiles and incremental ones).
+ *
+ * In the even longer term (Scala 3?), I agree with @DarkDimius that it would make sense
+ * to hide the difference between strict and lazy vals. All vals are lazy,
+ * but the memoization overhead is removed when we statically know they are forced during initialization.
+ * We could still expose the low-level field semantics through `private[this] val`s.
+ *
+ * In any case, the current behavior of overriding vals is pretty surprising.
+ * An overridden val's side-effect is still performed.
+ * The only change due to overriding is that its value is never written to the field
+ * (the overridden val's value is, of course, stored in the field in addition to its side-effect being performed).
+ *
+ * TODO: Java 9 support for vals defined in traits. They are currently emitted as final,
+ * but the write (putfield) to the val does not occur syntactically within the <init> method
+ * (it's done by the trait setter, which is called from the trait's mixin constructor,
+ * which is called from the subclass's constructor...)
+ */
+abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransformers with AccessorSynthesis {
+ import global._
+ import definitions._
+
+ /** the following two members override abstract members in Transform */
+ val phaseName: String = "fields"
+
+ protected def newTransformer(unit: CompilationUnit): Transformer = new FieldsTransformer(unit)
+ override def transformInfo(sym: Symbol, tp: Type): Type =
+ if (sym.isJavaDefined || sym.isPackageClass || !sym.isClass) tp
+ else synthFieldsAndAccessors(tp)
+
+ // TODO: drop PRESUPER support when we implement trait parameters in 2.13
+ private def excludedAccessorOrFieldByFlags(statSym: Symbol): Boolean = statSym hasFlag PRESUPER
+
+ // used for internal communication between info and tree transform of this phase -- not pickled, not in initialflags
+ // TODO: reuse MIXEDIN for NEEDS_TREES?
+ override def phaseNewFlags: Long = NEEDS_TREES | OVERRIDDEN_TRAIT_SETTER
+
+ // informs the tree traversal of the shape of the tree to emit
+ // (it's an *overridden* trait setter)
+ private final val OVERRIDDEN_TRAIT_SETTER = TRANS_FLAG
+
+ final val TRAIT_SETTER_FLAGS = NEEDS_TREES | DEFERRED | ProtectedLocal
+
+ private def accessorImplementedInSubclass(accessor: Symbol) =
+ (accessor hasFlag SYNTHESIZE_IMPL_IN_SUBCLASS) && (accessor hasFlag (ACCESSOR | MODULE))
+
+ @inline final def notDeferredOrSynthImpl(sym: Symbol): Boolean = !(sym hasFlag DEFERRED) || (sym hasFlag SYNTHESIZE_IMPL_IN_SUBCLASS)
+
+ private def synthesizeImplInSubclasses(accessor: Symbol): Unit =
+ accessor setFlag SYNTHESIZE_IMPL_IN_SUBCLASS
+
+ private def setClonedTraitSetterFlags(clazz: Symbol, correspondingGetter: Symbol, cloneInSubclass: Symbol): Unit = {
+ val overridden = isOverriddenAccessor(correspondingGetter, clazz)
+ if (overridden) cloneInSubclass setFlag OVERRIDDEN_TRAIT_SETTER
+ else if (correspondingGetter.isEffectivelyFinal) cloneInSubclass setFlag FINAL
+ }
+
+ // TODO: add MIXEDIN (see e.g., `accessed` on `Symbol`)
+ private def setMixedinAccessorFlags(orig: Symbol, cloneInSubclass: Symbol): Unit =
+ cloneInSubclass setFlag OVERRIDE | NEEDS_TREES resetFlag DEFERRED | SYNTHESIZE_IMPL_IN_SUBCLASS
+
+ private def setFieldFlags(accessor: Symbol, fieldInSubclass: TermSymbol): Unit =
+ fieldInSubclass setFlag (NEEDS_TREES |
+ PrivateLocal
+ | (accessor getFlag MUTABLE | LAZY)
+ | (if (accessor hasFlag STABLE) 0 else MUTABLE)
+ )
+
+
+ def checkAndClearOverriddenTraitSetter(setter: Symbol) = checkAndClear(OVERRIDDEN_TRAIT_SETTER)(setter)
+ def checkAndClearNeedsTrees(setter: Symbol) = checkAndClear(NEEDS_TREES)(setter)
+ def checkAndClear(flag: Long)(sym: Symbol) =
+ sym.hasFlag(flag) match {
+ case overridden =>
+ sym resetFlag flag
+ overridden
+ }
+
+
+ private def isOverriddenAccessor(member: Symbol, site: Symbol): Boolean = {
+ val pre = site.thisType
+ @tailrec def loop(bcs: List[Symbol]): Boolean = {
+ // println(s"checking ${bcs.head} for member overriding $member (of ${member.owner})")
+ bcs.nonEmpty && bcs.head != member.owner && (matchingAccessor(pre, member, bcs.head) != NoSymbol || loop(bcs.tail))
+ }
+
+ member.exists && loop(site.info.baseClasses)
+ }
+
+
+ def matchingAccessor(pre: Type, member: Symbol, clazz: Symbol) = {
+ val res = member.matchingSymbol(clazz, pre) filter (sym => (sym hasFlag ACCESSOR) && notDeferredOrSynthImpl(sym))
+ // if (res != NoSymbol) println(s"matching accessor for $member in $clazz = $res (under $pre)")
+ // else println(s"no matching accessor for $member in $clazz (under $pre) among ${clazz.info.decls}")
+ res
+ }
+
+
+ class FieldMemoization(accessorOrField: Symbol, site: Symbol) {
+ val tp = fieldTypeOfAccessorIn(accessorOrField, site.thisType)
+ // We can only omit strict vals of ConstantType. Lazy vals do not receive constant types (anymore).
+ // (See note at widenIfNecessary -- for example, the REPL breaks when we omit constant lazy vals)
+ // Note that a strict unit-typed val does receive a field, because we cannot omit the write to the field
+ // (well, we could emit it for non-@volatile ones, if I understand the memory model correctly,
+ // but that seems pretty edge-casey)
+ val constantTyped = tp.isInstanceOf[ConstantType]
+ }
+
+ private def fieldTypeForGetterIn(getter: Symbol, pre: Type): Type = getter.info.finalResultType.asSeenFrom(pre, getter.owner)
+ private def fieldTypeForSetterIn(setter: Symbol, pre: Type): Type = setter.info.paramTypes.head.asSeenFrom(pre, setter.owner)
+
+ // TODO: is there a more elegant way?
+ def fieldTypeOfAccessorIn(accessor: Symbol, pre: Type) =
+ if (accessor.isSetter) fieldTypeForSetterIn(accessor, pre)
+ else fieldTypeForGetterIn(accessor, pre)
+
+
+ // Constant/unit typed vals are not memoized (their value is so cheap it doesn't make sense to store it in a field)
+ // for a unit-typed getter, we perform the effect at the appropriate time (constructor for eager ones, lzyCompute for lazy),
+ // and have the getter just return Unit (who does that!?)
+ // NOTE: this only considers type, filter on flags first!
+ def fieldMemoizationIn(accessorOrField: Symbol, site: Symbol) = new FieldMemoization(accessorOrField, site)
+
+ // drop field-targeting annotations from getters (done during erasure because we first need to create the field symbol)
+ // (in traits, getters must also hold annotations that target the underlying field,
+ // because the latter won't be created until the trait is mixed into a class)
+ // TODO do bean getters need special treatment to suppress field-targeting annotations in traits?
+ def dropFieldAnnotationsFromGetter(sym: Symbol) =
+ sym setAnnotations (sym.annotations filter AnnotationInfo.mkFilter(GetterTargetClass, defaultRetention = false))
+
+ def symbolAnnotationsTargetFieldAndGetter(sym: Symbol): Boolean = sym.isGetter && (sym.isLazy || sym.owner.isTrait)
+
+ // A trait val/var or a lazy val does not receive an underlying field symbol until this phase.
+ // Since annotations need a carrier symbol from the beginning, both field- and getter-targeting annotations
+ // are kept on the getter symbol for these until they are dropped by dropFieldAnnotationsFromGetter
+ def getterTreeAnnotationsTargetFieldAndGetter(owner: Symbol, mods: Modifiers) = mods.isLazy || owner.isTrait
+
+ // Propagate field-targeting annotations from getter to field.
+ // By the way, we must keep them around long enough to see them here (now that we have created the field),
+ // which is why dropFieldAnnotationsFromGetter is not called until erasure.
+ private def propagateFieldAnnotations(getter: Symbol, field: TermSymbol): Unit =
+ field setAnnotations (getter.annotations filter AnnotationInfo.mkFilter(FieldTargetClass, defaultRetention = true))
+
+
+ // can't use the referenced field since it already tracks the module's moduleClass
+ private[this] val moduleOrLazyVarOf = perRunCaches.newMap[Symbol, Symbol]
+
+ // TODO: can we drop FINAL? In any case, since these variables are MUTABLE, they cannot and will
+ // not be emitted as ACC_FINAL. They are FINAL in the Scala sense, though: cannot be overridden.
+ private final val ModuleOrLazyFieldFlags = FINAL | PrivateLocal | SYNTHETIC | NEEDS_TREES
+
+ private def moduleInit(module: Symbol, moduleVar: Symbol) = {
+// println(s"moduleInit for $module in ${module.ownerChain} --> ${moduleVarOf.get(module)}")
+ def moduleVarRef = gen.mkAttributedRef(moduleVar)
+
+ // for local modules, we synchronize on the owner of the method that owns the module
+ val monitorHolder = This(moduleVar.owner.enclClass)
+ def needsInit = Apply(Select(moduleVarRef, Object_eq), List(CODE.NULL))
+ val init = Assign(moduleVarRef, gen.newModule(module, moduleVar.info))
+
+ /** double-checked locking following https://shipilev.net/blog/2014/safe-public-construction/#_safe_publication
+ *
+ * public class SafeDCLFactory {
+ * private volatile Singleton instance;
+ *
+ * public Singleton get() {
+ * if (instance == null) { // check 1
+ * synchronized(this) {
+ * if (instance == null) { // check 2
+ * instance = new Singleton();
+ * }
+ * }
+ * }
+ * return instance;
+ * }
+ * }
+ *
+ * TODO: optimize using local variable?
+ */
+ val computeName = nme.newLazyValSlowComputeName(module.name)
+ val computeMethod = DefDef(NoMods, computeName, Nil, ListOfNil, TypeTree(UnitTpe), gen.mkSynchronized(monitorHolder)(If(needsInit, init, EmptyTree)))
+ Block(computeMethod :: If(needsInit, Apply(Ident(computeName), Nil), EmptyTree) :: Nil,
+ gen.mkCast(moduleVarRef, module.info.resultType))
+ }
+
+ // NoSymbol for lazy accessor sym with unit result type
+ def lazyVarOf(sym: Symbol) = moduleOrLazyVarOf.getOrElse(sym, NoSymbol)
+
+ private def newLazyVarMember(clazz: Symbol, member: Symbol, tp: Type): TermSymbol = {
+ val flags = LAZY | (member.flags & FieldFlags) | ModuleOrLazyFieldFlags
+ val name = member.name.toTermName.append(reflect.NameTransformer.LOCAL_SUFFIX_STRING)
+
+ // Set the MUTABLE flag because the field cannot be ACC_FINAL since we write to it outside of a constructor.
+ val sym = clazz.newVariable(name, member.pos.focus, flags) setInfo tp
+
+ moduleOrLazyVarOf(member) = sym
+ sym
+ }
+
+
+ private object synthFieldsAndAccessors extends TypeMap {
+ private def newTraitSetter(getter: Symbol, clazz: Symbol) = {
+ // Add setter for an immutable, memoizing getter
+ // (can't emit during namers because we don't yet know whether it's going to be memoized or not)
+ val setterFlags = (getter.flags & ~(STABLE | PrivateLocal | OVERRIDE | IMPLICIT | FINAL)) | MUTABLE | ACCESSOR | TRAIT_SETTER_FLAGS
+ val setterName = nme.expandedSetterName(getter.name.setterName, clazz)
+ val setter = clazz.newMethod(setterName, getter.pos.focus, setterFlags)
+ val fieldTp = fieldTypeForGetterIn(getter, clazz.thisType)
+ // println(s"newTraitSetter in $clazz for $getter = $setterName : $fieldTp")
+
+ getter.asTerm.referenced = setter
+
+ setter setInfo MethodType(List(setter.newSyntheticValueParam(fieldTp)), UnitTpe)
+ setter
+ }
+
+ private def newModuleAccessor(module: Symbol, site: Symbol, moduleVar: Symbol) = {
+ val accessor = site.newMethod(module.name.toTermName, site.pos, STABLE | MODULE | NEEDS_TREES)
+
+ moduleOrLazyVarOf(accessor) = moduleVar
+
+ // we're in the same prefix as module, so no need for site.thisType.memberType(module)
+ accessor setInfo MethodType(Nil, moduleVar.info)
+ accessor.setModuleClass(module.moduleClass)
+
+ if (module.isPrivate) accessor.expandName(module.owner)
+
+ accessor
+ }
+
+ // needed for the following scenario (T could be trait or class)
+ // trait T { def f: Object }; object O extends T { object f }. Need to generate method f in O.
+ // marking it as an ACCESSOR so that it will get to `getterBody` when synthesizing trees below
+ // it should not be considered a MODULE
+ def newMatchingModuleAccessor(clazz: Symbol, module: Symbol): MethodSymbol = {
+ val acc = clazz.newMethod(module.name.toTermName, module.pos, (module.flags & ~MODULE) | STABLE | NEEDS_TREES | ACCESSOR)
+ acc.referenced = module
+ acc setInfo MethodType(Nil, module.moduleClass.tpe)
+ }
+
+
+ private def newSuperLazy(lazyCallingSuper: Symbol, site: Type, lazyVar: Symbol) = {
+ lazyCallingSuper.asTerm.referenced = lazyVar
+
+ val tp = site.memberInfo(lazyCallingSuper)
+
+ lazyVar setInfo tp.resultType
+ lazyCallingSuper setInfo tp
+ }
+
+ private def classNeedsInfoTransform(cls: Symbol): Boolean = {
+ !(cls.isPackageClass || cls.isJavaDefined) && (currentRun.compiles(cls) || refChecks.isSeparatelyCompiledScalaSuperclass(cls))
+ }
+
+ def apply(tp0: Type): Type = tp0 match {
+ // TODO: make less destructive (name changes, decl additions, flag setting --
+ // none of this is actually undone when travelling back in time using atPhase)
+ case tp@ClassInfoType(parents, decls, clazz) if clazz.isTrait =>
+ // setters for trait vars or module accessor
+ val newDecls = collection.mutable.ListBuffer[Symbol]()
+ val origDecls = decls.toList
+
+ // strict, memoized accessors will receive an implementation in first real class to extend this trait
+ origDecls.foreach { member =>
+ if (member hasFlag ACCESSOR) {
+ val fieldMemoization = fieldMemoizationIn(member, clazz)
+ // check flags before calling makeNotPrivate
+ val accessorUnderConsideration = !(member hasFlag DEFERRED)
+
+ // destructively mangle accessor's name (which may cause rehashing of decls), also sets flags
+ // this accessor has to be implemented in a subclass -- can't be private
+ if ((member hasFlag PRIVATE) && !fieldMemoization.constantTyped) member makeNotPrivate clazz
+ // Since we need to refer to `member` using a super call in a subclass, we must ensure that access is allowed.
+ // If `member` has an access boundary, make sure the `PROTECTED` flag is set,
+ // to widen from `private[foo]` to `protected[foo]`
+ // (note that `member.hasAccessBoundary` implies `!member.hasFlag(PRIVATE)`, so we don't have to `resetFlag PRIVATE`)
+ else if (member.isLazy && member.hasAccessBoundary) member setFlag PROTECTED
+
+ // This must remain in synch with publicizeTraitMethod in Mixins, so that the
+ // synthesized member in a subclass and the trait member remain in synch regarding access.
+ // Otherwise, the member will not be seen as overriding the trait member, and `addForwarders`'s call to
+ // `membersBasedOnFlags` would see the deferred member in the trait, instead of the concrete (desired) one in the class
+ // not doing: if (member hasFlag PROTECTED) member setFlag notPROTECTED
+
+ // must not reset LOCAL, as we must maintain protected[this]ness to allow that variance hole
+ // (not sure why this only problem only arose when we started setting the notPROTECTED flag)
+
+ // derive trait setter after calling makeNotPrivate (so that names are mangled consistently)
+ if (accessorUnderConsideration && !fieldMemoization.constantTyped) {
+ synthesizeImplInSubclasses(member)
+
+ if ((member hasFlag STABLE) && !(member hasFlag LAZY))
+ newDecls += newTraitSetter(member, clazz)
+ }
+ } else if (member hasFlag MODULE) {
+ nonStaticModuleToMethod(member)
+
+ member setFlag NEEDS_TREES
+ synthesizeImplInSubclasses(member)
+ }
+ }
+
+ if (newDecls nonEmpty) {
+ val allDecls = newScope
+ origDecls foreach allDecls.enter
+ newDecls foreach allDecls.enter
+ ClassInfoType(parents, allDecls, clazz)
+ } else tp
+
+
+ case tp@ClassInfoType(parents, oldDecls, clazz) if !classNeedsInfoTransform(clazz) => tp
+
+ // mix in fields & accessors for all mixed in traits
+ case tp@ClassInfoType(parents, oldDecls, clazz) =>
+
+ val site = clazz.thisType
+
+ // setter conflicts cannot arise independently from a getter conflict, since a setter without a getter does not a val definition make
+ def getterConflictsExistingVal(getter: Symbol): Boolean =
+ getter.isGetter && {
+ val existingGetter = oldDecls.lookup(getter.name)
+ (existingGetter ne NoSymbol) &&
+ ((site memberInfo existingGetter) matches (site memberInfo getter))
+ }
+
+ def newModuleVarMember(module: Symbol): TermSymbol = {
+ val moduleVar =
+ (clazz.newVariable(nme.moduleVarName(module.name.toTermName), module.pos.focus, MODULEVAR | ModuleOrLazyFieldFlags)
+ setInfo site.memberType(module).resultType
+ addAnnotation VolatileAttr)
+
+ moduleOrLazyVarOf(module) = moduleVar
+
+ moduleVar
+ }
+
+ def newLazyVarMember(member: Symbol): TermSymbol =
+ Fields.this.newLazyVarMember(clazz, member, site.memberType(member).resultType)
+
+ // a module does not need treatment here if it's static, unless it has a matching member in a superclass
+ // a non-static method needs a module var
+ val modulesAndLazyValsNeedingExpansion =
+ oldDecls.toList.filter(m => (m.isModule && (!m.isStatic || m.isOverridingSymbol)) || m.isLazy)
+
+ val accessorSymbolSynth = checkedAccessorSymbolSynth(tp.typeSymbol)
+
+ // expand module def in class/object (if they need it -- see modulesNeedingExpansion above)
+ val expandedModulesAndLazyVals =
+ modulesAndLazyValsNeedingExpansion flatMap { member =>
+ if (member.isLazy) {
+ val lazyVar = newLazyVarMember(member)
+ propagateFieldAnnotations(member, lazyVar)
+ List(lazyVar, accessorSymbolSynth.newSlowPathSymbol(member))
+ }
+ // expanding module def (top-level or nested in static module)
+ else List(if (member.isStatic) { // implies m.isOverridingSymbol as per above filter
+ // Need a module accessor, to implement/override a matching member in a superclass.
+ // Never a need for a module var if the module is static.
+ newMatchingModuleAccessor(clazz, member)
+ } else {
+ nonStaticModuleToMethod(member)
+ // must reuse symbol instead of creating an accessor
+ member setFlag NEEDS_TREES
+ newModuleVarMember(member)
+ })
+ }
+
+// println(s"expanded modules for $clazz: $expandedModules")
+
+ // afterOwnPhase, so traits receive trait setters for vals (needs to be at finest grain to avoid looping)
+ val synthInSubclass =
+ clazz.mixinClasses.flatMap(mixin => afterOwnPhase{mixin.info}.decls.toList.filter(accessorImplementedInSubclass))
+
+ // mixin field accessors --
+ // invariant: (accessorsMaybeNeedingImpl, mixedInAccessorAndFields).zipped.forall(case (acc, clone :: _) => `clone` is clone of `acc` case _ => true)
+ val mixedInAccessorAndFields = synthInSubclass.map{ member =>
+ def cloneAccessor() = {
+ val clonedAccessor = (member cloneSymbol clazz) setPos clazz.pos
+ setMixedinAccessorFlags(member, clonedAccessor)
+
+ // note: check original member when deciding how to triage annotations, then act on the cloned accessor
+ if (symbolAnnotationsTargetFieldAndGetter(member)) // this simplifies to member.isGetter, but the full formulation really ties the triage together
+ dropFieldAnnotationsFromGetter(clonedAccessor)
+
+ // if we don't cloneInfo, method argument symbols are shared between trait and subclasses --> lambalift proxy crash
+ // TODO: use derive symbol variant?
+// println(s"cloning accessor $member to $clazz")
+ // start at uncurry so that we preserve that part of the history where an accessor has a NullaryMethodType
+ enteringUncurry { clonedAccessor setInfo ((clazz.thisType memberType member) cloneInfo clonedAccessor) }
+ clonedAccessor
+ }
+
+ // when considering whether to mix in the trait setter, forget about conflicts -- they are reported for the getter
+ // a trait setter for an overridden val will receive a unit body in the tree transform
+ if (nme.isTraitSetterName(member.name)) {
+ val getter = member.getterIn(member.owner)
+ val clone = cloneAccessor()
+
+ setClonedTraitSetterFlags(clazz, getter, clone)
+ // println(s"mixed in trait setter ${clone.defString}")
+
+ List(clone)
+ }
+ // don't cause conflicts, skip overridden accessors contributed by supertraits (only act on the last overriding one)
+ // see pos/trait_fields_dependent_conflict.scala and neg/t1960.scala
+ else if (getterConflictsExistingVal(member) || isOverriddenAccessor(member, clazz)) Nil
+ else if (member hasFlag MODULE) {
+ val moduleVar = newModuleVarMember(member)
+ List(moduleVar, newModuleAccessor(member, clazz, moduleVar))
+ }
+ else if (member hasFlag LAZY) {
+ val mixedinLazy = cloneAccessor()
+ val lazyVar = newLazyVarMember(mixedinLazy) // link lazy var member to the mixedin lazy accessor
+
+ // propagate from original member. since mixed in one has only retained the annotations targeting the getter
+ propagateFieldAnnotations(member, lazyVar)
+
+ // println(s"mixing in lazy var: $lazyVar for $member")
+ List(lazyVar, accessorSymbolSynth.newSlowPathSymbol(mixedinLazy), newSuperLazy(mixedinLazy, site, lazyVar))
+ }
+ else if (member.isGetter && !fieldMemoizationIn(member, clazz).constantTyped) {
+ // add field if needed
+ val field = clazz.newValue(member.localName, member.pos) setInfo fieldTypeForGetterIn(member, clazz.thisType)
+
+ setFieldFlags(member, field)
+
+ propagateFieldAnnotations(member, field)
+
+ List(cloneAccessor(), field)
+ } else List(cloneAccessor()) // no field needed (constant-typed getter has constant as its RHS)
+ }
+
+ // println(s"mixedInAccessorAndFields for $clazz: $mixedInAccessorAndFields")
+
+ // omit fields that are not memoized, retain all other members
+ def omittableField(sym: Symbol) = sym.isValue && !sym.isMethod && fieldMemoizationIn(sym, clazz).constantTyped
+
+ val newDecls =
+ // under -Xcheckinit we generate all kinds of bitmaps, even when there are no lazy vals
+ if (expandedModulesAndLazyVals.isEmpty && mixedInAccessorAndFields.isEmpty && !settings.checkInit)
+ oldDecls.filterNot(omittableField)
+ else {
+ // must not alter `decls` directly
+ val newDecls = newScope
+ val enter = newDecls enter (_: Symbol)
+ val enterAll = (_: List[Symbol]) foreach enter
+
+ expandedModulesAndLazyVals foreach enter
+ oldDecls foreach { d => if (!omittableField(d)) enter(d) }
+ mixedInAccessorAndFields foreach enterAll
+
+ // both oldDecls and mixedInAccessorAndFields (a list of lists) contribute
+ val bitmapSyms = accessorSymbolSynth.computeBitmapInfos(newDecls.toList)
+
+ bitmapSyms foreach enter
+
+ newDecls
+ }
+
+ // println(s"new decls for $clazz: $expandedModules ++ $mixedInAccessorAndFields")
+
+ if (newDecls eq oldDecls) tp
+ else ClassInfoType(parents, newDecls, clazz)
+
+ case tp => mapOver(tp)
+ }
+ }
+
+
+ // done by uncurry's info transformer
+ // instead of forcing every member's info to run said transformer, duplicate the flag update logic...
+ def nonStaticModuleToMethod(module: Symbol): Unit =
+ if (!module.isStatic) module setFlag METHOD | STABLE
+
+ // scala/scala-dev#219, scala/scala-dev#268
+ // Cast to avoid spurious mismatch in paths containing trait vals that have
+ // not been rebound to accessors in the subclass we're in now.
+ // For example, for a lazy val mixed into a class, the lazy var's info
+ // will not refer to symbols created during our info transformer,
+ // so if its type depends on a val that is now implemented after the info transformer,
+ // we'll get a mismatch when assigning `rhs` to `lazyVarOf(getter)`.
+ // TODO: could we rebind more aggressively? consider overriding in type equality?
+ def castHack(tree: Tree, pt: Type) = gen.mkAsInstanceOf(tree, pt)
+
+ class FieldsTransformer(unit: CompilationUnit) extends TypingTransformer(unit) with CheckedAccessorTreeSynthesis {
+ protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree)
+
+ def mkTypedUnit(pos: Position) = typedPos(pos)(CODE.UNIT)
+ // TODO: clean up. this method is not used
+ def deriveUnitDef(stat: Tree) = deriveDefDef(stat)(_ => mkTypedUnit(stat.pos))
+
+ def mkAccessor(accessor: Symbol)(body: Tree) = typedPos(accessor.pos)(DefDef(accessor, body)).asInstanceOf[DefDef]
+
+ // this makes trees for mixed in fields, as well as for bitmap fields (their RHS will be EmptyTree because they are initialized implicitly)
+ // if we decide to explicitly initialize, use this RHS: if (symbol.info.typeSymbol.asClass == BooleanClass) FALSE else ZERO)
+ // could detect it's a bitmap field with something like `sym.name.startsWith(nme.BITMAP_PREFIX)` (or perhaps something more robust...)
+ def mkTypedValDef(sym: Symbol, rhs: Tree = EmptyTree) = typedPos(sym.pos)(ValDef(sym, rhs)).asInstanceOf[ValDef]
+
+ /**
+ * Desugar a local `lazy val x: Int = rhs`
+ * or a local `object x { ...}` (the rhs will be instantiating the module's class) into:
+ *
+ * ```
+ * val x$lzy = new scala.runtime.LazyInt()
+ * def x$lzycompute(): Int =
+ * x$lzy.synchronized {
+ * if (x$lzy.initialized()) x$lzy.value()
+ * else x$lzy.initialize(rhs) // for a Unit-typed lazy val, this becomes `{ rhs ; x$lzy.initialize() }` to avoid passing around BoxedUnit
+ * }
+ * def x(): Int = if (x$lzy.initialized()) x$lzy.value() else x$lzycompute()
+ * ```
+ *
+ * The expansion is the same for local lazy vals and local objects,
+ * except for the suffix of the underlying val's name ($lzy or $module)
+ */
+ private def mkLazyLocalDef(lazySym: Symbol, rhs: Tree): Tree = {
+ import CODE._
+ import scala.reflect.{NameTransformer => nx}
+ val owner = lazySym.owner
+
+ val lazyValType = lazySym.tpe.resultType
+ val refClass = lazyHolders.getOrElse(lazyValType.typeSymbol, LazyRefClass)
+ val isUnit = refClass == LazyUnitClass
+ val refTpe = if (refClass != LazyRefClass) refClass.tpe else appliedType(refClass.typeConstructor, List(lazyValType))
+
+ val lazyName = lazySym.name.toTermName
+ val pos = lazySym.pos.focus
+
+ val localLazyName = lazyName append (if (lazySym.isModule) nx.MODULE_VAR_SUFFIX_STRING else nx.LAZY_LOCAL_SUFFIX_STRING)
+
+ // The lazy holder val need not be mutable, as we write to its field.
+ // In fact, it MUST not be mutable to avoid capturing it as an ObjectRef in lambdalift
+ // Must be marked LAZY to allow forward references, as in `def test2 { println(s.length) ; lazy val s = "abc" }
+ val holderSym = owner.newValue(localLazyName, pos, LAZY | ARTIFACT) setInfo refTpe
+
+ val initializedSym = refTpe.member(nme.initialized)
+ val initializeSym = refTpe.member(nme.initialize)
+
+ // LazyUnit does not have a `value` member
+ val valueSym = if (isUnit) NoSymbol else refTpe.member(nme.value)
+
+ def initialized = Select(Ident(holderSym), initializedSym)
+ def initialize = Select(Ident(holderSym), initializeSym)
+ def getValue = if (isUnit) UNIT else Apply(Select(Ident(holderSym), valueSym), Nil)
+
+ val computerSym =
+ owner.newMethod(lazyName append nme.LAZY_SLOW_SUFFIX, pos, ARTIFACT | PRIVATE) setInfo MethodType(Nil, lazyValType)
+
+ val rhsAtComputer = rhs.changeOwner(lazySym -> computerSym)
+
+ val computer = mkAccessor(computerSym)(gen.mkSynchronized(Ident(holderSym))(
+ If(initialized, getValue,
+ if (isUnit) Block(rhsAtComputer :: Nil, Apply(initialize, Nil))
+ else Apply(initialize, rhsAtComputer :: Nil))))
+
+ val accessor = mkAccessor(lazySym)(
+ If(initialized, getValue,
+ Apply(Ident(computerSym), Nil)))
+
+ // do last!
+ // remove STABLE: prevent replacing accessor call of type Unit by BoxedUnit.UNIT in erasure
+ // remove ACCESSOR: prevent constructors from eliminating the method body if the lazy val is
+ // lifted into a trait (TODO: not sure about the details here)
+ lazySym.resetFlag(STABLE | ACCESSOR)
+
+ Thicket(mkTypedValDef(holderSym, New(refTpe)) :: computer :: accessor :: Nil)
+ }
+
+ // synth trees for accessors/fields and trait setters when they are mixed into a class
+ def fieldsAndAccessors(clazz: Symbol): List[Tree] = {
+
+ // Could be NoSymbol, which denotes an error, but it's refchecks' job to report it (this fallback is for robustness).
+ // This is the result of overriding a val with a def, so that no field is found in the subclass.
+ def fieldAccess(accessor: Symbol): Symbol =
+ afterOwnPhase { clazz.info.decl(accessor.localName) }
+
+ def getterBody(getter: Symbol): Tree =
+ // accessor created by newMatchingModuleAccessor for a static module that does need an accessor
+ // (because there's a matching member in a super class)
+ if (getter.asTerm.referenced.isModule)
+ mkAccessor(getter)(castHack(Select(This(clazz), getter.asTerm.referenced), getter.info.resultType))
+ else {
+ val fieldMemoization = fieldMemoizationIn(getter, clazz)
+ // TODO: drop getter for constant? (when we no longer care about producing identical bytecode?)
+ if (fieldMemoization.constantTyped) mkAccessor(getter)(gen.mkAttributedQualifier(fieldMemoization.tp))
+ else fieldAccess(getter) match {
+ case NoSymbol => EmptyTree
+ case fieldSel => mkAccessor(getter)(castHack(Select(This(clazz), fieldSel), getter.info.resultType))
+ }
+ }
+
+ // println(s"accessorsAndFieldsNeedingTrees for $templateSym: $accessorsAndFieldsNeedingTrees")
+ def setterBody(setter: Symbol): Tree =
+ // trait setter in trait
+ if (clazz.isTrait) mkAccessor(setter)(EmptyTree)
+ // trait setter for overridden val in class
+ else if (checkAndClearOverriddenTraitSetter(setter)) mkAccessor(setter)(mkTypedUnit(setter.pos))
+ // trait val/var setter mixed into class
+ else fieldAccess(setter) match {
+ case NoSymbol => EmptyTree
+ case fieldSel => afterOwnPhase { // the assign only type checks after our phase (assignment to val)
+ mkAccessor(setter)(Assign(Select(This(clazz), fieldSel), castHack(Ident(setter.firstParam), fieldSel.info)))
+ }
+ }
+
+ def moduleAccessorBody(module: Symbol): Tree =
+ // added during synthFieldsAndAccessors using newModuleAccessor
+ // a module defined in a trait by definition can't be static (it's a member of the trait and thus gets a new instance for every outer instance)
+ if (clazz.isTrait) mkAccessor(module)(EmptyTree)
+ // symbol created by newModuleAccessor for a (non-trait) class
+ else {
+ mkAccessor(module)(moduleInit(module, moduleOrLazyVarOf(module)))
+ }
+
+ val synthAccessorInClass = new SynthLazyAccessorsIn(clazz)
+ def superLazy(getter: Symbol): Tree = {
+ assert(!clazz.isTrait)
+ // this contortion was the only way I can get the super select to be type checked correctly..
+ // TODO: why does SelectSuper not work?
+ val selectSuper = Select(Super(This(clazz), tpnme.EMPTY), getter.name)
+
+ val lazyVar = lazyVarOf(getter)
+ val rhs = castHack(Apply(selectSuper, Nil), lazyVar.info)
+
+ synthAccessorInClass.expandLazyClassMember(lazyVar, getter, rhs)
+ }
+
+ (afterOwnPhase { clazz.info.decls } toList) filter checkAndClearNeedsTrees map {
+ case module if module hasAllFlags (MODULE | METHOD) => moduleAccessorBody(module)
+ case getter if getter hasAllFlags (LAZY | METHOD) => superLazy(getter)
+ case setter if setter.isSetter => setterBody(setter)
+ case getter if getter.hasFlag(ACCESSOR) => getterBody(getter)
+ case field if !(field hasFlag METHOD) => mkTypedValDef(field) // vals/vars and module vars (cannot have flags PACKAGE | JAVA since those never receive NEEDS_TREES)
+ case _ => EmptyTree
+ } filterNot (_ == EmptyTree) // there will likely be many EmptyTrees, but perhaps no thicket blocks that need expanding
+ }
+
+ def rhsAtOwner(stat: ValOrDefDef, newOwner: Symbol): Tree =
+ atOwner(newOwner)(super.transform(stat.rhs.changeOwner(stat.symbol -> newOwner)))
+
+ override def transform(stat: Tree): Tree = {
+ val currOwner = currentOwner // often a class, but not necessarily
+ val statSym = stat.symbol
+
+ /*
+ For traits, the getter has the val's RHS, which is already constant-folded. There is no valdef.
+ For classes, we still have the classic scheme of private[this] valdef + getter & setter that read/assign to the field.
+
+ There are two axes: (1) is there a side-effect to the val (2) does the val need storage?
+ For a ConstantType, both answers are "no". (For a unit-typed field, there's a side-effect, but no storage needed.)
+
+ All others (getter for trait field, valdef for class field) have their rhs moved to an initialization statement.
+ Trait accessors for stored fields are made abstract (there can be no field in a trait).
+ (In some future version, accessors for non-stored, but effectful fields,
+ would receive a constant rhs, as the effect is performed by the initialization statement.
+ We could do this for unit-typed fields, but have chosen not to for backwards compatibility.)
+ */
+ stat match {
+ // TODO: consolidate with ValDef case
+ // TODO: defer replacing ConstantTyped tree by the corresponding constant until erasure
+ // (until then, trees should not be constant-folded -- only their type tracks the resulting constant)
+ // also remove ACCESSOR flag since there won't be an underlying field to access?
+ case DefDef(_, _, _, _, _, rhs) if (statSym hasFlag ACCESSOR)
+ && (rhs ne EmptyTree) && !excludedAccessorOrFieldByFlags(statSym)
+ && !currOwner.isTrait // we've already done this for traits.. the asymmetry will be solved by the above todo
+ && fieldMemoizationIn(statSym, currOwner).constantTyped =>
+ deriveDefDef(stat)(_ => gen.mkAttributedQualifier(rhs.tpe))
+
+ // deferred val, trait val, lazy val (local or in class)
+ case vd@ValDef(mods, name, tpt, rhs) if vd.symbol.hasFlag(ACCESSOR) && treeInfo.noFieldFor(vd, currOwner) =>
+ val transformedRhs = atOwner(statSym)(transform(rhs))
+
+ if (rhs == EmptyTree) mkAccessor(statSym)(EmptyTree)
+ else if (currOwner.isTrait) mkAccessor(statSym)(castHack(transformedRhs, statSym.info.resultType))
+ else if (!currOwner.isClass) mkLazyLocalDef(vd.symbol, transformedRhs)
+ else {
+ // TODO: make `synthAccessorInClass` a field and update it in atOwner?
+ // note that `LazyAccessorTreeSynth` is pretty lightweight
+ // (it's just a bunch of methods that all take a `clazz` parameter, which is thus stored as a field)
+ val synthAccessorInClass = new SynthLazyAccessorsIn(currOwner)
+ synthAccessorInClass.expandLazyClassMember(lazyVarOf(statSym), statSym, transformedRhs)
+ }
+
+ // drop the val for (a) constant (pure & not-stored) and (b) not-stored (but still effectful) fields
+ case ValDef(mods, _, _, rhs) if (rhs ne EmptyTree) && !excludedAccessorOrFieldByFlags(statSym)
+ && currOwner.isClass && fieldMemoizationIn(statSym, currOwner).constantTyped =>
+ EmptyThicket
+
+ case ModuleDef(_, _, impl) =>
+ // ??? The typer doesn't take kindly to seeing this ClassDef; we have to set NoType so it will be ignored.
+ val cd = super.transform(ClassDef(statSym.moduleClass, impl) setType NoType)
+ if (currOwner.isClass) cd
+ else { // local module -- symbols cannot be generated by info transformer, so do it all here
+ val Block(stats, _) = mkLazyLocalDef(statSym, gen.newModule(statSym, statSym.info.resultType))
+
+ Thicket(cd :: stats)
+ }
+
+ case tree =>
+ super.transform(tree)
+
+ }
+ }
+
+
+ def transformTermsAtExprOwner(exprOwner: Symbol)(stat: Tree) =
+ if (stat.isTerm) atOwner(exprOwner)(transform(stat))
+ else transform(stat)
+
+ override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = {
+ val addedStats =
+ if (!currentOwner.isClass || currentOwner.isPackageClass) Nil
+ else {
+ val thickets = fieldsAndAccessors(currentOwner)
+ if (thickets exists mustExplodeThicket)
+ thickets flatMap explodeThicket
+ else thickets
+ }
+
+ val newStats =
+ stats mapConserve (if (exprOwner != currentOwner) transformTermsAtExprOwner(exprOwner) else transform)
+
+ addedStats ::: (if (newStats eq stats) stats else {
+ // check whether we need to flatten thickets and drop empty ones
+ if (newStats exists mustExplodeThicket)
+ newStats flatMap explodeThicket
+ else newStats
+ })
+ }
+
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/transform/Flatten.scala b/src/compiler/scala/tools/nsc/transform/Flatten.scala
index fbb0307773..29ba21cba7 100644
--- a/src/compiler/scala/tools/nsc/transform/Flatten.scala
+++ b/src/compiler/scala/tools/nsc/transform/Flatten.scala
@@ -41,8 +41,6 @@ abstract class Flatten extends InfoTransform {
}
private def liftSymbol(sym: Symbol) {
liftClass(sym)
- if (sym.needsImplClass)
- liftClass(erasure implClass sym)
}
// This is a short-term measure partially working around objects being
// lifted out of parameterized classes, leaving them referencing
@@ -78,7 +76,7 @@ abstract class Flatten extends InfoTransform {
decls1 enter sym
if (sym.isModule) {
// In theory, we could assert(sym.isMethod), because nested, non-static modules are
- // transformed to methods (lateMETHOD flag added in RefChecks). But this requires
+ // transformed to methods (METHOD flag added in UnCurry). But this requires
// forcing sym.info (see comment on isModuleNotMethod), which forces stub symbols
// too eagerly (SI-8907).
diff --git a/src/compiler/scala/tools/nsc/transform/InlineErasure.scala b/src/compiler/scala/tools/nsc/transform/InlineErasure.scala
deleted file mode 100644
index 1bbe1b8410..0000000000
--- a/src/compiler/scala/tools/nsc/transform/InlineErasure.scala
+++ /dev/null
@@ -1,11 +0,0 @@
-package scala.tools.nsc
-package transform
-
-trait InlineErasure {
- self: Erasure =>
-
-/*
- import global._
- import definitions._
- */
-}
diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala
index d1be1558b9..169fe7588e 100644
--- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala
+++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala
@@ -8,7 +8,7 @@ package transform
import symtab._
import Flags._
-import scala.collection.{ mutable, immutable }
+import scala.collection.mutable
import scala.collection.mutable.{ LinkedHashMap, LinkedHashSet, TreeSet }
abstract class LambdaLift extends InfoTransform {
@@ -31,11 +31,6 @@ abstract class LambdaLift extends InfoTransform {
}
}
- /** scala.runtime.*Ref classes */
- private lazy val allRefClasses: Set[Symbol] = {
- refClass.values.toSet ++ volatileRefClass.values.toSet ++ Set(VolatileObjectRefClass, ObjectRefClass)
- }
-
/** Each scala.runtime.*Ref class has a static method `create(value)` that simply instantiates the Ref to carry that value. */
private lazy val refCreateMethod: Map[Symbol, Symbol] = {
mapFrom(allRefClasses.toList)(x => getMemberMethod(x.companionModule, nme.create))
@@ -103,19 +98,37 @@ abstract class LambdaLift extends InfoTransform {
*/
private val proxyNames = mutable.HashMap[Symbol, Name]()
- // (trait, name) -> owner
- private val localTraits = mutable.HashMap[(Symbol, Name), Symbol]()
- // (owner, name) -> implClass
- private val localImplClasses = mutable.HashMap[(Symbol, Name), Symbol]()
-
/** A flag to indicate whether new free variables have been found */
private var changedFreeVars: Boolean = _
/** Buffers for lifted out classes and methods */
private val liftedDefs = new LinkedHashMap[Symbol, List[Tree]]
+ val delayedInitDummies = new mutable.HashMap[Symbol, Symbol]
+
+ /**
+ * For classes capturing locals, LambdaLift uses `local.logicallyEnclosingMember` to decide
+ * whether an access to the local is re-written to the field or constructor parameter. If the
+ * access is in a constructor statement, the constructor parameter is used.
+ *
+ * For DelayedInit subclasses, constructor statements end up in the synthetic init method
+ * instead of the constructor itself, so the access should go to the field. This method changes
+ * `logicallyEnclosingMember` in this case to return a temporary symbol corresponding to that
+ * method.
+ */
+ private def logicallyEnclosingMember(sym: Symbol): Symbol = {
+ if (sym.isLocalDummy) {
+ val enclClass = sym.enclClass
+ if (enclClass.isSubClass(DelayedInitClass))
+ delayedInitDummies.getOrElseUpdate(enclClass, enclClass.newMethod(nme.delayedInit))
+ else
+ enclClass.primaryConstructor
+ } else if (sym.isMethod || sym.isClass || sym == NoSymbol) sym
+ else logicallyEnclosingMember(sym.owner)
+ }
+
private def isSameOwnerEnclosure(sym: Symbol) =
- sym.owner.logicallyEnclosingMember == currentOwner.logicallyEnclosingMember
+ logicallyEnclosingMember(sym.owner) == logicallyEnclosingMember(currentOwner)
/** Mark symbol `sym` as being free in `enclosure`, unless `sym`
* is defined in `enclosure` or there is a class between `enclosure`s owner
@@ -148,17 +161,17 @@ abstract class LambdaLift extends InfoTransform {
* }
*/
private def markFree(sym: Symbol, enclosure: Symbol): Boolean = {
- debuglog("mark free: " + sym.fullLocationString + " marked free in " + enclosure)
- (enclosure == sym.owner.logicallyEnclosingMember) || {
- debuglog("%s != %s".format(enclosure, sym.owner.logicallyEnclosingMember))
- if (enclosure.isPackageClass || !markFree(sym, enclosure.skipConstructor.owner.logicallyEnclosingMember)) false
+// println(s"mark free: ${sym.fullLocationString} marked free in $enclosure")
+ (enclosure == logicallyEnclosingMember(sym.owner)) || {
+ debuglog("%s != %s".format(enclosure, logicallyEnclosingMember(sym.owner)))
+ if (enclosure.isPackageClass || !markFree(sym, logicallyEnclosingMember(enclosure.skipConstructor.owner))) false
else {
val ss = symSet(free, enclosure)
if (!ss(sym)) {
ss += sym
renamable += sym
changedFreeVars = true
- debuglog("" + sym + " is free in " + enclosure)
+ debuglog(s"$sym is free in $enclosure")
if (sym.isVariable) sym setFlag CAPTURED
}
!enclosure.isClass
@@ -167,7 +180,7 @@ abstract class LambdaLift extends InfoTransform {
}
private def markCalled(sym: Symbol, owner: Symbol) {
- debuglog("mark called: " + sym + " of " + sym.owner + " is called by " + owner)
+// println(s"mark called: $sym of ${sym.owner} is called by $owner")
symSet(called, owner) += sym
if (sym.enclClass != owner.enclClass) calledFromInner += sym
}
@@ -175,30 +188,13 @@ abstract class LambdaLift extends InfoTransform {
/** The traverse function */
private val freeVarTraverser = new Traverser {
override def traverse(tree: Tree) {
- try { //debug
+// try { //debug
val sym = tree.symbol
tree match {
case ClassDef(_, _, _, _) =>
liftedDefs(tree.symbol) = Nil
if (sym.isLocalToBlock) {
- // Don't rename implementation classes independently of their interfaces. If
- // the interface is to be renamed, then we will rename the implementation
- // class at that time. You'd think we could call ".implClass" on the trait
- // rather than collecting them in another map, but that seems to fail for
- // exactly the traits being renamed here (i.e. defined in methods.)
- //
- // !!! - it makes no sense to have methods like "implClass" and
- // "companionClass" which fail for an arbitrary subset of nesting
- // arrangements, and then have separate methods which attempt to compensate
- // for that failure. There should be exactly one method for any given
- // entity which always gives the right answer.
- if (sym.isImplClass)
- localImplClasses((sym.owner, tpnme.interfaceName(sym.name))) = sym
- else {
- renamable += sym
- if (sym.isTrait)
- localTraits((sym, sym.name)) = sym.owner
- }
+ renamable += sym
}
case DefDef(_, _, _, _, _, _) =>
if (sym.isLocalToBlock) {
@@ -211,22 +207,22 @@ abstract class LambdaLift extends InfoTransform {
if (sym == NoSymbol) {
assert(name == nme.WILDCARD)
} else if (sym.isLocalToBlock) {
- val owner = currentOwner.logicallyEnclosingMember
+ val owner = logicallyEnclosingMember(currentOwner)
if (sym.isTerm && !sym.isMethod) markFree(sym, owner)
else if (sym.isMethod) markCalled(sym, owner)
//symSet(called, owner) += sym
}
case Select(_, _) =>
if (sym.isConstructor && sym.owner.isLocalToBlock)
- markCalled(sym, currentOwner.logicallyEnclosingMember)
+ markCalled(sym, logicallyEnclosingMember(currentOwner))
case _ =>
}
super.traverse(tree)
- } catch {//debug
- case ex: Throwable =>
- Console.println(s"$ex while traversing $tree")
- throw ex
- }
+// } catch {//debug
+// case ex: Throwable =>
+// Console.println(s"$ex while traversing $tree")
+// throw ex
+// }
}
}
@@ -240,7 +236,7 @@ abstract class LambdaLift extends InfoTransform {
do {
changedFreeVars = false
- for (caller <- called.keys ; callee <- called(caller) ; fvs <- free get callee ; fv <- fvs)
+ for ((caller, callees) <- called ; callee <- callees ; fvs <- free get callee ; fv <- fvs)
markFree(fv, caller)
} while (changedFreeVars)
@@ -250,11 +246,6 @@ 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 =
@@ -263,57 +254,49 @@ abstract class LambdaLift extends InfoTransform {
val join = nme.NAME_JOIN_STRING
if (sym.isAnonymousFunction && sym.owner.isMethod) {
- freshen(sym.name + join + nonAnon(sym.owner.name.toString) + join)
+ freshen(sym.name + join + nme.ensureNonAnon(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 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)
+ if (originalName.isTermName && calledFromInner(sym))
+ newTermNameCached(nme.ensureNonAnon(sym.enclClass.fullName('$')) + nme.EXPAND_SEPARATOR_STRING + name)
else
name
}
}
- /* Rename a trait's interface and implementation class in coordinated fashion. */
- def renameTrait(traitSym: Symbol, implSym: Symbol) {
- val originalImplName = implSym.name
- renameSym(traitSym)
- implSym setName tpnme.implClassName(traitSym.name)
-
- debuglog("renaming impl class in step with %s: %s => %s".format(traitSym, originalImplName, implSym.name))
- }
-
val allFree: Set[Symbol] = free.values.flatMap(_.iterator).toSet
for (sym <- renamable) {
- // If we renamed a trait from Foo to Foo$1, we must rename the implementation
- // class from Foo$class to Foo$1$class. (Without special consideration it would
- // become Foo$class$1 instead.) Since the symbols are being renamed out from
- // under us, and there's no reliable link between trait symbol and impl symbol,
- // we have maps from ((trait, name)) -> owner and ((owner, name)) -> impl.
- localTraits remove ((sym, sym.name)) match {
- case None =>
- if (allFree(sym)) proxyNames(sym) = newName(sym)
- else renameSym(sym)
- case Some(owner) =>
- localImplClasses remove ((owner, sym.name)) match {
- case Some(implSym) => renameTrait(sym, implSym)
- case _ => renameSym(sym) // pure interface, no impl class
- }
- }
+ if (allFree(sym)) proxyNames(sym) = newName(sym)
+ else renameSym(sym)
}
afterOwnPhase {
for ((owner, freeValues) <- free.toList) {
- val newFlags = SYNTHETIC | ( if (owner.isClass) PARAMACCESSOR | PrivateLocal else PARAM )
- debuglog("free var proxy: %s, %s".format(owner.fullLocationString, freeValues.toList.mkString(", ")))
+ val newFlags = SYNTHETIC | (if (owner.isClass) PARAMACCESSOR else PARAM)
+
proxies(owner) =
for (fv <- freeValues.toList) yield {
val proxyName = proxyNames.getOrElse(fv, fv.name)
- val proxy = owner.newValue(proxyName.toTermName, owner.pos, newFlags.toLong) setInfo fv.info
+ debuglog(s"new proxy ${proxyName} in ${owner.fullLocationString}")
+ val proxy =
+ if (owner.isTrait) {
+ val accessorFlags = newFlags.toLong | ACCESSOR | SYNTHESIZE_IMPL_IN_SUBCLASS
+
+ // TODO do we need to preserve pre-erasure info for the accessors (and a NullaryMethodType for the getter)?
+ // can't have a field in the trait, so add a setter
+ val setter = owner.newMethod(nme.expandedSetterName(proxyName.setterName, owner), fv.pos, accessorFlags)
+ setter setInfoAndEnter MethodType(setter.newSyntheticValueParams(List(fv.info)), UnitTpe)
+
+ // the getter serves as the proxy -- entered below
+ owner.newMethod(proxyName.getterName, fv.pos, accessorFlags | STABLE) setInfo MethodType(Nil, fv.info)
+ } else
+ owner.newValue(proxyName.toTermName, fv.pos, newFlags.toLong | PrivateLocal) setInfo fv.info
+
if (owner.isClass) owner.info.decls enter proxy
proxy
}
@@ -323,17 +306,18 @@ abstract class LambdaLift extends InfoTransform {
private def proxy(sym: Symbol) = {
def searchIn(enclosure: Symbol): Symbol = {
- if (enclosure eq NoSymbol) throw new IllegalArgumentException("Could not find proxy for "+ sym.defString +" in "+ sym.ownerChain +" (currentOwner= "+ currentOwner +" )")
- debuglog("searching for " + sym + "(" + sym.owner + ") in " + enclosure + " " + enclosure.logicallyEnclosingMember)
+ if (enclosure eq NoSymbol)
+ throw new IllegalArgumentException("Could not find proxy for "+ sym.defString +" in "+ sym.ownerChain +" (currentOwner= "+ currentOwner +" )")
+ debuglog("searching for " + sym + "(" + sym.owner + ") in " + enclosure + " " + logicallyEnclosingMember(enclosure))
val proxyName = proxyNames.getOrElse(sym, sym.name)
- val ps = (proxies get enclosure.logicallyEnclosingMember).toList.flatten find (_.name == proxyName)
+ val ps = (proxies get logicallyEnclosingMember(enclosure)).toList.flatten find (_.name == proxyName)
ps getOrElse searchIn(enclosure.skipConstructor.owner)
}
debuglog("proxy %s from %s has logical enclosure %s".format(
sym.debugLocationString,
currentOwner.debugLocationString,
- sym.owner.logicallyEnclosingMember.debugLocationString)
+ logicallyEnclosingMember(sym.owner).debugLocationString)
)
if (isSameOwnerEnclosure(sym)) sym
@@ -342,73 +326,96 @@ abstract class LambdaLift extends InfoTransform {
private def memberRef(sym: Symbol): Tree = {
val clazz = sym.owner.enclClass
- //Console.println("memberRef from "+currentClass+" to "+sym+" in "+clazz)
- def prematureSelfReference() {
+ // println(s"memberRef from $currentClass to $sym in $clazz (currentClass=$currentClass)")
+ def prematureSelfReference(): Tree = {
val what =
if (clazz.isStaticOwner) clazz.fullLocationString
else s"the unconstructed `this` of ${clazz.fullLocationString}"
val msg = s"Implementation restriction: access of ${sym.fullLocationString} from ${currentClass.fullLocationString}, would require illegal premature access to $what"
reporter.error(curTree.pos, msg)
+ EmptyTree
}
- val qual =
+ def qual =
if (clazz == currentClass) gen.mkAttributedThis(clazz)
else {
sym resetFlag (LOCAL | PRIVATE)
- if (isUnderConstruction(clazz)) {
- prematureSelfReference()
- EmptyTree
- }
+ if (isUnderConstruction(clazz)) prematureSelfReference()
else if (clazz.isStaticOwner) gen.mkAttributedQualifier(clazz.thisType)
- else {
- outerValue match {
- case EmptyTree => prematureSelfReference(); return EmptyTree
- case o => outerPath(o, currentClass.outerClass, clazz)
- }
+ else outerValue match {
+ case EmptyTree => prematureSelfReference()
+ case o =>
+ val path = outerPath(o, currentClass.outerClass, clazz)
+ if (path.tpe <:< clazz.tpeHK) path
+ else {
+ // SI-9920 The outer accessor might have an erased type of the self type of a trait,
+ // rather than the trait itself. Add a cast if necessary.
+ gen.mkAttributedCast(path, clazz.tpeHK)
+ }
}
}
- Select(qual, sym) setType sym.tpe
+
+ qual match {
+ case EmptyTree => EmptyTree
+ case qual => Select(qual, sym) setType sym.tpe
+ }
}
private def proxyRef(sym: Symbol) = {
val psym = proxy(sym)
- if (psym.isLocalToBlock) gen.mkAttributedIdent(psym) else memberRef(psym)
+ if (psym.isLocalToBlock) gen.mkAttributedIdent(psym)
+ else {
+ val ref = memberRef(psym)
+ if (psym.isMethod) Apply(ref, Nil) setType ref.tpe.resultType
+ else ref
+ }
}
- private def addFreeArgs(pos: Position, sym: Symbol, args: List[Tree]) = {
- free get sym match {
- case Some(fvs) => addFree(sym, free = fvs.toList map (fv => atPos(pos)(proxyRef(fv))), original = args)
- case _ => args
+ def freeArgsOrNil(sym: Symbol) = free.getOrElse(sym, Nil).toList
+
+ private def freeArgs(sym: Symbol): List[Symbol] =
+ freeArgsOrNil(sym)
+
+ private def addFreeArgs(pos: Position, sym: Symbol, args: List[Tree]) =
+ freeArgs(sym) match {
+ case Nil => args
+ case fvs => addFree(sym, free = fvs map (fv => atPos(pos)(proxyRef(fv))), original = args)
}
- }
- private def addFreeParams(tree: Tree, sym: Symbol): Tree = proxies.get(sym) match {
- case Some(ps) =>
- val freeParams = ps map (p => ValDef(p) setPos tree.pos setType NoType)
- tree match {
- case DefDef(_, _, _, vparams :: _, _, _) =>
- val addParams = cloneSymbols(ps).map(_.setFlag(PARAM))
- sym.updateInfo(
- lifted(MethodType(addFree(sym, free = addParams, original = sym.info.params), sym.info.resultType)))
+ def proxiesOrNil(sym: Symbol) = proxies.getOrElse(sym, Nil)
+
+ private def freeParams(sym: Symbol): List[Symbol] =
+ proxiesOrNil(sym)
+
+ private def addFreeParams(tree: Tree, sym: Symbol): Tree =
+ tree match {
+ case DefDef(_, _, _, vparams :: _, _, _) =>
+ val ps = freeParams(sym)
+
+ if (ps.isEmpty) tree
+ else {
+ val paramSyms = cloneSymbols(ps).map(_.setFlag(PARAM))
+ val paramDefs = ps map (p => ValDef(p) setPos tree.pos setType NoType)
+
+ sym.updateInfo(lifted(MethodType(addFree(sym, free = paramSyms, original = sym.info.params), sym.info.resultType)))
+ copyDefDef(tree)(vparamss = List(addFree(sym, free = paramDefs, original = vparams)))
+ }
+
+ case ClassDef(_, _, _, _) =>
+ val freeParamSyms = freeParams(sym)
+ val freeParamDefs =
+ if (tree.symbol.isTrait) {
+ freeParamSyms flatMap { getter =>
+ val setter = getter.setterIn(tree.symbol, hasExpandedName = true)
+ List(DefDef(getter, EmptyTree) setPos tree.pos setType NoType, DefDef(setter, EmptyTree) setPos tree.pos setType NoType)
+ }
+ } else freeParamSyms map (p => ValDef(p) setPos tree.pos setType NoType)
+
+ if (freeParamDefs.isEmpty) tree
+ else deriveClassDef(tree)(impl => deriveTemplate(impl)(_ ::: freeParamDefs))
+
+ case _ => tree
+ }
- copyDefDef(tree)(vparamss = List(addFree(sym, free = freeParams, original = vparams)))
- case ClassDef(_, _, _, _) =>
- // SI-6231
- // Disabled attempt to to add getters to freeParams
- // this does not work yet. Problem is that local symbols need local names
- // and references to local symbols need to be transformed into
- // method calls to setters.
- // def paramGetter(param: Symbol): Tree = {
- // val getter = param.newGetter setFlag TRANS_FLAG resetFlag PARAMACCESSOR // mark because we have to add them to interface
- // sym.info.decls.enter(getter)
- // val rhs = Select(gen.mkAttributedThis(sym), param) setType param.tpe
- // DefDef(getter, rhs) setPos tree.pos setType NoType
- // }
- // val newDefs = if (sym.isTrait) freeParams ::: (ps map paramGetter) else freeParams
- deriveClassDef(tree)(impl => deriveTemplate(impl)(_ ::: freeParams))
- }
- case None =>
- tree
- }
/* SI-6231: Something like this will be necessary to eliminate the implementation
* restriction from paramGetter above:
@@ -451,11 +458,10 @@ abstract class LambdaLift extends InfoTransform {
// See neg/t1909-object.scala
def msg = s"SI-1909 Unable to STATICally lift $sym, which is defined in the self- or super-constructor call of ${sym.owner.owner}. A VerifyError is likely."
devWarning(tree.pos, msg)
- } else sym setFlag STATIC
+ } else sym setFlag STATIC
}
sym.owner = sym.owner.enclClass
- if (sym.isClass) sym.owner = sym.owner.toInterface
if (sym.isMethod) sym setFlag LIFTED
liftedDefs(sym.owner) ::= tree
// TODO: this modifies the ClassInfotype of the enclosing class, which is associated with another phase (explicitouter).
@@ -468,12 +474,11 @@ abstract class LambdaLift extends InfoTransform {
private def postTransform(tree: Tree, isBoxedRef: Boolean = false): Tree = {
val sym = tree.symbol
tree match {
- case ClassDef(_, _, _, _) =>
- val tree1 = addFreeParams(tree, sym)
- if (sym.isLocalToBlock) liftDef(tree1) else tree1
- case DefDef(_, _, _, _, _, _) =>
- val tree1 = addFreeParams(tree, sym)
- if (sym.isLocalToBlock) liftDef(tree1) else tree1
+ case _: ClassDef | _: DefDef =>
+ val withFreeParams = addFreeParams(tree, sym)
+ if (sym.isLocalToBlock) liftDef(withFreeParams)
+ else withFreeParams
+
case ValDef(mods, name, tpt, rhs) =>
if (sym.isCapturedVariable) {
val tpt1 = TypeTree(sym.tpe) setPos tpt.pos
diff --git a/src/compiler/scala/tools/nsc/transform/LazyVals.scala b/src/compiler/scala/tools/nsc/transform/LazyVals.scala
deleted file mode 100644
index b6695efb0b..0000000000
--- a/src/compiler/scala/tools/nsc/transform/LazyVals.scala
+++ /dev/null
@@ -1,293 +0,0 @@
-package scala.tools.nsc
-package transform
-
-import scala.collection.{ mutable, immutable }
-
-abstract class LazyVals extends Transform with TypingTransformers with ast.TreeDSL {
- // inherits abstract value `global` and class `Phase` from Transform
-
- import global._ // the global environment
- import definitions._ // standard classes and methods
- import typer.{typed, atOwner} // methods to type trees
- import CODE._
-
- val phaseName: String = "lazyvals"
- private val FLAGS_PER_BYTE: Int = 8 // Byte
- private def bitmapKind = ByteClass
-
- def newTransformer(unit: CompilationUnit): Transformer =
- new LazyValues(unit)
-
- private def lazyUnit(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass
-
- object LocalLazyValFinder extends Traverser {
- var result: Boolean = _
-
- def find(t: Tree) = {result = false; traverse(t); result}
- def find(ts: List[Tree]) = {result = false; traverseTrees(ts); result}
-
- override def traverse(t: Tree) {
- if (!result)
- t match {
- case v@ValDef(_, _, _, _) if v.symbol.isLazy =>
- result = true
-
- case d@DefDef(_, _, _, _, _, _) if d.symbol.isLazy && lazyUnit(d.symbol) =>
- d.symbol.resetFlag(symtab.Flags.LAZY)
- result = true
-
- case ClassDef(_, _, _, _) | DefDef(_, _, _, _, _, _) | ModuleDef(_, _, _) =>
-
- case LabelDef(name, _, _) if nme.isLoopHeaderLabel(name) =>
-
- case _ =>
- super.traverse(t)
- }
- }
- }
-
- /**
- * Transform local lazy accessors to check for the initialized bit.
- */
- class LazyValues(unit: CompilationUnit) extends TypingTransformer(unit) {
- /** map from method symbols to the number of lazy values it defines. */
- private val lazyVals = perRunCaches.newMap[Symbol, Int]() withDefaultValue 0
-
- import symtab.Flags._
-
- /** Perform the following transformations:
- * - for a lazy accessor inside a method, make it check the initialization bitmap
- * - for all methods, add enough int vars to allow one flag per lazy local value
- * - blocks in template bodies behave almost like methods. A single bitmaps section is
- * added in the first block, for all lazy values defined in such blocks.
- * - remove ACCESSOR flags: accessors in traits are not statically implemented,
- * but moved to the host class. local lazy values should be statically implemented.
- */
- override def transform(tree: Tree): Tree = {
- val sym = tree.symbol
- curTree = tree
-
- tree match {
-
- case Block(_, _) =>
- val block1 = super.transform(tree)
- val Block(stats, expr) = block1
- val stats1 = stats.flatMap(_ match {
- case Block(List(d1@DefDef(_, n1, _, _, _, _)), d2@DefDef(_, n2, _, _, _, _)) if (nme.newLazyValSlowComputeName(n2) == n1) =>
- List(d1, d2)
- case stat =>
- List(stat)
- })
- treeCopy.Block(block1, stats1, expr)
-
- case DefDef(_, _, _, _, _, rhs) => atOwner(tree.symbol) {
- val (res, slowPathDef) = if (!sym.owner.isClass && sym.isLazy) {
- val enclosingClassOrDummyOrMethod = {
- val enclMethod = sym.enclMethod
-
- if (enclMethod != NoSymbol ) {
- val enclClass = sym.enclClass
- if (enclClass != NoSymbol && enclMethod == enclClass.enclMethod)
- enclClass
- else
- enclMethod
- } else
- sym.owner
- }
- debuglog(s"determined enclosing class/dummy/method for lazy val as $enclosingClassOrDummyOrMethod given symbol $sym")
- val idx = lazyVals(enclosingClassOrDummyOrMethod)
- lazyVals(enclosingClassOrDummyOrMethod) = idx + 1
- val (rhs1, sDef) = mkLazyDef(enclosingClassOrDummyOrMethod, transform(rhs), idx, sym)
- sym.resetFlag((if (lazyUnit(sym)) 0 else LAZY) | ACCESSOR)
- (rhs1, sDef)
- } else
- (transform(rhs), EmptyTree)
-
- val ddef1 = deriveDefDef(tree)(_ => if (LocalLazyValFinder.find(res)) typed(addBitmapDefs(sym, res)) else res)
- if (slowPathDef != EmptyTree) Block(slowPathDef, ddef1) else ddef1
- }
-
- case Template(_, _, body) => atOwner(currentOwner) {
- val body1 = super.transformTrees(body)
- var added = false
- val stats =
- for (stat <- body1) yield stat match {
- case Block(_, _) | Apply(_, _) | If(_, _, _) | Try(_, _, _) if !added =>
- // Avoid adding bitmaps when they are fully overshadowed by those
- // that are added inside loops
- if (LocalLazyValFinder.find(stat)) {
- added = true
- typed(addBitmapDefs(sym, stat))
- } else stat
- case ValDef(_, _, _, _) =>
- typed(deriveValDef(stat)(addBitmapDefs(stat.symbol, _)))
- case _ =>
- stat
- }
- val innerClassBitmaps = if (!added && currentOwner.isClass && bitmaps.contains(currentOwner)) {
- // add bitmap to inner class if necessary
- val toAdd0 = bitmaps(currentOwner).map(s => typed(ValDef(s, ZERO)))
- toAdd0.foreach(t => {
- if (currentOwner.info.decl(t.symbol.name) == NoSymbol) {
- t.symbol.setFlag(PROTECTED)
- currentOwner.info.decls.enter(t.symbol)
- }
- })
- toAdd0
- } else List()
- deriveTemplate(tree)(_ => innerClassBitmaps ++ stats)
- }
-
- case ValDef(_, _, _, _) if !sym.owner.isModule && !sym.owner.isClass =>
- deriveValDef(tree) { rhs0 =>
- val rhs = transform(rhs0)
- if (LocalLazyValFinder.find(rhs)) typed(addBitmapDefs(sym, rhs)) else rhs
- }
-
- case l@LabelDef(name0, params0, ifp0@If(_, _, _)) if name0.startsWith(nme.WHILE_PREFIX) =>
- val ifp1 = super.transform(ifp0)
- val If(cond0, thenp0, elsep0) = ifp1
-
- if (LocalLazyValFinder.find(thenp0))
- deriveLabelDef(l)(_ => treeCopy.If(ifp1, cond0, typed(addBitmapDefs(sym.owner, thenp0)), elsep0))
- else
- l
-
- case l@LabelDef(name0, params0, block@Block(stats0, expr))
- if name0.startsWith(nme.WHILE_PREFIX) || name0.startsWith(nme.DO_WHILE_PREFIX) =>
- val stats1 = super.transformTrees(stats0)
- if (LocalLazyValFinder.find(stats1))
- deriveLabelDef(l)(_ => treeCopy.Block(block, typed(addBitmapDefs(sym.owner, stats1.head))::stats1.tail, expr))
- else
- l
-
- case _ => super.transform(tree)
- }
- }
-
- /** Add the bitmap definitions to the rhs of a method definition.
- * If the rhs has been tail-call transformed, insert the bitmap
- * definitions inside the top-level label definition, so that each
- * iteration has the lazy values uninitialized. Otherwise add them
- * at the very beginning of the method.
- */
- private def addBitmapDefs(methSym: Symbol, rhs: Tree): Tree = {
- def prependStats(stats: List[Tree], tree: Tree): Block = tree match {
- case Block(stats1, res) => Block(stats ::: stats1, res)
- case _ => Block(stats, tree)
- }
-
- val bmps = bitmaps(methSym) map (ValDef(_, ZERO))
-
- def isMatch(params: List[Ident]) = (params.tail corresponds methSym.tpe.params)(_.tpe == _.tpe)
-
- if (bmps.isEmpty) rhs else rhs match {
- case Block(assign, l @ LabelDef(name, params, _))
- if (name string_== "_" + methSym.name) && isMatch(params) =>
- Block(assign, deriveLabelDef(l)(rhs => typed(prependStats(bmps, rhs))))
-
- case _ => prependStats(bmps, rhs)
- }
- }
-
- def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree],
- stats: List[Tree], retVal: Tree): Tree = {
- // Q: is there a reason to first set owner to `clazz` (by using clazz.newMethod), and then
- // changing it to lzyVal.owner very soon after? Could we just do lzyVal.owner.newMethod?
- val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, STABLE | PRIVATE)
- defSym setInfo MethodType(List(), lzyVal.tpe.resultType)
- defSym.owner = lzyVal.owner
- debuglog(s"crete slow compute path $defSym with owner ${defSym.owner} for lazy val $lzyVal")
- if (bitmaps.contains(lzyVal))
- bitmaps(lzyVal).map(_.owner = defSym)
- val rhs: Tree = gen.mkSynchronizedCheck(clazz, cond, syncBody, stats).changeOwner(currentOwner -> defSym)
-
- DefDef(defSym, addBitmapDefs(lzyVal, BLOCK(rhs, retVal)))
- }
-
-
- def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree],
- stats: List[Tree], retVal: Tree): (Tree, Tree) = {
- val slowPathDef: Tree = mkSlowPathDef(clazz, lzyVal, cond, syncBody, stats, retVal)
- (If(cond, Apply(Ident(slowPathDef.symbol), Nil), retVal), slowPathDef)
- }
-
- /** return a 'lazified' version of rhs. Rhs should conform to the
- * following schema:
- * {
- * l$ = <rhs>
- * l$
- * } or
- * <rhs> when the lazy value has type Unit (for which there is no field
- * to cache its value.
- *
- * Similarly as for normal lazy val members (see Mixin), the result will be a tree of the form
- * { if ((bitmap&n & MASK) == 0) this.l$compute()
- * else l$
- *
- * def l$compute() = { synchronized(enclosing_class_or_dummy) {
- * if ((bitmap$n & MASK) == 0) {
- * l$ = <rhs>
- * bitmap$n = bimap$n | MASK
- * }}
- * l$
- * }
- * }
- * where bitmap$n is a byte value acting as a bitmap of initialized values. It is
- * the 'n' is (offset / 8), the MASK is (1 << (offset % 8)). If the value has type
- * unit, no field is used to cache the value, so the l$compute will now look as following:
- * {
- * def l$compute() = { synchronized(enclosing_class_or_dummy) {
- * if ((bitmap$n & MASK) == 0) {
- * <rhs>;
- * bitmap$n = bimap$n | MASK
- * }}
- * ()
- * }
- * }
- */
- private def mkLazyDef(methOrClass: Symbol, tree: Tree, offset: Int, lazyVal: Symbol): (Tree, Tree) = {
- val bitmapSym = getBitmapFor(methOrClass, offset)
- val mask = LIT(1 << (offset % FLAGS_PER_BYTE))
- val bitmapRef = if (methOrClass.isClass) Select(This(methOrClass), bitmapSym) else Ident(bitmapSym)
-
- def mkBlock(stmt: Tree) = BLOCK(stmt, mkSetFlag(bitmapSym, mask, bitmapRef), UNIT)
-
- debuglog(s"create complete lazy def in $methOrClass for $lazyVal")
- val (block, res) = tree match {
- case Block(List(assignment), res) if !lazyUnit(lazyVal) =>
- (mkBlock(assignment), res)
- case rhs =>
- (mkBlock(rhs), UNIT)
- }
-
- val cond = (bitmapRef GEN_& (mask, bitmapKind)) GEN_== (ZERO, bitmapKind)
- val lazyDefs = mkFastPathBody(methOrClass.enclClass, lazyVal, cond, List(block), Nil, res)
- (atPos(tree.pos)(localTyper.typed {lazyDefs._1 }), atPos(tree.pos)(localTyper.typed {lazyDefs._2 }))
- }
-
- private def mkSetFlag(bmp: Symbol, mask: Tree, bmpRef: Tree): Tree =
- bmpRef === (bmpRef GEN_| (mask, bitmapKind))
-
- val bitmaps = mutable.Map[Symbol, List[Symbol]]() withDefaultValue Nil
-
- /** Return the symbol corresponding of the right bitmap int inside meth,
- * given offset.
- */
- private def getBitmapFor(meth: Symbol, offset: Int): Symbol = {
- val n = offset / FLAGS_PER_BYTE
- val bmps = bitmaps(meth)
- if (bmps.length > n)
- bmps(n)
- else {
- val sym = meth.newVariable(nme.newBitmapName(nme.BITMAP_NORMAL, n), meth.pos).setInfo(ByteTpe)
- enteringTyper {
- sym addAnnotation VolatileAttr
- }
-
- bitmaps(meth) = (sym :: bmps).reverse
- sym
- }
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala
index a079a76ce7..96e2135c52 100644
--- a/src/compiler/scala/tools/nsc/transform/Mixin.scala
+++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala
@@ -1,5 +1,6 @@
/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
+ * Copyright 2005-2016 LAMP/EPFL and Lightbend, Inc
+ *
* @author Martin Odersky
*/
@@ -8,110 +9,85 @@ package transform
import symtab._
import Flags._
-import scala.collection.{ mutable, immutable }
+import scala.annotation.tailrec
+import scala.collection.mutable
-abstract class Mixin extends InfoTransform with ast.TreeDSL {
+
+abstract class Mixin extends InfoTransform with ast.TreeDSL with AccessorSynthesis {
import global._
import definitions._
import CODE._
+
/** The name of the phase: */
val phaseName: String = "mixin"
- /** The phase might set the following new flags: */
- override def phaseNewFlags: Long = lateMODULE | notOVERRIDE
+ /** Some trait methods need to be implemented in subclasses, so they cannot be private.
+ *
+ * We used to publicize during explicitouter (for some reason), so the condition is a bit more involved now it's done here
+ * (need to exclude lambdaLIFTED methods, as they do no exist during explicitouter and thus did not need to be excluded...)
+ *
+ * They may be protected, now that traits are compiled 1:1 to interfaces.
+ * The same disclaimers about mapping Scala's notion of visibility to Java's apply:
+ * we cannot emit PROTECTED methods in interfaces on the JVM,
+ * but knowing that these trait methods are protected means we won't emit static forwarders.
+ *
+ * JVMS: "Methods of interfaces may have any of the flags in Table 4.6-A set
+ * except ACC_PROTECTED, ACC_FINAL, ACC_SYNCHRONIZED, and ACC_NATIVE (JLS §9.4)."
+ *
+ * TODO: can we just set the right flags from the start??
+ * could we use the final flag to indicate a private method is really-really-private?
+ */
+ def publicizeTraitMethod(sym: Symbol): Unit = {
+ if ((sym hasFlag PRIVATE) && !(sym hasFlag LIFTED) && ( // lambdalifted methods can remain private
+ // super accessors by definition must be implemented in a subclass, so can't be private
+ // TODO: why are they ever private in a trait to begin with!?!? (could just name mangle them to begin with)
+ // TODO: can we add the SYNTHESIZE_IMPL_IN_SUBCLASS flag to super accessors symbols?
+ (sym hasFlag SUPERACCESSOR)
+ // an accessor / module *may* need to be implemented in a subclass, and thus cannot be private
+ // TODO: document how we get here (lambdalift? fields has already made accessors not-private)
+ || (sym hasFlag ACCESSOR | MODULE) && (sym hasFlag SYNTHESIZE_IMPL_IN_SUBCLASS)))
+ sym.makeNotPrivate(sym.owner)
+
+ // no need to make trait methods not-protected
+ // (we used to have to move them to another class when interfaces could not have concrete methods)
+ // see note in `synthFieldsAndAccessors` in Fields.scala
+ // if (sym hasFlag PROTECTED) sym setFlag notPROTECTED
+ }
/** This map contains a binding (class -> info) if
* the class with this info at phase mixinPhase has been treated for mixin composition
*/
private val treatedClassInfos = perRunCaches.newMap[Symbol, Type]() withDefaultValue NoType
- /** Map a lazy, mixedin field accessor to its trait member accessor */
- private val initializer = perRunCaches.newMap[Symbol, Symbol]()
// --------- helper functions -----------------------------------------------
/** A member of a trait is implemented statically if its implementation after the
- * mixin transform is in the static implementation module. To be statically
- * implemented, a member must be a method that belonged to the trait's implementation class
+ * mixin transform is RHS of the method body (destined to be in an interface default method)
+ *
+ * To be statically implemented, a member must be a method that belonged to the trait's implementation class
* before (i.e. it is not abstract). Not statically implemented are
* - non-private modules: these are implemented directly in the mixin composition class
* (private modules, on the other hand, are implemented statically, but their
* module variable is not. all such private modules are lifted, because
* non-lifted private modules have been eliminated in ExplicitOuter)
- * - field accessors and superaccessors, except for lazy value accessors which become initializer
- * methods in the impl class (because they can have arbitrary initializers)
+ * - field accessors and superaccessors
*/
private def isImplementedStatically(sym: Symbol) = (
- sym.owner.isImplClass
- && sym.isMethod
+ (sym.isMethod || ((sym hasFlag MODULE) && !sym.isStatic))
+ // TODO: ^^^ non-static modules should have been turned into methods by fields by now, no? maybe the info transformer hasn't run???
+ && notDeferred(sym)
+ && sym.owner.isTrait
&& (!sym.isModule || sym.hasFlag(PRIVATE | LIFTED))
- && (!(sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isLazy)
- )
-
- /** A member of a trait is static only if it belongs only to the
- * implementation class, not the interface, and it is implemented
- * statically.
- */
- private def isStaticOnly(sym: Symbol) =
- isImplementedStatically(sym) && sym.isImplOnly
-
- /** A member of a trait is forwarded if it is implemented statically and it
- * is also visible in the trait's interface. In that case, a forwarder to
- * the member's static implementation will be added to the class that
- * inherits the trait.
- */
- private def isForwarded(sym: Symbol) =
- isImplementedStatically(sym) && !sym.isImplOnly
-
- /** Maps the type of an implementation class to its interface;
- * maps all other types to themselves.
- */
- private def toInterface(tp: Type): Type =
- enteringMixin(tp.typeSymbol.toInterface).tpe
-
- private def isFieldWithBitmap(field: Symbol) = {
- field.info // ensure that nested objects are transformed
- // For checkinit consider normal value getters
- // but for lazy values only take into account lazy getters
- field.isLazy && field.isMethod && !field.isDeferred
- }
-
- /** Does this field require an initialized bit?
- * Note: fields of classes inheriting DelayedInit are not checked.
- * This is because they are neither initialized in the constructor
- * nor do they have a setter (not if they are vals anyway). The usual
- * logic for setting bitmaps does therefore not work for such fields.
- * That's why they are excluded.
- * Note: The `checkinit` option does not check if transient fields are initialized.
- */
- private def needsInitFlag(sym: Symbol) = (
- settings.checkInit
- && sym.isGetter
- && !sym.isInitializedToDefault
- && !isConstantType(sym.info.finalResultType) // SI-4742
- && !sym.hasFlag(PARAMACCESSOR | SPECIALIZED | LAZY)
- && !sym.accessed.hasFlag(PRESUPER)
- && !sym.isOuterAccessor
- && !(sym.owner isSubClass DelayedInitClass)
- && !(sym.accessed hasAnnotation TransientAttr)
+ && (!(sym hasFlag (ACCESSOR | SUPERACCESSOR)) || (sym hasFlag LAZY))
+ && !sym.isPrivate
+ && !sym.hasAllFlags(LIFTED | MODULE | METHOD)
+ && !sym.isConstructor
+ && (!sym.hasFlag(notPRIVATE | LIFTED) || sym.hasFlag(ACCESSOR | SUPERACCESSOR | MODULE))
)
- /** Maps all parts of this type that refer to implementation classes to
- * their corresponding interfaces.
- */
- private val toInterfaceMap = new TypeMap {
- def apply(tp: Type): Type = mapOver( tp match {
- case TypeRef(pre, sym, args) if sym.isImplClass =>
- typeRef(pre, enteringMixin(sym.toInterface), args)
- case _ => tp
- })
- }
- /** The implementation class corresponding to a currently compiled interface.
- * todo: try to use Symbol.implClass instead?
- */
- private def implClass(iface: Symbol) = iface.implClass orElse (erasure implClass iface)
/** Returns the symbol that is accessed by a super-accessor in a mixin composition.
*
@@ -139,16 +115,16 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
// --------- type transformation -----------------------------------------------
- def isConcreteAccessor(member: Symbol) =
- member.hasAccessorFlag && (!member.isDeferred || (member hasFlag lateDEFERRED))
+ @inline final def notDeferred(sym: Symbol) = fields.notDeferredOrSynthImpl(sym)
/** Is member overridden (either directly or via a bridge) in base class sequence `bcs`? */
def isOverriddenAccessor(member: Symbol, bcs: List[Symbol]): Boolean = beforeOwnPhase {
def hasOverridingAccessor(clazz: Symbol) = {
clazz.info.nonPrivateDecl(member.name).alternatives.exists(
sym =>
- isConcreteAccessor(sym) &&
+ sym.hasFlag(ACCESSOR) &&
!sym.hasFlag(MIXEDIN) &&
+ notDeferred(sym) &&
matchesType(sym.tpe, member.tpe, alwaysMatchSimple = true))
}
( bcs.head != member.owner
@@ -156,11 +132,16 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
)
}
+
/** Add given member to given class, and mark member as mixed-in.
*/
def addMember(clazz: Symbol, member: Symbol): Symbol = {
- debuglog("new member of " + clazz + ":" + member.defString)
- clazz.info.decls enter member setFlag MIXEDIN
+ debuglog(s"mixing into $clazz: ${member.defString}")
+ // This attachment is used to instruct the backend about which methods in traits require
+ // a static trait impl method. We remove this from the new symbol created for the method
+ // mixed into the subclass.
+ member.removeAttachment[NeedStaticImpl.type]
+ clazz.info.decls enter member setFlag MIXEDIN resetFlag JAVA_DEFAULTMETHOD
}
def cloneAndAddMember(mixinClass: Symbol, mixinMember: Symbol, clazz: Symbol): Symbol =
addMember(clazz, cloneBeforeErasure(mixinClass, mixinMember, clazz))
@@ -191,57 +172,20 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
newSym
}
- /** Add getters and setters for all non-module fields of an implementation
- * class to its interface unless they are already present. This is done
- * only once per class. The mixedin flag is used to remember whether late
- * members have been added to an interface.
- * - lazy fields don't get a setter.
- */
- def addLateInterfaceMembers(clazz: Symbol) {
+ def publicizeTraitMethods(clazz: Symbol) {
if (treatedClassInfos(clazz) != clazz.info) {
treatedClassInfos(clazz) = clazz.info
assert(phase == currentRun.mixinPhase, phase)
- /* Create a new getter. Getters are never private or local. They are
- * always accessors and deferred. */
- def newGetter(field: Symbol): Symbol = {
- // println("creating new getter for "+ field +" : "+ field.info +" at "+ field.locationString+(field hasFlag MUTABLE))
- val newFlags = field.flags & ~PrivateLocal | ACCESSOR | lateDEFERRED | ( if (field.isMutable) 0 else STABLE )
- // TODO preserve pre-erasure info?
- clazz.newMethod(field.getterName, field.pos, newFlags) setInfo MethodType(Nil, field.info)
- }
-
- /* Create a new setter. Setters are never private or local. They are
- * always accessors and deferred. */
- def newSetter(field: Symbol): Symbol = {
- //println("creating new setter for "+field+field.locationString+(field hasFlag MUTABLE))
- val setterName = field.setterName
- val newFlags = field.flags & ~PrivateLocal | ACCESSOR | lateDEFERRED
- val setter = clazz.newMethod(setterName, field.pos, newFlags)
- // TODO preserve pre-erasure info?
- setter setInfo MethodType(setter.newSyntheticValueParams(List(field.info)), UnitTpe)
- if (field.needsExpandedSetterName)
- setter.name = nme.expandedSetterName(setter.name, clazz)
-
- setter
- }
-
- clazz.info // make sure info is up to date, so that implClass is set.
- val impl = implClass(clazz) orElse abort("No impl class for " + clazz)
-
- for (member <- impl.info.decls) {
- if (!member.isMethod && !member.isModule && !member.isModuleVar) {
+ for (member <- clazz.info.decls) {
+ if (member.isMethod) publicizeTraitMethod(member)
+ else {
assert(member.isTerm && !member.isDeferred, member)
- if (member.getterIn(impl).isPrivate) {
- member.makeNotPrivate(clazz) // this will also make getter&setter not private
- }
- val getter = member.getterIn(clazz)
- if (getter == NoSymbol) addMember(clazz, newGetter(member))
- if (!member.tpe.isInstanceOf[ConstantType] && !member.isLazy) {
- val setter = member.setterIn(clazz)
- if (setter == NoSymbol) addMember(clazz, newSetter(member))
- }
+ // disable assert to support compiling against code compiled by an older compiler (until we re-starr)
+ // assert(member hasFlag PRESUPER, s"unexpected $member in $clazz ${member.debugFlagString}")
+ clazz.info.decls.unlink(member)
}
+
}
debuglog("new defs of " + clazz + " = " + clazz.info.decls)
}
@@ -262,75 +206,83 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
def cloneAndAddMixinMember(mixinClass: Symbol, mixinMember: Symbol): Symbol = (
cloneAndAddMember(mixinClass, mixinMember, clazz)
setPos clazz.pos
- resetFlag DEFERRED | lateDEFERRED
+ resetFlag DEFERRED
)
/* Mix in members of implementation class mixinClass into class clazz */
- def mixinImplClassMembers(mixinClass: Symbol, mixinInterface: Symbol) {
- if (!mixinClass.isImplClass) devWarning ("Impl class flag is not set " +
- ((mixinClass.debugLocationString, mixinInterface.debugLocationString)))
-
- for (member <- mixinClass.info.decls ; if isForwarded(member)) {
- val imember = member overriddenSymbol mixinInterface
- imember overridingSymbol clazz match {
+ def mixinTraitForwarders(mixinClass: Symbol) {
+ for (member <- mixinClass.info.decls ; if isImplementedStatically(member)) {
+ member overridingSymbol clazz match {
case NoSymbol =>
- if (clazz.info.findMember(member.name, 0, lateDEFERRED, stableOnly = false).alternatives contains imember)
- cloneAndAddMixinMember(mixinInterface, imember).asInstanceOf[TermSymbol] setAlias member
+ val isMemberOfClazz = clazz.info.findMember(member.name, 0, 0L, stableOnly = false).alternatives.contains(member)
+ if (isMemberOfClazz) {
+ def genForwarder(required: Boolean): Unit = {
+ val owner = member.owner
+ if (owner.isJavaDefined && owner.isInterface && !clazz.parentSymbols.contains(owner)) {
+ if (required) {
+ val text = s"Unable to implement a mixin forwarder for $member in $clazz unless interface ${owner.name} is directly extended by $clazz."
+ reporter.error(clazz.pos, text)
+ }
+ } else
+ cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member
+ }
+
+ // `member` is a concrete method defined in `mixinClass`, which is a base class of
+ // `clazz`, and the method is not overridden in `clazz`. A forwarder is needed if:
+ //
+ // - A non-trait base class of `clazz` defines a matching method. Example:
+ // class C {def f: Int}; trait T extends C {def f = 1}; class D extends T
+ // Even if C.f is abstract, the forwarder in D is needed, otherwise the JVM would
+ // resolve `D.f` to `C.f`, see jvms-6.5.invokevirtual.
+ //
+ // - There exists another concrete, matching method in a parent interface `p` of
+ // `clazz`, and the `mixinClass` does not itself extend `p`. In this case the
+ // forwarder is needed to disambiguate. Example:
+ // trait T1 {def f = 1}; trait T2 extends T1 {override def f = 2}; class C extends T2
+ // In C we don't need a forwarder for f because T2 extends T1, so the JVM resolves
+ // C.f to T2.f non-ambiguously. See jvms-5.4.3.3, "maximally-specific method".
+ // trait U1 {def f = 1}; trait U2 {self:U1 => override def f = 2}; class D extends U2
+ // In D the forwarder is needed, the interfaces U1 and U2 are unrelated at the JVM
+ // level.
+
+ @tailrec
+ def existsCompetingMethod(baseClasses: List[Symbol]): Boolean = baseClasses match {
+ case baseClass :: rest =>
+ if (baseClass ne mixinClass) {
+ val m = member.overriddenSymbol(baseClass)
+ val isCompeting = m.exists && {
+ !m.owner.isTraitOrInterface ||
+ (!m.isDeferred && !mixinClass.isNonBottomSubClass(m.owner))
+ }
+ isCompeting || existsCompetingMethod(rest)
+ } else existsCompetingMethod(rest)
+
+ case _ => false
+ }
+
+ def generateJUnitForwarder: Boolean = {
+ settings.mixinForwarderChoices.isAtLeastJunit &&
+ member.annotations.nonEmpty &&
+ JUnitAnnotations.exists(annot => annot.exists && member.hasAnnotation(annot))
+ }
+
+ if (existsCompetingMethod(clazz.baseClasses) || generateJUnitForwarder)
+ genForwarder(required = true)
+ else if (settings.mixinForwarderChoices.isTruthy)
+ genForwarder(required = false)
+ }
+
case _ =>
}
}
}
- /* Mix in members of trait mixinClass into class clazz. Also,
- * for each lazy field in mixinClass, add a link from its mixed in member to its
- * initializer method inside the implclass.
+ /* Mix in members of trait mixinClass into class clazz.
*/
def mixinTraitMembers(mixinClass: Symbol) {
// For all members of a trait's interface do:
for (mixinMember <- mixinClass.info.decls) {
- if (isConcreteAccessor(mixinMember)) {
- if (isOverriddenAccessor(mixinMember, clazz.info.baseClasses))
- devWarning(s"Overridden concrete accessor: ${mixinMember.fullLocationString}")
- else {
- // mixin field accessors
- val mixedInAccessor = cloneAndAddMixinMember(mixinClass, mixinMember)
- if (mixinMember.isLazy) {
- initializer(mixedInAccessor) = (
- implClass(mixinClass).info.decl(mixinMember.name)
- orElse abort("Could not find initializer for " + mixinMember.name)
- )
- }
- if (!mixinMember.isSetter)
- mixinMember.tpe match {
- case MethodType(Nil, ConstantType(_)) =>
- // mixinMember is a constant; only getter is needed
- ;
- case MethodType(Nil, TypeRef(_, UnitClass, _)) =>
- // mixinMember is a value of type unit. No field needed
- ;
- case _ => // otherwise mixin a field as well
- // enteringPhase: the private field is moved to the implementation class by erasure,
- // so it can no longer be found in the mixinMember's owner (the trait)
- val accessed = enteringPickler(mixinMember.accessed)
- // #3857, need to retain info before erasure when cloning (since cloning only
- // carries over the current entry in the type history)
- val sym = enteringErasure {
- // so we have a type history entry before erasure
- clazz.newValue(mixinMember.localName, mixinMember.pos).setInfo(mixinMember.tpe.resultType)
- }
- sym updateInfo mixinMember.tpe.resultType // info at current phase
-
- val newFlags = (
- ( PrivateLocal )
- | ( mixinMember getFlag MUTABLE | LAZY)
- | ( if (mixinMember.hasStableFlag) 0 else MUTABLE )
- )
-
- addMember(clazz, sym setFlag newFlags setAnnotations accessed.annotations)
- }
- }
- }
- else if (mixinMember.isSuperAccessor) { // mixin super accessors
+ if (mixinMember.hasFlag(SUPERACCESSOR)) { // mixin super accessors
val superAccessor = addMember(clazz, mixinMember.cloneSymbol(clazz)) setPos clazz.pos
assert(superAccessor.alias != NoSymbol, superAccessor)
@@ -339,12 +291,42 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
reporter.error(clazz.pos, "Member %s of mixin %s is missing a concrete super implementation.".format(
mixinMember.alias, mixinClass))
case alias1 =>
+ if (alias1.owner.isJavaDefined && alias1.owner.isInterface && !clazz.parentSymbols.contains(alias1.owner)) {
+ val suggestedParent = exitingTyper(clazz.info.baseType(alias1.owner))
+ reporter.error(clazz.pos, s"Unable to implement a super accessor required by trait ${mixinClass.name} unless $suggestedParent is directly extended by $clazz.")
+ }
superAccessor.asInstanceOf[TermSymbol] setAlias alias1
}
}
- else if (mixinMember.isMethod && mixinMember.isModule && mixinMember.hasNoFlags(LIFTED | BRIDGE)) {
- // mixin objects: todo what happens with abstract objects?
- addMember(clazz, mixinMember.cloneSymbol(clazz, mixinMember.flags & ~(DEFERRED | lateDEFERRED)) setPos clazz.pos)
+ else if (mixinMember.hasFlag(ACCESSOR) && notDeferred(mixinMember)
+ && (mixinMember hasFlag PARAMACCESSOR)
+ && !isOverriddenAccessor(mixinMember, clazz.info.baseClasses)) {
+ // mixin accessor for constructor parameter
+ // (note that a paramaccessor cannot have a constant type as it must have a user-defined type)
+ cloneAndAddMixinMember(mixinClass, mixinMember)
+
+ val name = mixinMember.name
+
+ if (!nme.isSetterName(name)) {
+ // enteringPhase: the private field is moved to the implementation class by erasure,
+ // so it can no longer be found in the mixinMember's owner (the trait)
+ val accessed = enteringPickler(mixinMember.accessed)
+ // #3857, need to retain info before erasure when cloning (since cloning only
+ // carries over the current entry in the type history)
+ val sym = enteringErasure {
+ // so we have a type history entry before erasure
+ clazz.newValue(mixinMember.localName, mixinMember.pos).setInfo(mixinMember.tpe.resultType)
+ }
+ sym updateInfo mixinMember.tpe.resultType // info at current phase
+
+ val newFlags = (
+ (PrivateLocal)
+ | (mixinMember getFlag MUTABLE)
+ | (if (mixinMember.hasStableFlag) 0 else MUTABLE)
+ )
+
+ addMember(clazz, sym setFlag newFlags setAnnotations accessed.annotations)
+ }
}
}
}
@@ -358,162 +340,38 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
// first complete the superclass with mixed in members
addMixedinMembers(clazz.superClass, unit)
- for (mc <- clazz.mixinClasses ; if mc hasFlag lateINTERFACE) {
+ for (mc <- clazz.mixinClasses ; if mc.isTrait) {
// @SEAN: adding trait tracking so we don't have to recompile transitive closures
unit.depends += mc
- addLateInterfaceMembers(mc)
+ publicizeTraitMethods(mc)
mixinTraitMembers(mc)
- mixinImplClassMembers(implClass(mc), mc)
+ mixinTraitForwarders(mc)
}
}
- /** The info transform for this phase does the following:
- * - The parents of every class are mapped from implementation class to interface
- * - Implementation classes become modules that inherit nothing
- * and that define all.
- */
- override def transformInfo(sym: Symbol, tp: Type): Type = tp match {
- case ClassInfoType(parents, decls, clazz) =>
- var parents1 = parents
- var decls1 = decls
- if (!clazz.isPackageClass) {
- exitingMixin(clazz.owner.info)
- if (clazz.isImplClass) {
- clazz setFlag lateMODULE
- var sourceModule = clazz.owner.info.decls.lookup(sym.name.toTermName)
- if (sourceModule == NoSymbol) {
- sourceModule = (
- clazz.owner.newModuleSymbol(sym.name.toTermName, sym.pos, MODULE)
- setModuleClass sym.asInstanceOf[ClassSymbol]
- )
- clazz.owner.info.decls enter sourceModule
- }
- else {
- sourceModule setPos sym.pos
- if (sourceModule.flags != MODULE) {
- log(s"!!! Directly setting sourceModule flags for $sourceModule from ${sourceModule.flagString} to MODULE")
- sourceModule.flags = MODULE
- }
- }
- sourceModule setInfo sym.tpe
- // Companion module isn't visible for anonymous class at this point anyway
- assert(clazz.sourceModule != NoSymbol || clazz.isAnonymousClass, s"$clazz has no sourceModule: $sym ${sym.tpe}")
- parents1 = List()
- decls1 = newScopeWith(decls.toList filter isImplementedStatically: _*)
- } else if (!parents.isEmpty) {
- parents1 = parents.head :: (parents.tail map toInterface)
- }
- }
- //decls1 = enteringPhase(phase.next)(newScopeWith(decls1.toList: _*))//debug
- if ((parents1 eq parents) && (decls1 eq decls)) tp
- else ClassInfoType(parents1, decls1, clazz)
-
- case MethodType(params, restp) =>
- toInterfaceMap(
- if (isImplementedStatically(sym)) {
- val ownerParam = sym.newSyntheticValueParam(toInterface(sym.owner.typeOfThis))
- MethodType(ownerParam :: params, restp)
- } else
- tp)
-
- case _ =>
- tp
- }
-
- /** Return a map of single-use fields to the lazy value that uses them during initialization.
- * Each field has to be private and defined in the enclosing class, and there must
- * be exactly one lazy value using it.
- *
- * Such fields will be nulled after the initializer has memoized the lazy value.
- */
- def singleUseFields(templ: Template): scala.collection.Map[Symbol, List[Symbol]] = {
- val usedIn = mutable.HashMap[Symbol, List[Symbol]]() withDefaultValue Nil
-
- object SingleUseTraverser extends Traverser {
- override def traverse(tree: Tree) {
- tree match {
- case Assign(lhs, rhs) => traverse(rhs) // assignments don't count
- case _ =>
- if (tree.hasSymbolField && tree.symbol != NoSymbol) {
- val sym = tree.symbol
- if ((sym.hasAccessorFlag || (sym.isTerm && !sym.isMethod))
- && sym.isPrivate
- && !(currentOwner.isGetter && currentOwner.accessed == sym) // getter
- && !definitions.isPrimitiveValueClass(sym.tpe.resultType.typeSymbol)
- && sym.owner == templ.symbol.owner
- && !sym.isLazy
- && !tree.isDef) {
- debuglog("added use in: " + currentOwner + " -- " + tree)
- usedIn(sym) ::= currentOwner
-
- }
- }
- super.traverse(tree)
- }
- }
- }
- SingleUseTraverser(templ)
- debuglog("usedIn: " + usedIn)
- usedIn filter {
- case (_, member :: Nil) => member.isValue && member.isLazy
- case _ => false
- }
- }
+ override def transformInfo(sym: Symbol, tp: Type): Type = tp
// --------- term transformation -----------------------------------------------
protected def newTransformer(unit: CompilationUnit): Transformer =
new MixinTransformer(unit)
- class MixinTransformer(unit : CompilationUnit) extends Transformer {
- /** Within a static implementation method: the parameter referring to the
- * current object. Undefined everywhere else.
- */
- private var self: Symbol = _
+ class MixinTransformer(unit : CompilationUnit) extends Transformer with AccessorTreeSynthesis {
+ /** The typer */
+ private var localTyper: erasure.Typer = _
+ protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree)
/** The rootContext used for typing */
private val rootContext =
erasure.NoContext.make(EmptyTree, rootMirror.RootClass, newScope)
- /** The typer */
- private var localTyper: erasure.Typer = _
- private def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree)
-
- /** Map lazy values to the fields they should null after initialization. */
- private var lazyValNullables: Map[Symbol, Set[Symbol]] = _
-
- /** Map a field symbol to a unique integer denoting its position in the class layout.
- * For each class, fields defined by the class come after inherited fields. Mixed-in
- * fields count as fields defined by the class itself.
- */
- private val fieldOffset = perRunCaches.newMap[Symbol, Int]()
-
- private val bitmapKindForCategory = perRunCaches.newMap[Name, ClassSymbol]()
-
- // ByteClass, IntClass, LongClass
- private def bitmapKind(field: Symbol): ClassSymbol = bitmapKindForCategory(bitmapCategory(field))
-
- private def flagsPerBitmap(field: Symbol): Int = bitmapKind(field) match {
- case BooleanClass => 1
- case ByteClass => 8
- case IntClass => 32
- case LongClass => 64
- }
-
+ private val nullables = mutable.AnyRefMap[Symbol, Map[Symbol, List[Symbol]]]()
/** The first transform; called in a pre-order traversal at phase mixin
* (that is, every node is processed before its children).
* What transform does:
* - For every non-trait class, add all mixed in members to the class info.
- * - For every trait, add all late interface members to the class info
- * - For every static implementation method:
- * - remove override flag
- * - create a new method definition that also has a `self` parameter
- * (which comes first) Iuli: this position is assumed by tail call elimination
- * on a different receiver. Storing a new 'this' assumes it is located at
- * index 0 in the local variable table. See 'STORE_THIS' and GenASM.
- * - Map implementation class types in type-apply's to their interfaces
- * - Remove all fields in implementation classes
+ * - For every non-trait class, assign null to singly used private fields after use in lazy initialization.
*/
private def preTransform(tree: Tree): Tree = {
val sym = tree.symbol
@@ -524,617 +382,160 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
if (!currentOwner.isTrait && !isPrimitiveValueClass(currentOwner))
addMixedinMembers(currentOwner, unit)
- else if (currentOwner hasFlag lateINTERFACE)
- addLateInterfaceMembers(currentOwner)
+ else if (currentOwner.isTrait)
+ publicizeTraitMethods(currentOwner)
+
+ if (!currentOwner.isTrait)
+ nullables(currentOwner) = lazyValNullables(currentOwner, body)
tree
- case DefDef(_, _, _, vparams :: Nil, _, _) =>
- if (currentOwner.isImplClass) {
- if (isImplementedStatically(sym)) {
- sym setFlag notOVERRIDE
- self = sym.newValueParameter(nme.SELF, sym.pos) setInfo toInterface(currentOwner.typeOfThis)
- val selfdef = ValDef(self) setType NoType
- copyDefDef(tree)(vparamss = List(selfdef :: vparams))
- }
- else EmptyTree
- }
+ case dd: DefDef if dd.symbol.name.endsWith(nme.LAZY_SLOW_SUFFIX) =>
+ val fieldsToNull = nullables.getOrElse(sym.enclClass, Map()).getOrElse(sym, Nil)
+ if (fieldsToNull.isEmpty) dd
else {
- if (currentOwner.isTrait && sym.isSetter && !enteringPickler(sym.isDeferred)) {
- sym.addAnnotation(TraitSetterAnnotationClass)
+ deriveDefDef(dd) {
+ case blk@Block(stats, expr) =>
+ assert(dd.symbol.originalOwner.isClass, dd.symbol)
+ def nullify(sym: Symbol) =
+ Select(gen.mkAttributedThis(sym.enclClass), sym.accessedOrSelf) === NULL
+ val stats1 = stats ::: fieldsToNull.map(nullify)
+ treeCopy.Block(blk, stats1, expr)
+ case tree =>
+ devWarning("Unexpected tree shape in lazy slow path")
+ tree
}
- tree
- }
- // !!! What is this doing, and why is it only looking for exactly
- // one type parameter? It would seem to be
- // "Map implementation class types in type-apply's to their interfaces"
- // from the comment on preTransform, but is there some way we should know
- // that impl class types in type applies can only appear in single
- // type parameter type constructors?
- case Apply(tapp @ TypeApply(fn, List(arg)), List()) =>
- if (arg.tpe.typeSymbol.isImplClass) {
- val ifacetpe = toInterface(arg.tpe)
- arg setType ifacetpe
- tapp setType MethodType(Nil, ifacetpe)
- tree setType ifacetpe
}
- tree
- case ValDef(_, _, _, _) if currentOwner.isImplClass =>
- EmptyTree
- case _ =>
- tree
- }
- }
- /** Create an identifier which references self parameter.
- */
- private def selfRef(pos: Position) =
- gen.mkAttributedIdent(self) setPos pos
-
- /** Replace a super reference by this or the self parameter, depending
- * on whether we are in an implementation class or not.
- * Leave all other trees unchanged.
- */
- private def transformSuper(tree: Tree) = tree match {
- case Super(qual, _) =>
- transformThis(qual)
- case _ =>
- tree
- }
-
- /** Replace a this reference to the current implementation class by the self
- * parameter. Leave all other trees unchanged.
- */
- private def transformThis(tree: Tree) = tree match {
- case This(_) if tree.symbol.isImplClass =>
- assert(tree.symbol == currentOwner.enclClass)
- selfRef(tree.pos)
- case _ =>
- tree
- }
-
- /** Create a static reference to given symbol `sym` of the
- * form `M.sym` where M is the symbol's implementation module.
- */
- private def staticRef(sym: Symbol): Tree = {
- sym.owner.info //todo: needed?
- sym.owner.owner.info //todo: needed?
-
- if (sym.owner.sourceModule eq NoSymbol)
- abort(s"Cannot create static reference to $sym because ${sym.safeOwner} has no source module")
- else
- REF(sym.owner.sourceModule) DOT sym
- }
-
- def needsInitAndHasOffset(sym: Symbol) =
- needsInitFlag(sym) && (fieldOffset contains sym)
-
- /** Examines the symbol and returns a name indicating what brand of
- * bitmap it requires. The possibilities are the BITMAP_* vals
- * defined in StdNames. If it needs no bitmap, nme.NO_NAME.
- */
- def bitmapCategory(field: Symbol): Name = {
- import nme._
- val isNormal = (
- if (isFieldWithBitmap(field)) true
- // bitmaps for checkinit fields are not inherited
- else if (needsInitFlag(field) && !field.isDeferred) false
- else return NO_NAME
- )
- if (field.accessed hasAnnotation TransientAttr) {
- if (isNormal) BITMAP_TRANSIENT
- else BITMAP_CHECKINIT_TRANSIENT
- } else {
- if (isNormal) BITMAP_NORMAL
- else BITMAP_CHECKINIT
+ case _ => tree
}
}
- /** Add all new definitions to a non-trait class
- * These fall into the following categories:
- * - for a trait interface:
- * - abstract accessors for all fields in the implementation class
- * - for a non-trait class:
- * - A field for every in a mixin class
- * - Setters and getters for such fields
- * - getters for mixed in lazy fields are completed
- * - module variables and module creators for every module in a mixin class
- * (except if module is lifted -- in this case the module variable
- * is local to some function, and the creator method is static.)
- * - A super accessor for every super accessor in a mixin class
- * - Forwarders for all methods that are implemented statically
- * All superaccessors are completed with right-hand sides (@see completeSuperAccessor)
- * @param clazz The class to which definitions are added
- */
- private def addNewDefs(clazz: Symbol, stats: List[Tree]): List[Tree] = {
- val newDefs = mutable.ListBuffer[Tree]()
-
- /* Attribute given tree and anchor at given position */
- def attributedDef(pos: Position, tree: Tree): Tree = {
- debuglog("add new def to " + clazz + ": " + tree)
- typedPos(pos)(tree)
- }
-
- /* The position of given symbol, or, if this is undefined,
- * the position of the current class.
- */
- def position(sym: Symbol) =
- if (sym.pos == NoPosition) clazz.pos else sym.pos
-
- /* Add tree at given position as new definition */
- def addDef(pos: Position, tree: Tree) {
- newDefs += attributedDef(pos, tree)
- }
-
- /* Add new method definition.
- *
- * @param sym The method symbol.
- * @param rhs The method body.
- */
- def addDefDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(position(sym), DefDef(sym, rhs))
- def addValDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(position(sym), ValDef(sym, rhs))
-
- /* Add `newdefs` to `stats`, removing any abstract method definitions
- * in `stats` that are matched by some symbol defined in
- * `newDefs`.
- */
- def add(stats: List[Tree], newDefs: List[Tree]) = {
- val newSyms = newDefs map (_.symbol)
- def isNotDuplicate(tree: Tree) = tree match {
- case DefDef(_, _, _, _, _, _) =>
- val sym = tree.symbol
- !(sym.isDeferred &&
- (newSyms exists (nsym => nsym.name == sym.name && (nsym.tpe matches sym.tpe))))
- case _ =>
- true
- }
- if (newDefs.isEmpty) stats
- else newDefs ::: (stats filter isNotDuplicate)
- }
-
- /* If `stat` is a superaccessor, complete it by adding a right-hand side.
- * Note: superaccessors are always abstract until this point.
- * The method to call in a superaccessor is stored in the accessor symbol's alias field.
- * The rhs is:
- * super.A(xs) where A is the super accessor's alias and xs are its formal parameters.
- * This rhs is typed and then mixin transformed.
- */
- def completeSuperAccessor(stat: Tree) = stat match {
- case DefDef(_, _, _, vparams :: Nil, _, EmptyTree) if stat.symbol.isSuperAccessor =>
- val body = atPos(stat.pos)(Apply(Select(Super(clazz, tpnme.EMPTY), stat.symbol.alias), vparams map (v => Ident(v.symbol))))
- val pt = stat.symbol.tpe.resultType
-
- copyDefDef(stat)(rhs = enteringMixin(transform(localTyper.typed(body, pt))))
- case _ =>
- stat
- }
-
- /*
- * Return the bitmap field for 'offset'. Depending on the hierarchy it is possible to reuse
- * the bitmap of its parents. If that does not exist yet we create one.
- */
- def bitmapFor(clazz0: Symbol, offset: Int, field: Symbol): Symbol = {
- val category = bitmapCategory(field)
- val bitmapName = nme.newBitmapName(category, offset / flagsPerBitmap(field)).toTermName
- val sym = clazz0.info.decl(bitmapName)
-
- assert(!sym.isOverloaded, sym)
-
- def createBitmap: Symbol = {
- val bitmapKind = bitmapKindForCategory(category)
- val sym = clazz0.newVariable(bitmapName, clazz0.pos) setInfo bitmapKind.tpe
- enteringTyper(sym addAnnotation VolatileAttr)
-
- category match {
- case nme.BITMAP_TRANSIENT | nme.BITMAP_CHECKINIT_TRANSIENT => sym addAnnotation TransientAttr
- case _ =>
- }
- val init = bitmapKind match {
- case BooleanClass => ValDef(sym, FALSE)
- case _ => ValDef(sym, ZERO)
+ /** Map lazy values to the fields they should null after initialization. */
+ def lazyValNullables(clazz: Symbol, templStats: List[Tree]): Map[Symbol, List[Symbol]] = {
+ // if there are no lazy fields, take the fast path and save a traversal of the whole AST
+ if (!clazz.info.decls.exists(_.isLazy)) Map()
+ else {
+ // A map of single-use fields to the lazy value that uses them during initialization.
+ // Each field has to be private and defined in the enclosing class, and there must
+ // be exactly one lazy value using it.
+ //
+ // Such fields will be nulled after the initializer has memoized the lazy value.
+ val singleUseFields: Map[Symbol, List[Symbol]] = {
+ val usedIn = mutable.HashMap[Symbol, List[Symbol]]() withDefaultValue Nil
+
+ object SingleUseTraverser extends Traverser {
+ override def traverse(tree: Tree) {
+ tree match {
+ // assignment targets don't count as a dereference -- only check the rhs
+ case Assign(_, rhs) => traverse(rhs)
+ case tree: RefTree if tree.symbol != NoSymbol =>
+ val sym = tree.symbol
+ // println(s"$sym in ${sym.owner} from $currentOwner ($tree)")
+ if ((sym.hasAccessorFlag || (sym.isTerm && !sym.isMethod)) && sym.isPrivate && !sym.isLazy && !sym.isModule // non-lazy private field or its accessor
+ && !definitions.isPrimitiveValueClass(sym.tpe.resultType.typeSymbol) // primitives don't hang on to significant amounts of heap
+ && sym.owner == currentOwner.enclClass && !(currentOwner.isGetter && currentOwner.accessed == sym)) {
+
+ // println("added use in: " + currentOwner + " -- " + tree)
+ usedIn(sym) ::= currentOwner
+ }
+ super.traverse(tree)
+ case _ => super.traverse(tree)
+ }
+ }
}
-
- sym setFlag PrivateLocal
- clazz0.info.decls.enter(sym)
- addDef(clazz0.pos, init)
- sym
+ templStats foreach SingleUseTraverser.apply
+ // println("usedIn: " + usedIn)
+
+ // only consider usages from non-transient lazy vals (SI-9365)
+ val singlyUsedIn = usedIn.filter {
+ case (_, member :: Nil) if member.name.endsWith(nme.LAZY_SLOW_SUFFIX) =>
+ val lazyAccessor = member.owner.info.decl(member.name.stripSuffix(nme.LAZY_SLOW_SUFFIX))
+ !lazyAccessor.accessedOrSelf.hasAnnotation(TransientAttr)
+ case _ => false
+ }.toMap
+
+ // println("singlyUsedIn: " + singlyUsedIn)
+ singlyUsedIn
}
- sym orElse createBitmap
- }
-
- def maskForOffset(offset: Int, sym: Symbol, kind: ClassSymbol): Tree = {
- def realOffset = offset % flagsPerBitmap(sym)
- if (kind == LongClass ) LIT(1L << realOffset) else LIT(1 << realOffset)
- }
-
- /* Return an (untyped) tree of the form 'Clazz.this.bmp = Clazz.this.bmp | mask'. */
- def mkSetFlag(clazz: Symbol, offset: Int, valSym: Symbol, kind: ClassSymbol): Tree = {
- val bmp = bitmapFor(clazz, offset, valSym)
- def mask = maskForOffset(offset, valSym, kind)
- def x = This(clazz) DOT bmp
- def newValue = if (kind == BooleanClass) TRUE else (x GEN_| (mask, kind))
-
- x === newValue
- }
-
- /* Return an (untyped) tree of the form 'clazz.this.bitmapSym & mask (==|!=) 0', the
- * precise comparison operator depending on the value of 'equalToZero'.
- */
- def mkTest(clazz: Symbol, mask: Tree, bitmapSym: Symbol, equalToZero: Boolean, kind: ClassSymbol): Tree = {
- val bitmapTree = (This(clazz) DOT bitmapSym)
- def lhs = bitmapTree GEN_& (mask, kind)
- kind match {
- case BooleanClass =>
- if (equalToZero) NOT(bitmapTree)
- else bitmapTree
- case _ =>
- if (equalToZero) lhs GEN_== (ZERO, kind)
- else lhs GEN_!= (ZERO, kind)
- }
- }
-
- def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree],
- stats: List[Tree], retVal: Tree, attrThis: Tree, args: List[Tree]): Symbol = {
- val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, PRIVATE)
- val params = defSym newSyntheticValueParams args.map(_.symbol.tpe)
- defSym setInfoAndEnter MethodType(params, lzyVal.tpe.resultType)
- val rhs: Tree = (gen.mkSynchronizedCheck(attrThis, cond, syncBody, stats)).changeOwner(currentOwner -> defSym)
- val strictSubst = new TreeSymSubstituterWithCopying(args.map(_.symbol), params)
- addDef(position(defSym), DefDef(defSym, strictSubst(BLOCK(rhs, retVal))))
- defSym
- }
-
- def mkFastPathLazyBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree],
- stats: List[Tree], retVal: Tree): Tree = {
- mkFastPathBody(clazz, lzyVal, cond, syncBody, stats, retVal, gen.mkAttributedThis(clazz), List())
- }
-
- def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree],
- stats: List[Tree], retVal: Tree, attrThis: Tree, args: List[Tree]): Tree = {
- val slowPathSym: Symbol = mkSlowPathDef(clazz, lzyVal, cond, syncBody, stats, retVal, attrThis, args)
- If(cond, fn (This(clazz), slowPathSym, args.map(arg => Ident(arg.symbol)): _*), retVal)
- }
-
-
- /* Always copy the tree if we are going to perform sym substitution,
- * otherwise we will side-effect on the tree that is used in the fast path
- */
- class TreeSymSubstituterWithCopying(from: List[Symbol], to: List[Symbol]) extends TreeSymSubstituter(from, to) {
- override def transform(tree: Tree): Tree =
- if (tree.hasSymbolField && from.contains(tree.symbol))
- super.transform(tree.duplicate)
- else super.transform(tree.duplicate)
-
- override def apply[T <: Tree](tree: T): T = if (from.isEmpty) tree else super.apply(tree)
- }
-
- /* return a 'lazified' version of rhs. It uses double-checked locking to ensure
- * initialization is performed at most once. For performance reasons the double-checked
- * locking is split into two parts, the first (fast) path checks the bitmap without
- * synchronizing, and if that fails it initializes the lazy val within the
- * synchronization block (slow path). This way the inliner should optimize
- * the fast path because the method body is small enough.
- * Private fields used only in this initializer are subsequently set to null.
- *
- * @param clazz The class symbol
- * @param lzyVal The symbol of this lazy field
- * @param init The tree which initializes the field ( f = <rhs> )
- * @param offset The offset of this field in the flags bitmap
- *
- * The result will be a tree of the form
- * { if ((bitmap&n & MASK) == 0) this.l$compute()
- * else l$
- *
- * ...
- * def l$compute() = { synchronized(this) {
- * if ((bitmap$n & MASK) == 0) {
- * init // l$ = <rhs>
- * bitmap$n = bimap$n | MASK
- * }}
- * l$
- * }
- *
- * ...
- * this.f1 = null
- * ... this.fn = null
- * }
- * where bitmap$n is a byte, int or long value acting as a bitmap of initialized values.
- * The kind of the bitmap determines how many bit indicators for lazy vals are stored in it.
- * For Int bitmap it is 32 and then 'n' in the above code is: (offset / 32),
- * the MASK is (1 << (offset % 32)).
- * If the class contains only a single lazy val then the bitmap is represented
- * as a Boolean and the condition checking is a simple bool test.
- */
- def mkLazyDef(clazz: Symbol, lzyVal: Symbol, init: List[Tree], retVal: Tree, offset: Int): Tree = {
- def nullify(sym: Symbol) = Select(This(clazz), sym.accessedOrSelf) === LIT(null)
-
- val bitmapSym = bitmapFor(clazz, offset, lzyVal)
- val kind = bitmapKind(lzyVal)
- val mask = maskForOffset(offset, lzyVal, kind)
- def cond = mkTest(clazz, mask, bitmapSym, equalToZero = true, kind)
- val nulls = lazyValNullables(lzyVal).toList sortBy (_.id) map nullify
- def syncBody = init ::: List(mkSetFlag(clazz, offset, lzyVal, kind), UNIT)
-
- if (nulls.nonEmpty)
- log("nulling fields inside " + lzyVal + ": " + nulls)
-
- typedPos(init.head.pos)(mkFastPathLazyBody(clazz, lzyVal, cond, syncBody, nulls, retVal))
- }
-
- def mkInnerClassAccessorDoubleChecked(attrThis: Tree, rhs: Tree, moduleSym: Symbol, args: List[Tree]): Tree =
- rhs match {
- case Block(List(assign), returnTree) =>
- val Assign(moduleVarRef, _) = assign
- val cond = Apply(Select(moduleVarRef, Object_eq), List(NULL))
- mkFastPathBody(clazz, moduleSym, cond, List(assign), List(NULL), returnTree, attrThis, args)
- case _ =>
- abort(s"Invalid getter $rhs for module in $clazz")
- }
+ val map = mutable.Map[Symbol, Set[Symbol]]() withDefaultValue Set()
+ // invert the map to see which fields can be nulled for each non-transient lazy val
+ for ((field, users) <- singleUseFields; lazyFld <- users) map(lazyFld) += field
- def mkCheckedAccessor(clazz: Symbol, retVal: Tree, offset: Int, pos: Position, fieldSym: Symbol): Tree = {
- val sym = fieldSym.getterIn(fieldSym.owner)
- val bitmapSym = bitmapFor(clazz, offset, sym)
- val kind = bitmapKind(sym)
- val mask = maskForOffset(offset, sym, kind)
- val msg = s"Uninitialized field: ${unit.source}: ${pos.line}"
- val result =
- IF (mkTest(clazz, mask, bitmapSym, equalToZero = false, kind)) .
- THEN (retVal) .
- ELSE (Throw(NewFromConstructor(UninitializedFieldConstructor, LIT(msg))))
-
- typedPos(pos)(BLOCK(result, retVal))
+ map.mapValues(_.toList sortBy (_.id)).toMap
}
+ }
- /* Complete lazy field accessors. Applies only to classes,
- * for its own (non inherited) lazy fields. If 'checkinit'
- * is enabled, getters that check for the initialized bit are
- * generated, and the class constructor is changed to set the
- * initialized bits.
- */
- def addCheckedGetters(clazz: Symbol, stats: List[Tree]): List[Tree] = {
- def dd(stat: DefDef) = {
- val sym = stat.symbol
- def isUnit = sym.tpe.resultType.typeSymbol == UnitClass
- def isEmpty = stat.rhs == EmptyTree
-
- if (sym.isLazy && !isEmpty && !clazz.isImplClass) {
- assert(fieldOffset contains sym, sym)
- deriveDefDef(stat) {
- case t if isUnit => mkLazyDef(clazz, sym, List(t), UNIT, fieldOffset(sym))
-
- case Block(stats, res) =>
- mkLazyDef(clazz, sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym))
-
- case t => t // pass specialized lazy vals through
- }
- }
- else if (needsInitFlag(sym) && !isEmpty && !clazz.hasFlag(IMPLCLASS | TRAIT)) {
- assert(fieldOffset contains sym, sym)
- deriveDefDef(stat)(rhs =>
- (mkCheckedAccessor(clazz, _: Tree, fieldOffset(sym), stat.pos, sym))(
- if (sym.tpe.resultType.typeSymbol == UnitClass) UNIT
- else rhs
- )
- )
- }
- else if (sym.isConstructor) {
- deriveDefDef(stat)(addInitBits(clazz, _))
- }
- else if (settings.checkInit && !clazz.isTrait && sym.isSetter) {
- 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
- }
- else if (sym.isModule && (!clazz.isTrait || clazz.isImplClass) && !sym.isBridge) {
- deriveDefDef(stat)(rhs =>
- typedPos(stat.pos)(
- mkInnerClassAccessorDoubleChecked(
- // Martin to Hubert: I think this can be replaced by selfRef(tree.pos)
- // @PP: It does not seem so, it crashes for me trying to bootstrap.
- if (clazz.isImplClass) gen.mkAttributedIdent(stat.vparamss.head.head.symbol) else gen.mkAttributedThis(clazz),
- rhs, sym, stat.vparamss.head
- )
- )
- )
- }
- else stat
- }
- stats map {
- case defn: DefDef => dd(defn)
- case stat => stat
- }
- }
+ /** Add all new definitions to a non-trait class
+ *
+ * These fall into the following categories:
+ * - for a trait interface:
+ * - abstract accessors for all paramaccessor or early initialized fields
+ * - for a non-trait class:
+ * - field and accessor implementations for each inherited paramaccessor or early initialized field
+ * - A super accessor for every super accessor in a mixin class
+ * - Forwarders for all methods that are implemented statically
+ *
+ * All superaccessors are completed with right-hand sides (@see completeSuperAccessor)
+ *
+ * @param clazz The class to which definitions are added
+ */
+ private def addNewDefs(clazz: Symbol, stats: List[Tree]): List[Tree] = {
+ val accessorSynth = new UncheckedAccessorSynth(clazz)
+ import accessorSynth._
- class AddInitBitsTransformer(clazz: Symbol) extends Transformer {
- private def checkedGetter(lhs: Tree) = {
- val sym = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter)
- if (needsInitAndHasOffset(sym)) {
- debuglog("adding checked getter for: " + sym + " " + lhs.symbol.flagString)
- List(localTyper typed mkSetFlag(clazz, fieldOffset(sym), sym, bitmapKind(sym)))
- }
- else Nil
+ // for all symbols `sym` in the class definition, which are mixed in by mixinTraitMembers
+ for (sym <- clazz.info.decls ; if sym hasFlag MIXEDIN) {
+ // if current class is a trait, add an abstract method for accessor `sym`
+ // ditto for a super accessor (will get an RHS in completeSuperAccessor)
+ if (clazz.isTrait || sym.isSuperAccessor) addDefDef(sym)
+ // implement methods mixed in from a supertrait (the symbols were created by mixinTraitMembers)
+ else if (sym.hasFlag(ACCESSOR) && !sym.hasFlag(DEFERRED)) {
+ assert(sym hasFlag (PARAMACCESSOR), s"mixed in $sym from $clazz is not param?!?")
+
+ // add accessor definitions
+ addDefDef(sym, accessorBody(sym))
}
- override def transformStats(stats: List[Tree], exprOwner: Symbol) = {
- // !!! Ident(self) is never referenced, is it supposed to be confirming
- // that self is anything in particular?
- super.transformStats(
- stats flatMap {
- case stat @ Assign(lhs @ Select(This(_), _), rhs) => stat :: checkedGetter(lhs)
- // remove initialization for default values
- case Apply(lhs @ Select(Ident(self), _), EmptyTree.asList) if lhs.symbol.isSetter => Nil
- case stat => List(stat)
- },
- exprOwner
- )
+ else if (!sym.isMethod) addValDef(sym) // field
+ else if (!sym.isMacro) { // forwarder
+ assert(sym.alias != NoSymbol, (sym, sym.debugFlagString, clazz))
+ // debuglog("New forwarder: " + sym.defString + " => " + sym.alias.defString)
+ addDefDef(sym, Apply(SuperSelect(clazz, sym.alias), sym.paramss.head.map(Ident(_))))
}
}
- /* Adds statements to set the 'init' bit for each field initialized
- * in the body of a constructor.
- */
- def addInitBits(clazz: Symbol, rhs: Tree): Tree =
- new AddInitBitsTransformer(clazz) transform rhs
-
- // begin addNewDefs
-
- /* Fill the map from fields to offset numbers.
- * Instead of field symbols, the map keeps their getter symbols. This makes
- * code generation easier later.
- */
- def buildBitmapOffsets() {
- def fold(fields: List[Symbol], category: Name) = {
- var idx = 0
- fields foreach { f =>
- fieldOffset(f) = idx
- idx += 1
- }
+ val implementedAccessors = implementWithNewDefs(stats)
- if (idx == 0) ()
- else if (idx == 1) bitmapKindForCategory(category) = BooleanClass
- else if (idx < 9) bitmapKindForCategory(category) = ByteClass
- else if (idx < 33) bitmapKindForCategory(category) = IntClass
- else bitmapKindForCategory(category) = LongClass
- }
- clazz.info.decls.toList groupBy bitmapCategory foreach {
- case (nme.NO_NAME, _) => ()
- case (category, fields) => fold(fields, category)
+ if (clazz.isTrait)
+ implementedAccessors filter {
+ case vd: ValDef => assert(vd.symbol.hasFlag(PRESUPER | PARAMACCESSOR), s"unexpected valdef $vd in trait $clazz"); false
+ case _ => true
}
- }
- buildBitmapOffsets()
- var stats1 = addCheckedGetters(clazz, stats)
-
- def getterBody(getter: Symbol) = {
- assert(getter.isGetter)
- val readValue = getter.tpe match {
- // A field "final val f = const" in a trait generates a getter with a ConstantType.
- case MethodType(Nil, ConstantType(c)) =>
- Literal(c)
+ else {
+ /* If `stat` is a superaccessor, complete it by adding a right-hand side.
+ * Note: superaccessors are always abstract until this point.
+ * The method to call in a superaccessor is stored in the accessor symbol's alias field.
+ * The rhs is:
+ * super.A(xs) where A is the super accessor's alias and xs are its formal parameters.
+ * This rhs is typed and then mixin transformed.
+ */
+ def completeSuperAccessor(stat: Tree) = stat match {
+ case DefDef(_, _, _, vparams :: Nil, _, EmptyTree) if stat.symbol.isSuperAccessor =>
+ val body = atPos(stat.pos)(Apply(SuperSelect(clazz, stat.symbol.alias), vparams map (v => Ident(v.symbol))))
+ val pt = stat.symbol.tpe.resultType
+
+ copyDefDef(stat)(rhs = enteringMixin(transform(localTyper.typed(body, pt))))
case _ =>
- // if it is a mixed-in lazy value, complete the accessor
- if (getter.isLazy) {
- val isUnit = isUnitGetter(getter)
- val initCall = Apply(staticRef(initializer(getter)), gen.mkAttributedThis(clazz) :: Nil)
- val selection = fieldAccess(getter)
- val init = if (isUnit) initCall else atPos(getter.pos)(Assign(selection, initCall))
- val returns = if (isUnit) UNIT else selection
- mkLazyDef(clazz, getter, List(init), returns, fieldOffset(getter))
- }
- // For a field of type Unit in a trait, no actual field is generated when being mixed in.
- else if (isUnitGetter(getter)) UNIT
- else fieldAccess(getter)
+ stat
}
- if (!needsInitFlag(getter)) readValue
- else mkCheckedAccessor(clazz, readValue, fieldOffset(getter), getter.pos, getter)
- }
- def setterBody(setter: Symbol) = {
- val getter = setter.getterIn(clazz)
-
- // A trait with a field of type Unit creates a trait setter (invoked by the
- // implementation class constructor), like for any other trait field.
- // However, no actual field is created in the class that mixes in the trait.
- // Therefore the setter does nothing (except setting the -Xcheckinit flag).
-
- val setInitFlag =
- if (!needsInitFlag(getter)) Nil
- else List(mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter)))
-
- val fieldInitializer =
- if (isUnitGetter(getter)) Nil
- else List(Assign(fieldAccess(setter), Ident(setter.firstParam)))
-
- (fieldInitializer ::: setInitFlag) match {
- case Nil => UNIT
- // If there's only one statement, the Block factory does not actually create a Block.
- case stats => Block(stats: _*)
- }
+ implementedAccessors map completeSuperAccessor
}
-
- def isUnitGetter(getter: Symbol) = getter.tpe.resultType.typeSymbol == UnitClass
- def fieldAccess(accessor: Symbol) = Select(This(clazz), accessor.accessed)
-
- def isOverriddenSetter(sym: Symbol) =
- nme.isTraitSetterName(sym.name) && {
- val other = sym.nextOverriddenSymbol
- isOverriddenAccessor(other.getterIn(other.owner), clazz.info.baseClasses)
- }
-
- // for all symbols `sym` in the class definition, which are mixed in:
- for (sym <- clazz.info.decls ; if sym hasFlag MIXEDIN) {
- // if current class is a trait interface, add an abstract method for accessor `sym`
- if (clazz hasFlag lateINTERFACE) {
- addDefDef(sym)
- }
- // if class is not a trait add accessor definitions
- else if (!clazz.isTrait) {
- if (isConcreteAccessor(sym)) {
- // add accessor definitions
- addDefDef(sym, {
- if (sym.isSetter) {
- // If this is a setter of a mixed-in field which is overridden by another mixin,
- // the trait setter of the overridden one does not need to do anything - the
- // trait setter of the overriding field will initialize the field.
- if (isOverriddenSetter(sym)) UNIT
- else setterBody(sym)
- }
- else getterBody(sym)
- })
- }
- else if (sym.isModule && !(sym hasFlag LIFTED | BRIDGE)) {
- // add modules
- val vsym = sym.owner.newModuleVarSymbol(sym)
- addDef(position(sym), ValDef(vsym))
-
- // !!! TODO - unravel the enormous duplication between this code and
- // eliminateModuleDefs in RefChecks.
- val rhs = gen.newModule(sym, vsym.tpe)
- val assignAndRet = gen.mkAssignAndReturn(vsym, rhs)
- val attrThis = gen.mkAttributedThis(clazz)
- val rhs1 = mkInnerClassAccessorDoubleChecked(attrThis, assignAndRet, sym, List())
-
- addDefDef(sym, rhs1)
- }
- else if (!sym.isMethod) {
- // add fields
- addValDef(sym)
- }
- else if (sym.isSuperAccessor) {
- // add superaccessors
- addDefDef(sym)
- }
- else {
- // add forwarders
- assert(sym.alias != NoSymbol, sym)
- // debuglog("New forwarder: " + sym.defString + " => " + sym.alias.defString)
- if (!sym.isMacro) addDefDef(sym, Apply(staticRef(sym.alias), gen.mkAttributedThis(clazz) :: sym.paramss.head.map(Ident)))
- }
- }
- }
- stats1 = add(stats1, newDefs.toList)
- if (!clazz.isTrait) stats1 = stats1 map completeSuperAccessor
- stats1
- }
-
- private def nullableFields(templ: Template): Map[Symbol, Set[Symbol]] = {
- val scope = templ.symbol.owner.info.decls
- // if there are no lazy fields, take the fast path and save a traversal of the whole AST
- if (scope exists (_.isLazy)) {
- val map = mutable.Map[Symbol, Set[Symbol]]() withDefaultValue Set()
- // check what fields can be nulled for
- for ((field, users) <- singleUseFields(templ); lazyFld <- users if !lazyFld.accessed.hasAnnotation(TransientAttr))
- map(lazyFld) += field
-
- map.toMap
- }
- else Map()
}
/** The transform that gets applied to a tree after it has been completely
* traversed and possible modified by a preTransform.
* This step will
- * - change every node type that refers to an implementation class to its
- * corresponding interface, unless the node's symbol is an implementation class.
* - change parents of templates to conform to parents in the symbol info
* - add all new definitions to a class or interface
* - remove widening casts
@@ -1142,105 +543,37 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
* to static calls of methods in implementation modules (@see staticCall)
* - change super calls to methods in implementation classes to static calls
* (@see staticCall)
- * - change `this` in implementation modules to references to the self parameter
- * - refer to fields in some implementation class via an abstract method in the interface.
*/
private def postTransform(tree: Tree): Tree = {
- def siteWithinImplClass = currentOwner.enclClass.isImplClass
val sym = tree.symbol
- // change every node type that refers to an implementation class to its
- // corresponding interface, unless the node's symbol is an implementation class.
- if (tree.tpe.typeSymbol.isImplClass && ((sym eq null) || !sym.isImplClass))
- tree modifyType toInterface
-
tree match {
case templ @ Template(parents, self, body) =>
// change parents of templates to conform to parents in the symbol info
val parents1 = currentOwner.info.parents map (t => TypeTree(t) setPos tree.pos)
- // mark fields which can be nulled afterward
- lazyValNullables = nullableFields(templ) withDefaultValue Set()
- // add all new definitions to current class or interface
- treeCopy.Template(tree, parents1, self, addNewDefs(currentOwner, body))
-
- // remove widening casts
- case Apply(TypeApply(Select(qual, _), targ :: _), _) if isCastSymbol(sym) && (qual.tpe <:< targ.tpe) =>
- qual
-
- case Apply(Select(qual, _), args) =>
- /* Changes `qual.m(args)` where m refers to an implementation
- * class method to Q.m(S, args) where Q is the implementation module of
- * `m` and S is the self parameter for the call, which
- * is determined as follows:
- * - if qual != super, qual itself
- * - if qual == super, and we are in an implementation class,
- * the current self parameter.
- * - if qual == super, and we are not in an implementation class, `this`
- */
- def staticCall(target: Symbol) = {
- def implSym = implClass(sym.owner).info.member(sym.name)
- assert(target ne NoSymbol,
- List(sym + ":", sym.tpe, sym.owner, implClass(sym.owner), implSym,
- enteringPrevPhase(implSym.tpe), phase) mkString " "
- )
- typedPos(tree.pos)(Apply(staticRef(target), transformSuper(qual) :: args))
- }
-
- if (isStaticOnly(sym)) {
- // change calls to methods which are defined only in implementation
- // classes to static calls of methods in implementation modules
- staticCall(sym)
- }
- else qual match {
- case Super(_, mix) =>
- // change super calls to methods in implementation classes to static calls.
- // Transform references super.m(args) as follows:
- // - if `m` refers to a trait, insert a static call to the corresponding static
- // implementation
- // - otherwise return tree unchanged
- assert(
- !(mix == tpnme.EMPTY && siteWithinImplClass),
- "illegal super in trait: " + currentOwner.enclClass + " " + tree
- )
- if (sym.owner hasFlag lateINTERFACE) {
- if (sym.hasAccessorFlag) {
- assert(args.isEmpty, args)
- val sym1 = sym.overridingSymbol(currentOwner.enclClass)
- typedPos(tree.pos)((transformSuper(qual) DOT sym1)())
- }
- else {
- staticCall(enteringPrevPhase(sym.overridingSymbol(implClass(sym.owner))))
- }
- }
- else {
- assert(!siteWithinImplClass, currentOwner.enclClass)
- tree
- }
+ // add all new definitions to current class or interface
+ val statsWithNewDefs = addNewDefs(currentOwner, body)
+ statsWithNewDefs foreach {
+ case dd: DefDef if isTraitMethodRequiringStaticImpl(dd) =>
+ dd.symbol.updateAttachment(NeedStaticImpl)
case _ =>
- tree
}
+ treeCopy.Template(tree, parents1, self, statsWithNewDefs)
- case This(_) =>
- transformThis(tree)
-
- case Select(Super(_, _), name) =>
- tree
+ case Select(qual, name) if sym.owner.isTrait && !sym.isMethod =>
+ assert(sym.hasFlag(PARAMACCESSOR | PRESUPER), s"!!! Unexpected reference to field $sym in trait $currentOwner")
- case Select(qual, name) if sym.owner.isImplClass && !isStaticOnly(sym) =>
- assert(!sym.isMethod, "no method allowed here: %s%s %s".format(sym, sym.isImplOnly, sym.flagString))
- // refer to fields in some implementation class via an abstract
- // getter in the interface.
- val iface = toInterface(sym.owner.tpe).typeSymbol
- val ifaceGetter = sym getterIn iface
+ // refer to fields in some trait an abstract getter in the interface.
+ val ifaceGetter = sym getterIn sym.owner
- if (ifaceGetter == NoSymbol) abort("No getter for " + sym + " in " + iface)
+ if (ifaceGetter == NoSymbol) abort("No getter for " + sym + " in " + sym.owner)
else typedPos(tree.pos)((qual DOT ifaceGetter)())
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.setterIn(toInterface(lhs.symbol.owner.tpe).typeSymbol) setPos lhs.pos
+ // assign to fields in some trait via an abstract setter in the interface.
+ // Note that the case above has added the empty application.
+ val setter = lhs.symbol.setterIn(lhs.symbol.owner.tpe.typeSymbol) setPos lhs.pos
typedPos(tree.pos)((qual DOT setter)(rhs))
@@ -1262,4 +595,14 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
finally localTyper = saved
}
}
+
+ private def isTraitMethodRequiringStaticImpl(dd: DefDef): Boolean = {
+ val sym = dd.symbol
+ dd.rhs.nonEmpty &&
+ sym.owner.isTrait &&
+ !sym.isPrivate && // no need to put implementations of private methods into a static method
+ !sym.hasFlag(Flags.STATIC)
+ }
+
+ case object NeedStaticImpl extends PlainAttachment
}
diff --git a/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala b/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala
index e4082eb376..a861115cab 100644
--- a/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala
+++ b/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala
@@ -18,8 +18,6 @@ abstract class OverridingPairs extends SymbolPairs {
import global._
class Cursor(base: Symbol) extends super.Cursor(base) {
- lazy val relatively = new RelativeTo(base.thisType)
-
/** Symbols to exclude: Here these are constructors and private/artifact symbols,
* including bridges. But it may be refined in subclasses.
*/
@@ -37,7 +35,7 @@ abstract class OverridingPairs extends SymbolPairs {
(lo.owner != high.owner) // don't try to form pairs from overloaded members
&& !high.isPrivate // private or private[this] members never are overridden
&& !exclude(lo) // this admits private, as one can't have a private member that matches a less-private member.
- && relatively.matches(lo, high)
+ && ((self memberType lo) matches (self memberType high))
) // TODO we don't call exclude(high), should we?
}
}
diff --git a/src/compiler/scala/tools/nsc/transform/SampleTransform.scala b/src/compiler/scala/tools/nsc/transform/SampleTransform.scala
index ba303f7c2b..4c1705e386 100644
--- a/src/compiler/scala/tools/nsc/transform/SampleTransform.scala
+++ b/src/compiler/scala/tools/nsc/transform/SampleTransform.scala
@@ -35,7 +35,7 @@ abstract class SampleTransform extends Transform {
atPos(tree1.pos)( // `atPos` fills in position of its tree argument
Select( // The `Select` factory method is defined in class `Trees`
sup,
- currentOwner.newValue( // creates a new term symbol owned by `currentowner`
+ currentOwner.newValue( // creates a new term symbol owned by `currentOwner`
newTermName("sample"), // The standard term name creator
tree1.pos)))))
case _ =>
diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
index 53a1347a48..9161786d76 100644
--- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
+++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
@@ -9,8 +9,6 @@ package transform
import scala.tools.nsc.symtab.Flags
import scala.collection.{ mutable, immutable }
-import scala.language.postfixOps
-import scala.language.existentials
import scala.annotation.tailrec
/** Specialize code on types.
@@ -61,7 +59,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
val phaseName: String = "specialize"
/** The following flags may be set by this phase: */
- override def phaseNewFlags: Long = notPRIVATE | lateFINAL
+ override def phaseNewFlags: Long = notPRIVATE
/** This phase changes base classes. */
override def changesBaseClasses = true
@@ -168,7 +166,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
/** Reduce the given environment to contain mappings only for type variables in tps. */
def restrict(env: TypeEnv, tps: immutable.Set[Symbol]): TypeEnv =
- env filterKeys tps toMap
+ env.filterKeys(tps).toMap
/** Is the given environment a valid specialization for sym?
* It is valid if each binding is from a @specialized type parameter in sym (or its owner)
@@ -285,6 +283,19 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
for ((tvar, tpe) <- sym.info.typeParams.zip(args) if !tvar.isSpecialized || !isPrimitiveValueType(tpe))
yield tpe
+ /** Is `member` potentially affected by specialization? This is a gross overapproximation,
+ * but it should be okay for use outside of specialization.
+ */
+ def possiblySpecialized(sym: Symbol) = specializedTypeVars(sym).nonEmpty
+
+ /** Refines possiblySpecialized taking into account the instantiation of the specialized type variables at `site` */
+ def isSpecializedIn(sym: Symbol, site: Type) =
+ specializedTypeVars(sym) exists { tvar =>
+ val concretes = concreteTypes(tvar)
+ (concretes contains AnyRefClass) || (concretes contains site.memberType(tvar))
+ }
+
+
val specializedType = new TypeMap {
override def apply(tp: Type): Type = tp match {
case TypeRef(pre, sym, args) if args.nonEmpty =>
@@ -354,7 +365,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
}
)
- lazy val specializableTypes = ScalaValueClasses map (_.tpe) sorted
+ lazy val specializableTypes = ScalaValueClasses.map(_.tpe).sorted
/** If the symbol is the companion of a value class, the value class.
* Otherwise, AnyRef.
@@ -373,7 +384,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
val types = if (!sym.isSpecialized)
Nil // no @specialized Annotation
else
- specializedOn(sym) map (s => specializesClass(s).tpe) sorted
+ specializedOn(sym).map(s => specializesClass(s).tpe).sorted
if (isBoundedGeneric(sym.tpe) && (types contains AnyRefClass))
reporter.warning(sym.pos, sym + " is always a subtype of " + AnyRefTpe + ".")
@@ -461,7 +472,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
case ExistentialType(_, res) => specializedTypeVars(res)
case AnnotatedType(_, tp) => specializedTypeVars(tp)
case TypeBounds(lo, hi) => specializedTypeVars(lo :: hi :: Nil)
- case RefinedType(parents, _) => parents flatMap specializedTypeVars toSet
+ case RefinedType(parents, _) => parents.flatMap(specializedTypeVars).toSet
case _ => immutable.Set.empty
}
@@ -697,7 +708,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
else
debuglog("conflicting env for " + m + " env: " + env)
}
- else if (m.isDeferred) { // abstract methods
+ else if (m.isDeferred && m.isSpecialized) { // abstract methods
val specMember = enterMember(cloneInSpecializedClass(m, _ | DEFERRED))
// debuglog("deferred " + specMember.fullName + " remains abstract")
@@ -705,14 +716,14 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
// was: new Forward(specMember) {
// override def target = m.owner.info.member(specializedName(m, env))
// }
- } else if (m.isMethod && !m.hasAccessorFlag) { // other concrete methods
+ } else if (!sClass.isTrait && m.isMethod && !m.hasAccessorFlag) { // other concrete methods
// log("other concrete " + m)
forwardToOverload(m)
- } else if (m.isMethod && m.hasFlag(LAZY)) {
+ } else if (!sClass.isTrait && m.isMethod && m.hasFlag(LAZY)) {
forwardToOverload(m)
- } else if (m.isValue && !m.isMethod && !m.hasFlag(LAZY)) { // concrete value definition
+ } else if (m.isValue && !m.isMethod) { // concrete value definition
def mkAccessor(field: Symbol, name: Name) = {
val newFlags = (SPECIALIZED | m.getterIn(clazz).flags) & ~(LOCAL | CASEACCESSOR | PARAMACCESSOR)
// we rely on the super class to initialize param accessors
@@ -733,7 +744,14 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
enterMember(specVal)
// create accessors
- if (nme.isLocalName(m.name)) {
+ if (m.isLazy) {
+ // no getters needed (we'll specialize the compute method and accessor separately), can stay private
+ // m.setFlag(PRIVATE) -- TODO: figure out how to leave the non-specialized lazy var private
+ // (the implementation needs it to be visible while duplicating and retypechecking,
+ // but it really could be private in bytecode)
+ specVal.setFlag(PRIVATE)
+ }
+ else if (nme.isLocalName(m.name)) {
val specGetter = mkAccessor(specVal, specVal.getterName) setInfo MethodType(Nil, specVal.info)
val origGetter = overrideIn(sClass, m.getterIn(clazz))
info(origGetter) = Forward(specGetter)
@@ -848,7 +866,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
if (unusedStvars.length == 1) "is" else "are")
)
unusedStvars foreach (_ removeAnnotation SpecializedClass)
- specializingOn = specializingOn filterNot (unusedStvars contains)
+ specializingOn = specializingOn filterNot (unusedStvars contains _)
}
for (env0 <- specializations(specializingOn) if needsSpecialization(env0, sym)) yield {
// !!! Can't this logic be structured so that the new symbol's name is
@@ -1008,7 +1026,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
case (NoSymbol, _) =>
if (overriding.isSuperAccessor) {
val alias = overriding.alias
- debuglog("checking special overload for super accessor: %s, alias for %s".format(overriding.fullName, alias.fullName))
+ debuglog(s"checking special overload for super accessor: ${overriding.fullName}, alias for ${alias.fullName}")
needsSpecialOverride(alias) match {
case nope @ (NoSymbol, _) => None
case (overridden, env) =>
@@ -1030,8 +1048,9 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
param.name = overriding.paramss(i)(j).name // SI-6555 Retain the parameter names from the subclass.
}
}
- debuglog("specialized overload %s for %s in %s: %s".format(om, overriding.name.decode, pp(env), om.info))
- if (overriding.isAbstractOverride) om.setFlag(ABSOVERRIDE)
+ debuglog(s"specialized overload $om for ${overriding.name.decode} in ${pp(env)}: ${om.info}")
+ om.setFlag(overriding.flags & (ABSOVERRIDE | SYNCHRONIZED))
+ om.withAnnotations(overriding.annotations.filter(_.symbol == ScalaStrictFPAttr))
typeEnv(om) = env
addConcreteSpecMethod(overriding)
if (overriding.isDeferred) { // abstract override
@@ -1079,7 +1098,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
*/
private def unify(tp1: Type, tp2: Type, env: TypeEnv, strict: Boolean, tparams: Boolean = false): TypeEnv = (tp1, tp2) match {
case (TypeRef(_, sym1, _), _) if sym1.isSpecialized =>
- debuglog("Unify " + tp1 + ", " + tp2)
+ debuglog(s"Unify $tp1, $tp2")
if (isPrimitiveValueClass(tp2.typeSymbol) || isSpecializedAnyRefSubtype(tp2, sym1))
env + ((sym1, tp2))
else if (isSpecializedAnyRefSubtype(tp2, sym1))
@@ -1090,20 +1109,20 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
env
case (TypeRef(_, sym1, args1), TypeRef(_, sym2, args2)) =>
if (args1.nonEmpty || args2.nonEmpty)
- debuglog("Unify types " + tp1 + " and " + tp2)
+ debuglog(s"Unify types $tp1 and $tp2")
if (strict && args1.length != args2.length) unifyError(tp1, tp2)
val e = unify(args1, args2, env, strict)
- if (e.nonEmpty) debuglog("unified to: " + e)
+ if (e.nonEmpty) debuglog(s"unified to: $e")
e
case (TypeRef(_, sym1, _), _) if sym1.isTypeParameterOrSkolem =>
env
case (MethodType(params1, res1), MethodType(params2, res2)) =>
if (strict && params1.length != params2.length) unifyError(tp1, tp2)
- debuglog("Unify methods " + tp1 + " and " + tp2)
+ debuglog(s"Unify methods $tp1 and $tp2")
unify(res1 :: (params1 map (_.tpe)), res2 :: (params2 map (_.tpe)), env, strict)
case (PolyType(tparams1, res1), PolyType(tparams2, res2)) =>
- debuglog("Unify polytypes " + tp1 + " and " + tp2)
+ debuglog(s"Unify polytypes $tp1 and $tp2")
if (strict && tparams1.length != tparams2.length)
unifyError(tp1, tp2)
else if (tparams && tparams1.length == tparams2.length)
@@ -1121,7 +1140,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
case (ExistentialType(_, res1), _) => unify(tp2, res1, env, strict)
case (TypeBounds(lo1, hi1), TypeBounds(lo2, hi2)) => unify(List(lo1, hi1), List(lo2, hi2), env, strict)
case _ =>
- debuglog("don't know how to unify %s [%s] with %s [%s]".format(tp1, tp1.getClass, tp2, tp2.getClass))
+ debuglog(s"don't know how to unify $tp1 [${tp1.getClass}] with $tp2 [${tp2.getClass}]")
env
}
@@ -1131,9 +1150,9 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
if (!strict) unify(args._1, args._2, env, strict)
else {
val nenv = unify(args._1, args._2, emptyEnv, strict)
- if (env.keySet intersect nenv.keySet isEmpty) env ++ nenv
+ if (env.keySet.intersect(nenv.keySet).isEmpty) env ++ nenv
else {
- debuglog("could not unify: u(" + args._1 + ", " + args._2 + ") yields " + nenv + ", env: " + env)
+ debuglog(s"could not unify: u(${args._1}, ${args._2}) yields $nenv, env: $env")
unifyError(tp1, tp2)
}
}
@@ -1229,7 +1248,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
env forall { case (tvar, tpe) =>
matches(tvar.info.bounds.lo, tpe) && matches(tpe, tvar.info.bounds.hi) || {
if (warnings)
- reporter.warning(tvar.pos, "Bounds prevent specialization of " + tvar)
+ reporter.warning(tvar.pos, s"Bounds prevent specialization of $tvar")
debuglog("specvars: " +
tvar.info.bounds.lo + ": " +
@@ -1318,6 +1337,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
class SpecializationDuplicator(casts: Map[Symbol, Type]) extends Duplicator(casts) {
override def retyped(context: Context, tree: Tree, oldThis: Symbol, newThis: Symbol, env: scala.collection.Map[Symbol, Type]): Tree =
enteringSpecialize(super.retyped(context, tree, oldThis, newThis, env))
+
}
/** A tree symbol substituter that substitutes on type skolems.
@@ -1360,7 +1380,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
sym, currentClass, sym.owner.enclClass, isAccessible(sym), nme.isLocalName(sym.name))
)
if (shouldMakePublic(sym) && !isAccessible(sym)) {
- debuglog("changing private flag of " + sym)
+ debuglog(s"changing private flag of $sym")
sym.makeNotPrivate(sym.owner)
}
super.transform(tree)
@@ -1415,10 +1435,10 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
(treeType =:= memberType) || { // anyref specialization
memberType match {
case PolyType(_, resTpe) =>
- debuglog("Conformance for anyref - polytype with result type: " + resTpe + " and " + treeType + "\nOrig. sym.: " + origSymbol)
+ debuglog(s"Conformance for anyref - polytype with result type: $resTpe and $treeType\nOrig. sym.: $origSymbol")
try {
val e = unify(origSymbol.tpe, memberType, emptyEnv, true)
- debuglog("obtained env: " + e)
+ debuglog(s"obtained env: $e")
e.keySet == env.keySet
} catch {
case _: Throwable =>
@@ -1518,7 +1538,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
)
val tree1 = gen.mkTypeApply(specTree, residualTargs)
- debuglog("rewrote " + tree + " to " + tree1)
+ debuglog(s"rewrote $tree to $tree1")
localTyper.typedOperator(atPos(tree.pos)(tree1)) // being polymorphic, it must be a method
}
@@ -1526,7 +1546,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
tree match {
case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) =>
def transformNew = {
- debuglog("Attempting to specialize new %s(%s)".format(tpt, args.mkString(", ")))
+ debuglog(s"Attempting to specialize new $tpt(${args.mkString(", ")})")
val found = specializedType(tpt.tpe)
if (found.typeSymbol ne tpt.tpe.typeSymbol) { // the ctor can be specialized
val inst = New(found, transformTrees(args): _*)
@@ -1900,8 +1920,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
/** Forward to the generic class constructor. If the current class initializes
* specialized fields corresponding to parameters, it passes null to the superclass
- * constructor. This saves the boxing cost for initializing generic fields that are
- * never used.
+ * constructor.
*
* For example:
* {{{
@@ -1915,7 +1934,17 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
* super.this(null.asInstanceOf[Int], null.asInstanceOf[Int])
* }
* }
- * }}
+ * }}}
+ *
+ * Note that erasure first transforms `null.asInstanceOf[Int]` to `unbox(null)`, which is 0.
+ * Then it adapts the argument `unbox(null)` of type Int to the erased parameter type of Tuple2,
+ * which is Object, so it inserts a `box` call and we get `box(unbox(null))`, which is
+ * `new Integer(0)` (not `null`).
+ *
+ * However it does not make sense to create an Integer instance to be stored in the generic field
+ * of the superclass: that field is never used. Therefore we mark the `null` tree with the
+ * [[SpecializedSuperConstructorCallArgument]] attachment and special-case erasure to replace
+ * `box(unbox(null))` by `null` in this case.
*/
private def forwardCtorCall(pos: scala.reflect.internal.util.Position, receiver: Tree, paramss: List[List[ValDef]], clazz: Symbol): Tree = {
log(s"forwardCtorCall($pos, $receiver, $paramss, $clazz)")
@@ -1934,7 +1963,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
val argss = mmap(paramss)(x =>
if (initializesSpecializedField(x.symbol))
- gen.mkAsInstanceOf(Literal(Constant(null)), x.symbol.tpe)
+ gen.mkAsInstanceOf(Literal(Constant(null)).updateAttachment(SpecializedSuperConstructorCallArgument), x.symbol.tpe)
else
Ident(x.symbol)
)
@@ -1963,7 +1992,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
else exitingSpecialize(specializeCalls(unit).transform(tree))
// Remove the final modifier and @inline annotation from anything in the
- // original class (since it's being overridden in at least onesubclass).
+ // original class (since it's being overridden in at least one subclass).
//
// We do this here so that the specialized subclasses will correctly copy
// final and @inline.
@@ -1978,5 +2007,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
}
resultTree
- } }
+ }
+ }
+ object SpecializedSuperConstructorCallArgument
}
diff --git a/src/compiler/scala/tools/nsc/transform/Statics.scala b/src/compiler/scala/tools/nsc/transform/Statics.scala
index 4673be6de7..776805fd9f 100644
--- a/src/compiler/scala/tools/nsc/transform/Statics.scala
+++ b/src/compiler/scala/tools/nsc/transform/Statics.scala
@@ -1,49 +1,32 @@
package scala.tools.nsc
package transform
-import collection.mutable.Buffer
-
abstract class Statics extends Transform with ast.TreeDSL {
import global._
- class StaticsTransformer extends Transformer {
-
- /** finds the static ctor DefDef tree within the template if it exists. */
- def findStaticCtor(template: Template): Option[Tree] =
- template.body find {
- case defdef @ DefDef(_, nme.CONSTRUCTOR, _, _, _, _) => defdef.symbol.hasStaticFlag
- case _ => false
- }
-
- /** changes the template for the class so that it contains a static constructor with symbol fields inits,
- * augments an existing static ctor if one already existed.
+ trait StaticsTransformer extends Transformer {
+ /** generate a static constructor with symbol fields inits, or an augmented existing static ctor
*/
- def addStaticInits(template: Template, newStaticInits: Buffer[Tree], localTyper: analyzer.Typer): Template = {
- if (newStaticInits.isEmpty)
- template
- else {
- val newCtor = findStaticCtor(template) match {
- // in case there already were static ctors - augment existing ones
- // currently, however, static ctors aren't being generated anywhere else
- case Some(ctor @ DefDef(_,_,_,_,_,_)) =>
- // modify existing static ctor
- deriveDefDef(ctor) {
- case block @ Block(stats, expr) =>
- // need to add inits to existing block
- treeCopy.Block(block, newStaticInits.toList ::: stats, expr)
- case term: TermTree =>
- // need to create a new block with inits and the old term
- treeCopy.Block(term, newStaticInits.toList, term)
- }
- case _ =>
- // create new static ctor
- val staticCtorSym = currentClass.newStaticConstructor(template.pos)
- val rhs = Block(newStaticInits.toList, Literal(Constant(())))
+ def staticConstructor(body: List[Tree], localTyper: analyzer.Typer, pos: Position)(newStaticInits: List[Tree]): Tree =
+ body.collectFirst {
+ // If there already was a static ctor - augment existing one
+ // currently, however, static ctors aren't being generated anywhere else (!!!)
+ case ctor@DefDef(_, nme.CONSTRUCTOR, _, _, _, _) if ctor.symbol.hasStaticFlag =>
+ // modify existing static ctor
+ deriveDefDef(ctor) {
+ case block@Block(stats, expr) =>
+ // need to add inits to existing block
+ treeCopy.Block(block, newStaticInits ::: stats, expr)
+ case term: TermTree =>
+ // need to create a new block with inits and the old term
+ treeCopy.Block(term, newStaticInits, term)
+ }
+ } getOrElse {
+ // create new static ctor
+ val staticCtorSym = currentClass.newStaticConstructor(pos)
+ val rhs = Block(newStaticInits, Literal(Constant(())))
- localTyper.typedPos(template.pos)(DefDef(staticCtorSym, rhs))
- }
- deriveTemplate(template)(newCtor :: _)
+ localTyper.typedPos(pos)(DefDef(staticCtorSym, rhs))
}
- }
}
}
diff --git a/src/compiler/scala/tools/nsc/transform/TailCalls.scala b/src/compiler/scala/tools/nsc/transform/TailCalls.scala
index 16ea3ea90f..9e3e8ff455 100644
--- a/src/compiler/scala/tools/nsc/transform/TailCalls.scala
+++ b/src/compiler/scala/tools/nsc/transform/TailCalls.scala
@@ -69,7 +69,7 @@ abstract class TailCalls extends Transform {
* are optimized. Since 'this' is not a local variable, a dummy local val
* is added and used as a label parameter. The backend knows to load
* the corresponding argument in the 'this' (local at index 0). This dummy local
- * is never used and should be cleand up by dead code elimination (when enabled).
+ * is never used and should be cleaned up by dead code elimination (when enabled).
* </p>
* <p>
* This phase has been moved before pattern matching to catch more
@@ -84,7 +84,7 @@ abstract class TailCalls extends Transform {
* </p>
* <p>
* Assumes: `Uncurry` has been run already, and no multiple
- * parameter lists exit.
+ * parameter lists exist.
* </p>
*/
class TailCallElimination(unit: CompilationUnit) extends Transformer {
@@ -274,10 +274,8 @@ abstract class TailCalls extends Transform {
import runDefinitions.{Boolean_or, Boolean_and}
tree match {
- case ValDef(_, _, _, _) =>
- if (tree.symbol.isLazy && tree.symbol.hasAnnotation(TailrecClass))
- reporter.error(tree.pos, "lazy vals are not tailcall transformed")
-
+ case dd: DefDef if tree.symbol.isLazy && tree.symbol.hasAnnotation(TailrecClass) =>
+ reporter.error(tree.pos, "lazy vals are not tailcall transformed")
super.transform(tree)
case dd @ DefDef(_, name, _, vparamss0, _, rhs0) if isEligible(dd) =>
diff --git a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala
index 3b23306386..52d7c0b897 100644
--- a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala
+++ b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala
@@ -1,90 +1,64 @@
package scala.tools.nsc
package transform
+import scala.annotation.tailrec
import scala.tools.nsc.ast.TreeDSL
-import scala.tools.nsc.Global
/**
* A trait usable by transforms that need to adapt trees of one type to another type
*/
-trait TypeAdaptingTransformer {
- self: TreeDSL =>
-
- val analyzer: typechecker.Analyzer { val global: self.global.type }
-
- trait TypeAdapter {
- val typer: analyzer.Typer
+trait TypeAdaptingTransformer { self: TreeDSL =>
+ abstract class TypeAdapter {
import global._
import definitions._
- import CODE._
- def isMethodTypeWithEmptyParams(tpe: Type) = tpe match {
- case MethodType(Nil, _) => true
- case _ => false
- }
+ def typedPos(pos: Position)(tree: Tree): Tree
+ /**
+ * SI-4148: can't always replace box(unbox(x)) by x because
+ * - unboxing x may lead to throwing an exception, e.g. in "aah".asInstanceOf[Int]
+ * - box(unbox(null)) is not `null` but the box of zero
+ */
private def isSafelyRemovableUnbox(fn: Tree, arg: Tree): Boolean = {
- currentRun.runDefinitions.isUnbox(fn.symbol) && {
- val cls = arg.tpe.typeSymbol
- (cls == definitions.NullClass) || isBoxedValueClass(cls)
- }
+ currentRun.runDefinitions.isUnbox(fn.symbol) && {
+ // replace box(unbox(null)) by null when passed to the super constructor in a specialized
+ // class, see comment in SpecializeTypes.forwardCtorCall.
+ arg.hasAttachment[specializeTypes.SpecializedSuperConstructorCallArgument.type] ||
+ isBoxedValueClass(arg.tpe.typeSymbol)
+ }
}
- private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol)
-
- private def isErasedValueType(tpe: Type) = tpe.isInstanceOf[ErasedValueType]
-
- private def isDifferentErasedValueType(tpe: Type, other: Type) =
- isErasedValueType(tpe) && (tpe ne other)
+ private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol)
+ final def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner)
+ final def isMethodTypeWithEmptyParams(tpe: Type) = tpe.isInstanceOf[MethodType] && tpe.params.isEmpty
+ final def applyMethodWithEmptyParams(qual: Tree) = Apply(qual, List()) setPos qual.pos setType qual.tpe.resultType
- def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner)
-
- @inline def box(tree: Tree, target: => String): Tree = {
- val result = box1(tree)
- if (tree.tpe =:= UnitTpe) ()
- else log(s"boxing ${tree.summaryString}: ${tree.tpe} into $target: ${result.tpe}")
- result
- }
+ import CODE._
/** Box `tree` of unboxed type */
- private def box1(tree: Tree): Tree = tree match {
+ final def box(tree: Tree): Tree = tree match {
case LabelDef(_, _, _) =>
- val ldef = deriveLabelDef(tree)(box1)
+ val ldef = deriveLabelDef(tree)(box)
ldef setType ldef.rhs.tpe
case _ =>
val tree1 = tree.tpe match {
- case ErasedValueType(clazz, _) =>
- New(clazz, cast(tree, underlyingOfValueClass(clazz)))
- case _ =>
- tree.tpe.typeSymbol match {
- case UnitClass =>
- if (treeInfo isExprSafeToInline tree) REF(BoxedUnit_UNIT)
- else BLOCK(tree, REF(BoxedUnit_UNIT))
- case NothingClass => tree // a non-terminating expression doesn't need boxing
- case x =>
- assert(x != ArrayClass)
- tree match {
- /* Can't always remove a Box(Unbox(x)) combination because the process of boxing x
- * may lead to throwing an exception.
- *
- * This is important for specialization: calls to the super constructor should not box/unbox specialized
- * fields (see TupleX). (ID)
- */
- case Apply(boxFun, List(arg)) if isSafelyRemovableUnbox(tree, arg) =>
- log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}")
- arg
- case _ =>
- (REF(currentRun.runDefinitions.boxMethod(x)) APPLY tree) setPos (tree.pos) setType ObjectTpe
- }
- }
+ case ErasedValueType(clazz, _) => New(clazz, cast(tree, underlyingOfValueClass(clazz)))
+ case _ => tree.tpe.typeSymbol match {
+ case UnitClass =>
+ if (treeInfo isExprSafeToInline tree) REF(BoxedUnit_UNIT)
+ else BLOCK(tree, REF(BoxedUnit_UNIT))
+ case NothingClass => tree // a non-terminating expression doesn't need boxing
+ case x =>
+ assert(x != ArrayClass)
+ tree match {
+ case Apply(boxFun, List(arg)) if isSafelyRemovableUnbox(tree, arg) =>
+ arg
+ case _ =>
+ (REF(currentRun.runDefinitions.boxMethod(x)) APPLY tree) setPos (tree.pos) setType ObjectTpe
+ }
+ }
}
- typer.typedPos(tree.pos)(tree1)
- }
-
- def unbox(tree: Tree, pt: Type): Tree = {
- val result = unbox1(tree, pt)
- log(s"unboxing ${tree.shortClass}: ${tree.tpe} as a ${result.tpe}")
- result
+ typedPos(tree.pos)(tree1)
}
/** Unbox `tree` of boxed type to expected type `pt`.
@@ -93,27 +67,13 @@ trait TypeAdaptingTransformer {
* @param pt the expected type.
* @return the unboxed tree
*/
- private def unbox1(tree: Tree, pt: Type): Tree = tree match {
-/*
- case Boxed(unboxed) =>
- println("unbox shorten: "+tree) // this never seems to kick in during build and test; therefore disabled.
- adaptToType(unboxed, pt)
- */
+ final def unbox(tree: Tree, pt: Type): Tree = tree match {
case LabelDef(_, _, _) =>
val ldef = deriveLabelDef(tree)(unbox(_, pt))
ldef setType ldef.rhs.tpe
case _ =>
val tree1 = pt match {
- case ErasedValueType(clazz, underlying) =>
- val tree0 =
- if (tree.tpe.typeSymbol == NullClass &&
- isPrimitiveValueClass(underlying.typeSymbol)) {
- // convert `null` directly to underlying type, as going
- // via the unboxed type would yield a NPE (see SI-5866)
- unbox1(tree, underlying)
- } else
- Apply(Select(adaptToType(tree, clazz.tpe), clazz.derivedValueClassUnbox), List())
- cast(tree0, pt)
+ case ErasedValueType(clazz, underlying) => cast(unboxValueClass(tree, clazz, underlying), pt)
case _ =>
pt.typeSymbol match {
case UnitClass =>
@@ -125,21 +85,28 @@ trait TypeAdaptingTransformer {
Apply(currentRun.runDefinitions.unboxMethod(pt.typeSymbol), tree)
}
}
- typer.typedPos(tree.pos)(tree1)
+ typedPos(tree.pos)(tree1)
}
+ final def unboxValueClass(tree: Tree, clazz: Symbol, underlying: Type): Tree =
+ if (tree.tpe.typeSymbol == NullClass && isPrimitiveValueClass(underlying.typeSymbol)) {
+ // convert `null` directly to underlying type, as going via the unboxed type would yield a NPE (see SI-5866)
+ unbox(tree, underlying)
+ } else
+ Apply(Select(adaptToType(tree, clazz.tpe), clazz.derivedValueClassUnbox), List())
+
/** Generate a synthetic cast operation from tree.tpe to pt.
- * @pre pt eq pt.normalize
+ *
+ * @pre pt eq pt.normalize
*/
- def cast(tree: Tree, pt: Type): Tree = {
- if ((tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) {
- def word = (
+ final def cast(tree: Tree, pt: Type): Tree = {
+ if (settings.debug && (tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) {
+ def word =
if (tree.tpe <:< pt) "upcast"
else if (pt <:< tree.tpe) "downcast"
else if (pt weak_<:< tree.tpe) "coerce"
else if (tree.tpe weak_<:< pt) "widen"
else "cast"
- )
log(s"erasure ${word}s from ${tree.tpe} to $pt")
}
if (pt =:= UnitTpe) {
@@ -160,27 +127,23 @@ trait TypeAdaptingTransformer {
* @param pt the expected type
* @return the adapted tree
*/
- def adaptToType(tree: Tree, pt: Type): Tree = {
- if (settings.debug && pt != WildcardType)
- log("adapting " + tree + ":" + tree.tpe + " : " + tree.tpe.parents + " to " + pt)//debug
- if (tree.tpe <:< pt)
- tree
- else if (isDifferentErasedValueType(tree.tpe, pt))
- adaptToType(box(tree, pt.toString), pt)
- else if (isDifferentErasedValueType(pt, tree.tpe))
- adaptToType(unbox(tree, pt), pt)
- else if (isPrimitiveValueType(tree.tpe) && !isPrimitiveValueType(pt)) {
- adaptToType(box(tree, pt.toString), pt)
- } else if (isMethodTypeWithEmptyParams(tree.tpe)) {
- // [H] this assert fails when trying to typecheck tree !(SomeClass.this.bitmap) for single lazy val
- //assert(tree.symbol.isStable, "adapt "+tree+":"+tree.tpe+" to "+pt)
- adaptToType(Apply(tree, List()) setPos tree.pos setType tree.tpe.resultType, pt)
-// } else if (pt <:< tree.tpe)
-// cast(tree, pt)
- } else if (isPrimitiveValueType(pt) && !isPrimitiveValueType(tree.tpe))
- adaptToType(unbox(tree, pt), pt)
- else
- cast(tree, pt)
+ @tailrec final def adaptToType(tree: Tree, pt: Type): Tree = {
+ val tpe = tree.tpe
+
+ if ((tpe eq pt) || tpe <:< pt) tree
+ else if (tpe.isInstanceOf[ErasedValueType]) adaptToType(box(tree), pt) // what if pt is an erased value type?
+ else if (pt.isInstanceOf[ErasedValueType]) adaptToType(unbox(tree, pt), pt)
+ // See corresponding case in `Eraser`'s `adaptMember`
+ // [H] this does not hold here, however: `assert(tree.symbol.isStable)` (when typechecking !(SomeClass.this.bitmap) for single lazy val)
+ else if (isMethodTypeWithEmptyParams(tpe)) adaptToType(applyMethodWithEmptyParams(tree), pt)
+ else {
+ val gotPrimitiveVC = isPrimitiveValueType(tpe)
+ val expectedPrimitiveVC = isPrimitiveValueType(pt)
+
+ if (gotPrimitiveVC && !expectedPrimitiveVC) adaptToType(box(tree), pt)
+ else if (!gotPrimitiveVC && expectedPrimitiveVC) adaptToType(unbox(tree, pt), pt)
+ else cast(tree, pt)
+ }
}
}
}
diff --git a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala
index dc3313e2e4..d5adfe12e9 100644
--- a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala
+++ b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala
@@ -26,7 +26,7 @@ trait TypingTransformers {
def atOwner[A](tree: Tree, owner: Symbol)(trans: => A): A = {
val savedLocalTyper = localTyper
- localTyper = localTyper.atOwner(tree, if (owner.isModule) owner.moduleClass else owner)
+ localTyper = localTyper.atOwner(tree, if (owner.isModuleNotMethod) owner.moduleClass else owner)
val result = super.atOwner(owner)(trans)
localTyper = savedLocalTyper
result
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index d5a7213cfb..f35dd6556f 100644
--- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala
+++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -7,9 +7,10 @@ package scala
package tools.nsc
package transform
+import scala.annotation.tailrec
+
import symtab.Flags._
-import scala.collection.{ mutable, immutable }
-import scala.language.postfixOps
+import scala.collection.mutable
import scala.reflect.internal.util.ListOfNil
/*<export> */
@@ -68,19 +69,30 @@ abstract class UnCurry extends InfoTransform
// uncurry and uncurryType expand type aliases
class UnCurryTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {
- private val inlineFunctionExpansion = settings.Ydelambdafy.value == "inline"
+ private val forceExpandFunction = settings.Ydelambdafy.value == "inline"
private var needTryLift = false
private var inConstructorFlag = 0L
private val byNameArgs = mutable.HashSet[Tree]()
private val noApply = mutable.HashSet[Tree]()
private val newMembers = mutable.Map[Symbol, mutable.Buffer[Tree]]()
- private lazy val forceSpecializationInfoTransformOfFunctionN: Unit = {
- if (currentRun.specializePhase != NoPhase) { // be robust in case of -Ystop-after:uncurry
- exitingSpecialize {
- FunctionClass.seq.foreach(cls => cls.info)
- }
- }
+ // Expand `Function`s in constructors to class instance creation (SI-6666, SI-8363)
+ // We use Java's LambdaMetaFactory (LMF), which requires an interface for the sam's owner
+ private def mustExpandFunction(fun: Function) = {
+ // (TODO: Can't use isInterface, yet, as it hasn't been updated for the new trait encoding)
+ val canUseLambdaMetaFactory = (fun.attachments.get[SAMFunction] match {
+ case Some(SAMFunction(userDefinedSamTp, sam)) =>
+ // LambdaMetaFactory cannot mix in trait members for us, or instantiate classes -- only pure interfaces need apply
+ erasure.compilesToPureInterface(erasure.javaErasure(userDefinedSamTp).typeSymbol) &&
+ // impl restriction -- we currently use the boxed apply, so not really useful to allow specialized sam types (https://github.com/scala/scala/pull/4971#issuecomment-198119167)
+ // specialization and LMF are at odds, since LMF implements the single abstract method,
+ // but that's the one that specialization leaves generic, whereas we need to implement the specialized one to avoid boxing
+ !specializeTypes.isSpecializedIn(sam, userDefinedSamTp)
+
+ case _ => true // our built-in FunctionN's are suitable for LambdaMetaFactory by construction
+ })
+
+ !canUseLambdaMetaFactory
}
/** Add a new synthetic member for `currentOwner` */
@@ -91,25 +103,17 @@ abstract class UnCurry extends InfoTransform
@inline private def useNewMembers[T](owner: Symbol)(f: List[Tree] => T): T =
f(newMembers.remove(owner).getOrElse(Nil).toList)
- private def newFunction0(body: Tree): Tree = {
- val result = localTyper.typedPos(body.pos)(Function(Nil, body)).asInstanceOf[Function]
- log("Change owner from %s to %s in %s".format(currentOwner, result.symbol, result.body))
- result.body changeOwner (currentOwner -> result.symbol)
- transformFunction(result)
- }
-
// I don't have a clue why I'm catching TypeErrors here, but it's better
// than spewing stack traces at end users for internal errors. Examples
// which hit at this point should not be hard to come by, but the immediate
// motivation can be seen in continuations-neg/t3718.
- override def transform(tree: Tree): Tree = (
+ override def transform(tree: Tree): Tree =
try postTransform(mainTransform(tree))
catch { case ex: TypeError =>
reporter.error(ex.pos, ex.msg)
debugStack(ex)
EmptyTree
}
- )
/* Is tree a reference `x` to a call by name parameter that needs to be converted to
* x.apply()? Note that this is not the case if `x` is used as an argument to another
@@ -118,7 +122,7 @@ abstract class UnCurry extends InfoTransform
def isByNameRef(tree: Tree) = (
tree.isTerm
&& (tree.symbol ne null)
- && (isByName(tree.symbol))
+ && isByName(tree.symbol)
&& !byNameArgs(tree)
)
@@ -195,16 +199,6 @@ abstract class UnCurry extends InfoTransform
// ------ Transforming anonymous functions and by-name-arguments ----------------
- /** Undo eta expansion for parameterless and nullary methods */
- def deEta(fun: Function): Tree = fun match {
- case Function(List(), expr) if isByNameRef(expr) =>
- noApply += expr
- expr
- case _ =>
- fun
- }
-
-
/** Transform a function node (x_1,...,x_n) => body of type FunctionN[T_1, .., T_N, R] to
*
* class $anon() extends AbstractFunctionN[T_1, .., T_N, R] with Serializable {
@@ -213,66 +207,38 @@ abstract class UnCurry extends InfoTransform
* new $anon()
*
*/
- def transformFunction(fun: Function): Tree = {
- fun.tpe match {
- // can happen when analyzer plugins assign refined types to functions, e.g.
- // (() => Int) { def apply(): Int @typeConstraint }
- case RefinedType(List(funTp), decls) =>
- debuglog(s"eliminate refinement from function type ${fun.tpe}")
- fun.setType(funTp)
- case _ =>
- ()
- }
-
- deEta(fun) match {
- // nullary or parameterless
- case fun1 if fun1 ne fun => fun1
- case _ =>
- def typedFunPos(t: Tree) = localTyper.typedPos(fun.pos)(t)
- val funParams = fun.vparams map (_.symbol)
- def mkMethod(owner: Symbol, name: TermName, additionalFlags: FlagSet = NoFlags): DefDef =
- gen.mkMethodFromFunction(localTyper)(fun, owner, name, additionalFlags)
-
- def isSpecialized = {
- forceSpecializationInfoTransformOfFunctionN
- val specialized = specializeTypes.specializedType(fun.tpe)
- !(specialized =:= fun.tpe)
- }
+ def transformFunction(fun: Function): Tree =
+ // Undo eta expansion for parameterless and nullary methods, EXCEPT if `fun` targets a SAM.
+ // Normally, we can unwrap `() => cbn` to `cbn` where `cbn` refers to a CBN argument (typically `cbn` is an Ident),
+ // because we know `cbn` will already be a `Function0` thunk. When we're targeting a SAM,
+ // the types don't align and we must preserve the function wrapper.
+ if (fun.vparams.isEmpty && isByNameRef(fun.body) && fun.attachments.get[SAMFunction].isEmpty) { noApply += fun.body ; fun.body }
+ else if (forceExpandFunction || inConstructorFlag != 0) {
+ // Expand the function body into an anonymous class
+ gen.expandFunction(localTyper)(fun, inConstructorFlag)
+ } else {
+ val mustExpand = mustExpandFunction(fun)
+ // method definition with the same arguments, return type, and body as the original lambda
+ val liftedMethod = gen.mkLiftedFunctionBodyMethod(localTyper)(fun.symbol.owner, fun)
+
+ // new function whose body is just a call to the lifted method
+ val newFun = deriveFunction(fun)(_ => localTyper.typedPos(fun.pos)(
+ gen.mkForwarder(gen.mkAttributedRef(liftedMethod.symbol), (fun.vparams map (_.symbol)) :: Nil)
+ ))
- def canUseDelamdafyMethod = (
- (inConstructorFlag == 0) // Avoiding synthesizing code prone to SI-6666, SI-8363 by using old-style lambda translation
- && (!isSpecialized || (settings.isBCodeActive && settings.target.value == "jvm-1.8")) // DelambdafyTransformer currently only emits generic FunctionN-s, use the old style in the meantime
- )
- 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)
- anonClass.info.decls enter applyMethodDef.symbol
-
- typedFunPos {
- Block(
- ClassDef(anonClass, NoMods, ListOfNil, List(applyMethodDef), fun.pos),
- Typed(New(anonClass.tpe), TypeTree(fun.tpe)))
- }
- } else {
- // method definition with the same arguments, return type, and body as the original lambda
- val liftedMethod = mkMethod(fun.symbol.owner, nme.ANON_FUN_NAME, additionalFlags = ARTIFACT)
-
- // new function whose body is just a call to the lifted method
- val newFun = deriveFunction(fun)(_ => typedFunPos(
- gen.mkForwarder(gen.mkAttributedRef(liftedMethod.symbol), funParams :: Nil)
- ))
- typedFunPos(Block(liftedMethod, super.transform(newFun)))
- }
+ if (!mustExpand) {
+ liftedMethod.symbol.updateAttachment(DelambdafyTarget)
+ liftedMethod.updateAttachment(DelambdafyTarget)
}
- }
+ val typedNewFun = localTyper.typedPos(fun.pos)(Block(liftedMethod, super.transform(newFun)))
+ if (mustExpand) {
+ val Block(stats, expr : Function) = typedNewFun
+ treeCopy.Block(typedNewFun, stats, gen.expandFunction(localTyper)(expr, inConstructorFlag))
+ } else {
+ typedNewFun
+ }
+ }
def transformArgs(pos: Position, fun: Symbol, args: List[Tree], formals: List[Type]) = {
val isJava = fun.isJavaDefined
@@ -350,25 +316,22 @@ abstract class UnCurry extends InfoTransform
val args1 = if (isVarArgTypes(formals)) transformVarargs(formals.last.typeArgs.head) else args
map2(formals, args1) { (formal, arg) =>
- if (!isByNameParamType(formal))
- arg
- else if (isByNameRef(arg)) {
+ if (!isByNameParamType(formal)) arg
+ else if (isByNameRef(arg)) { // thunk does not need to be forced because it's a reference to a by-name arg passed to a by-name param
byNameArgs += arg
arg setType functionType(Nil, arg.tpe)
- }
- else {
+ } else {
log(s"Argument '$arg' at line ${arg.pos.line} is $formal from ${fun.fullName}")
- def canUseDirectly(recv: Tree) = (
- recv.tpe.typeSymbol.isSubClass(FunctionClass(0))
- && treeInfo.isExprSafeToInline(recv)
- )
+ def canUseDirectly(qual: Tree) = qual.tpe.typeSymbol.isSubClass(FunctionClass(0)) && treeInfo.isExprSafeToInline(qual)
arg match {
// don't add a thunk for by-name argument if argument already is an application of
// a Function0. We can then remove the application and use the existing Function0.
- case Apply(Select(recv, nme.apply), Nil) if canUseDirectly(recv) =>
- recv
- case _ =>
- newFunction0(arg)
+ case Apply(Select(qual, nme.apply), Nil) if canUseDirectly(qual) => qual
+ case body =>
+ val thunkFun = localTyper.typedPos(body.pos)(Function(Nil, body)).asInstanceOf[Function]
+ log(s"Change owner from $currentOwner to ${thunkFun.symbol} in ${thunkFun.body}")
+ thunkFun.body.changeOwner((currentOwner, thunkFun.symbol))
+ transformFunction(thunkFun)
}
}
}
@@ -379,23 +342,33 @@ abstract class UnCurry extends InfoTransform
* the whole tree with it.
*/
private def replaceElidableTree(tree: Tree): Tree = {
+ def elisionOf(t: Type): Tree = t.typeSymbol match {
+ case StringClass => Literal(Constant("")) setType t
+ case _ => gen.mkZero(t)
+ }
tree match {
- case DefDef(_,_,_,_,_,_) =>
- deriveDefDef(tree)(rhs => Block(Nil, gen.mkZero(rhs.tpe)) setType rhs.tpe) setSymbol tree.symbol setType tree.tpe
+ case DefDef(_,_,_,_,_,rhs) =>
+ val rhs1 = if (rhs == EmptyTree) rhs else Block(Nil, elisionOf(rhs.tpe)) setType rhs.tpe
+ deriveDefDef(tree)(_ => rhs1) setSymbol tree.symbol setType tree.tpe
case _ =>
- gen.mkZero(tree.tpe) setType tree.tpe
+ elisionOf(tree.tpe)
}
}
private def isSelfSynchronized(ddef: DefDef) = ddef.rhs match {
case Apply(fn @ TypeApply(Select(sel, _), _), _) =>
- fn.symbol == Object_synchronized && sel.symbol == ddef.symbol.enclClass && !ddef.symbol.enclClass.isTrait
+ fn.symbol == Object_synchronized && sel.symbol == ddef.symbol.enclClass && !ddef.symbol.enclClass.isTrait &&
+ !ddef.symbol.isDelambdafyTarget /* these become static later, unsuitable for ACC_SYNCHRONIZED */
case _ => false
}
/** If an eligible method is entirely wrapped in a call to synchronized
* locked on the same instance, remove the synchronized scaffolding and
* mark the method symbol SYNCHRONIZED for bytecode generation.
+ *
+ * Delambdafy targets are deemed ineligible as the Delambdafy phase will
+ * replace `this.synchronized` with `$this.synchronized` now that it emits
+ * all lambda impl methods as static.
*/
private def translateSynchronized(tree: Tree) = tree match {
case dd @ DefDef(_, _, _, _, _, Apply(fn, body :: Nil)) if isSelfSynchronized(dd) =>
@@ -438,10 +411,20 @@ abstract class UnCurry extends InfoTransform
val sym = tree.symbol
// true if the target is a lambda body that's been lifted into a method
- def isLiftedLambdaBody(target: Tree) = target.symbol.isLocalToBlock && target.symbol.isArtifact && target.symbol.name.containsName(nme.ANON_FUN_NAME)
+ def isLiftedLambdaMethod(funSym: Symbol) =
+ funSym.isArtifact && funSym.name.containsName(nme.ANON_FUN_NAME) && funSym.isLocalToBlock
- val result = (
- if ((sym ne null) && sym.elisionLevel.exists(_ < settings.elidebelow.value))
+ def checkIsElisible(sym: Symbol): Boolean =
+ (sym ne null) && sym.elisionLevel.exists { level =>
+ if (sym.isMethod) level < settings.elidebelow.value
+ else {
+ if (settings.isScala213) reporter.error(sym.pos, s"${sym.name}: Only methods can be marked @elidable!")
+ false
+ }
+ }
+
+ val result =
+ if (checkIsElisible(sym))
replaceElidableTree(tree)
else translateSynchronized(tree) match {
case dd @ DefDef(mods, name, tparams, _, tpt, rhs) =>
@@ -473,9 +456,9 @@ abstract class UnCurry extends InfoTransform
super.transform(treeCopy.DefDef(dd, mods, name, tparams, vparamssNoRhs, tpt, rhs))
}
}
- case ValDef(_, _, _, rhs) =>
+ case ValDef(mods, _, _, rhs) =>
if (sym eq NoSymbol) throw new IllegalStateException("Encountered Valdef without symbol: "+ tree + " in "+ unit)
- if (!sym.owner.isSourceMethod)
+ if (!sym.owner.isSourceMethod || mods.isLazy)
withNeedLift(needLift = true) { super.transform(tree) }
else
super.transform(tree)
@@ -493,7 +476,7 @@ abstract class UnCurry extends InfoTransform
case Assign(lhs, _) if lhs.symbol.owner != currentMethod || lhs.symbol.hasFlag(LAZY | ACCESSOR) =>
withNeedLift(needLift = true) { super.transform(tree) }
- case ret @ Return(_) if (isNonLocalReturn(ret)) =>
+ case ret @ Return(_) if isNonLocalReturn(ret) =>
withNeedLift(needLift = true) { super.transform(ret) }
case Try(_, Nil, _) =>
@@ -512,7 +495,7 @@ abstract class UnCurry extends InfoTransform
treeCopy.CaseDef(tree, pat1, transform(guard), transform(body))
// if a lambda is already the right shape we don't need to transform it again
- case fun @ Function(_, Apply(target, _)) if (!inlineFunctionExpansion) && isLiftedLambdaBody(target) =>
+ case fun @ Function(_, Apply(target, _)) if !forceExpandFunction && isLiftedLambdaMethod(target.symbol) =>
super.transform(fun)
case fun @ Function(_, _) =>
@@ -532,9 +515,8 @@ abstract class UnCurry extends InfoTransform
}
tree1
}
- )
- assert(result.tpe != null, result.shortClass + " tpe is null:\n" + result)
- result modifyType uncurry
+
+ result.setType(uncurry(result.tpe))
}
def postTransform(tree: Tree): Tree = exitingUncurry {
@@ -545,15 +527,18 @@ abstract class UnCurry extends InfoTransform
case MethodType(_, _) => tree
case tp => tree setType MethodType(Nil, tp.resultType)
}
- if (tree.symbol.isMethod && !tree.tpe.isInstanceOf[PolyType])
- gen.mkApplyIfNeeded(removeNullary())
+ val sym = tree.symbol
+ // our info transformer may not have run yet, so duplicate flag logic instead of forcing it to run
+ val isMethodExitingUncurry = (sym hasFlag METHOD) || (sym hasFlag MODULE) && !sym.isStatic
+ if (isMethodExitingUncurry && !tree.tpe.isInstanceOf[PolyType])
+ gen.mkApplyIfNeeded(removeNullary()) // apply () if tree.tpe has zero-arg MethodType
else if (tree.isType)
TypeTree(tree.tpe) setPos tree.pos
else
tree
}
- def isThrowable(pat: Tree): Boolean = pat match {
+ @tailrec def isThrowable(pat: Tree): Boolean = pat match {
case Typed(Ident(nme.WILDCARD), tpt) =>
tpt.tpe =:= ThrowableTpe
case Bind(_, pat) =>
@@ -579,6 +564,7 @@ abstract class UnCurry extends InfoTransform
}
case dd @ DefDef(_, _, _, vparamss0, _, rhs0) =>
+ val ddSym = dd.symbol
val (newParamss, newRhs): (List[List[ValDef]], Tree) =
if (dependentParamTypeErasure isDependent dd)
dependentParamTypeErasure erase dd
@@ -590,11 +576,22 @@ abstract class UnCurry extends InfoTransform
(vparamss1, rhs0)
}
+ // A no-arg method with ConstantType result type can safely be reduced to the corresponding Literal
+ // (only pure methods are typed as ConstantType). We could also do this for methods with arguments,
+ // after ensuring the arguments are not referenced.
+ val literalRhsIfConst =
+ if (newParamss.head.isEmpty) { // We know newParamss.length == 1 from above
+ ddSym.info.resultType match {
+ case tp@ConstantType(value) => Literal(value) setType tp setPos newRhs.pos // inlining of gen.mkAttributedQualifier(tp)
+ case _ => newRhs
+ }
+ } else newRhs
+
val flatdd = copyDefDef(dd)(
vparamss = newParamss,
- rhs = nonLocalReturnKeys get dd.symbol match {
- case Some(k) => atPos(newRhs.pos)(nonLocalReturnTry(newRhs, k, dd.symbol))
- case None => newRhs
+ rhs = nonLocalReturnKeys get ddSym match {
+ case Some(k) => atPos(newRhs.pos)(nonLocalReturnTry(literalRhsIfConst, k, ddSym))
+ case None => literalRhsIfConst
}
)
// Only class members can reasonably be called from Java due to name mangling.
@@ -619,7 +616,7 @@ abstract class UnCurry extends InfoTransform
case Select(_, _) | TypeApply(_, _) =>
applyUnary()
case ret @ Return(expr) if isNonLocalReturn(ret) =>
- log("non-local return from %s to %s".format(currentOwner.enclMethod, ret.symbol))
+ log(s"non-local return from ${currentOwner.enclMethod} to ${ret.symbol}")
atPos(ret.pos)(nonLocalReturnThrow(expr, ret.symbol))
case TypeTree() =>
tree
@@ -712,7 +709,7 @@ abstract class UnCurry extends InfoTransform
//
// So what we need to do is to use the pre-uncurry type when creating `l$1`, which is `c.Tree` and is
// correct. Now, there are two additional problems:
- // 1. when varargs and byname params are involved, the uncurry transformation desugares these special
+ // 1. when varargs and byname params are involved, the uncurry transformation desugars these special
// cases to actual typerefs, eg:
// ```
// T* ~> Seq[T] (Scala-defined varargs)
@@ -744,7 +741,7 @@ abstract class UnCurry extends InfoTransform
case Packed(param, tempVal) => (param, tempVal)
}.unzip
- val rhs1 = if (tempVals.isEmpty) rhs else {
+ val rhs1 = if (rhs == EmptyTree || tempVals.isEmpty) rhs else {
localTyper.typedPos(rhs.pos) {
// Patch the method body to refer to the temp vals
val rhsSubstituted = rhs.substituteSymbols(packedParams map (_.symbol), tempVals map (_.symbol))
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala
index 40fcceb0bf..db6eac34cb 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala
@@ -8,9 +8,9 @@ package scala
package tools.nsc.transform.patmat
import scala.language.postfixOps
+
import scala.collection.mutable
import scala.reflect.internal.util.{NoPosition, Position, Statistics, HashSet}
-import scala.tools.nsc.Global
trait Logic extends Debugging {
import PatternMatchingStats._
@@ -184,8 +184,8 @@ trait Logic extends Debugging {
// push negation inside formula
def negationNormalFormNot(p: Prop): Prop = p match {
- case And(ops) => Or(ops.map(negationNormalFormNot)) // De'Morgan
- case Or(ops) => And(ops.map(negationNormalFormNot)) // De'Morgan
+ case And(ops) => Or(ops.map(negationNormalFormNot)) // De Morgan
+ case Or(ops) => And(ops.map(negationNormalFormNot)) // De Morgan
case Not(p) => negationNormalForm(p)
case True => False
case False => True
@@ -646,7 +646,7 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis {
}
- import global.{ConstantType, Constant, EmptyScope, SingletonType, Literal, Ident, refinedType, singleType, TypeBounds, NoSymbol}
+ import global.{ConstantType, SingletonType, Literal, Ident, singleType, TypeBounds, NoSymbol}
import global.definitions._
@@ -682,7 +682,7 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis {
private[TreesAndTypesDomain] def uniqueTpForTree(t: Tree): Type = {
def freshExistentialSubtype(tp: Type): Type = {
// SI-8611 tp.narrow is tempting, but unsuitable. See `testRefinedTypeSI8611` for an explanation.
- NoSymbol.freshExistential("").setInfo(TypeBounds.upper(tp)).tpe
+ NoSymbol.freshExistential("", 0).setInfo(TypeBounds.upper(tp)).tpe
}
if (!t.symbol.isStable) {
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala
index c71299b893..b6978f37df 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala
@@ -6,9 +6,6 @@
package scala.tools.nsc.transform.patmat
-import scala.annotation.tailrec
-import scala.collection.immutable.{IndexedSeq, Iterable}
-import scala.language.postfixOps
import scala.collection.mutable
import scala.reflect.internal.util.Statistics
@@ -142,7 +139,7 @@ trait TreeAndTypeAnalysis extends Debugging {
if(grouped) {
def enumerateChildren(sym: Symbol) = {
- sym.children.toList
+ sym.sealedChildren.toList
.sortBy(_.sealedSortName)
.filterNot(x => x.isSealed && x.isAbstractClass && !isPrimitiveValueClass(x))
}
@@ -177,6 +174,8 @@ trait TreeAndTypeAnalysis extends Debugging {
filterChildren(subclasses)
})
}
+ case sym if sym.isCase =>
+ List(List(tp))
case sym =>
debug.patmat("enum unsealed "+ ((tp, sym, sym.isSealed, isPrimitiveValueClass(sym))))
@@ -350,7 +349,7 @@ trait MatchApproximation extends TreeAndTypeAnalysis with ScalaLogic with MatchT
object condStrategy extends TypeTestTreeMaker.TypeTestCondStrategy {
type Result = Prop
def and(a: Result, b: Result) = And(a, b)
- def outerTest(testedBinder: Symbol, expectedTp: Type) = True // TODO OuterEqProp(testedBinder, expectedType)
+ def withOuterTest(testedBinder: Symbol, expectedTp: Type) = True // TODO OuterEqProp(testedBinder, expectedType)
def typeTest(b: Symbol, pt: Type) = { // a type test implies the tested path is non-null (null.isInstanceOf[T] is false for all T)
val p = binderToUniqueTree(b); And(uniqueNonNullProp(p), uniqueTypeProp(p, uniqueTp(pt)))
}
@@ -711,9 +710,8 @@ trait MatchAnalysis extends MatchApproximation {
val (equal, notEqual) = varAssignment.getOrElse(variable, Nil -> Nil)
- def addVarAssignment(equalTo: List[Const], notEqualTo: List[Const]) = {
- Map(variable ->(equal ++ equalTo, notEqual ++ notEqualTo))
- }
+ def addVarAssignment(equalTo: List[Const], notEqualTo: List[Const]) =
+ Map(variable ->((equal ++ equalTo, notEqual ++ notEqualTo)))
// this assignment is needed in case that
// there exists already an assign
@@ -738,7 +736,7 @@ trait MatchAnalysis extends MatchApproximation {
if (expanded.isEmpty) {
List(varAssignment)
} else {
- // we need the cartesian product here,
+ // we need the Cartesian product here,
// since we want to report all missing cases
// (i.e., combinations)
val cartesianProd = expanded.reduceLeft((xs, ys) =>
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala
index 1642613b9b..03d0a28fb1 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala
@@ -6,9 +6,9 @@
package scala.tools.nsc.transform.patmat
-import scala.tools.nsc.symtab.Flags.SYNTHETIC
import scala.language.postfixOps
-import scala.reflect.internal.util.Statistics
+
+import scala.tools.nsc.symtab.Flags.SYNTHETIC
import scala.reflect.internal.util.Position
/** Factory methods used by TreeMakers to make the actual trees.
@@ -55,7 +55,15 @@ trait MatchCodeGen extends Interface {
def flatMap(prev: Tree, b: Symbol, next: Tree): Tree
def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, next: Tree): Tree
def flatMapGuard(cond: Tree, next: Tree): Tree
- def ifThenElseZero(c: Tree, thenp: Tree): Tree = IF (c) THEN thenp ELSE zero
+ def ifThenElseZero(c: Tree, thenp: Tree): Tree = {
+ val z = zero
+ thenp match {
+ case If(c1, thenp1, elsep1) if z equalsStructure elsep1 =>
+ If(c AND c1, thenp1, elsep1) // cleaner, leaner trees
+ case _ =>
+ If(c, thenp, zero)
+ }
+ }
protected def zero: Tree
}
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala
index f827043094..dc0a457be7 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala
@@ -6,10 +6,10 @@
package scala.tools.nsc.transform.patmat
-import scala.tools.nsc.symtab.Flags.MUTABLE
import scala.language.postfixOps
+
+import scala.tools.nsc.symtab.Flags.MUTABLE
import scala.collection.mutable
-import scala.reflect.internal.util.Statistics
import scala.reflect.internal.util.Position
/** Optimize and analyze matches based on their TreeMaker-representation.
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala
index bf3bc6b26e..39971590c7 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala
@@ -7,7 +7,7 @@
package scala.tools.nsc.transform.patmat
import scala.language.postfixOps
-import scala.collection.mutable
+
import scala.reflect.internal.util.Statistics
/** Translate typed Trees that represent pattern matches into the patternmatching IR, defined by TreeMakers.
@@ -18,8 +18,7 @@ trait MatchTranslation {
import PatternMatchingStats._
import global._
import definitions._
- import global.analyzer.{ErrorUtils, formalTypes}
- import treeInfo.{ WildcardStarArg, Unapplied, isStar, unbind }
+ import treeInfo.{ Unapplied, unbind }
import CODE._
// Always map repeated params to sequences
@@ -117,7 +116,7 @@ trait MatchTranslation {
val makers = {
val paramType = extractor.aligner.wholeType
// Statically conforms to paramType
- if (this ensureConformsTo paramType) treeMaker(binder, false, pos) :: Nil
+ if (tpe <:< paramType) treeMaker(binder, false, pos) :: Nil
else {
// chain a type-testing extractor before the actual extractor call
// it tests the type, checks the outer pointer and casts to the expected type
@@ -167,16 +166,6 @@ trait MatchTranslation {
setVarInfo(binder, paramType)
true
}
- // If <:< but not =:=, no type test needed, but the tree maker relies on the binder having
- // exactly paramType (and not just some type compatible with it.) SI-6624 shows this is necessary
- // because apparently patBinder may have an unfortunate type (.decls don't have the case field
- // accessors) TODO: get to the bottom of this -- I assume it happens when type checking
- // infers a weird type for an unapply call. By going back to the parameterType for the
- // extractor call we get a saner type, so let's just do that for now.
- def ensureConformsTo(paramType: Type): Boolean = (
- (tpe =:= paramType)
- || (tpe <:< paramType) && setInfo(paramType)
- )
private def concreteType = tpe.bounds.hi
private def unbound = unbind(tree)
@@ -401,7 +390,6 @@ trait MatchTranslation {
/** Create the TreeMaker that embodies this extractor call
*
- * `binder` has been casted to `paramType` if necessary
* `binderKnownNonNull` indicates whether the cast implies `binder` cannot be null
* when `binderKnownNonNull` is `true`, `ProductExtractorTreeMaker` does not do a (redundant) null check on binder
*/
@@ -507,7 +495,7 @@ trait MatchTranslation {
* when `binderKnownNonNull` is `true`, `ProductExtractorTreeMaker` does not do a (redundant) null check on binder
*/
def treeMaker(binder: Symbol, binderKnownNonNull: Boolean, pos: Position): TreeMaker = {
- val paramAccessors = binder.constrParamAccessors
+ val paramAccessors = aligner.wholeType.typeSymbol.constrParamAccessors
val numParams = paramAccessors.length
def paramAccessorAt(subPatIndex: Int) = paramAccessors(math.min(subPatIndex, numParams - 1))
// binders corresponding to mutable fields should be stored (SI-5158, SI-6070)
@@ -536,7 +524,7 @@ trait MatchTranslation {
// reference the (i-1)th case accessor if it exists, otherwise the (i-1)th tuple component
override protected def tupleSel(binder: Symbol)(i: Int): Tree = {
- val accessors = binder.caseFieldAccessors
+ val accessors = aligner.wholeType.typeSymbol.caseFieldAccessors
if (accessors isDefinedAt (i-1)) gen.mkAttributedStableRef(binder) DOT accessors(i-1)
else codegen.tupleSel(binder)(i) // this won't type check for case classes, as they do not inherit ProductN
}
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
index 3ace61411f..794d3d442a 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
@@ -6,10 +6,10 @@
package scala.tools.nsc.transform.patmat
-import scala.tools.nsc.symtab.Flags.{SYNTHETIC, ARTIFACT}
import scala.language.postfixOps
+
+import scala.tools.nsc.symtab.Flags.{SYNTHETIC, ARTIFACT}
import scala.collection.mutable
-import scala.reflect.internal.util.Statistics
import scala.reflect.internal.util.Position
/** Translate our IR (TreeMakers) into actual Scala Trees using the factory methods in MatchCodeGen.
@@ -101,7 +101,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
case class SubstOnlyTreeMaker(prevBinder: Symbol, nextBinder: Symbol) extends TreeMaker {
val pos = NoPosition
- val localSubstitution = Substitution(prevBinder, CODE.REF(nextBinder))
+ val localSubstitution = Substitution(prevBinder, gen.mkAttributedStableRef(nextBinder))
def chainBefore(next: Tree)(casegen: Casegen): Tree = substitution(next)
override def toString = "S"+ localSubstitution
}
@@ -118,7 +118,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
val res: Tree
lazy val nextBinder = freshSym(pos, nextBinderTp)
- lazy val localSubstitution = Substitution(List(prevBinder), List(CODE.REF(nextBinder)))
+ lazy val localSubstitution = Substitution(List(prevBinder), List(gen.mkAttributedStableRef(nextBinder)))
def chainBefore(next: Tree)(casegen: Casegen): Tree =
atPos(pos)(casegen.flatMapCond(cond, res, nextBinder, substitution(next)))
@@ -316,7 +316,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
trait TypeTestCondStrategy {
type Result
- def outerTest(testedBinder: Symbol, expectedTp: Type): Result
+ def withOuterTest(orig: Result)(testedBinder: Symbol, expectedTp: Type): Result = orig
// TODO: can probably always widen
def typeTest(testedBinder: Symbol, expectedTp: Type): Result
def nonNullTest(testedBinder: Symbol): Result
@@ -336,15 +336,34 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
def equalsTest(pat: Tree, testedBinder: Symbol) = codegen._equals(pat, testedBinder)
def eqTest(pat: Tree, testedBinder: Symbol) = REF(testedBinder) OBJ_EQ pat
- def outerTest(testedBinder: Symbol, expectedTp: Type): Tree = {
+ override def withOuterTest(orig: Tree)(testedBinder: Symbol, expectedTp: Type): Tree = {
val expectedPrefix = expectedTp.prefix
- if (expectedPrefix eq NoType) mkTRUE // fallback for SI-6183
- else {
- // ExplicitOuter replaces `Select(q, outerSym) OBJ_EQ expectedPrefix` by `Select(q, outerAccessor(outerSym.owner)) OBJ_EQ expectedPrefix`
- // if there's an outer accessor, otherwise the condition becomes `true` -- TODO: can we improve needsOuterTest so there's always an outerAccessor?
- val outerFor = expectedTp.typeSymbol
- val outerMarker = outerFor.newMethod(vpmName.outer, newFlags = SYNTHETIC | ARTIFACT) setInfo expectedPrefix
- Select(codegen._asInstanceOf(testedBinder, expectedTp), outerMarker) OBJ_EQ gen.mkAttributedQualifier(expectedPrefix)
+ val testedPrefix = testedBinder.info.prefix
+
+ // Check if a type is defined in a static location. Unlike `tp.isStatic` before `flatten`,
+ // this also includes methods and (possibly nested) objects inside of methods.
+ def definedInStaticLocation(tp: Type): Boolean = {
+ def isStatic(tp: Type): Boolean =
+ if (tp == NoType || tp.typeSymbol.isPackageClass || tp == NoPrefix) true
+ else if (tp.typeSymbol.isModuleClass) isStatic(tp.prefix)
+ else false
+ tp.typeSymbol.owner == tp.prefix.typeSymbol && isStatic(tp.prefix)
+ }
+
+ if ((expectedPrefix eq NoPrefix)
+ || expectedTp.typeSymbol.isJava
+ || definedInStaticLocation(expectedTp)
+ || testedPrefix =:= expectedPrefix) orig
+ else gen.mkAttributedQualifierIfPossible(expectedPrefix) match {
+ case None => orig
+ case Some(expectedOuterRef) =>
+ // ExplicitOuter replaces `Select(q, outerSym) OBJ_EQ expectedPrefix`
+ // by `Select(q, outerAccessor(outerSym.owner)) OBJ_EQ expectedPrefix`
+ // if there's an outer accessor, otherwise the condition becomes `true`
+ // TODO: centralize logic whether there's an outer accessor and use here?
+ val synthOuterGetter = expectedTp.typeSymbol.newMethod(vpmName.outer, newFlags = SYNTHETIC | ARTIFACT) setInfo expectedPrefix
+ val outerTest = (Select(codegen._asInstanceOf(testedBinder, expectedTp), synthOuterGetter)) OBJ_EQ expectedOuterRef
+ and(orig, outerTest)
}
}
}
@@ -354,7 +373,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
def typeTest(testedBinder: Symbol, expectedTp: Type): Result = true
- def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false
def nonNullTest(testedBinder: Symbol): Result = false
def equalsTest(pat: Tree, testedBinder: Symbol): Result = false
def eqTest(pat: Tree, testedBinder: Symbol): Result = false
@@ -366,7 +384,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
type Result = Boolean
def typeTest(testedBinder: Symbol, expectedTp: Type): Result = testedBinder eq binder
- def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false
def nonNullTest(testedBinder: Symbol): Result = testedBinder eq binder
def equalsTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null
def eqTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null
@@ -403,12 +420,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
import TypeTestTreeMaker._
debug.patmat("TTTM"+((prevBinder, extractorArgTypeTest, testedBinder, expectedTp, nextBinderTp)))
- lazy val outerTestNeeded = (
- (expectedTp.prefix ne NoPrefix)
- && !expectedTp.prefix.typeSymbol.isPackageClass
- && needsOuterTest(expectedTp, testedBinder.info, matchOwner)
- )
-
// the logic to generate the run-time test that follows from the fact that
// a `prevBinder` is expected to have type `expectedTp`
// the actual tree-generation logic is factored out, since the analyses generate Cond(ition)s rather than Trees
@@ -427,12 +438,11 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
def isExpectedPrimitiveType = isAsExpected && isPrimitiveValueType(expectedTp)
def isExpectedReferenceType = isAsExpected && (expectedTp <:< AnyRefTpe)
def mkNullTest = nonNullTest(testedBinder)
- def mkOuterTest = outerTest(testedBinder, expectedTp)
def mkTypeTest = typeTest(testedBinder, expectedWide)
def mkEqualsTest(lhs: Tree): cs.Result = equalsTest(lhs, testedBinder)
def mkEqTest(lhs: Tree): cs.Result = eqTest(lhs, testedBinder)
- def addOuterTest(res: cs.Result): cs.Result = if (outerTestNeeded) and(res, mkOuterTest) else res
+ def addOuterTest(res: cs.Result): cs.Result = withOuterTest(res)(testedBinder, expectedTp)
// If we conform to expected primitive type:
// it cannot be null and cannot have an outer pointer. No further checking.
@@ -483,7 +493,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
// NOTE: generate `patTree == patBinder`, since the extractor must be in control of the equals method (also, patBinder may be null)
// equals need not be well-behaved, so don't intersect with pattern's (stabilized) type (unlike MaybeBoundTyped's accumType, where it's required)
val cond = codegen._equals(patTree, prevBinder)
- val res = CODE.REF(prevBinder)
+ val res = gen.mkAttributedStableRef(prevBinder)
override def toString = "ET"+((prevBinder.name, patTree))
}
@@ -554,11 +564,11 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
else scrut match {
case Typed(tree, tpt) =>
val suppressExhaustive = tpt.tpe hasAnnotation UncheckedClass
- val supressUnreachable = tree match {
+ val suppressUnreachable = tree match {
case Ident(name) if name startsWith nme.CHECK_IF_REFUTABLE_STRING => true // SI-7183 don't warn for withFilter's that turn out to be irrefutable.
case _ => false
}
- val suppression = Suppression(suppressExhaustive, supressUnreachable)
+ val suppression = Suppression(suppressExhaustive, suppressUnreachable)
val hasSwitchAnnotation = treeInfo.isSwitchAnnotation(tpt.tpe)
// matches with two or fewer cases need not apply for switchiness (if-then-else will do)
// `case 1 | 2` is considered as two cases.
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala
index 8beb1837ad..3f27d18e64 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala
@@ -6,10 +6,6 @@
package scala.tools.nsc.transform.patmat
-import scala.language.postfixOps
-import scala.collection.mutable
-import scala.reflect.internal.util.Statistics
-
trait MatchWarnings {
self: PatternMatching =>
@@ -83,4 +79,4 @@ trait MatchWarnings {
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala
index b2f2516b5b..05f2d60be1 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala
@@ -222,7 +222,7 @@ trait Interface extends ast.TreeDSL {
object substIdentsForTrees extends Transformer {
private def typedIfOrigTyped(to: Tree, origTp: Type): Tree =
if (origTp == null || origTp == NoType) to
- // important: only type when actually substing and when original tree was typed
+ // important: only type when actually substituting and when original tree was typed
// (don't need to use origTp as the expected type, though, and can't always do this anyway due to unknown type params stemming from polymorphic extractors)
else typer.typed(to)
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala b/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala
index d4f44303bb..2c1fb064cc 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala
@@ -148,7 +148,7 @@ trait ScalacPatternExpanders {
val tupled = extractor.asSinglePattern
if (effectivePatternArity(args) == 1 && isTupleType(extractor.typeOfSinglePattern)) {
val sym = sel.symbol.owner
- currentRun.reporting.deprecationWarning(sel.pos, sym, s"${sym} expects $productArity patterns$acceptMessage but crushing into $productArity-tuple to fit single pattern (SI-6675)")
+ currentRun.reporting.deprecationWarning(sel.pos, sym, s"${sym} expects $productArity patterns$acceptMessage but crushing into $productArity-tuple to fit single pattern (SI-6675)", "2.11.0")
}
tupled
} else extractor
diff --git a/src/compiler/scala/tools/nsc/typechecker/Adaptations.scala b/src/compiler/scala/tools/nsc/typechecker/Adaptations.scala
index 2f4d228347..b1901c04bb 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Adaptations.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Adaptations.scala
@@ -74,13 +74,15 @@ trait Adaptations {
if (settings.future)
context.error(t.pos, adaptWarningMessage("Adaptation of argument list by inserting () has been removed.", showAdaptation = false))
else {
- val msg = "Adaptation of argument list by inserting () has been deprecated: " + (
+ val msg = "Adaptation of argument list by inserting () is deprecated: " + (
if (isLeakyTarget) "leaky (Object-receiving) target makes this especially dangerous."
else "this is unlikely to be what you want.")
- context.deprecationWarning(t.pos, t.symbol, adaptWarningMessage(msg))
+ context.deprecationWarning(t.pos, t.symbol, adaptWarningMessage(msg), "2.11.0")
}
} else if (settings.warnAdaptedArgs)
- context.warning(t.pos, adaptWarningMessage(s"Adapting argument list by creating a ${args.size}-tuple: this may not be what you want."))
+ context.warning(t.pos, adaptWarningMessage(
+ s"Adapting argument list by creating a ${args.size}-tuple: this may not be what you want.")
+ )
// return `true` if the adaptation should be kept
!(settings.noAdaptedArgs || (args.isEmpty && settings.future))
diff --git a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala
index 323fe1c171..b8ef439e03 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala
@@ -104,7 +104,7 @@ trait Analyzer extends AnyRef
for (workItem <- unit.toCheck) workItem()
if (settings.warnUnusedImport)
warnUnusedImports(unit)
- if (settings.warnUnused)
+ if (settings.warnUnused.isSetByUser)
typer checkUnused unit
}
finally {
diff --git a/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala b/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala
index 9898cfd785..e9cce95096 100644
--- a/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala
@@ -38,7 +38,7 @@ trait AnalyzerPlugins { self: Analyzer =>
* Let analyzer plugins modify the type that has been computed for a tree.
*
* @param tpe The type inferred by the type checker, initially (for first plugin) `tree.tpe`
- * @param typer The yper that type checked `tree`
+ * @param typer The typer that type checked `tree`
* @param tree The type-checked tree
* @param mode Mode that was used for typing `tree`
* @param pt Expected type that was used for typing `tree`
diff --git a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala
index 309b80f9ba..215ee1c42b 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala
@@ -212,8 +212,8 @@ trait Checkable {
)
/** Are all children of these symbols pairwise irreconcilable? */
def allChildrenAreIrreconcilable(sym1: Symbol, sym2: Symbol) = (
- sym1.children.toList forall (c1 =>
- sym2.children.toList forall (c2 =>
+ sym1.sealedChildren.toList forall (c1 =>
+ sym2.sealedChildren.toList forall (c2 =>
areIrreconcilableAsParents(c1, c2)
)
)
@@ -241,9 +241,7 @@ trait Checkable {
private def isSealedOrFinal(sym: Symbol) = sym.isSealed || sym.isFinal
private def isEffectivelyFinal(sym: Symbol): Boolean = (
// initialization important
- sym.initialize.isEffectivelyFinalOrNotOverridden || (
- settings.future && isTupleSymbol(sym) // SI-7294 step into the future and treat TupleN as final.
- )
+ sym.initialize.isEffectivelyFinalOrNotOverridden
)
def isNeverSubClass(sym1: Symbol, sym2: Symbol) = areIrreconcilableAsParents(sym1, sym2)
diff --git a/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala b/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala
index 56ed0ee16c..8b62409076 100644
--- a/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala
@@ -40,9 +40,10 @@ abstract class ConstantFolder {
if ((x ne null) && x.tag != UnitTag) tree setType ConstantType(x)
else tree
} catch {
- case _: ArithmeticException => tree // the code will crash at runtime,
- // but that is better than the
- // compiler itself crashing
+ case e: ArithmeticException =>
+ if (settings.warnConstant)
+ warning(tree.pos, s"Evaluation of a constant expression results in an arithmetic error: ${e.getMessage}")
+ tree
}
private def foldUnop(op: Name, x: Constant): Constant = (op, x.tag) match {
@@ -75,7 +76,7 @@ abstract class ConstantFolder {
case nme.AND => Constant(x.booleanValue & y.booleanValue)
case nme.EQ => Constant(x.booleanValue == y.booleanValue)
case nme.NE => Constant(x.booleanValue != y.booleanValue)
- case _ => null
+ case _ => null
}
private def foldSubrangeOp(op: Name, x: Constant, y: Constant): Constant = op match {
case nme.OR => Constant(x.intValue | y.intValue)
@@ -95,14 +96,20 @@ abstract class ConstantFolder {
case nme.MUL => Constant(x.intValue * y.intValue)
case nme.DIV => Constant(x.intValue / y.intValue)
case nme.MOD => Constant(x.intValue % y.intValue)
- case _ => null
+ case _ => null
}
private def foldLongOp(op: Name, x: Constant, y: Constant): Constant = op match {
case nme.OR => Constant(x.longValue | y.longValue)
case nme.XOR => Constant(x.longValue ^ y.longValue)
case nme.AND => Constant(x.longValue & y.longValue)
- case nme.LSL => Constant(x.longValue << y.longValue)
+ case nme.LSL if x.tag <= IntTag
+ => Constant(x.intValue << y.longValue)
+ case nme.LSL => Constant(x.longValue << y.longValue)
+ case nme.LSR if x.tag <= IntTag
+ => Constant(x.intValue >>> y.longValue)
case nme.LSR => Constant(x.longValue >>> y.longValue)
+ case nme.ASR if x.tag <= IntTag
+ => Constant(x.intValue >> y.longValue)
case nme.ASR => Constant(x.longValue >> y.longValue)
case nme.EQ => Constant(x.longValue == y.longValue)
case nme.NE => Constant(x.longValue != y.longValue)
@@ -115,7 +122,7 @@ abstract class ConstantFolder {
case nme.MUL => Constant(x.longValue * y.longValue)
case nme.DIV => Constant(x.longValue / y.longValue)
case nme.MOD => Constant(x.longValue % y.longValue)
- case _ => null
+ case _ => null
}
private def foldFloatOp(op: Name, x: Constant, y: Constant): Constant = op match {
case nme.EQ => Constant(x.floatValue == y.floatValue)
@@ -129,7 +136,7 @@ abstract class ConstantFolder {
case nme.MUL => Constant(x.floatValue * y.floatValue)
case nme.DIV => Constant(x.floatValue / y.floatValue)
case nme.MOD => Constant(x.floatValue % y.floatValue)
- case _ => null
+ case _ => null
}
private def foldDoubleOp(op: Name, x: Constant, y: Constant): Constant = op match {
case nme.EQ => Constant(x.doubleValue == y.doubleValue)
@@ -143,7 +150,7 @@ abstract class ConstantFolder {
case nme.MUL => Constant(x.doubleValue * y.doubleValue)
case nme.DIV => Constant(x.doubleValue / y.doubleValue)
case nme.MOD => Constant(x.doubleValue % y.doubleValue)
- case _ => null
+ case _ => null
}
private def foldBinop(op: Name, x: Constant, y: Constant): Constant = {
@@ -152,7 +159,7 @@ abstract class ConstantFolder {
else if (x.isNumeric && y.isNumeric) math.max(x.tag, y.tag)
else NoTag
- try optag match {
+ optag match {
case BooleanTag => foldBooleanOp(op, x, y)
case ByteTag | ShortTag | CharTag | IntTag => foldSubrangeOp(op, x, y)
case LongTag => foldLongOp(op, x, y)
@@ -161,8 +168,5 @@ abstract class ConstantFolder {
case StringTag if op == nme.ADD => Constant(x.stringValue + y.stringValue)
case _ => null
}
- catch {
- case ex: ArithmeticException => null
- }
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
index 80cccaf2ae..3bbc9f3a62 100644
--- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
@@ -7,13 +7,13 @@ package scala.tools.nsc
package typechecker
import scala.reflect.internal.util.StringOps.{ countElementsAsString, countAsString }
-import symtab.Flags.IS_ERROR
import scala.compat.Platform.EOL
import scala.reflect.runtime.ReflectionUtils
import scala.reflect.macros.runtime.AbortMacroException
import scala.util.control.NonFatal
import scala.tools.nsc.util.stackTraceString
import scala.reflect.io.NoAbstractFile
+import scala.reflect.internal.util.NoSourceFile
trait ContextErrors {
self: Analyzer =>
@@ -199,7 +199,7 @@ trait ContextErrors {
val foundType: Type = req.dealiasWiden match {
case RefinedType(parents, decls) if !decls.isEmpty && found.typeSymbol.isAnonOrRefinementClass =>
val retyped = typed (tree.duplicate.clearType())
- val foundDecls = retyped.tpe.decls filter (sym => !sym.isConstructor && !sym.isSynthetic)
+ val foundDecls = retyped.tpe.decls filter (sym => !sym.isConstructor && !sym.isSynthetic && !sym.isErroneous)
if (foundDecls.isEmpty || (found.typeSymbol eq NoSymbol)) found
else {
// The members arrive marked private, presumably because there was no
@@ -213,7 +213,8 @@ trait ContextErrors {
case _ =>
found
}
- assert(!foundType.isErroneous && !req.isErroneous, (foundType, req))
+ assert(!foundType.isErroneous, s"AdaptTypeError - foundType is Erroneous: $foundType")
+ assert(!req.isErroneous, s"AdaptTypeError - req is Erroneous: $req")
issueNormalTypeError(callee, withAddendum(callee.pos)(typeErrorMsg(foundType, req)))
infer.explainTypes(foundType, req)
@@ -375,7 +376,7 @@ trait ContextErrors {
}
issueNormalTypeError(sel, errMsg)
// the error has to be set for the copied tree, otherwise
- // the error remains persistent acros multiple compilations
+ // the error remains persistent across multiple compilations
// and causes problems
//setError(sel)
}
@@ -470,6 +471,11 @@ trait ContextErrors {
setError(tree)
}
+ def ConstructorRecursesError(tree: Tree) = {
+ issueNormalTypeError(tree, "constructor invokes itself")
+ setError(tree)
+ }
+
def OnlyDeclarationsError(tree: Tree) = {
issueNormalTypeError(tree, "only declarations allowed here")
setError(tree)
@@ -534,8 +540,43 @@ trait ContextErrors {
def NamedAndDefaultArgumentsNotSupportedForMacros(tree: Tree, fun: Tree) =
NormalTypeError(tree, "macro applications do not support named and/or default arguments")
- def TooManyArgsNamesDefaultsError(tree: Tree, fun: Tree) =
- NormalTypeError(tree, "too many arguments for "+treeSymTypeMsg(fun))
+ def TooManyArgsNamesDefaultsError(tree: Tree, fun: Tree, formals: List[Type], args: List[Tree], namelessArgs: List[Tree], argPos: Array[Int]) = {
+ val expected = formals.size
+ val supplied = args.size
+ // pick a caret. For f(k=1,i=2,j=3), argPos[0,-1,1] b/c `k=1` taken as arg0
+ val excessive = {
+ val i = argPos.indexWhere(_ >= expected)
+ if (i < 0) tree else args(i min (supplied - 1))
+ }
+ val msg = {
+ val badappl = {
+ val excess = supplied - expected
+ val target = treeSymTypeMsg(fun)
+
+ if (expected == 0) s"no arguments allowed for nullary $target"
+ else if (excess < 3 && expected <= 5) s"too many arguments ($supplied) for $target"
+ else if (expected > 10) s"$supplied arguments but expected $expected for $target"
+ else {
+ val more =
+ if (excess == 1) "one more argument"
+ else if (excess > 0) s"$excess more arguments"
+ else "too many arguments"
+ s"$more than can be applied to $target"
+ }
+ }
+ val unknowns = (namelessArgs zip args) collect {
+ case (_: Assign, AssignOrNamedArg(Ident(name), _)) => name
+ }
+ val suppl =
+ unknowns.size match {
+ case 0 => ""
+ case 1 => s"\nNote that '${unknowns.head}' is not a parameter name of the invoked method."
+ case _ => unknowns.mkString("\nNote that '", "', '", "' are not parameter names of the invoked method.")
+ }
+ s"${badappl}${suppl}"
+ }
+ NormalTypeError(excessive, msg)
+ }
// can it still happen? see test case neg/overloaded-unapply.scala
def OverloadedUnapplyError(tree: Tree) =
@@ -547,7 +588,7 @@ trait ContextErrors {
def MultipleVarargError(tree: Tree) =
NormalTypeError(tree, "when using named arguments, the vararg parameter has to be specified exactly once")
- def ModuleUsingCompanionClassDefaultArgsErrror(tree: Tree) =
+ def ModuleUsingCompanionClassDefaultArgsError(tree: Tree) =
NormalTypeError(tree, "module extending its companion class cannot use default constructor arguments")
def NotEnoughArgsError(tree: Tree, fun: Tree, missing: List[Symbol]) = {
@@ -624,7 +665,7 @@ trait ContextErrors {
//adapt
def MissingArgsForMethodTpeError(tree: Tree, meth: Symbol) = {
- val f = meth.name
+ val f = meth.name.decoded
val paf = s"$f(${ meth.asMethod.paramLists map (_ map (_ => "_") mkString ",") mkString ")(" })"
val advice = s"""
|Unapplied methods are only converted to functions when a function type is expected.
@@ -714,22 +755,18 @@ trait ContextErrors {
}
def DefDefinedTwiceError(sym0: Symbol, sym1: Symbol) = {
+ val addPref = s";\n the conflicting $sym1 was defined"
+ val bugNote = "\n Note: this may be due to a bug in the compiler involving wildcards in package objects"
+
// Most of this hard work is associated with SI-4893.
val isBug = sym0.isAbstractType && sym1.isAbstractType && (sym0.name startsWith "_$")
- val addendums = List(
- if (sym0.associatedFile eq sym1.associatedFile)
- Some("conflicting symbols both originated in file '%s'".format(sym0.associatedFile.canonicalPath))
- else if ((sym0.associatedFile ne NoAbstractFile) && (sym1.associatedFile ne NoAbstractFile))
- Some("conflicting symbols originated in files '%s' and '%s'".format(sym0.associatedFile.canonicalPath, sym1.associatedFile.canonicalPath))
- else None ,
- if (isBug) Some("Note: this may be due to a bug in the compiler involving wildcards in package objects") else None
- )
- val addendum = addendums.flatten match {
- case Nil => ""
- case xs => xs.mkString("\n ", "\n ", "")
- }
+ val addendum = (
+ if (sym0.pos.source eq sym1.pos.source) s"$addPref at line ${sym1.pos.line}:${sym1.pos.column}"
+ else if (sym1.pos.source ne NoSourceFile) s"$addPref at line ${sym1.pos.line}:${sym1.pos.column} of '${sym1.pos.source.path}'"
+ else if (sym1.associatedFile ne NoAbstractFile) s"$addPref in '${sym1.associatedFile.canonicalPath}'"
+ else "") + (if (isBug) bugNote else "")
- issueSymbolTypeError(sym0, sym1+" is defined twice" + addendum)
+ issueSymbolTypeError(sym0, s"$sym0 is defined twice$addendum")
}
// cyclic errors
@@ -1102,7 +1139,7 @@ trait ContextErrors {
def GetterDefinedTwiceError(getter: Symbol) =
issueSymbolTypeError(getter, getter+" is defined twice")
- def ValOrValWithSetterSuffixError(tree: Tree) =
+ def ValOrVarWithSetterSuffixError(tree: Tree) =
issueNormalTypeError(tree, "Names of vals or vars may not end in `_='")
def PrivateThisCaseClassParameterError(tree: Tree) =
@@ -1174,7 +1211,7 @@ trait ContextErrors {
"pass-by-name arguments not allowed for case class parameters"
case AbstractVar =>
- "only classes can have declared but undefined members" + abstractVarMessage(sym)
+ "only traits and abstract classes can have declared but undefined members" + abstractVarMessage(sym)
}
issueSymbolTypeError(sym, msg)
@@ -1212,7 +1249,8 @@ trait ContextErrors {
import definitions._
- def AmbiguousImplicitError(info1: ImplicitInfo, info2: ImplicitInfo,
+ def AmbiguousImplicitError(info1: ImplicitInfo, tree1: Tree,
+ info2: ImplicitInfo, tree2: Tree,
pre1: String, pre2: String, trailer: String)
(isView: Boolean, pt: Type, tree: Tree)(implicit context0: Context) = {
if (!info1.tpe.isErroneous && !info2.tpe.isErroneous) {
@@ -1248,10 +1286,21 @@ trait ContextErrors {
if (explanation == "") "" else "\n" + explanation
)
}
+
+ def treeTypeArgs(annotatedTree: Tree): List[String] = annotatedTree match {
+ case TypeApply(_, args) => args.map(_.toString)
+ case Block(_, Function(_, treeInfo.Applied(_, targs, _))) => targs.map(_.toString) // eta expansion, see neg/t9527b.scala
+ case _ => Nil
+ }
+
context.issueAmbiguousError(AmbiguousImplicitTypeError(tree,
- if (isView) viewMsg
- else s"ambiguous implicit values:\n${coreMsg}match expected type $pt")
- )
+ (info1.sym, info2.sym) match {
+ case (ImplicitAmbiguousMsg(msg), _) => msg.format(treeTypeArgs(tree1))
+ case (_, ImplicitAmbiguousMsg(msg)) => msg.format(treeTypeArgs(tree2))
+ case (_, _) if isView => viewMsg
+ case (_, _) => s"ambiguous implicit values:\n${coreMsg}match expected type $pt"
+ }
+ ))
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
index 5ec16e84bb..c80bdb180b 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
@@ -24,7 +24,8 @@ trait Contexts { self: Analyzer =>
object NoContext
extends Context(EmptyTree, NoSymbol, EmptyScope, NoCompilationUnit,
- null) { // We can't pass the uninitialized `this`. Instead, we treat null specially in `Context#outer`
+ // We can't pass the uninitialized `this`. Instead, we treat null specially in `Context#outer`
+ null) {
enclClass = this
enclMethod = this
@@ -48,32 +49,32 @@ trait Contexts { self: Analyzer =>
def ambiguousDefnAndImport(owner: Symbol, imp: ImportInfo) =
LookupAmbiguous(s"it is both defined in $owner and imported subsequently by \n$imp")
- private lazy val startContext = {
- NoContext.make(
+ private lazy val startContext = NoContext.make(
Template(List(), noSelfType, List()) setSymbol global.NoSymbol setType global.NoType,
rootMirror.RootClass,
- rootMirror.RootClass.info.decls)
- }
+ rootMirror.RootClass.info.decls
+ )
private lazy val allUsedSelectors =
mutable.Map[ImportInfo, Set[ImportSelector]]() withDefaultValue Set()
private lazy val allImportInfos =
mutable.Map[CompilationUnit, List[ImportInfo]]() withDefaultValue Nil
- def warnUnusedImports(unit: CompilationUnit) = {
+ def warnUnusedImports(unit: CompilationUnit) = if (!unit.isJava) {
for (imps <- allImportInfos.remove(unit)) {
- for (imp <- imps.reverse.distinct) {
+ for (imp <- imps.distinct.reverse) {
val used = allUsedSelectors(imp)
- def isMask(s: ImportSelector) = s.name != nme.WILDCARD && s.rename == nme.WILDCARD
-
- imp.tree.selectors filterNot (s => isMask(s) || used(s)) foreach { sel =>
- reporter.warning(imp posOf sel, "Unused import")
- }
+ for (sel <- imp.tree.selectors if !isMaskImport(sel) && !used(sel))
+ reporter.warning(imp.posOf(sel), "Unused import")
}
allUsedSelectors --= imps
}
}
+ def isMaskImport(s: ImportSelector): Boolean = s.name != nme.WILDCARD && s.rename == nme.WILDCARD
+ def isIndividualImport(s: ImportSelector): Boolean = s.name != nme.WILDCARD && s.rename != nme.WILDCARD
+ def isWildcardImport(s: ImportSelector): Boolean = s.name == nme.WILDCARD
+
var lastAccessCheckDetails: String = ""
/** List of symbols to import from in a root context. Typically that
@@ -387,8 +388,10 @@ trait Contexts { self: Analyzer =>
@inline final def withImplicitsEnabled[T](op: => T): T = withMode(enabled = ImplicitsEnabled)(op)
@inline final def withImplicitsDisabled[T](op: => T): T = withMode(disabled = ImplicitsEnabled | EnrichmentEnabled)(op)
@inline final def withImplicitsDisabledAllowEnrichment[T](op: => T): T = withMode(enabled = EnrichmentEnabled, disabled = ImplicitsEnabled)(op)
+ @inline final def withImplicits[T](enabled: Boolean)(op: => T): T = if (enabled) withImplicitsEnabled(op) else withImplicitsDisabled(op)
@inline final def withMacrosEnabled[T](op: => T): T = withMode(enabled = MacrosEnabled)(op)
@inline final def withMacrosDisabled[T](op: => T): T = withMode(disabled = MacrosEnabled)(op)
+ @inline final def withMacros[T](enabled: Boolean)(op: => T): T = if (enabled) withMacrosEnabled(op) else withMacrosDisabled(op)
@inline final def withinStarPatterns[T](op: => T): T = withMode(enabled = StarPatterns)(op)
@inline final def withinSuperInit[T](op: => T): T = withMode(enabled = SuperInit)(op)
@inline final def withinSecondTry[T](op: => T): T = withMode(enabled = SecondTry)(op)
@@ -434,7 +437,7 @@ trait Contexts { self: Analyzer =>
* Construct a child context. The parent and child will share the report buffer.
* Compare with `makeSilent`, in which the child has a fresh report buffer.
*
- * If `tree` is an `Import`, that import will be avaiable at the head of
+ * If `tree` is an `Import`, that import will be available at the head of
* `Context#imports`.
*/
def make(tree: Tree = tree, owner: Symbol = owner,
@@ -584,8 +587,8 @@ trait Contexts { self: Analyzer =>
}
- def deprecationWarning(pos: Position, sym: Symbol, msg: String): Unit =
- currentRun.reporting.deprecationWarning(fixPosition(pos), sym, msg)
+ def deprecationWarning(pos: Position, sym: Symbol, msg: String, since: String): Unit =
+ currentRun.reporting.deprecationWarning(fixPosition(pos), sym, msg, since)
def deprecationWarning(pos: Position, sym: Symbol): Unit =
currentRun.reporting.deprecationWarning(fixPosition(pos), sym) // TODO: allow this to escalate to an error, and implicit search will ignore deprecated implicits
@@ -723,7 +726,6 @@ trait Contexts { self: Analyzer =>
( (ab.isTerm || ab == rootMirror.RootClass)
|| (accessWithin(ab) || accessWithinLinked(ab)) &&
( !sym.isLocalToThis
- || sym.owner.isImplClass // allow private local accesses to impl classes
|| sym.isProtected && isSubThisType(pre, sym.owner)
|| pre =:= sym.owner.thisType
)
@@ -806,11 +808,13 @@ trait Contexts { self: Analyzer =>
(e ne null) && (e.owner == scope) && (!settings.isScala212 || e.sym.exists)
})
- private def withQualifyingImplicitAlternatives(imp: ImportInfo, name: Name, pre: Type)(f: Symbol => Unit) =
- for {
- sym <- importedAccessibleSymbol(imp, name, requireExplicit = false, record = false).alternatives
- if isQualifyingImplicit(name, sym, pre, imported = true)
- } f(sym)
+ /** Do something with the symbols with name `name` imported via the import in `imp`,
+ * if any such symbol is accessible from this context and is a qualifying implicit.
+ */
+ private def withQualifyingImplicitAlternatives(imp: ImportInfo, name: Name, pre: Type)(f: Symbol => Unit) = for {
+ sym <- importedAccessibleSymbol(imp, name, requireExplicit = false, record = false).alternatives
+ if isQualifyingImplicit(name, sym, pre, imported = true)
+ } f(sym)
private def collectImplicits(syms: Scope, pre: Type, imported: Boolean = false): List[ImplicitInfo] =
for (sym <- syms.toList if isQualifyingImplicit(sym.name, sym, pre, imported)) yield
@@ -819,12 +823,7 @@ trait Contexts { self: Analyzer =>
private def collectImplicitImports(imp: ImportInfo): List[ImplicitInfo] = {
val qual = imp.qual
- val pre =
- if (qual.tpe.typeSymbol.isPackageClass)
- // SI-6225 important if the imported symbol is inherited by the package object.
- singleType(qual.tpe, qual.tpe member nme.PACKAGE)
- else
- qual.tpe
+ val pre = qual.tpe
def collect(sels: List[ImportSelector]): List[ImplicitInfo] = sels match {
case List() =>
List()
@@ -900,7 +899,8 @@ trait Contexts { self: Analyzer =>
Some(collectImplicitImports(imports.head))
} else if (owner.isPackageClass) {
// the corresponding package object may contain implicit members.
- Some(collectImplicits(owner.tpe.implicitMembers, owner.tpe))
+ val pre = owner.packageObject.typeOfThis
+ Some(collectImplicits(pre.implicitMembers, pre))
} else Some(Nil)
}
@@ -967,51 +967,19 @@ trait Contexts { self: Analyzer =>
private def importedAccessibleSymbol(imp: ImportInfo, name: Name, requireExplicit: Boolean, record: Boolean): Symbol =
imp.importedSymbol(name, requireExplicit, record) filter (s => isAccessible(s, imp.qual.tpe, superAccess = false))
- /** Is `sym` defined in package object of package `pkg`?
- * Since sym may be defined in some parent of the package object,
- * we cannot inspect its owner only; we have to go through the
- * info of the package object. However to avoid cycles we'll check
- * what other ways we can before pushing that way.
+ private def requiresQualifier(s: Symbol): Boolean = (
+ s.owner.isClass
+ && !s.owner.isPackageClass
+ && !s.isTypeParameterOrSkolem
+ && !s.isExistentiallyBound
+ )
+
+ /** Must `sym` defined in package object of package `pkg`, if
+ * it selected from a prefix with `pkg` as its type symbol?
*/
def isInPackageObject(sym: Symbol, pkg: Symbol): Boolean = {
- def uninitialized(what: String) = {
- log(s"Cannot look for $sym in package object of $pkg; $what is not initialized.")
- false
- }
- def pkgClass = if (pkg.isTerm) pkg.moduleClass else pkg
- def matchesInfo = (
- // need to be careful here to not get a cyclic reference during bootstrap
- if (pkg.isInitialized) {
- val module = pkg.info member nme.PACKAGEkw
- if (module.isInitialized)
- module.info.member(sym.name).alternatives contains sym
- else
- uninitialized("" + module)
- }
- else uninitialized("" + pkg)
- )
- def inPackageObject(sym: Symbol) = (
- // To be in the package object, one of these must be true:
- // 1) sym.owner is a package object class, and sym.owner.owner is the package class for `pkg`
- // 2) sym.owner is inherited by the correct package object class
- // We try to establish 1) by inspecting the owners directly, and then we try
- // to rule out 2), and only if both those fail do we resort to looking in the info.
- !sym.hasPackageFlag && sym.owner.exists && (
- if (sym.owner.isPackageObjectClass)
- sym.owner.owner == pkgClass
- else
- !sym.owner.isPackageClass && matchesInfo
- )
- )
-
- // An overloaded symbol might not have the expected owner!
- // The alternatives must be inspected directly.
- pkgClass.isPackageClass && (
- if (sym.isOverloaded)
- sym.alternatives forall (isInPackageObject(_, pkg))
- else
- inPackageObject(sym)
- )
+ if (sym.isOverloaded) sym.alternatives.exists(alt => isInPackageObject(alt, pkg))
+ else pkg.hasPackageFlag && sym.owner != pkg && requiresQualifier(sym)
}
def isNameInScope(name: Name) = lookupSymbol(name, _ => true).isSuccess
@@ -1048,12 +1016,16 @@ trait Contexts { self: Analyzer =>
|| unit.exists && s.sourceFile != unit.source.file
)
)
- def requiresQualifier(s: Symbol) = (
- s.owner.isClass
- && !s.owner.isPackageClass
- && !s.isTypeParameterOrSkolem
- )
- def lookupInPrefix(name: Name) = pre member name filter qualifies
+ def lookupInPrefix(name: Name) = {
+ val sym = pre.member(name).filter(qualifies)
+ def isNonPackageNoModuleClass(sym: Symbol) =
+ sym.isClass && !sym.isModuleClass && !sym.isPackageClass
+ if (!sym.exists && unit.isJava && isNonPackageNoModuleClass(pre.typeSymbol)) {
+ // TODO factor out duplication with Typer::inCompanionForJavaStatic
+ val pre1 = companionSymbolOf(pre.typeSymbol, this).typeOfThis
+ pre1.member(name).filter(qualifies).andAlso(_ => pre = pre1)
+ } else sym
+ }
def accessibleInPrefix(s: Symbol) = isAccessible(s, pre, superAccess = false)
def searchPrefix = {
@@ -1220,6 +1192,34 @@ trait Contexts { self: Analyzer =>
}
res
}
+
+ final def lookupCompanionInIncompleteOwner(original: Symbol): Symbol = {
+ /* Search scopes in current and enclosing contexts for the definition of `symbol` */
+ def lookupScopeEntry(symbol: Symbol): ScopeEntry = {
+ var res: ScopeEntry = null
+ var ctx = this
+ while (res == null && ctx.outer != ctx) {
+ val s = ctx.scope lookupSymbolEntry symbol
+ if (s != null)
+ res = s
+ else
+ ctx = ctx.outer
+ }
+ res
+ }
+
+ // 1) Must be owned by the same Scope, to ensure that in
+ // `{ class C; { ...; object C } }`, the class is not seen as a companion of the object.
+ // 2) Must be a class and module symbol, so that `{ class C; def C }` or `{ type T; object T }` are not companions.
+ lookupScopeEntry(original) match {
+ case null => NoSymbol
+ case entry =>
+ def isCompanion(sym: Symbol): Boolean =
+ (original.isModule && sym.isClass || sym.isModule && original.isClass) && sym.isCoDefinedWith(original)
+ entry.owner.lookupNameInSameScopeAs(original, original.name.companionName).filter(isCompanion)
+ }
+ }
+
} //class Context
/** A `Context` focussed on an `Import` tree */
@@ -1407,10 +1407,10 @@ trait Contexts { self: Analyzer =>
protected def handleError(pos: Position, msg: String): Unit = onTreeCheckerError(pos, msg)
}
-
class ImportInfo(val tree: Import, val depth: Int) {
def pos = tree.pos
- def posOf(sel: ImportSelector) = tree.pos withPoint sel.namePos
+ def posOf(sel: ImportSelector) =
+ if (sel.namePos >= 0) tree.pos withPoint sel.namePos else tree.pos
/** The prefix expression */
def qual: Tree = tree.symbol.info match {
@@ -1423,14 +1423,15 @@ trait Contexts { self: Analyzer =>
def isExplicitImport(name: Name): Boolean =
tree.selectors exists (_.rename == name.toTermName)
- /** The symbol with name `name` imported from import clause `tree`.
- */
+ /** The symbol with name `name` imported from import clause `tree`. */
def importedSymbol(name: Name): Symbol = importedSymbol(name, requireExplicit = false, record = true)
- private def recordUsage(sel: ImportSelector, result: Symbol) {
- def posstr = pos.source.file.name + ":" + posOf(sel).line
- def resstr = if (tree.symbol.hasCompleteInfo) s"(qual=$qual, $result)" else s"(expr=${tree.expr}, ${result.fullLocationString})"
- debuglog(s"In $this at $posstr, selector '${selectorString(sel)}' resolved to $resstr")
+ private def recordUsage(sel: ImportSelector, result: Symbol): Unit = {
+ debuglog(s"In $this at ${ pos.source.file.name }:${ posOf(sel).line }, selector '${ selectorString(sel)
+ }' resolved to ${
+ if (tree.symbol.hasCompleteInfo) s"(qual=$qual, $result)"
+ else s"(expr=${tree.expr}, ${result.fullLocationString})"
+ }")
allUsedSelectors(this) += sel
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala
index 69ae6ec0c8..ea82739504 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala
@@ -7,7 +7,7 @@ package scala.tools.nsc
package typechecker
import scala.tools.nsc.symtab.Flags
-import scala.collection.{ mutable, immutable }
+import scala.collection.mutable
/** Duplicate trees and re-type check them, taking care to replace
* and create fresh symbols for new local definitions.
@@ -151,8 +151,12 @@ abstract class Duplicators extends Analyzer {
ldef.symbol = newsym
debuglog("newsym: " + newsym + " info: " + newsym.info)
- case vdef @ ValDef(mods, name, _, rhs) if mods.hasFlag(Flags.LAZY) =>
- debuglog("ValDef " + name + " sym.info: " + vdef.symbol.info)
+ // don't retypecheck val members or local lazy vals -- you'll end up with duplicate symbols because
+ // entering a valdef results in synthesizing getters etc
+ // TODO: why retype check any valdefs?? I checked and the rhs is specialized just fine this way
+ // (and there are no args/type params/... to warrant full type checking?)
+ case vdef @ ValDef(mods, name, _, rhs) if mods.hasFlag(Flags.LAZY) || owner.isClass =>
+ debuglog(s"ValDef $name in $owner sym.info: ${vdef.symbol.info}")
invalidSyms(vdef.symbol) = vdef
val newowner = owner orElse context.owner
val newsym = vdef.symbol.cloneSymbol(newowner)
@@ -229,11 +233,12 @@ abstract class Duplicators extends Analyzer {
case ddef @ DefDef(_, _, _, _, tpt, rhs) =>
ddef.tpt modifyType fixType
- super.typed(ddef.clearType(), mode, pt)
-
- case fun: Function =>
- debuglog("Clearing the type and retyping Function: " + fun)
- super.typed(fun.clearType, mode, pt)
+ val result = super.typed(ddef.clearType(), mode, pt)
+ // TODO this is a hack, we really need a cleaner way to transport symbol attachments to duplicated methods
+ // bodies in specialized subclasses.
+ if (ddef.hasAttachment[DelambdafyTarget.type])
+ result.symbol.updateAttachment(DelambdafyTarget)
+ result
case vdef @ ValDef(mods, name, tpt, rhs) =>
// log("vdef fixing tpe: " + tree.tpe + " with sym: " + tree.tpe.typeSymbol + " and " + invalidSyms)
diff --git a/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala b/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala
index 7092f00bff..5f4fa499b6 100644
--- a/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala
@@ -15,39 +15,29 @@ import symtab.Flags._
* @version 1.0
*/
trait EtaExpansion { self: Analyzer =>
-
import global._
- object etaExpansion {
- private def isMatch(vparam: ValDef, arg: Tree) = arg match {
- case Ident(name) => vparam.name == name
- case _ => false
- }
-
- def unapply(tree: Tree): Option[(List[ValDef], Tree, List[Tree])] = tree match {
- case Function(vparams, Apply(fn, args)) if (vparams corresponds args)(isMatch) =>
- Some((vparams, fn, args))
- case _ =>
- None
- }
- }
-
- /** <p>
- * Expand partial function applications of type `type`.
- * </p><pre>
- * p.f(es_1)...(es_n)
- * ==> {
- * <b>private synthetic val</b> eta$f = p.f // if p is not stable
- * ...
- * <b>private synthetic val</b> eta$e_i = e_i // if e_i is not stable
- * ...
- * (ps_1 => ... => ps_m => eta$f([es_1])...([es_m])(ps_1)...(ps_m))
- * }</pre>
- * <p>
- * tree is already attributed
- * </p>
- */
- def etaExpand(unit : CompilationUnit, tree: Tree, typer: Typer): Tree = {
+ /** Expand partial method application `p.f(es_1)...(es_n)`.
+ *
+ * We expand this to the following block, which evaluates
+ * the target of the application and its supplied arguments if needed (they are not stable),
+ * and then wraps a Function that abstracts over the missing arguments.
+ *
+ * ```
+ * {
+ * private synthetic val eta$f = p.f // if p is not stable
+ * ...
+ * private synthetic val eta$e_i = e_i // if e_i is not stable
+ * ...
+ * (ps_1 => ... => ps_m => eta$f([es_1])...([es_m])(ps_1)...(ps_m))
+ * }
+ * ```
+ *
+ * This is called from instantiateToMethodType after type checking `tree`,
+ * and we realize we have a method type, where a function type (builtin or SAM) is expected.
+ *
+ **/
+ def etaExpand(unit: CompilationUnit, tree: Tree, typer: Typer): Tree = {
val tpe = tree.tpe
var cnt = 0 // for NoPosition
def freshName() = {
@@ -115,7 +105,7 @@ trait EtaExpansion { self: Analyzer =>
val origTpe = sym.tpe
val isRepeated = definitions.isRepeatedParamType(origTpe)
// SI-4176 Don't leak A* in eta-expanded function types. See t4176b.scala
- val droppedStarTpe = if (settings.etaExpandKeepsStar) origTpe else dropIllegalStarTypes(origTpe)
+ val droppedStarTpe = dropIllegalStarTypes(origTpe)
val valDef = ValDef(Modifiers(SYNTHETIC | PARAM), sym.name.toTermName, TypeTree(droppedStarTpe), EmptyTree)
(valDef, isRepeated)
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
index 2333c29b30..66ed0902d8 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
@@ -13,7 +13,7 @@ package tools.nsc
package typechecker
import scala.annotation.tailrec
-import scala.collection.{ mutable, immutable }
+import scala.collection.mutable
import mutable.{ LinkedHashMap, ListBuffer }
import scala.util.matching.Regex
import symtab.Flags._
@@ -34,14 +34,33 @@ trait Implicits {
import typingStack.{ printTyping }
import typeDebug._
+ // standard usage
+ def inferImplicitFor(pt: Type, tree: Tree, context: Context, reportAmbiguous: Boolean = true): SearchResult =
+ inferImplicit(tree, pt, reportAmbiguous, isView = false, context, saveAmbiguousDivergent = true, tree.pos)
+
+ // used by typer to find an implicit coercion
+ def inferImplicitView(from: Type, to: Type, tree: Tree, context: Context, reportAmbiguous: Boolean, saveAmbiguousDivergent: Boolean) =
+ inferImplicit(tree, Function1(from, to), reportAmbiguous, isView = true, context, saveAmbiguousDivergent, tree.pos)
+
+ // used for manifests, typetags, checking language features, scaladoc
+ def inferImplicitByType(pt: Type, context: Context, pos: Position = NoPosition): SearchResult =
+ inferImplicit(EmptyTree, pt, reportAmbiguous = true, isView = false, context, saveAmbiguousDivergent = true, pos)
+
+ def inferImplicitByTypeSilent(pt: Type, context: Context, pos: Position = NoPosition): SearchResult =
+ inferImplicit(EmptyTree, pt, reportAmbiguous = false, isView = false, context, saveAmbiguousDivergent = false, pos)
+
+ @deprecated("Unused in scalac", "2.12.0-M4")
def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context): SearchResult =
inferImplicit(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent = true, tree.pos)
+ @deprecated("Unused in scalac", "2.12.0-M4")
def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean): SearchResult =
inferImplicit(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent, tree.pos)
- /** Search for an implicit value. See the comment on `result` at the end of class `ImplicitSearch`
- * for more info how the search is conducted.
+ /** Search for an implicit value. Consider using one of the convenience methods above. This one has many boolean levers.
+ *
+ * See the comment on `result` at the end of class `ImplicitSearch` for more info how the search is conducted.
+ *
* @param tree The tree for which the implicit needs to be inserted.
* (the inference might instantiate some of the undetermined
* type parameters of that tree.
@@ -92,9 +111,10 @@ trait Implicits {
/** A friendly wrapper over inferImplicit to be used in macro contexts and toolboxes.
*/
def inferImplicit(tree: Tree, pt: Type, isView: Boolean, context: Context, silent: Boolean, withMacrosDisabled: Boolean, pos: Position, onError: (Position, String) => Unit): Tree = {
- val wrapper1 = if (!withMacrosDisabled) (context.withMacrosEnabled[SearchResult] _) else (context.withMacrosDisabled[SearchResult] _)
- def wrapper(inference: => SearchResult) = wrapper1(inference)
- val result = wrapper(inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context = context, saveAmbiguousDivergent = !silent, pos = pos))
+ val result = context.withMacros(enabled = !withMacrosDisabled) {
+ inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context, saveAmbiguousDivergent = !silent, pos)
+ }
+
if (result.isFailure && !silent) {
val err = context.reporter.firstError
val errPos = err.map(_.errPos).getOrElse(pos)
@@ -141,7 +161,7 @@ trait Implicits {
}
/* Map a polytype to one in which all type parameters and argument-dependent types are replaced by wildcards.
- * Consider `implicit def b(implicit x: A): x.T = error("")`. We need to approximate debruijn index types
+ * Consider `implicit def b(implicit x: A): x.T = error("")`. We need to approximate de Bruijn index types
* when checking whether `b` is a valid implicit, as we haven't even searched a value for the implicit arg `x`,
* so we have to approximate (otherwise it is excluded a priori).
*/
@@ -230,7 +250,10 @@ trait Implicits {
this.sym == that.sym
case _ => false
}
- override def hashCode = name.## + pre.## + sym.##
+ override def hashCode = {
+ import scala.util.hashing.MurmurHash3._
+ finalizeHash(mix(mix(productSeed, name.##), sym.##), 2)
+ }
override def toString = (
if (tpeCache eq null) name + ": ?"
else name + ": " + tpe
@@ -299,6 +322,10 @@ trait Implicits {
*/
object Function1 {
val Sym = FunctionClass(1)
+ val Pre = Sym.typeConstructor.prefix
+
+ def apply(from: Type, to: Type) = TypeRef(Pre, Sym, List(from, to))
+
// It is tempting to think that this should be inspecting "tp baseType Sym"
// rather than tp. See test case run/t8280 and the commit message which
// accompanies it for explanation why that isn't done.
@@ -331,8 +358,8 @@ trait Implicits {
val undetParams = if (isView) Nil else context.outer.undetparams
val wildPt = approximate(pt)
- private val runDefintions = currentRun.runDefinitions
- import runDefintions._
+ private val stableRunDefsForImport = currentRun.runDefinitions
+ import stableRunDefsForImport._
def undet_s = if (undetParams.isEmpty) "" else undetParams.mkString(" inferring ", ", ", "")
def tree_s = typeDebug ptTree tree
@@ -882,7 +909,7 @@ trait Implicits {
* - find the most likely one
* - if it matches, forget about all others it improves upon
*/
- @tailrec private def rankImplicits(pending: Infos, acc: Infos): Infos = pending match {
+ @tailrec private def rankImplicits(pending: Infos, acc: List[(SearchResult, ImplicitInfo)]): List[(SearchResult, ImplicitInfo)] = pending match {
case Nil => acc
case firstPending :: otherPending =>
def firstPendingImproves(alt: ImplicitInfo) =
@@ -909,7 +936,7 @@ trait Implicits {
val pendingImprovingBest = undoLog undo {
otherPending filterNot firstPendingImproves
}
- rankImplicits(pendingImprovingBest, firstPending :: acc)
+ rankImplicits(pendingImprovingBest, (newBest, firstPending) :: acc)
}
}
@@ -925,14 +952,14 @@ trait Implicits {
// So if there is any element not improved upon by the first it is an error.
rankImplicits(eligible, Nil) match {
case Nil => ()
- case chosen :: rest =>
- rest find (alt => !improves(chosen, alt)) match {
- case Some(competing) =>
- AmbiguousImplicitError(chosen, competing, "both", "and", "")(isView, pt, tree)(context)
+ case (chosenResult, chosenInfo) :: rest =>
+ rest find { case (_, alt) => !improves(chosenInfo, alt) } match {
+ case Some((competingResult, competingInfo)) =>
+ AmbiguousImplicitError(chosenInfo, chosenResult.tree, competingInfo, competingResult.tree, "both", "and", "")(isView, pt, tree)(context)
return AmbiguousSearchFailure // Stop the search once ambiguity is encountered, see t4457_2.scala
case _ =>
- if (isView) chosen.useCountView += 1
- else chosen.useCountArg += 1
+ if (isView) chosenInfo.useCountView += 1
+ else chosenInfo.useCountArg += 1
}
}
@@ -1009,15 +1036,12 @@ trait Implicits {
}
case None =>
if (pre.isStable && !pre.typeSymbol.isExistentiallyBound) {
- val companion = companionSymbolOf(sym, context)
- companion.moduleClass match {
- case mc: ModuleClassSymbol =>
- val infos =
- for (im <- mc.implicitMembers.toList) yield new ImplicitInfo(im.name, singleType(pre, companion), im)
- if (infos.nonEmpty)
- infoMap += (sym -> infos)
- case _ =>
- }
+ val pre1 =
+ if (sym.isPackageClass) sym.packageObject.typeOfThis
+ else singleType(pre, companionSymbolOf(sym, context))
+ val infos = pre1.implicitMembers.iterator.map(mem => new ImplicitInfo(mem.name, pre1, mem)).toList
+ if (infos.nonEmpty)
+ infoMap += (sym -> infos)
}
val bts = tp.baseTypeSeq
var i = 1
@@ -1205,7 +1229,7 @@ trait Implicits {
/* Re-wraps a type in a manifest before calling inferImplicit on the result */
def findManifest(tp: Type, manifestClass: Symbol = if (full) FullManifestClass else PartialManifestClass) =
- inferImplicit(tree, appliedType(manifestClass, tp), reportAmbiguous = true, isView = false, context).tree
+ inferImplicitFor(appliedType(manifestClass, tp), tree, context).tree
def findSubManifest(tp: Type) = findManifest(tp, if (full) FullManifestClass else OptManifestClass)
def mot(tp0: Type, from: List[Symbol], to: List[Type]): SearchResult = {
@@ -1413,7 +1437,7 @@ trait Implicits {
}
if (result.isFailure && settings.debug) // debuglog is not inlined for some reason
- log("no implicits found for "+pt+" "+pt.typeSymbol.info.baseClasses+" "+implicitsOfExpectedType)
+ log(s"no implicits found for ${pt} ${pt.typeSymbol.info.baseClasses} ${implicitsOfExpectedType}")
result
}
@@ -1453,9 +1477,9 @@ trait Implicits {
}
}
- object ImplicitNotFoundMsg {
- def unapply(sym: Symbol): Option[(Message)] = sym.implicitNotFoundMsg match {
- case Some(m) => Some(new Message(sym, m))
+ class ImplicitAnnotationMsg(f: Symbol => Option[String], clazz: Symbol, annotationName: String) {
+ def unapply(sym: Symbol): Option[(Message)] = f(sym) match {
+ case Some(m) => Some(new Message(sym, m, annotationName))
case None if sym.isAliasType =>
// perform exactly one step of dealiasing
// this is necessary because ClassManifests are now aliased to ClassTags
@@ -1467,41 +1491,45 @@ trait Implicits {
// check the message's syntax: should be a string literal that may contain occurrences of the string "${X}",
// where `X` refers to a type parameter of `sym`
def check(sym: Symbol): Option[String] =
- sym.getAnnotation(ImplicitNotFoundClass).flatMap(_.stringArg(0) match {
- case Some(m) => new Message(sym, m).validate
- case None => Some("Missing argument `msg` on implicitNotFound annotation.")
+ sym.getAnnotation(clazz).flatMap(_.stringArg(0) match {
+ case Some(m) => new Message(sym, m, annotationName).validate
+ case None => Some(s"Missing argument `msg` on $annotationName annotation.")
})
+ }
+ object ImplicitNotFoundMsg extends ImplicitAnnotationMsg(_.implicitNotFoundMsg, ImplicitNotFoundClass, "implicitNotFound")
+
+ object ImplicitAmbiguousMsg extends ImplicitAnnotationMsg(_.implicitAmbiguousMsg, ImplicitAmbiguousClass, "implicitAmbiguous")
+
+ class Message(sym: Symbol, msg: String, annotationName: String) {
// http://dcsobral.blogspot.com/2010/01/string-interpolation-in-scala-with.html
private val Intersobralator = """\$\{\s*([^}\s]+)\s*\}""".r
- class Message(sym: Symbol, msg: String) {
- private def interpolate(text: String, vars: Map[String, String]) =
- Intersobralator.replaceAllIn(text, (_: Regex.Match) match {
- case Regex.Groups(v) => Regex quoteReplacement vars.getOrElse(v, "")
+ private def interpolate(text: String, vars: Map[String, String]) =
+ Intersobralator.replaceAllIn(text, (_: Regex.Match) match {
+ case Regex.Groups(v) => Regex quoteReplacement vars.getOrElse(v, "")
// #3915: need to quote replacement string since it may include $'s (such as the interpreter's $iw)
- })
+ })
- private lazy val typeParamNames: List[String] = sym.typeParams.map(_.decodedName)
- private def typeArgsAtSym(paramTp: Type) = paramTp.baseType(sym).typeArgs
+ private lazy val typeParamNames: List[String] = sym.typeParams.map(_.decodedName)
+ private def typeArgsAtSym(paramTp: Type) = paramTp.baseType(sym).typeArgs
- def format(paramName: Name, paramTp: Type): String = format(typeArgsAtSym(paramTp) map (_.toString))
+ def format(paramName: Name, paramTp: Type): String = format(typeArgsAtSym(paramTp) map (_.toString))
- def format(typeArgs: List[String]): String =
- interpolate(msg, Map((typeParamNames zip typeArgs): _*)) // TODO: give access to the name and type of the implicit argument, etc?
+ def format(typeArgs: List[String]): String =
+ interpolate(msg, Map((typeParamNames zip typeArgs): _*)) // TODO: give access to the name and type of the implicit argument, etc?
- def validate: Option[String] = {
- val refs = Intersobralator.findAllMatchIn(msg).map(_ group 1).toSet
- val decls = typeParamNames.toSet
+ def validate: Option[String] = {
+ val refs = Intersobralator.findAllMatchIn(msg).map(_ group 1).toSet
+ val decls = typeParamNames.toSet
- (refs &~ decls) match {
- case s if s.isEmpty => None
- case unboundNames =>
- val singular = unboundNames.size == 1
- val ess = if (singular) "" else "s"
- val bee = if (singular) "is" else "are"
- Some(s"The type parameter$ess ${unboundNames mkString ", "} referenced in the message of the @implicitNotFound annotation $bee not defined by $sym.")
- }
+ (refs &~ decls) match {
+ case s if s.isEmpty => None
+ case unboundNames =>
+ val singular = unboundNames.size == 1
+ val ess = if (singular) "" else "s"
+ val bee = if (singular) "is" else "are"
+ Some(s"The type parameter$ess ${unboundNames mkString ", "} referenced in the message of the @$annotationName annotation $bee not defined by $sym.")
}
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
index c188c326c3..9dd260b274 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
@@ -164,7 +164,9 @@ trait Infer extends Checkable {
| was: $restpe
| now""")(normalize(restpe))
case mt @ MethodType(_, restpe) if mt.isImplicit => normalize(restpe)
- case mt @ MethodType(_, restpe) if !mt.isDependentMethodType => functionType(mt.paramTypes, normalize(restpe))
+ case mt @ MethodType(_, restpe) if !mt.isDependentMethodType =>
+ if (phase.erasedTypes) FunctionClass(mt.params.length).tpe
+ else functionType(mt.paramTypes, normalize(restpe))
case NullaryMethodType(restpe) => normalize(restpe)
case ExistentialType(tparams, qtpe) => newExistentialType(tparams, normalize(qtpe))
case _ => tp // @MAT aliases already handled by subtyping
@@ -295,7 +297,7 @@ trait Infer extends Checkable {
&& !isByNameParamType(tp)
&& isCompatible(tp, dropByName(pt))
)
- def isCompatibleSam(tp: Type, pt: Type): Boolean = {
+ def isCompatibleSam(tp: Type, pt: Type): Boolean = (definitions.isFunctionType(tp) || tp.isInstanceOf[MethodType] || tp.isInstanceOf[PolyType]) && {
val samFun = typer.samToFunctionType(pt)
(samFun ne NoType) && isCompatible(tp, samFun)
}
@@ -493,21 +495,22 @@ trait Infer extends Checkable {
}
/** Return inferred type arguments, given type parameters, formal parameters,
- * argument types, result type and expected result type.
- * If this is not possible, throw a `NoInstance` exception.
- * Undetermined type arguments are represented by `definitions.NothingTpe`.
- * No check that inferred parameters conform to their bounds is made here.
- *
- * @param tparams the type parameters of the method
- * @param formals the value parameter types of the method
- * @param restpe the result type of the method
- * @param argtpes the argument types of the application
- * @param pt the expected return type of the application
- * @return @see adjustTypeArgs
- *
- * @throws NoInstance
- */
- def methTypeArgs(tparams: List[Symbol], formals: List[Type], restpe: Type,
+ * argument types, result type and expected result type.
+ * If this is not possible, throw a `NoInstance` exception.
+ * Undetermined type arguments are represented by `definitions.NothingTpe`.
+ * No check that inferred parameters conform to their bounds is made here.
+ *
+ * @param fn the function for reporting, may be empty
+ * @param tparams the type parameters of the method
+ * @param formals the value parameter types of the method
+ * @param restpe the result type of the method
+ * @param argtpes the argument types of the application
+ * @param pt the expected return type of the application
+ * @return @see adjustTypeArgs
+ *
+ * @throws NoInstance
+ */
+ def methTypeArgs(fn: Tree, tparams: List[Symbol], formals: List[Type], restpe: Type,
argtpes: List[Type], pt: Type): AdjustedTypeArgs.Result = {
val tvars = tparams map freshVar
if (!sameLength(formals, argtpes))
@@ -557,21 +560,12 @@ trait Infer extends Checkable {
val hasAny = pt :: restpe :: formals ::: argtpes ::: loBounds exists (_.dealiasWidenChain exists containsAny)
!hasAny
}
- def argumentPosition(idx: Int): Position = context.tree match {
- case x: ValOrDefDef => x.rhs match {
- case Apply(fn, args) if idx < args.size => args(idx).pos
- case _ => context.tree.pos
- }
- case _ => context.tree.pos
- }
- if (settings.warnInferAny && context.reportErrors && canWarnAboutAny) {
- foreachWithIndex(targs) ((targ, idx) =>
- targ.typeSymbol match {
- case sym @ (AnyClass | AnyValClass) =>
- reporter.warning(argumentPosition(idx), s"a type was inferred to be `${sym.name}`; this may indicate a programming error.")
- case _ =>
- }
- )
+ if (settings.warnInferAny && context.reportErrors && !fn.isEmpty && canWarnAboutAny) {
+ targs.foreach(_.typeSymbol match {
+ case sym @ (AnyClass | AnyValClass) =>
+ reporter.warning(fn.pos, s"a type was inferred to be `${sym.name}`; this may indicate a programming error.")
+ case _ =>
+ })
}
adjustTypeArgs(tparams, tvars, targs, restpe)
}
@@ -729,11 +723,11 @@ trait Infer extends Checkable {
// If args eq the incoming arg types, fail; otherwise recurse with these args.
def tryWithArgs(args: List[Type]) = (
(args ne argtpes0)
- && isApplicable(undetparams, mt, args, pt)
+ && isApplicableToMethod(undetparams, mt, args, pt) // used to be isApplicable(undetparams, mt, args, pt), knowing mt: MethodType
)
def tryInstantiating(args: List[Type]) = falseIfNoInstance {
val restpe = mt resultType args
- val AdjustedTypeArgs.Undets(okparams, okargs, leftUndet) = methTypeArgs(undetparams, formals, restpe, args, pt)
+ val AdjustedTypeArgs.Undets(okparams, okargs, leftUndet) = methTypeArgs(EmptyTree, undetparams, formals, restpe, args, pt)
val restpeInst = restpe.instantiateTypeParams(okparams, okargs)
// #2665: must use weak conformance, not regular one (follow the monomorphic case above)
exprTypeArgs(leftUndet, restpeInst, pt, useWeaklyCompatible = true) match {
@@ -934,10 +928,8 @@ trait Infer extends Checkable {
def infer_s = map3(tparams, tvars, targs)((tparam, tvar, targ) => s"$tparam=$tvar/$targ") mkString ","
printTyping(tree, s"infer expr instance from pt=$pt, $infer_s")
- // SI-7899 inferring by-name types is unsound. The correct behaviour is conditional because the hole is
- // exploited in Scalaz (Free.scala), as seen in: run/t7899-regression.
- def dropByNameIfStrict(tp: Type): Type = if (settings.inferByName) tp else dropByName(tp)
- def targsStrict = if (targs eq null) null else targs mapConserve dropByNameIfStrict
+ // SI-7899 inferring by-name types is unsound
+ def targsStrict = if (targs eq null) null else targs mapConserve dropByName
if (keepNothings || (targs eq null)) { //@M: adjustTypeArgs fails if targs==null, neg/t0226
substExpr(tree, tparams, targsStrict, pt)
@@ -989,7 +981,7 @@ trait Infer extends Checkable {
val restpe = fn.tpe.resultType(argtpes)
val AdjustedTypeArgs.AllArgsAndUndets(okparams, okargs, allargs, leftUndet) =
- methTypeArgs(undetparams, formals, restpe, argtpes, pt)
+ methTypeArgs(fn, undetparams, formals, restpe, argtpes, pt)
if (checkBounds(fn, NoPrefix, NoSymbol, undetparams, allargs, "inferred ")) {
val treeSubst = new TreeTypeSubstituter(okparams, okargs)
@@ -1207,6 +1199,7 @@ trait Infer extends Checkable {
}
}
tvars foreach instantiateTypeVar
+ invalidateTreeTpeCaches(tree0, tvars.map(_.origin.typeSymbol))
}
/* If the scrutinee has free type parameters but the pattern does not,
* we have to flip the arguments so the expected type is treated as more
@@ -1217,7 +1210,7 @@ trait Infer extends Checkable {
}
def inferModulePattern(pat: Tree, pt: Type) =
- if (!(pat.tpe <:< pt)) {
+ if ((pat.symbol ne null) && pat.symbol.isModule && !(pat.tpe <:< pt)) {
val ptparams = freeTypeParamsOfTerms(pt)
debuglog("free type params (2) = " + ptparams)
val ptvars = ptparams map freshVar
@@ -1445,7 +1438,7 @@ trait Infer extends Checkable {
log(s"Attaching AntiPolyType-carrying overloaded type to $sym")
// Multiple alternatives which are within bounds; spin up an
// overloaded type which carries an "AntiPolyType" as a prefix.
- val tparams = newAsSeenFromMap(pre, hd.owner) mapOver hd.typeParams
+ val tparams = new AsSeenFromMap(pre, hd.owner) mapOver hd.typeParams
val bounds = tparams map (_.tpeHK) // see e.g., #1236
val tpe = PolyType(tparams, OverloadedType(AntiPolyType(pre, bounds), alts))
finish(sym setInfo tpe, tpe)
diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala
index 3ed128cbc5..6de95ab658 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala
@@ -5,17 +5,13 @@ import java.lang.Math.min
import symtab.Flags._
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
import scala.tools.reflect.FastTrack
-import scala.runtime.ScalaRunTime
import Fingerprint._
/**
@@ -242,7 +238,7 @@ trait Macros extends MacroRuntimes with Traces with Helpers {
if (!payload.contains(field)) failField("is supposed to be there")
val raw: Any = payload(field)
if (raw == null) failField(s"is not supposed to be null")
- val expected = ScalaRunTime.box(clazz)
+ val expected = box(clazz)
val actual = raw.getClass
if (!expected.isAssignableFrom(actual)) failField(s"has wrong type: expected $expected, actual $actual")
raw.asInstanceOf[T]
@@ -259,6 +255,19 @@ trait Macros extends MacroRuntimes with Traces with Helpers {
val signature = unpickle("signature", classOf[List[List[Fingerprint]]])
MacroImplBinding(isBundle, isBlackbox, className, methodName, signature, targs)
}
+
+ private def box[T](clazz: Class[T]): Class[_] = clazz match {
+ case java.lang.Byte.TYPE => classOf[java.lang.Byte]
+ case java.lang.Short.TYPE => classOf[java.lang.Short]
+ case java.lang.Character.TYPE => classOf[java.lang.Character]
+ case java.lang.Integer.TYPE => classOf[java.lang.Integer]
+ case java.lang.Long.TYPE => classOf[java.lang.Long]
+ case java.lang.Float.TYPE => classOf[java.lang.Float]
+ case java.lang.Double.TYPE => classOf[java.lang.Double]
+ case java.lang.Void.TYPE => classOf[scala.runtime.BoxedUnit]
+ case java.lang.Boolean.TYPE => classOf[java.lang.Boolean]
+ case _ => clazz
+ }
}
def bindMacroImpl(macroDef: Symbol, macroImplRef: Tree): Unit = {
@@ -688,7 +697,7 @@ trait Macros extends MacroRuntimes with Traces with Helpers {
// foo(Foo(23, "foo", true))
//
// In the snippet above, even though we know that there's a fundep going from T to U
- // (in a sense that a datatype's uniform representation is unambiguously determined by the datatype,
+ // (in a sense that a datatype's uniform representation is unambiguously determined by the data type,
// e.g. for Foo it will be Int :: String :: Boolean :: HNil), there's no way to convey this information
// to the typechecker. Therefore the typechecker will infer Nothing for L, which is hardly what we want.
//
diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala
index f3856db552..fea9debe7e 100644
--- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala
@@ -5,12 +5,10 @@
package scala.tools.nsc
package typechecker
+import scala.reflect.NameTransformer
import symtab.Flags._
-import scala.reflect.internal.util.StringOps.{ ojoin }
-import scala.reflect.ClassTag
+import scala.reflect.internal.util.StringOps.ojoin
import scala.reflect.internal.util.ListOfNil
-import scala.reflect.runtime.{ universe => ru }
-import scala.language.higherKinds
/** Logic related to method synthesis which involves cooperation between
* Namer and Typer.
@@ -22,17 +20,6 @@ trait MethodSynthesis {
import definitions._
import CODE._
- /** The annotations amongst those found on the original symbol which
- * should be propagated to this kind of accessor.
- */
- def deriveAnnotations(initial: List[AnnotationInfo], category: Symbol, keepClean: Boolean): List[AnnotationInfo] = {
- def annotationFilter(ann: AnnotationInfo) = ann.metaAnnotations match {
- case Nil if ann.defaultTargets.isEmpty => keepClean // no meta-annotations or default targets
- case Nil => ann.defaultTargets contains category // default targets exist for ann
- case metas => metas exists (_ matches category) // meta-annotations attached to ann
- }
- initial filter annotationFilter
- }
class ClassMethodSynthesis(val clazz: Symbol, localTyper: Typer) {
def mkThis = This(clazz) setPos clazz.pos.focus
@@ -130,420 +117,174 @@ trait MethodSynthesis {
import NamerErrorGen._
- def enterImplicitWrapper(tree: ClassDef) {
- ImplicitClassWrapper(tree).createAndEnterSymbol()
- }
- def enterGetterSetter(tree: ValDef) {
- val ValDef(mods, name, _, _) = tree
- if (nme.isSetterName(name))
- ValOrValWithSetterSuffixError(tree)
-
- tree.symbol = (
- if (mods.isLazy) {
- val lazyValGetter = LazyValGetter(tree).createAndEnterSymbol()
- enterLazyVal(tree, lazyValGetter)
- } else {
- if (mods.isPrivateLocal)
- PrivateThisCaseClassParameterError(tree)
- val getter = Getter(tree).createAndEnterSymbol()
- // Create the setter if necessary.
- if (mods.isMutable)
- Setter(tree).createAndEnterSymbol()
-
- // If abstract, the tree gets the getter's symbol. Otherwise, create a field.
- if (mods.isDeferred) getter setPos tree.pos
- else enterStrictVal(tree)
+ import treeInfo.noFieldFor
+
+ // populate synthetics for this unit with trees that will later be added by the typer
+ // we get here when entering the symbol for the valdef, so its rhs has not yet been type checked
+ def enterGetterSetter(tree: ValDef): Unit = {
+ val fieldSym =
+ if (noFieldFor(tree, owner)) NoSymbol
+ else owner.newValue(tree.name append NameTransformer.LOCAL_SUFFIX_STRING, tree.pos, tree.mods.flags & FieldFlags | PrivateLocal)
+
+ val getter = Getter(tree)
+ val getterSym = getter.createSym
+
+ // only one symbol can have `tree.pos`, the others must focus their position
+ // normally the field gets the range position, but if there is none, give it to the getter
+ //
+ // SI-10009 the tree's modifiers can be temporarily out of sync with the new symbol's flags.
+ // typedValDef corrects this later on.
+ tree.symbol = fieldSym orElse (getterSym setPos tree.pos)
+
+ val namer = namerOf(tree.symbol)
+
+ // the valdef gets the accessor symbol for a lazy val (too much going on in its RHS)
+ // the fields phase creates the field symbol
+ if (!tree.mods.isLazy) {
+ // if there's a field symbol, the getter is considered a synthetic that must be added later
+ // if there's no field symbol, the ValDef tree receives the getter symbol and thus is not a synthetic
+ if (fieldSym != NoSymbol) {
+ context.unit.synthetics(getterSym) = getter.derivedTree(getterSym)
+ getterSym setInfo namer.accessorTypeCompleter(tree, tree.tpt.isEmpty, isBean = false, isSetter = false)
+ } else getterSym setInfo namer.valTypeCompleter(tree)
+
+ enterInScope(getterSym)
+
+ if (getter.needsSetter) {
+ val setter = Setter(tree)
+ val setterSym = setter.createSym
+ context.unit.synthetics(setterSym) = setter.derivedTree(setterSym)
+ setterSym setInfo namer.accessorTypeCompleter(tree, tree.tpt.isEmpty, isBean = false, isSetter = true)
+ enterInScope(setterSym)
}
- )
-
- enterBeans(tree)
- }
- /** This is called for those ValDefs which addDerivedTrees ignores, but
- * which might have a warnable annotation situation.
- */
- private def warnForDroppedAnnotations(tree: Tree) {
- val annotations = tree.symbol.initialize.annotations
- val targetClass = defaultAnnotationTarget(tree)
- val retained = deriveAnnotations(annotations, targetClass, keepClean = true)
-
- annotations filterNot (retained contains _) foreach (ann => issueAnnotationWarning(tree, ann, targetClass))
- }
- private def issueAnnotationWarning(tree: Tree, ann: AnnotationInfo, defaultTarget: Symbol) {
- global.reporter.warning(ann.pos,
- s"no valid targets for annotation on ${tree.symbol} - it is discarded unused. " +
- s"You may specify targets with meta-annotations, e.g. @($ann @${defaultTarget.name})")
- }
-
- def addDerivedTrees(typer: Typer, stat: Tree): List[Tree] = stat match {
- case vd @ ValDef(mods, name, tpt, rhs) if !noFinishGetterSetter(vd) =>
- // If we don't save the annotations, they seem to wander off.
- val annotations = stat.symbol.initialize.annotations
- val trees = (
- allValDefDerived(vd)
- map (acc => atPos(vd.pos.focus)(acc derive annotations))
- filterNot (_ eq EmptyTree)
- )
- // Verify each annotation landed safely somewhere, else warn.
- // Filtering when isParamAccessor is a necessary simplification
- // because there's a bunch of unwritten annotation code involving
- // the propagation of annotations - constructor parameter annotations
- // may need to make their way to parameters of the constructor as
- // well as fields of the class, etc.
- if (!mods.isParamAccessor) annotations foreach (ann =>
- if (!trees.exists(_.symbol hasAnnotation ann.symbol))
- issueAnnotationWarning(vd, ann, GetterTargetClass)
- )
-
- trees
- case vd: ValDef =>
- warnForDroppedAnnotations(vd)
- vd :: Nil
- case cd @ ClassDef(mods, _, _, _) if mods.isImplicit =>
- val annotations = stat.symbol.initialize.annotations
- // TODO: need to shuffle annotations between wrapper and class.
- val wrapper = ImplicitClassWrapper(cd)
- val meth = wrapper.derivedSym
- context.unit.synthetics get meth match {
- case Some(mdef) =>
- context.unit.synthetics -= meth
- meth setAnnotations deriveAnnotations(annotations, MethodTargetClass, keepClean = false)
- cd.symbol setAnnotations deriveAnnotations(annotations, ClassTargetClass, keepClean = true)
- List(cd, mdef)
- case _ =>
- // Shouldn't happen, but let's give ourselves a reasonable error when it does
- context.error(cd.pos, s"Internal error: Symbol for synthetic factory method not found among ${context.unit.synthetics.keys.mkString(", ")}")
- // Soldier on for the sake of the presentation compiler
- List(cd)
+ // TODO: delay emitting the field to the fields phase (except for private[this] vals, which only get a field and no accessors)
+ if (fieldSym != NoSymbol) {
+ fieldSym setInfo namer.valTypeCompleter(tree)
+ enterInScope(fieldSym)
}
- case _ =>
- stat :: Nil
+ } else {
+ getterSym setInfo namer.valTypeCompleter(tree)
+ enterInScope(getterSym)
}
- def standardAccessors(vd: ValDef): List[DerivedFromValDef] = (
- if (vd.mods.isMutable && !vd.mods.isLazy) List(Getter(vd), Setter(vd))
- else if (vd.mods.isLazy) List(LazyValGetter(vd))
- else List(Getter(vd))
- )
- def beanAccessors(vd: ValDef): List[DerivedFromValDef] = {
- val setter = if (vd.mods.isMutable) List(BeanSetter(vd)) else Nil
- if (vd.symbol hasAnnotation BeanPropertyAttr)
- BeanGetter(vd) :: setter
- else if (vd.symbol hasAnnotation BooleanBeanPropertyAttr)
- BooleanBeanGetter(vd) :: setter
- else Nil
- }
- def allValDefDerived(vd: ValDef) = {
- val field = if (vd.mods.isDeferred || (vd.mods.isLazy && hasUnitType(vd.symbol))) Nil
- else List(Field(vd))
- field ::: standardAccessors(vd) ::: beanAccessors(vd)
+ deriveBeanAccessors(tree, namer)
}
- // Take into account annotations so that we keep annotated unit lazy val
- // to get better error message already from the cps plugin itself
- def hasUnitType(sym: Symbol) = (sym.tpe.typeSymbol == UnitClass) && sym.tpe.annotations.isEmpty
+ private def deriveBeanAccessors(tree: ValDef, namer: Namer): Unit = {
+ // TODO: can we look at the annotations symbols? (name-based introduced in 8cc477f8b6, see neg/t3403)
+ val hasBeanProperty = tree.mods hasAnnotationNamed tpnme.BeanPropertyAnnot
+ val hasBoolBP = tree.mods hasAnnotationNamed tpnme.BooleanBeanPropertyAnnot
- /** This trait assembles what's needed for synthesizing derived methods.
- * Important: Typically, instances of this trait are created TWICE for each derived
- * symbol; once form Namers in an enter method, and once from Typers in addDerivedTrees.
- * So it's important that creating an instance of Derived does not have a side effect,
- * or if it has a side effect, control that it is done only once.
- */
- sealed trait Derived {
+ if (hasBeanProperty || hasBoolBP) {
+ if (!tree.name.charAt(0).isLetter) BeanPropertyAnnotationFieldWithoutLetterError(tree)
+ // avoids name clashes with private fields in traits
+ else if (tree.mods.isPrivate) BeanPropertyAnnotationPrivateFieldError(tree)
- /** The tree from which we are deriving a synthetic member. Typically, that's
- * given as an argument of the instance. */
- def tree: Tree
+ val derivedPos = tree.pos.focus
+ val missingTpt = tree.tpt.isEmpty
- /** The name of the method */
- def name: TermName
+ def deriveBeanAccessor(prefix: String): Symbol = {
+ val isSetter = prefix == "set"
+ val name = newTermName(prefix + tree.name.toString.capitalize)
+ val setterParam = nme.syntheticParamName(1)
- /** The flags that are retained from the original symbol */
+ // note: tree.tpt may be EmptyTree, which will be a problem when use as the tpt of a parameter
+ // the completer will patch this up (we can't do this now without completing the field)
+ val tptToPatch = if (missingTpt) TypeTree() else tree.tpt.duplicate
- def flagsMask: Long
+ val (vparams, tpt) =
+ if (isSetter) (List(ValDef(Modifiers(PARAM | SYNTHETIC), setterParam, tptToPatch, EmptyTree)), TypeTree(UnitTpe))
+ else (Nil, tptToPatch)
- /** The flags that the derived symbol has in addition to those retained from
- * the original symbol*/
- def flagsExtra: Long
+ val rhs =
+ if (tree.mods.isDeferred) EmptyTree
+ else if (isSetter) Apply(Ident(tree.name.setterName), List(Ident(setterParam)))
+ else Select(This(owner), tree.name)
- /** type completer for the synthetic member.
- */
- def completer(sym: Symbol): Type
+ val sym = createMethod(tree, name, derivedPos, tree.mods.flags & BeanPropertyFlags)
+ context.unit.synthetics(sym) = newDefDef(sym, rhs)(tparams = Nil, vparamss = List(vparams), tpt = tpt)
+ sym
+ }
- /** The derived symbol. It is assumed that this symbol already exists and has been
- * entered in the parent scope when derivedSym is called */
- def derivedSym: Symbol
+ val getterCompleter = namer.accessorTypeCompleter(tree, missingTpt, isBean = true, isSetter = false)
+ enterInScope(deriveBeanAccessor(if (hasBeanProperty) "get" else "is") setInfo getterCompleter)
- /** The definition tree of the derived symbol. */
- def derivedTree: Tree
+ if (tree.mods.isMutable) {
+ val setterCompleter = namer.accessorTypeCompleter(tree, missingTpt, isBean = true, isSetter = true)
+ enterInScope(deriveBeanAccessor("set") setInfo setterCompleter)
+ }
+ }
}
- sealed trait DerivedFromMemberDef extends Derived {
- def tree: MemberDef
- def enclClass: Symbol
- // Final methods to make the rest easier to reason about.
- final def mods = tree.mods
- final def basisSym = tree.symbol
+ def enterImplicitWrapper(classDef: ClassDef): Unit = {
+ val methDef = factoryMeth(classDef.mods & AccessFlags | METHOD | IMPLICIT | SYNTHETIC, classDef.name.toTermName, classDef)
+ val methSym = enterInScope(assignMemberSymbol(methDef))
+ context.unit.synthetics(methSym) = methDef
+ methSym setInfo implicitFactoryMethodCompleter(methDef, classDef.symbol)
}
- sealed trait DerivedFromClassDef extends DerivedFromMemberDef {
- def tree: ClassDef
- final def enclClass = basisSym.owner.enclClass
- }
- sealed trait DerivedFromValDef extends DerivedFromMemberDef {
+ trait DerivedAccessor {
def tree: ValDef
- final def enclClass = basisSym.enclClass
-
- /** Which meta-annotation is associated with this kind of entity.
- * Presently one of: field, getter, setter, beanGetter, beanSetter, param.
- */
- def category: Symbol
-
- /* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */
- final def completer(sym: Symbol) = namerOf(sym).accessorTypeCompleter(tree, isSetter)
- final def fieldSelection = Select(This(enclClass), basisSym)
- final def derivedMods: Modifiers = mods & flagsMask | flagsExtra mapAnnotations (_ => Nil)
-
- def derivedSym: Symbol = tree.symbol
- def derivedTree: Tree = EmptyTree
-
- def isSetter = false
- def isDeferred = mods.isDeferred
- def keepClean = false // whether annotations whose definitions are not meta-annotated should be kept.
- def validate() { }
- def createAndEnterSymbol(): Symbol = {
- val sym = owner.newMethod(name, tree.pos.focus, (tree.mods.flags & flagsMask) | flagsExtra)
- setPrivateWithin(tree, sym)
- enterInScope(sym)
- sym setInfo completer(sym)
- }
- private def logDerived(result: Tree): Tree = {
- debuglog("[+derived] " + ojoin(mods.flagString, basisSym.accurateKindString, basisSym.getterName.decode)
- + " (" + derivedSym + ")\n " + result)
+ def derivedName: TermName
+ def derivedFlags: Long
+ def derivedTree(sym: Symbol): Tree
- result
- }
- final def derive(initial: List[AnnotationInfo]): Tree = {
- validate()
- derivedSym setAnnotations deriveAnnotations(initial, category, keepClean)
- logDerived(derivedTree)
- }
- }
- sealed trait DerivedGetter extends DerivedFromValDef {
- // TODO
- }
- sealed trait DerivedSetter extends DerivedFromValDef {
- override def isSetter = true
- private def setterParam = derivedSym.paramss match {
- case (p :: Nil) :: _ => p
- case _ => NoSymbol
- }
- private def setterRhs = (
- if (mods.isDeferred || derivedSym.isOverloaded) EmptyTree
- else Assign(fieldSelection, Ident(setterParam))
- )
- private def setterDef = DefDef(derivedSym, setterRhs)
- override def derivedTree: Tree = if (setterParam == NoSymbol) EmptyTree else setterDef
+ def derivedPos = tree.pos.focus
+ def createSym = createMethod(tree, derivedName, derivedPos, derivedFlags)
}
- /** A synthetic method which performs the implicit conversion implied by
- * the declaration of an implicit class.
- */
- case class ImplicitClassWrapper(tree: ClassDef) extends DerivedFromClassDef {
- def completer(sym: Symbol): Type = ??? // not needed
- def createAndEnterSymbol(): Symbol = enterSyntheticSym(derivedTree)
- def derivedSym: Symbol = {
- // Only methods will do! Don't want to pick up any stray
- // companion objects of the same name.
- val result = enclClass.info decl name filter (x => x.isMethod && x.isSynthetic)
- if (result == NoSymbol || result.isOverloaded)
- context.error(tree.pos, s"Internal error: Unable to find the synthetic factory method corresponding to implicit class $name in $enclClass / ${enclClass.info.decls}")
- result
- }
- def derivedTree: DefDef =
- factoryMeth(mods & flagsMask | flagsExtra, name, tree)
- def flagsExtra: Long = METHOD | IMPLICIT | SYNTHETIC
- def flagsMask: Long = AccessFlags
- def name: TermName = tree.name.toTermName
- }
+ case class Getter(tree: ValDef) extends DerivedAccessor {
+ def derivedName = tree.name
+ def derivedFlags = tree.mods.flags & GetterFlags | ACCESSOR.toLong | ( if (needsSetter) 0 else STABLE )
+ def needsSetter = tree.mods.isMutable // implies !lazy
- sealed abstract class BaseGetter(tree: ValDef) extends DerivedGetter {
- def name = tree.name
- def category = GetterTargetClass
- def flagsMask = GetterFlags
- def flagsExtra = ACCESSOR.toLong | ( if (tree.mods.isMutable) 0 else STABLE )
+ override def derivedTree(derivedSym: Symbol) = {
+ val missingTpt = tree.tpt.isEmpty
+ val tpt = if (missingTpt) TypeTree() else tree.tpt.duplicate
- override def validate() {
- assert(derivedSym != NoSymbol, tree)
- if (derivedSym.isOverloaded)
- GetterDefinedTwiceError(derivedSym)
+ val rhs =
+ if (noFieldFor(tree, owner)) tree.rhs // context.unit.transformed.getOrElse(tree.rhs, tree.rhs)
+ else Select(This(tree.symbol.enclClass), tree.symbol)
- super.validate()
- }
- }
- case class Getter(tree: ValDef) extends BaseGetter(tree) {
- 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
- // from the symbol! This leads to incompatible existentials for the field and
- // the getter. Let the typer do all the work. You might think "why only for
- // existentials, why not always," and you would be right, except: a single test
- // fails, but it looked like some work to deal with it. Test neg/t0606.scala
- // starts compiling (instead of failing like it's supposed to) because the typer
- // expects to be able to identify escaping locals in typedDefDef, and fails to
- // spot that brand of them. In other words it's an artifact of the implementation.
- val tpt = derivedSym.tpe_*.finalResultType.widen match {
- // Range position errors ensue if we don't duplicate this in some
- // circumstances (at least: concrete vals with existential types.)
- case ExistentialType(_, _) => TypeTree() setOriginal (tree.tpt.duplicate setPos tree.tpt.pos.focus)
- case _ if mods.isDeferred => TypeTree() setOriginal tree.tpt // keep type tree of original abstract field
- case tp => TypeTree(tp)
- }
- tpt setPos tree.tpt.pos.focus
- }
- override def derivedTree: DefDef = newDefDef(derivedSym, derivedRhs)(tpt = derivedTpt)
- }
- /** Implements lazy value accessors:
- * - for lazy values of type Unit and all lazy fields inside traits,
- * the rhs is the initializer itself
- * - for all other lazy values z the accessor is a block of this form:
- * { z = <rhs>; z } where z can be an identifier or a field.
- */
- case class LazyValGetter(tree: ValDef) extends BaseGetter(tree) {
- class ChangeOwnerAndModuleClassTraverser(oldowner: Symbol, newowner: Symbol)
- extends ChangeOwnerTraverser(oldowner, newowner) {
-
- override def traverse(tree: Tree) {
- tree match {
- case _: DefTree => change(tree.symbol.moduleClass)
- case _ =>
- }
- super.traverse(tree)
- }
+ newDefDef(derivedSym, rhs)(tparams = Nil, vparamss = Nil, tpt = tpt)
}
- // todo: in future this should be enabled but now other phases still depend on the flag for various reasons
- //override def flagsMask = (super.flagsMask & ~LAZY)
- override def derivedSym = basisSym.lazyAccessor
- override def derivedTree: DefDef = {
- val ValDef(_, _, tpt0, rhs0) = tree
- val rhs1 = context.unit.transformed.getOrElse(rhs0, rhs0)
- val body = (
- if (tree.symbol.owner.isTrait || hasUnitType(basisSym)) rhs1
- else gen.mkAssignAndReturn(basisSym, rhs1)
- )
- derivedSym setPos tree.pos // cannot set it at createAndEnterSymbol because basisSym can possibly still have NoPosition
- val ddefRes = DefDef(derivedSym, new ChangeOwnerAndModuleClassTraverser(basisSym, derivedSym)(body))
- // ValDef will have its position focused whereas DefDef will have original correct rangepos
- // ideally positions would be correct at the creation time but lazy vals are really a special case
- // here so for the sake of keeping api clean we fix positions manually in LazyValGetter
- ddefRes.tpt.setPos(tpt0.pos)
- tpt0.setPos(tpt0.pos.focus)
- ddefRes
- }
- }
- case class Setter(tree: ValDef) extends DerivedSetter {
- def name = tree.setterName
- def category = SetterTargetClass
- def flagsMask = SetterFlags
- def flagsExtra = ACCESSOR
+// derivedSym setPos tree.pos
+// // ValDef will have its position focused whereas DefDef will have original correct rangepos
+// // ideally positions would be correct at the creation time but lazy vals are really a special case
+// // here so for the sake of keeping api clean we fix positions manually in LazyValGetter
+// tpt.setPos(tree.tpt.pos)
+// tree.tpt.setPos(tree.tpt.pos.focus)
- override def derivedSym = basisSym.setterIn(enclClass)
- }
- case class Field(tree: ValDef) extends DerivedFromValDef {
- def name = tree.localName
- def category = FieldTargetClass
- def flagsMask = FieldFlags
- def flagsExtra = PrivateLocal
- // By default annotations go to the field, except if the field is
- // generated for a class parameter (PARAMACCESSOR).
- override def keepClean = !mods.isParamAccessor
- override def derivedTree = (
- if (mods.isDeferred) EmptyTree
- else if (mods.isLazy) copyValDef(tree)(mods = mods | flagsExtra, name = this.name, rhs = EmptyTree).setPos(tree.pos.focus)
- else copyValDef(tree)(mods = mods | flagsExtra, name = this.name)
- )
- }
- case class Param(tree: ValDef) extends DerivedFromValDef {
- def name = tree.name
- def category = ParamTargetClass
- def flagsMask = -1L
- def flagsExtra = 0L
- override def keepClean = true
- override def derivedTree = EmptyTree
- }
- def validateParam(tree: ValDef) {
- Param(tree).derive(tree.symbol.annotations)
}
- sealed abstract class BeanAccessor(bean: String) extends DerivedFromValDef {
- val name = newTermName(bean + tree.name.toString.capitalize)
- def flagsMask = BeanPropertyFlags
- def flagsExtra = 0
- override def derivedSym = enclClass.info decl name
- }
- sealed trait AnyBeanGetter extends BeanAccessor with DerivedGetter {
- def category = BeanGetterTargetClass
- override def validate() {
- if (derivedSym == NoSymbol) {
- // the namer decides whether to generate these symbols or not. at that point, we don't
- // have symbolic information yet, so we only look for annotations named "BeanProperty".
- BeanPropertyAnnotationLimitationError(tree)
- }
- super.validate()
- }
- }
- trait NoSymbolBeanGetter extends AnyBeanGetter {
- // Derives a tree without attempting to use the original tree's symbol.
- override def derivedTree = {
- atPos(tree.pos.focus) {
- DefDef(derivedMods, name, Nil, ListOfNil, tree.tpt.duplicate,
- if (isDeferred) EmptyTree else Select(This(owner), tree.name)
- )
- }
- }
- override def createAndEnterSymbol(): Symbol = enterSyntheticSym(derivedTree)
- }
- case class BooleanBeanGetter(tree: ValDef) extends BeanAccessor("is") with AnyBeanGetter { }
- case class BeanGetter(tree: ValDef) extends BeanAccessor("get") with AnyBeanGetter { }
- case class BeanSetter(tree: ValDef) extends BeanAccessor("set") with DerivedSetter {
- def category = BeanSetterTargetClass
- }
+ case class Setter(tree: ValDef) extends DerivedAccessor {
+ def derivedName = tree.setterName
+ def derivedFlags = tree.mods.flags & SetterFlags | ACCESSOR
+ def derivedTree(derivedSym: Symbol) = {
+ val setterParam = nme.syntheticParamName(1)
- // No Symbols available.
- private def beanAccessorsFromNames(tree: ValDef) = {
- val ValDef(mods, _, _, _) = tree
- val hasBP = mods hasAnnotationNamed tpnme.BeanPropertyAnnot
- val hasBoolBP = mods hasAnnotationNamed tpnme.BooleanBeanPropertyAnnot
-
- if (hasBP || hasBoolBP) {
- val getter = (
- if (hasBP) new BeanGetter(tree) with NoSymbolBeanGetter
- else new BooleanBeanGetter(tree) with NoSymbolBeanGetter
- )
- getter :: {
- if (mods.isMutable) List(BeanSetter(tree)) else Nil
- }
- }
- else Nil
- }
+ // note: tree.tpt may be EmptyTree, which will be a problem when use as the tpt of a parameter
+ // the completer will patch this up (we can't do this now without completing the field)
+ val missingTpt = tree.tpt.isEmpty
+ val tptToPatch = if (missingTpt) TypeTree() else tree.tpt.duplicate
+
+ val vparams = List(ValDef(Modifiers(PARAM | SYNTHETIC), setterParam, tptToPatch, EmptyTree))
+
+ val tpt = TypeTree(UnitTpe)
+
+ val rhs =
+ if (noFieldFor(tree, owner)) EmptyTree
+ else Assign(Select(This(tree.symbol.enclClass), tree.symbol), Ident(setterParam))
+
+ newDefDef(derivedSym, rhs)(tparams = Nil, vparamss = List(vparams), tpt = tpt)
- protected def enterBeans(tree: ValDef) {
- val ValDef(mods, name, _, _) = tree
- val beans = beanAccessorsFromNames(tree)
- if (beans.nonEmpty) {
- if (!name.charAt(0).isLetter)
- BeanPropertyAnnotationFieldWithoutLetterError(tree)
- else if (mods.isPrivate) // avoids name clashes with private fields in traits
- BeanPropertyAnnotationPrivateFieldError(tree)
-
- // Create and enter the symbols here, add the trees in finishGetterSetter.
- beans foreach (_.createAndEnterSymbol())
}
}
+
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala
index 81299dc425..f69d1d5254 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala
@@ -6,8 +6,8 @@
package scala.tools.nsc
package typechecker
-import scala.collection.mutable
import scala.annotation.tailrec
+import scala.collection.mutable
import symtab.Flags._
import scala.language.postfixOps
import scala.reflect.internal.util.ListOfNil
@@ -61,6 +61,11 @@ trait Namers extends MethodSynthesis {
private lazy val innerNamer =
if (isTemplateContext(context)) createInnerNamer() else this
+ // Cached as a val because `settings.isScala212` parses the Scala version each time...
+ // Not in Namers because then we need to go to outer first to check this.
+ // I do think it's ok to check every time we create a Namer instance (so, not a lazy val).
+ private[this] val isScala212 = settings.isScala212
+
def createNamer(tree: Tree): Namer = {
val sym = tree match {
case ModuleDef(_, _, _) => tree.symbol.moduleClass
@@ -98,14 +103,10 @@ trait Namers extends MethodSynthesis {
else newNamer(cx)
}
- def enterValueParams(vparamss: List[List[ValDef]]): List[List[Symbol]] = {
+ def enterValueParams(vparamss: List[List[ValDef]]): List[List[Symbol]] =
mmap(vparamss) { param =>
- val sym = assignSymbol(param, param.name, mask = ValueParameterFlags)
- setPrivateWithin(param, sym)
- enterInScope(sym)
- sym setInfo monoTypeCompleter(param)
+ enterInScope(assignMemberSymbol(param, mask = ValueParameterFlags)) setInfo monoTypeCompleter(param)
}
- }
protected def owner = context.owner
def contextFile = context.unit.source.file
@@ -115,21 +116,15 @@ trait Namers extends MethodSynthesis {
TypeSigError(tree, ex)
alt
}
- // PRIVATE | LOCAL are fields generated for primary constructor arguments
- // @PP: ...or fields declared as private[this]. PARAMACCESSOR marks constructor arguments.
- // Neither gets accessors so the code is as far as I know still correct.
- def noEnterGetterSetter(vd: ValDef) = !vd.mods.isLazy && (
- !owner.isClass
- || (vd.mods.isPrivateLocal && !vd.mods.isCaseAccessor)
- || (vd.name startsWith nme.OUTER)
- || (context.unit.isJava)
- || isEnumConstant(vd)
- )
- def noFinishGetterSetter(vd: ValDef) = (
- (vd.mods.isPrivateLocal && !vd.mods.isLazy) // all lazy vals need accessors, even private[this]
- || vd.symbol.isModuleVar
- || isEnumConstant(vd))
+ // All lazy vals need accessors, including those owned by terms (e.g., in method) or private[this] in a class
+ def deriveAccessors(vd: ValDef) = (vd.mods.isLazy || owner.isTrait || (owner.isClass && deriveAccessorsInClass(vd)))
+
+ private def deriveAccessorsInClass(vd: ValDef) =
+ !vd.mods.isPrivateLocal && // note, private[this] lazy vals do get accessors -- see outer disjunction of deriveAccessors
+ !(vd.name startsWith nme.OUTER) && // outer accessors are added later, in explicitouter
+ !isEnumConstant(vd) // enums can only occur in classes, so only check here
+
/** Determines whether this field holds an enum constant.
* To qualify, the following conditions must be met:
@@ -143,7 +138,7 @@ trait Namers extends MethodSynthesis {
val ownerHasEnumFlag =
// Necessary to check because scalac puts Java's static members into the companion object
// while Scala's enum constants live directly in the class.
- // We don't check for clazz.superClass == JavaEnumClass, because this causes a illegal
+ // We don't check for clazz.superClass == JavaEnumClass, because this causes an illegal
// cyclic reference error. See the commit message for details.
if (context.unit.isJava) owner.companionClass.hasJavaEnumFlag else owner.hasJavaEnumFlag
vd.mods.hasAllFlags(JAVA_ENUM | STABLE | STATIC) && ownerHasEnumFlag
@@ -170,13 +165,9 @@ trait Namers extends MethodSynthesis {
def updatePosFlags(sym: Symbol, pos: Position, flags: Long): Symbol = {
debuglog("[overwrite] " + sym)
val newFlags = (sym.flags & LOCKED) | flags
- sym.rawInfo match {
- case tr: TypeRef =>
- // !!! needed for: pos/t5954d; the uniques type cache will happily serve up the same TypeRef
- // over this mutated symbol, and we witness a stale cache for `parents`.
- tr.invalidateCaches()
- case _ =>
- }
+ // !!! needed for: pos/t5954d; the uniques type cache will happily serve up the same TypeRef
+ // over this mutated symbol, and we witness a stale cache for `parents`.
+ invalidateCaches(sym.rawInfo, sym :: sym.moduleClass :: Nil)
sym reset NoType setFlag newFlags setPos pos
sym.moduleClass andAlso (updatePosFlags(_, pos, moduleClassFlags(flags)))
@@ -225,7 +216,10 @@ trait Namers extends MethodSynthesis {
private def inCurrentScope(m: Symbol): Boolean = {
if (owner.isClass) owner == m.owner
- else m.owner.isClass && context.scope == m.owner.info.decls
+ else context.scope.lookupSymbolEntry(m) match {
+ case null => false
+ case entry => entry.owner eq context.scope
+ }
}
/** Enter symbol into context's scope and return symbol itself */
@@ -288,9 +282,7 @@ trait Namers extends MethodSynthesis {
case tree @ DefDef(_, _, _, _, _, _) => enterDefDef(tree)
case tree @ TypeDef(_, _, _, _) => enterTypeDef(tree)
case DocDef(_, defn) => enterSym(defn)
- case tree @ Import(_, _) =>
- assignSymbol(tree)
- returnContext = context.make(tree)
+ case tree @ Import(_, _) => enterImport(tree); returnContext = context.make(tree)
case _ =>
}
returnContext
@@ -301,40 +293,23 @@ trait Namers extends MethodSynthesis {
}
}
- /** Creates a new symbol and assigns it to the tree, returning the symbol
- */
- def assignSymbol(tree: Tree): Symbol =
- logAssignSymbol(tree, tree match {
- case PackageDef(pid, _) => createPackageSymbol(tree.pos, pid)
- case Import(_, _) => createImportSymbol(tree)
- case mdef: MemberDef => createMemberSymbol(mdef, mdef.name, -1L)
- case _ => abort("Unexpected tree: " + tree)
- })
- def assignSymbol(tree: MemberDef, name: Name, mask: Long): Symbol =
- logAssignSymbol(tree, createMemberSymbol(tree, name, mask))
-
- def assignAndEnterSymbol(tree: MemberDef): Symbol = {
- val sym = assignSymbol(tree, tree.name, -1L)
+ def assignMemberSymbol(tree: MemberDef, mask: Long = -1L): Symbol = {
+ val sym = createMemberSymbol(tree, tree.name, mask)
setPrivateWithin(tree, sym)
- enterInScope(sym)
+ tree.symbol = sym
+ sym
}
+
def assignAndEnterFinishedSymbol(tree: MemberDef): Symbol = {
- val sym = assignAndEnterSymbol(tree)
+ val sym = enterInScope(assignMemberSymbol(tree))
sym setInfo completerOf(tree)
// log("[+info] " + sym.fullLocationString)
sym
}
- private def logAssignSymbol(tree: Tree, sym: Symbol): Symbol = {
- if (isPastTyper) sym.name.toTermName match {
- case nme.IMPORT | nme.OUTER | nme.ANON_CLASS_NAME | nme.ANON_FUN_NAME | nme.CONSTRUCTOR => ()
- case _ =>
- tree match {
- case md: DefDef => log("[+symbol] " + sym.debugLocationString)
- case _ =>
- }
- }
- tree.symbol = sym
+ def createMethod(accessQual: MemberDef, name: TermName, pos: Position, flags: Long): MethodSymbol = {
+ val sym = owner.newMethod(name, pos, flags)
+ setPrivateWithin(accessQual, sym)
sym
}
@@ -361,11 +336,9 @@ trait Namers extends MethodSynthesis {
else owner.newValue(name.toTermName, pos, flags)
}
}
- def createFieldSymbol(tree: ValDef): TermSymbol =
- owner.newValue(tree.localName, tree.pos, tree.mods.flags & FieldFlags | PrivateLocal)
- def createImportSymbol(tree: Tree) =
- NoSymbol.newImport(tree.pos) setInfo completerOf(tree)
+ def createImportSymbol(tree: Import) =
+ NoSymbol.newImport(tree.pos) setInfo (namerOf(tree.symbol) importTypeCompleter tree)
/** All PackageClassInfoTypes come from here. */
def createPackageSymbol(pos: Position, pid: RefTree): Symbol = {
@@ -417,7 +390,7 @@ trait Namers extends MethodSynthesis {
clearRenamedCaseAccessors(existing)
existing
}
- else assignAndEnterSymbol(tree) setFlag inConstructorFlag
+ else enterInScope(assignMemberSymbol(tree)) setFlag inConstructorFlag
}
clazz match {
case csym: ClassSymbol if csym.isTopLevel => enterClassSymbol(tree, csym)
@@ -443,6 +416,7 @@ trait Namers extends MethodSynthesis {
&& !(module isCoDefinedWith clazz)
&& module.exists
&& clazz.exists
+ && (currentRun.compiles(clazz) == currentRun.compiles(module))
)
if (fails) {
reporter.error(tree.pos, (
@@ -463,9 +437,10 @@ trait Namers extends MethodSynthesis {
/** Enter a module symbol.
*/
def enterModuleSymbol(tree : ModuleDef): Symbol = {
- var m: Symbol = context.scope lookupModule tree.name
val moduleFlags = tree.mods.flags | MODULE
- if (m.isModule && !m.hasPackageFlag && inCurrentScope(m) && (currentRun.canRedefine(m) || m.isSynthetic)) {
+
+ val existingModule = context.scope lookupModule tree.name
+ if (existingModule.isModule && !existingModule.hasPackageFlag && inCurrentScope(existingModule) && (currentRun.canRedefine(existingModule) || existingModule.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
@@ -473,21 +448,24 @@ trait Namers extends MethodSynthesis {
//
// TODO SI-4695 Pursue the approach in https://github.com/scala/scala/pull/2789 that avoids
// opening up the package object on the classpath at all if one exists in source.
- if (m.isPackageObject) {
- val packageScope = m.enclosingPackageClass.rawInfo.decls
- packageScope.filter(_.owner != m.enclosingPackageClass).toList.foreach(packageScope unlink _)
+ if (existingModule.isPackageObject) {
+ val packageScope = existingModule.enclosingPackageClass.rawInfo.decls
+ packageScope.foreach(mem => if (mem.owner != existingModule.enclosingPackageClass) packageScope unlink mem)
}
- updatePosFlags(m, tree.pos, moduleFlags)
- setPrivateWithin(tree, m)
- m.moduleClass andAlso (setPrivateWithin(tree, _))
- context.unit.synthetics -= m
- tree.symbol = m
+ updatePosFlags(existingModule, tree.pos, moduleFlags)
+ setPrivateWithin(tree, existingModule)
+ existingModule.moduleClass andAlso (setPrivateWithin(tree, _))
+ context.unit.synthetics -= existingModule
+ tree.symbol = existingModule
}
else {
- m = assignAndEnterSymbol(tree)
+ enterInScope(assignMemberSymbol(tree))
+ val m = tree.symbol
m.moduleClass setFlag moduleClassFlags(moduleFlags)
setPrivateWithin(tree, m.moduleClass)
}
+
+ val m = tree.symbol
if (m.isTopLevel && !m.hasPackageFlag) {
m.moduleClass.associatedFile = contextFile
currentRun.symSource(m) = m.moduleClass.sourceFile
@@ -610,24 +588,11 @@ trait Namers extends MethodSynthesis {
noDuplicates(selectors map (_.rename), AppearsTwice)
}
- class CompleterWrapper(completer: TypeCompleter) extends TypeCompleter {
- // override important when completer.isInstanceOf[PolyTypeCompleter]!
- override val typeParams = completer.typeParams
-
- val tree = completer.tree
-
- override def complete(sym: Symbol): Unit = {
- completer.complete(sym)
- }
- }
-
def copyMethodCompleter(copyDef: DefDef): TypeCompleter = {
- val sym = copyDef.symbol
- val lazyType = completerOf(copyDef)
-
/* Assign the types of the class parameters to the parameters of the
- * copy method. See comment in `Unapplies.caseClassCopyMeth` */
- def assignParamTypes() {
+ * copy method. See comment in `Unapplies.caseClassCopyMeth`
+ */
+ def assignParamTypes(copyDef: DefDef, sym: Symbol) {
val clazz = sym.owner
val constructorType = clazz.primaryConstructor.tpe
val subst = new SubstSymMap(clazz.typeParams, copyDef.tparams map (_.symbol))
@@ -640,9 +605,11 @@ trait Namers extends MethodSynthesis {
)
}
- mkTypeCompleter(copyDef) { sym =>
- assignParamTypes()
- lazyType complete sym
+ new CompleterWrapper(completerOf(copyDef)) {
+ override def complete(sym: Symbol): Unit = {
+ assignParamTypes(tree.asInstanceOf[DefDef], sym)
+ super.complete(sym)
+ }
}
}
@@ -654,6 +621,12 @@ trait Namers extends MethodSynthesis {
super.complete(sym)
+ // don't propagate e.g. @volatile annot to apply's argument
+ def retainOnlyParamAnnots(param: Symbol) =
+ param setAnnotations (param.annotations filter AnnotationInfo.mkFilter(ParamTargetClass, defaultRetention = false))
+
+ sym.info.paramss.foreach(_.foreach(retainOnlyParamAnnots))
+
// owner won't be locked
val ownerInfo = companionContext.owner.info
@@ -691,19 +664,16 @@ trait Namers extends MethodSynthesis {
if (suppress) {
sym setInfo ErrorType
+
// There are two ways in which we exclude the symbol from being added in typedStats::addSynthetics,
// because we don't know when the completer runs with respect to this loop in addSynthetics
// for (sym <- scope)
// for (tree <- context.unit.synthetics.get(sym) if shouldAdd(sym)) {
// if (!sym.initialize.hasFlag(IS_ERROR))
// newStats += typedStat(tree)
- // (1) If we're already in the loop, set the IS_ERROR flag and trigger the condition
- // `sym.initialize.hasFlag(IS_ERROR)` in typedStats::addSynthetics,
- // (2) Or, if we are not yet in the addSynthetics loop (and we're not going to emit an error anyway),
- // we unlink the symbol from its scope.
+ // If we're already in the loop, set the IS_ERROR flag and trigger the condition `sym.initialize.hasFlag(IS_ERROR)`
sym setFlag IS_ERROR
-
- // For good measure. Removing it from its owner's scope and setting the IS_ERROR flag is enough to exclude it from addSynthetics
+ // Or, if we are not yet in the addSynthetics loop, we can just retract our symbol from the synthetics for this unit.
companionContext.unit.synthetics -= sym
// Don't unlink in an error situation to generate less confusing error messages.
@@ -715,12 +685,12 @@ trait Namers extends MethodSynthesis {
// I hesitate to provide more info, because it would involve a WildCard or something for its result type,
// which could upset other code paths)
if (!scopePartiallyCompleted)
- companionContext.scope.unlink(sym) // (2)
+ companionContext.scope.unlink(sym)
}
}
}
- def completerOf(tree: Tree): TypeCompleter = {
+ def completerOf(tree: MemberDef): TypeCompleter = {
val mono = namerOf(tree.symbol) monoTypeCompleter tree
val tparams = treeInfo.typeParameters(tree)
if (tparams.isEmpty) mono
@@ -738,52 +708,46 @@ trait Namers extends MethodSynthesis {
}
}
- def enterValDef(tree: ValDef) {
- if (noEnterGetterSetter(tree))
- assignAndEnterFinishedSymbol(tree)
- else
- enterGetterSetter(tree)
+ def enterValDef(tree: ValDef): Unit = {
+ val isScala = !context.unit.isJava
+ if (isScala) {
+ if (nme.isSetterName(tree.name)) ValOrVarWithSetterSuffixError(tree)
+ if (tree.mods.isPrivateLocal && tree.mods.isCaseAccessor) PrivateThisCaseClassParameterError(tree)
+ }
+
+ if (isScala && deriveAccessors(tree)) enterGetterSetter(tree)
+ else assignAndEnterFinishedSymbol(tree)
- if (isEnumConstant(tree))
+ if (isEnumConstant(tree)) {
tree.symbol setInfo ConstantType(Constant(tree.symbol))
+ tree.symbol.owner.linkedClassOfClass addChild tree.symbol
+ }
}
- def enterLazyVal(tree: ValDef, lazyAccessor: Symbol): TermSymbol = {
- // If the owner is not a class, this is a lazy val from a method,
- // with no associated field. It has an accessor with $lzy appended to its name and
- // its flags are set differently. The implicit flag is reset because otherwise
- // a local implicit "lazy val x" will create an ambiguity with itself
- // via "x$lzy" as can be seen in test #3927.
- val sym = (
- if (owner.isClass) createFieldSymbol(tree)
- else owner.newValue(tree.name append nme.LAZY_LOCAL, tree.pos, (tree.mods.flags | ARTIFACT) & ~IMPLICIT)
- )
- enterValSymbol(tree, sym setFlag MUTABLE setLazyAccessor lazyAccessor)
- }
- def enterStrictVal(tree: ValDef): TermSymbol = {
- enterValSymbol(tree, createFieldSymbol(tree))
- }
- def enterValSymbol(tree: ValDef, sym: TermSymbol): TermSymbol = {
- enterInScope(sym)
- sym setInfo namerOf(sym).monoTypeCompleter(tree)
- }
def enterPackage(tree: PackageDef) {
- val sym = assignSymbol(tree)
+ val sym = createPackageSymbol(tree.pos, tree.pid)
+ tree.symbol = sym
newNamer(context.make(tree, sym.moduleClass, sym.info.decls)) enterSyms tree.stats
}
+
+ private def enterImport(tree: Import) = {
+ val sym = createImportSymbol(tree)
+ tree.symbol = sym
+ }
+
def enterTypeDef(tree: TypeDef) = assignAndEnterFinishedSymbol(tree)
def enterDefDef(tree: DefDef): Unit = tree match {
case DefDef(_, nme.CONSTRUCTOR, _, _, _, _) =>
assignAndEnterFinishedSymbol(tree)
- case DefDef(mods, name, tparams, _, _, _) =>
+ case DefDef(mods, name, _, _, _, _) =>
val bridgeFlag = if (mods hasAnnotationNamed tpnme.bridgeAnnot) BRIDGE | ARTIFACT else 0
- val sym = assignAndEnterSymbol(tree) setFlag bridgeFlag
+ val sym = enterInScope(assignMemberSymbol(tree)) setFlag bridgeFlag
val completer =
if (sym hasFlag SYNTHETIC) {
if (name == nme.copy) copyMethodCompleter(tree)
- else if (settings.isScala212 && (sym hasFlag CASE)) applyUnapplyMethodCompleter(tree, context)
+ else if (sym hasFlag CASE) applyUnapplyMethodCompleter(tree, context)
else completerOf(tree)
} else completerOf(tree)
@@ -857,16 +821,20 @@ trait Namers extends MethodSynthesis {
NoSymbol
}
- def monoTypeCompleter(tree: Tree) = mkTypeCompleter(tree) { sym =>
- // this early test is there to avoid infinite baseTypes when
- // adding setters and getters --> bug798
- // It is a def in an attempt to provide some insulation against
- // uninitialized symbols misleading us. It is not a certainty
- // this accomplishes anything, but performance is a non-consideration
- // on these flag checks so it can't hurt.
- def needsCycleCheck = sym.isNonClassType && !sym.isParameter && !sym.isExistential
- logAndValidate(sym) {
- val tp = typeSig(tree)
+ def monoTypeCompleter(tree: MemberDef) = new MonoTypeCompleter(tree)
+ class MonoTypeCompleter(tree: MemberDef) extends TypeCompleterBase(tree) {
+ override def completeImpl(sym: Symbol): Unit = {
+ // this early test is there to avoid infinite baseTypes when
+ // adding setters and getters --> bug798
+ // It is a def in an attempt to provide some insulation against
+ // uninitialized symbols misleading us. It is not a certainty
+ // this accomplishes anything, but performance is a non-consideration
+ // on these flag checks so it can't hurt.
+ def needsCycleCheck = sym.isNonClassType && !sym.isParameter && !sym.isExistential
+
+ val annotations = annotSig(tree.mods.annotations)
+
+ val tp = typeSig(tree, annotations)
findCyclicalLowerBound(tp) andAlso { sym =>
if (needsCycleCheck) {
@@ -877,42 +845,175 @@ trait Namers extends MethodSynthesis {
sym.initialize
}
}
- sym setInfo {
- if (sym.isJavaDefined) RestrictJavaArraysMap(tp)
- else tp
- }
+
+ sym.setInfo(if (!sym.isJavaDefined) tp else RestrictJavaArraysMap(tp))
+
if (needsCycleCheck) {
log(s"Needs cycle check: ${sym.debugLocationString}")
if (!typer.checkNonCyclic(tree.pos, tp))
sym setInfo ErrorType
}
+
+ validate(sym)
}
}
- def moduleClassTypeCompleter(tree: ModuleDef) = {
- mkTypeCompleter(tree) { sym =>
+ def moduleClassTypeCompleter(tree: ModuleDef) = new ModuleClassTypeCompleter(tree)
+ class ModuleClassTypeCompleter(tree: ModuleDef) extends TypeCompleterBase(tree) {
+ override def completeImpl(sym: Symbol): Unit = {
val moduleSymbol = tree.symbol
assert(moduleSymbol.moduleClass == sym, moduleSymbol.moduleClass)
moduleSymbol.info // sets moduleClass info as a side effect.
}
}
- /* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */
- def accessorTypeCompleter(tree: ValDef, isSetter: Boolean) = mkTypeCompleter(tree) { sym =>
- logAndValidate(sym) {
- sym setInfo {
- val tp = if (isSetter) MethodType(List(sym.newSyntheticValueParam(typeSig(tree))), UnitTpe)
- else NullaryMethodType(typeSig(tree))
- pluginsTypeSigAccessor(tp, typer, tree, sym)
+ def importTypeCompleter(tree: Import) = new ImportTypeCompleter(tree)
+ class ImportTypeCompleter(imp: Import) extends TypeCompleterBase(imp) {
+ override def completeImpl(sym: Symbol): Unit = {
+ sym setInfo importSig(imp)
+ }
+ }
+
+ import AnnotationInfo.{mkFilter => annotationFilter}
+
+ def implicitFactoryMethodCompleter(tree: DefDef, classSym: Symbol) = new CompleterWrapper(completerOf(tree)) {
+ override def complete(methSym: Symbol): Unit = {
+ super.complete(methSym)
+ val annotations = classSym.initialize.annotations
+
+ methSym setAnnotations (annotations filter annotationFilter(MethodTargetClass, defaultRetention = false))
+ classSym setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true))
+ }
+ }
+
+ // complete the type of a value definition (may have a method symbol, for those valdefs that never receive a field,
+ // as specified by Field.noFieldFor)
+ def valTypeCompleter(tree: ValDef) = new ValTypeCompleter(tree)
+ class ValTypeCompleter(tree: ValDef) extends TypeCompleterBase(tree) {
+ override def completeImpl(fieldOrGetterSym: Symbol): Unit = {
+ val mods = tree.mods
+ val isGetter = fieldOrGetterSym.isMethod
+ val annots =
+ if (mods.annotations.isEmpty) Nil
+ else {
+ val annotSigs = annotSig(mods.annotations)
+ if (isGetter) filterAccessorAnnots(annotSigs, tree) // if this is really a getter, retain annots targeting either field/getter
+ else annotSigs filter annotationFilter(FieldTargetClass, !mods.isParamAccessor)
+ }
+
+ // must use typeSig, not memberSig (TODO: when do we need to switch namers?)
+ val sig = typeSig(tree, annots)
+
+ fieldOrGetterSym setInfo (if (isGetter) NullaryMethodType(sig) else sig)
+
+ validate(fieldOrGetterSym)
+ }
+ }
+
+ // knowing `isBean`, we could derive `isSetter` from `valDef.name`
+ def accessorTypeCompleter(valDef: ValDef, missingTpt: Boolean, isBean: Boolean, isSetter: Boolean) = new AccessorTypeCompleter(valDef, missingTpt, isBean, isSetter)
+ class AccessorTypeCompleter(valDef: ValDef, missingTpt: Boolean, isBean: Boolean, isSetter: Boolean) extends TypeCompleterBase(valDef) {
+ override def completeImpl(accessorSym: Symbol): Unit = {
+ context.unit.synthetics get accessorSym match {
+ case Some(ddef: DefDef) =>
+ // `accessorSym` is the accessor for which we're completing the info (tree == ddef),
+ // while `valDef` is the field definition that spawned the accessor
+ // NOTE: `valTypeCompleter` handles abstract vals, trait vals and lazy vals, where the ValDef carries the getter's symbol
+
+ // reuse work done in valTypeCompleter if we already computed the type signature of the val
+ // (assuming the field and accessor symbols are distinct -- i.e., we're not in a trait)
+ val valSig =
+ if ((accessorSym ne valDef.symbol) && valDef.symbol.isInitialized) valDef.symbol.info
+ else typeSig(valDef, Nil) // don't set annotations for the valdef -- we just want to compute the type sig (TODO: dig deeper and see if we can use memberSig)
+
+ // patch up the accessor's tree if the valdef's tpt was not known back when the tree was synthesized
+ // can't look at `valDef.tpt` here because it may have been completed by now (this is why we pass in `missingTpt`)
+ // HACK: a param accessor `ddef.tpt.tpe` somehow gets out of whack with `accessorSym.info`, so always patch it back...
+ // (the tpt is typed in the wrong namer, using the class as owner instead of the outer context, which is where param accessors should be typed)
+ if (missingTpt || accessorSym.isParamAccessor) {
+ if (!isSetter) ddef.tpt setType valSig
+ else if (ddef.vparamss.nonEmpty && ddef.vparamss.head.nonEmpty) ddef.vparamss.head.head.tpt setType valSig
+ else throw new TypeError(valDef.pos, s"Internal error: could not complete parameter/return type for $ddef from $accessorSym")
+ }
+
+ val mods = valDef.mods
+ val annots =
+ if (mods.annotations.isEmpty) Nil
+ else filterAccessorAnnots(annotSig(mods.annotations), valDef, isSetter, isBean)
+
+ // for a setter, call memberSig to attribute the parameter (for a bean, we always use the regular method sig completer since they receive method types)
+ // for a regular getter, make sure it gets a NullaryMethodType (also, no need to recompute it: we already have the valSig)
+ val sig =
+ if (isSetter || isBean) typeSig(ddef, annots)
+ else {
+ if (annots.nonEmpty) annotate(accessorSym, annots)
+
+ NullaryMethodType(valSig)
+ }
+
+ accessorSym setInfo pluginsTypeSigAccessor(sig, typer, valDef, accessorSym)
+
+ if (!isBean && accessorSym.isOverloaded)
+ if (isSetter) ddef.rhs.setType(ErrorType)
+ else GetterDefinedTwiceError(accessorSym)
+
+ validate(accessorSym)
+
+ case _ =>
+ throw new TypeError(valDef.pos, s"Internal error: no synthetic tree found for bean accessor $accessorSym")
}
}
}
- def selfTypeCompleter(tree: Tree) = mkTypeCompleter(tree) { sym =>
- val selftpe = typer.typedType(tree).tpe
- sym setInfo {
- if (selftpe.typeSymbol isNonBottomSubClass sym.owner) selftpe
- else intersectionType(List(sym.owner.tpe, selftpe))
+ // see scala.annotation.meta's package class for more info
+ // Annotations on ValDefs can be targeted towards the following: field, getter, setter, beanGetter, beanSetter, param.
+ // The defaults are:
+ // - (`val`-, `var`- or plain) constructor parameter annotations end up on the parameter, not on any other entity.
+ // - val/var member annotations solely end up on the underlying field, except in traits and for all lazy vals (@since 2.12),
+ // where there is no field, and the getter thus holds annotations targeting both getter & field.
+ // As soon as there is a field/getter (in subclasses mixing in the trait, or after expanding the lazy val during the fields phase),
+ // we triage the annotations.
+ //
+ // TODO: these defaults can be surprising for annotations not meant for accessors/fields -- should we revisit?
+ // (In order to have `@foo val X` result in the X getter being annotated with `@foo`, foo needs to be meta-annotated with @getter)
+ private def filterAccessorAnnots(annotSigs: List[global.AnnotationInfo], tree: global.ValDef, isSetter: Boolean = false, isBean: Boolean = false): List[AnnotationInfo] = {
+ val mods = tree.mods
+ if (!isBean) {
+ // neg/t3403: check that we didn't get a sneaky type alias/renamed import that we couldn't detect because we only look at names during synthesis
+ // (TODO: can we look at symbols earlier?)
+ if (!((mods hasAnnotationNamed tpnme.BeanPropertyAnnot) || (mods hasAnnotationNamed tpnme.BooleanBeanPropertyAnnot))
+ && annotSigs.exists(ann => (ann.matches(BeanPropertyAttr)) || ann.matches(BooleanBeanPropertyAttr)))
+ BeanPropertyAnnotationLimitationError(tree)
+ }
+
+ val canTriageAnnotations = isSetter || !fields.getterTreeAnnotationsTargetFieldAndGetter(owner, mods)
+
+ def filterAccessorAnnotations: AnnotationInfo => Boolean =
+ if (canTriageAnnotations)
+ annotationFilter(if (isSetter) SetterTargetClass else GetterTargetClass, defaultRetention = false)
+ else (ann =>
+ annotationFilter(FieldTargetClass, defaultRetention = true)(ann) ||
+ annotationFilter(GetterTargetClass, defaultRetention = true)(ann))
+
+ def filterBeanAccessorAnnotations: AnnotationInfo => Boolean =
+ if (canTriageAnnotations)
+ annotationFilter(if (isSetter) BeanSetterTargetClass else BeanGetterTargetClass, defaultRetention = false)
+ else (ann =>
+ annotationFilter(FieldTargetClass, defaultRetention = true)(ann) ||
+ annotationFilter(BeanGetterTargetClass, defaultRetention = true)(ann))
+
+ annotSigs filter (if (isBean) filterBeanAccessorAnnotations else filterAccessorAnnotations)
+ }
+
+
+ def selfTypeCompleter(tree: Tree) = new SelfTypeCompleter(tree)
+ class SelfTypeCompleter(tree: Tree) extends TypeCompleterBase(tree) {
+ override def completeImpl(sym: Symbol): Unit = {
+ val selftpe = typer.typedType(tree).tpe
+ sym setInfo {
+ if (selftpe.typeSymbol isNonBottomSubClass sym.owner) selftpe
+ else intersectionType(List(sym.owner.tpe, selftpe))
+ }
}
}
@@ -948,13 +1049,14 @@ trait Namers extends MethodSynthesis {
!tpe.typeSymbolDirect.isModuleClass // Infer Foo.type instead of "object Foo"
&& (tpe.widen <:< pt) // Don't widen our way out of conforming to pt
&& ( sym.isVariable
- || sym.isMethod && !sym.hasAccessorFlag
+ || sym.hasFlag(ACCESSOR) && !sym.hasFlag(STABLE)
+ || sym.isMethod && !sym.hasFlag(ACCESSOR)
|| isHidden(tpe)
)
)
dropIllegalStarTypes(
if (shouldWiden) tpe.widen
- else if (sym.isFinal) tpe // "final val" allowed to retain constant type
+ else if (sym.isFinal && !sym.isLazy) tpe // "final val" allowed to retain constant type
else tpe.deconst
)
}
@@ -1105,6 +1207,19 @@ trait Namers extends MethodSynthesis {
clazz.tpe_*
}
+
+ // make a java method type if meth.isJavaDefined
+ private def methodTypeFor(meth: Symbol, vparamSymss: List[List[Symbol]], restpe: Type) = {
+ def makeJavaMethodType(vparams: List[Symbol], restpe: Type) = {
+ vparams foreach (p => p setInfo objToAny(p.tpe))
+ JavaMethodType(vparams, restpe)
+ }
+ if (vparamSymss.isEmpty) NullaryMethodType(restpe)
+ else if (meth.isJavaDefined) vparamSymss.foldRight(restpe)(makeJavaMethodType)
+ else vparamSymss.foldRight(restpe)(MethodType(_, _))
+ }
+
+
/**
* The method type for `ddef`.
*
@@ -1122,166 +1237,140 @@ trait Namers extends MethodSynthesis {
* to the non-skolems.
*/
private def methodSig(ddef: DefDef): Type = {
-
- // DEPMETTODO: do we need to skolemize value parameter symbols?
-
val DefDef(_, _, tparams, vparamss, tpt, _) = ddef
val meth = owner
val methOwner = meth.owner
- val site = methOwner.thisType
/* tparams already have symbols (created in enterDefDef/completerOf), namely the skolemized ones (created
* by the PolyTypeCompleter constructor, and assigned to tparams). reenterTypeParams enters the type skolems
* into scope and returns the non-skolems.
*/
val tparamSyms = typer.reenterTypeParams(tparams)
-
val tparamSkolems = tparams.map(_.symbol)
- /* since the skolemized tparams are in scope, the TypeRefs in types of vparamSymss refer to the type skolems
- * note that for parameters with missing types, `methodSig` reassigns types of these symbols (the parameter
- * types from the overridden method).
- */
- var vparamSymss = enterValueParams(vparamss)
-
/*
* Creates a method type using tparamSyms and vparamsSymss as argument symbols and `respte` as result type.
* All typeRefs to type skolems are replaced by references to the corresponding non-skolem type parameter,
* so the resulting type is a valid external method type, it does not contain (references to) skolems.
+ *
+ * tparamSyms are deskolemized symbols -- TODO: check that their infos don't refer to method args?
+ * vparamss refer (if they do) to skolemized tparams
*/
- def thisMethodType(restpe: Type) = {
- if (vparamSymss.lengthCompare(0) > 0) { // OPT fast path for methods of 0-1 parameter lists
- val checkDependencies = new DependentTypeChecker(context)(this)
- checkDependencies check vparamSymss
- }
-
- val makeMethodType = (vparams: List[Symbol], restpe: Type) => {
- // TODODEPMET: check that we actually don't need to do anything here
- // new dependent method types: probably OK already, since 'enterValueParams' above
- // enters them in scope, and all have a lazy type. so they may depend on other params. but: need to
- // check that params only depend on ones in earlier sections, not the same. (done by checkDependencies,
- // so re-use / adapt that)
- if (meth.isJavaDefined)
- // TODODEPMET necessary?? new dependent types: replace symbols in restpe with the ones in vparams
- JavaMethodType(vparams map (p => p setInfo objToAny(p.tpe)), restpe)
- else
- MethodType(vparams, restpe)
- }
+ def deskolemizedPolySig(vparamSymss: List[List[Symbol]], restpe: Type) =
+ GenPolyType(tparamSyms, methodTypeFor(meth, vparamSymss, restpe).substSym(tparamSkolems, tparamSyms))
- val res = GenPolyType(
- tparamSyms, // deSkolemized symbols -- TODO: check that their infos don't refer to method args?
- if (vparamSymss.isEmpty) NullaryMethodType(restpe)
- // vparamss refer (if they do) to skolemized tparams
- else (vparamSymss :\ restpe) (makeMethodType)
- )
- res.substSym(tparamSkolems, tparamSyms)
+ if (tpt.isEmpty && meth.name == nme.CONSTRUCTOR) {
+ tpt defineType context.enclClass.owner.tpe_*
+ tpt setPos meth.pos.focus
}
+ /* since the skolemized tparams are in scope, the TypeRefs in types of vparamSymss refer to the type skolems
+ * note that for parameters with missing types, `methodSig` reassigns types of these symbols (the parameter
+ * types from the overridden method).
+ */
+ val vparamSymss: List[List[Symbol]] = enterValueParams(vparamss)
+
+ val resTpGiven =
+ if (tpt.isEmpty) WildcardType
+ else typer.typedType(tpt).tpe
+
+
+ // ignore missing types unless we can look to overridden method to recover the missing information
+ val canOverride = methOwner.isClass && !meth.isConstructor
+ val inferResTp = canOverride && tpt.isEmpty
+ val inferArgTp = canOverride && settings.YmethodInfer && mexists(vparamss)(_.tpt.isEmpty)
+
+
/*
- * Creates a schematic method type which has WildcardTypes for non specified
- * return or parameter types. For instance, in `def f[T](a: T, b) = ...`, the
- * type schema is
+ * Find the overridden method that matches a schematic method type,
+ * which has WildcardTypes for unspecified return or parameter types.
+ * For instance, in `def f[T](a: T, b) = ...`, the type schema is
*
* PolyType(T, MethodType(List(a: T, b: WildcardType), WildcardType))
*
* where T are non-skolems.
+ *
+ * NOTE: mutates info of symbol of vparamss that don't specify a type
*/
- def methodTypeSchema(resTp: Type) = {
- // for all params without type set WildcaradType
- mforeach(vparamss)(v => if (v.tpt.isEmpty) v.symbol setInfo WildcardType)
- thisMethodType(resTp)
- }
-
- def overriddenSymbol(resTp: Type) = {
- lazy val schema: Type = methodTypeSchema(resTp) // OPT create once. Must be lazy to avoid cycles in neg/t5093.scala
- intersectionType(methOwner.info.parents).nonPrivateMember(meth.name).filter { sym =>
- sym != NoSymbol && (site.memberType(sym) matches schema)
+ val methodSigApproxUnknownArgs: () => Type =
+ if (!inferArgTp) () => deskolemizedPolySig(vparamSymss, resTpGiven)
+ else () => {
+ // for all params without type set WildcardType
+ mforeach(vparamss)(v => if (v.tpt.isEmpty) v.symbol setInfo WildcardType)
+ // must wait to call deskolemizedPolySig until we've temporarily set the WildcardType info for the vparamSymss
+ // (Otherwise, valDefSig will complain about missing argument types.)
+ deskolemizedPolySig(vparamSymss, resTpGiven)
}
- }
- // TODO: see whether this or something similar would work instead:
- // def overriddenSymbol = meth.nextOverriddenSymbol
+ // Must be lazy about the schema to avoid cycles in neg/t5093.scala
+ val overridden =
+ if (!canOverride) NoSymbol
+ else safeNextOverriddenSymbolLazySchema(meth, methodSigApproxUnknownArgs)
/*
- * If `meth` doesn't have an explicit return type, extracts the return type from the method
- * overridden by `meth` (if there's an unique one). This type is lateron used as the expected
+ * If `meth` doesn't have an explicit return type, extract the return type from the method
+ * overridden by `meth` (if there's an unique one). This type is later used as the expected
* type for computing the type of the rhs. The resulting type references type skolems for
* type parameters (consistent with the result of `typer.typedType(tpt).tpe`).
*
- * As a first side effect, this method assigns a MethodType constructed using this
- * return type to `meth`. This allows omitting the result type for recursive methods.
+ * If the result type is missing, assign a MethodType to `meth` that's constructed using this return type.
+ * This allows omitting the result type for recursive methods.
*
- * As another side effect, this method also assigns parameter types from the overridden
- * method to parameters of `meth` that have missing types (the parser accepts missing
- * parameter types under -Yinfer-argument-types).
+ * Missing parameter types are also recovered from the overridden method (by mutating the info of their symbols).
+ * (The parser accepts missing parameter types under -Yinfer-argument-types.)
*/
- def typesFromOverridden(methResTp: Type): Type = {
- val overridden = overriddenSymbol(methResTp)
- if (overridden == NoSymbol || overridden.isOverloaded) {
- methResTp
- } else {
+ val resTpFromOverride =
+ if (!(inferArgTp || inferResTp) || overridden == NoSymbol || overridden.isOverloaded) resTpGiven
+ else {
overridden.cookJavaRawInfo() // #3404 xform java rawtypes into existentials
- var overriddenTp = site.memberType(overridden) match {
- case PolyType(tparams, rt) => rt.substSym(tparams, tparamSkolems)
- case mt => mt
+
+ val (overriddenTparams, overriddenTp) =
+ methOwner.thisType.memberType(overridden) match {
+ case PolyType(tparams, mt) => (tparams, mt.substSym(tparams, tparamSkolems))
+ case mt => (Nil, mt)
}
- for (vparams <- vparamss) {
- var overriddenParams = overriddenTp.params
- for (vparam <- vparams) {
+
+ // try to derive empty parameter types from the overridden method's argument types
+ if (inferArgTp) {
+ val overriddenSyms = overriddenTparams ++ overridden.paramss.flatten
+ val ourSyms = tparamSkolems ++ vparamSymss.flatten
+ foreach2(vparamss, overridden.paramss) { foreach2(_, _) { (vparam, overriddenParam) =>
+ // println(s"infer ${vparam.symbol} from ${overriddenParam}? ${vparam.tpt}")
if (vparam.tpt.isEmpty) {
- val overriddenParamTp = overriddenParams.head.tpe
+ val overriddenParamTp = overriddenParam.tpe.substSym(overriddenSyms, ourSyms)
+ // println(s"inferred ${vparam.symbol} : $overriddenParamTp")
// references to type parameters in overriddenParamTp link to the type skolems, so the
// assigned type is consistent with the other / existing parameter types in vparamSymss.
vparam.symbol setInfo overriddenParamTp
vparam.tpt defineType overriddenParamTp setPos vparam.pos.focus
}
- overriddenParams = overriddenParams.tail
- }
- overriddenTp = overriddenTp.resultType
+ }}
}
- // SI-7668 Substitute parameters from the parent method with those of the overriding method.
- overriddenTp = overriddenTp.substSym(overridden.paramss.flatten, vparamss.flatten.map(_.symbol))
+ @tailrec @inline def applyFully(tp: Type, paramss: List[List[Symbol]]): Type =
+ if (paramss.isEmpty) tp match {
+ case NullaryMethodType(rtpe) => rtpe
+ case MethodType(Nil, rtpe) => rtpe
+ case tp => tp
+ }
+ else applyFully(tp.resultType(paramss.head.map(_.tpe)), paramss.tail)
- overriddenTp match {
- case NullaryMethodType(rtpe) => overriddenTp = rtpe
- case MethodType(List(), rtpe) => overriddenTp = rtpe
- case _ =>
- }
+ if (inferResTp) {
+ // SI-7668 Substitute parameters from the parent method with those of the overriding method.
+ val overriddenResTp = applyFully(overriddenTp, vparamSymss).substSym(overriddenTparams, tparamSkolems)
- if (tpt.isEmpty) {
// provisionally assign `meth` a method type with inherited result type
// that way, we can leave out the result type even if method is recursive.
- meth setInfo thisMethodType(overriddenTp)
- overriddenTp
- } else {
- methResTp
- }
+ meth setInfo deskolemizedPolySig(vparamSymss, overriddenResTp)
+ overriddenResTp
+ } else resTpGiven
}
- }
-
- if (tpt.isEmpty && meth.name == nme.CONSTRUCTOR) {
- tpt defineType context.enclClass.owner.tpe_*
- tpt setPos meth.pos.focus
- }
-
- val methResTp = if (tpt.isEmpty) WildcardType else typer.typedType(tpt).tpe
- val resTpFromOverride = if (methOwner.isClass && (tpt.isEmpty || mexists(vparamss)(_.tpt.isEmpty))) {
- typesFromOverridden(methResTp)
- } else {
- methResTp
- }
-
- // Add a () parameter section if this overrides some method with () parameters
- if (methOwner.isClass && vparamss.isEmpty &&
- overriddenSymbol(methResTp).alternatives.exists(_.info.isInstanceOf[MethodType])) {
- vparamSymss = ListOfNil
- }
// issue an error for missing parameter types
+ // (computing resTpFromOverride may have required inferring some, meanwhile)
mforeach(vparamss) { vparam =>
if (vparam.tpt.isEmpty) {
MissingParameterOrValTypeError(vparam)
@@ -1289,13 +1378,9 @@ trait Namers extends MethodSynthesis {
}
}
- val overridden = {
- val isConstr = meth.isConstructor
- if (isConstr || !methOwner.isClass) NoSymbol else overriddenSymbol(methResTp)
- }
- val hasDefaults = mexists(vparamss)(_.symbol.hasDefault) || mexists(overridden.paramss)(_.hasDefault)
- if (hasDefaults)
- addDefaultGetters(meth, ddef, vparamss, tparams, overridden)
+ // If we, or the overridden method has defaults, add getters for them
+ if (mexists(vparamss)(_.symbol.hasDefault) || mexists(overridden.paramss)(_.hasDefault))
+ addDefaultGetters(meth, ddef, vparamss, tparams, overridden)
// fast track macros, i.e. macros defined inside the compiler, are hardcoded
// hence we make use of that and let them have whatever right-hand side they need
@@ -1306,27 +1391,35 @@ trait Namers extends MethodSynthesis {
// because @macroImpl annotation only gets assigned during typechecking
// otherwise macro defs wouldn't be able to robustly coexist with their clients
// because a client could be typechecked before a macro def that it uses
- if (meth.isMacro) {
- typer.computeMacroDefType(ddef, resTpFromOverride)
+ if (meth.isMacro) typer.computeMacroDefType(ddef, resTpFromOverride) // note: `pt` argument ignored in `computeMacroDefType`
+
+ if (vparamSymss.lengthCompare(0) > 0) { // OPT fast path for methods of 0-1 parameter lists
+ val checkDependencies = new DependentTypeChecker(context)(this)
+ checkDependencies check vparamSymss
}
- val res = thisMethodType({
- val rt = (
- if (!tpt.isEmpty) {
- methResTp
- } else {
- // return type is inferred, we don't just use resTpFromOverride. Here, C.f has type String:
- // trait T { def f: Object }; class C <: T { def f = "" }
- // using resTpFromOverride as expected type allows for the following (C.f has type A):
- // trait T { def f: A }; class C <: T { implicit def b2a(t: B): A = ???; def f = new B }
- assignTypeToTree(ddef, typer, resTpFromOverride)
- })
+ val resTp = {
+ // When return type is inferred, we don't just use resTpFromOverride -- it must be packed and widened.
+ // Here, C.f has type String:
+ // trait T { def f: Object }; class C extends T { def f = "" }
+ // using resTpFromOverride as expected type allows for the following (C.f has type A):
+ // trait T { def f: A }; class C extends T { implicit def b2a(t: B): A = ???; def f = new B }
+ val resTpComputedUnlessGiven =
+ if (tpt.isEmpty) assignTypeToTree(ddef, typer, resTpFromOverride)
+ else resTpGiven
+
// #2382: return type of default getters are always @uncheckedVariance
- if (meth.hasDefault)
- rt.withAnnotation(AnnotationInfo(uncheckedVarianceClass.tpe, List(), List()))
- else rt
- })
- pluginsTypeSig(res, typer, ddef, methResTp)
+ if (meth.hasDefault) resTpComputedUnlessGiven.withAnnotation(AnnotationInfo(uncheckedVarianceClass.tpe, List(), List()))
+ else resTpComputedUnlessGiven
+ }
+
+ // Add a () parameter section if this overrides some method with () parameters
+ val vparamSymssOrEmptyParamsFromOverride =
+ if (overridden != NoSymbol && vparamSymss.isEmpty && overridden.alternatives.exists(_.info.isInstanceOf[MethodType])) ListOfNil // NOTE: must check `.info.isInstanceOf[MethodType]`, not `.isMethod`!
+ else vparamSymss
+
+ val methSig = deskolemizedPolySig(vparamSymssOrEmptyParamsFromOverride, resTp)
+ pluginsTypeSig(methSig, typer, ddef, resTpGiven)
}
/**
@@ -1459,7 +1552,7 @@ trait Namers extends MethodSynthesis {
val defRhs = rvparam.rhs
val defaultTree = atPos(vparam.pos.focus) {
- DefDef(Modifiers(paramFlagsToDefaultGetter(meth.flags)) | oflag, name, defTparams, defVparamss, defTpt, defRhs)
+ DefDef(Modifiers(paramFlagsToDefaultGetter(meth.flags), ddef.mods.privateWithin) | oflag, name, defTparams, defVparamss, defTpt, defRhs)
}
if (!isConstr)
methOwner.resetFlag(INTERFACE) // there's a concrete member now
@@ -1482,19 +1575,78 @@ trait Namers extends MethodSynthesis {
private def valDefSig(vdef: ValDef) = {
val ValDef(_, _, tpt, rhs) = vdef
- val result = if (tpt.isEmpty) {
- if (rhs.isEmpty) {
- MissingParameterOrValTypeError(tpt)
- ErrorType
- }
- else assignTypeToTree(vdef, typer, WildcardType)
- } else {
- typer.typedType(tpt).tpe
- }
+ val result =
+ if (tpt.isEmpty) {
+ if (rhs.isEmpty) {
+ MissingParameterOrValTypeError(tpt)
+ ErrorType
+ } else {
+ // enterGetterSetter assigns the getter's symbol to a ValDef when there's no underlying field
+ // (a deferred val or most vals defined in a trait -- see Field.noFieldFor)
+ val isGetter = vdef.symbol hasFlag ACCESSOR
+
+ val pt = {
+ val valOwner = owner.owner
+ // there's no overriding outside of classes, and we didn't use to do this in 2.11, so provide opt-out
+
+ if (!isScala212 || !valOwner.isClass) WildcardType
+ else {
+ // normalize to getter so that we correctly consider a val overriding a def
+ // (a val's name ends in a " ", so can't compare to def)
+ val overridingSym = if (isGetter) vdef.symbol else vdef.symbol.getterIn(valOwner)
+
+ // We're called from an accessorTypeCompleter, which is completing the info for the accessor's symbol,
+ // which may or may not be `vdef.symbol` (see isGetter above)
+ val overridden = safeNextOverriddenSymbol(overridingSym)
+
+ if (overridden == NoSymbol || overridden.isOverloaded) WildcardType
+ else valOwner.thisType.memberType(overridden).resultType
+ }
+ }
+
+ def patchSymInfo(tp: Type): Unit =
+ if (pt ne WildcardType) // no patching up to do if we didn't infer a prototype
+ vdef.symbol setInfo (if (isGetter) NullaryMethodType(tp) else tp)
+
+ patchSymInfo(pt)
+
+ // derives the val's result type from type checking its rhs under the expected type `pt`
+ // vdef.tpt is mutated, and `vdef.tpt.tpe` is `assignTypeToTree`'s result
+ val tptFromRhsUnderPt = assignTypeToTree(vdef, typer, pt)
+
+ // need to re-align with assignTypeToTree, as the type we're returning from valDefSig (tptFromRhsUnderPt)
+ // may actually go to the accessor, not the valdef (and if assignTypeToTree returns a subtype of `pt`,
+ // we would be out of synch between field and its accessors), and thus the type completer won't
+ // fix the symbol's info for us -- we set it to tmpInfo above, which may need to be improved to tptFromRhsUnderPt
+ if (!isGetter) patchSymInfo(tptFromRhsUnderPt)
+
+ tptFromRhsUnderPt
+ }
+ } else typer.typedType(tpt).tpe
+
+// println(s"val: $result / ${vdef.tpt.tpe} / ")
+
pluginsTypeSig(result, typer, vdef, if (tpt.isEmpty) WildcardType else result)
+ }
+ // Pretend we're an erroneous symbol, for now, so that we match while finding the overridden symbol,
+ // but are not considered during implicit search.
+ private def safeNextOverriddenSymbol(sym: Symbol, schema: Type = ErrorType): Symbol = {
+ val savedInfo = sym.rawInfo
+ val savedFlags = sym.rawflags
+ try {
+ sym setInfo schema
+ sym.nextOverriddenSymbol
+ } finally {
+ sym setInfo savedInfo // setInfo resets the LOCKED flag, so restore saved flags as well
+ sym.rawflags = savedFlags
+ }
}
+ private def safeNextOverriddenSymbolLazySchema(sym: Symbol, schema: () => Type): Symbol =
+ safeNextOverriddenSymbol(sym, new LazyType { override def complete(sym: Symbol): Unit = sym setInfo schema() })
+
+
//@M! an abstract type definition (abstract type member/type parameter)
// may take type parameters, which are in scope in its bounds
private def typeDefSig(tdef: TypeDef) = {
@@ -1590,67 +1742,52 @@ trait Namers extends MethodSynthesis {
* is then assigned to the corresponding symbol (typeSig itself does not need to assign
* the type to the symbol, but it can if necessary).
*/
- def typeSig(tree: Tree): Type = {
- // log("typeSig " + tree)
- /* For definitions, transform Annotation trees to AnnotationInfos, assign
- * them to the sym's annotations. Type annotations: see Typer.typedAnnotated
- * We have to parse definition annotations here (not in the typer when traversing
- * the MemberDef tree): the typer looks at annotations of certain symbols; if
- * they were added only in typer, depending on the compilation order, they may
- * or may not be visible.
- */
- def annotate(annotated: Symbol) = {
- // typeSig might be called multiple times, e.g. on a ValDef: val, getter, setter
- // parse the annotations only once.
- if (!annotated.isInitialized) tree match {
- case defn: MemberDef =>
- val ainfos = defn.mods.annotations filterNot (_ eq null) map { ann =>
- val ctx = typer.context
- val annCtx = ctx.makeNonSilent(ann)
- // need to be lazy, #1782. beforeTyper to allow inferView in annotation args, SI-5892.
- AnnotationInfo lazily {
- enteringTyper(newTyper(annCtx) typedAnnotation ann)
- }
- }
- if (ainfos.nonEmpty) {
- annotated setAnnotations ainfos
- if (annotated.isTypeSkolem)
- annotated.deSkolemize setAnnotations ainfos
- }
- case _ =>
+ def typeSig(tree: Tree, annotSigs: List[AnnotationInfo]): Type = {
+ if (annotSigs.nonEmpty) annotate(tree.symbol, annotSigs)
+
+ try tree match {
+ case member: MemberDef => createNamer(tree).memberSig(member)
+ case imp: Import => importSig(imp)
+ } catch typeErrorHandler(tree, ErrorType)
+ }
+
+ /* For definitions, transform Annotation trees to AnnotationInfos, assign
+ * them to the sym's annotations. Type annotations: see Typer.typedAnnotated
+ * We have to parse definition annotations here (not in the typer when traversing
+ * the MemberDef tree): the typer looks at annotations of certain symbols; if
+ * they were added only in typer, depending on the compilation order, they may
+ * or may not be visible.
+ */
+ def annotSig(annotations: List[Tree]): List[AnnotationInfo] =
+ annotations filterNot (_ eq null) map { ann =>
+ val ctx = typer.context
+ // need to be lazy, #1782. enteringTyper to allow inferView in annotation args, SI-5892.
+ AnnotationInfo lazily {
+ enteringTyper {
+ newTyper(ctx.makeNonSilent(ann)) typedAnnotation ann
+ }
}
}
- val sym: Symbol = tree.symbol
+ private def annotate(sym: Symbol, annotSigs: List[AnnotationInfo]): Unit = {
+ sym setAnnotations annotSigs
// TODO: meta-annotations to indicate where module annotations should go (module vs moduleClass)
- annotate(sym)
- if (sym.isModule) annotate(sym.moduleClass)
-
- def getSig = tree match {
- case cdef: ClassDef =>
- createNamer(tree).classSig(cdef)
-
- case mdef: ModuleDef =>
- createNamer(tree).moduleSig(mdef)
-
- case ddef: DefDef =>
- createNamer(tree).methodSig(ddef)
-
- case vdef: ValDef =>
- createNamer(tree).valDefSig(vdef)
-
- case tdef: TypeDef =>
- createNamer(tree).typeDefSig(tdef) //@M!
+ if (sym.isModule) sym.moduleClass setAnnotations annotSigs
+ else if (sym.isTypeSkolem) sym.deSkolemize setAnnotations annotSigs
+ }
- case imp: Import =>
- importSig(imp)
+ // TODO OPT: move to method on MemberDef?
+ private def memberSig(member: MemberDef) =
+ member match {
+ case ddef: DefDef => methodSig(ddef)
+ case vdef: ValDef => valDefSig(vdef)
+ case tdef: TypeDef => typeDefSig(tdef)
+ case cdef: ClassDef => classSig(cdef)
+ case mdef: ModuleDef => moduleSig(mdef)
+ // skip PackageDef
}
- try getSig
- catch typeErrorHandler(tree, ErrorType)
- }
-
def includeParent(tpe: Type, parent: Symbol): Type = tpe match {
case PolyType(tparams, restpe) =>
PolyType(tparams, includeParent(restpe, parent))
@@ -1673,10 +1810,6 @@ trait Namers extends MethodSynthesis {
sym => "[define] >> " + sym.flagString + " " + sym.fullLocationString,
sym => "[define] << " + sym
)
- private def logAndValidate(sym: Symbol)(body: => Unit) {
- logDefinition(sym)(body)
- validate(sym)
- }
/** Convert Java generic array type T[] to (T with Object)[]
* (this is necessary because such arrays have a representation which is incompatible
@@ -1708,11 +1841,7 @@ trait Namers extends MethodSynthesis {
import SymValidateErrors._
def fail(kind: SymValidateErrors.Value) = SymbolValidationError(sym, kind)
- def checkWithDeferred(flag: Int) {
- if (sym hasFlag flag)
- AbstractMemberWithModiferError(sym, flag)
- }
- def checkNoConflict(flag1: Int, flag2: Int) {
+ def checkNoConflict(flag1: Int, flag2: Int) = {
if (sym hasAllFlags flag1.toLong | flag2)
IllegalModifierCombination(sym, flag1, flag2)
}
@@ -1751,6 +1880,10 @@ trait Namers extends MethodSynthesis {
checkNoConflict(ABSTRACT, FINAL)
if (sym.isDeferred) {
+ def checkWithDeferred(flag: Int) = {
+ if (sym hasFlag flag)
+ AbstractMemberWithModiferError(sym, flag)
+ }
// Is this symbol type always allowed the deferred flag?
def symbolAllowsDeferred = (
sym.isValueParameter
@@ -1766,14 +1899,16 @@ trait Namers extends MethodSynthesis {
)
if (sym hasAnnotation NativeAttr)
sym resetFlag DEFERRED
- else if (!symbolAllowsDeferred && ownerRequiresConcrete)
- fail(AbstractVar)
+ else {
+ if (!symbolAllowsDeferred && ownerRequiresConcrete) fail(AbstractVar)
- checkWithDeferred(PRIVATE)
- checkWithDeferred(FINAL)
+ checkWithDeferred(PRIVATE)
+ checkWithDeferred(FINAL)
+ }
}
- checkNoConflict(FINAL, SEALED)
+ if (!sym.isJavaEnum)
+ checkNoConflict(FINAL, SEALED)
checkNoConflict(PRIVATE, PROTECTED)
// checkNoConflict(PRIVATE, OVERRIDE) // this one leads to bad error messages like #4174, so catch in refchecks
// checkNoConflict(PRIVATE, FINAL) // can't do this because FINAL also means compile-time constant
@@ -1794,11 +1929,15 @@ trait Namers extends MethodSynthesis {
}
}
- def mkTypeCompleter(t: Tree)(c: Symbol => Unit) = new LockingTypeCompleter with FlagAgnosticCompleter {
- val tree = t
+ @deprecated("Instantiate TypeCompleterBase (for monomorphic, non-wrapping completer) or CompleterWrapper directly.", "2.12.2")
+ def mkTypeCompleter(t: Tree)(c: Symbol => Unit) = new TypeCompleterBase(t) {
def completeImpl(sym: Symbol) = c(sym)
}
+ // NOTE: only meant for monomorphic definitions,
+ // do not use to wrap existing completers (see CompleterWrapper for that)
+ abstract class TypeCompleterBase[T <: Tree](val tree: T) extends LockingTypeCompleter with FlagAgnosticCompleter
+
trait LockingTypeCompleter extends TypeCompleter {
def completeImpl(sym: Symbol): Unit
@@ -1841,6 +1980,22 @@ trait Namers extends MethodSynthesis {
}
}
+ /**
+ * Wrap an existing completer to do some post/pre-processing of the completed type.
+ *
+ * @param completer
+ */
+ class CompleterWrapper(completer: TypeCompleter) extends TypeCompleter {
+ // override important when completer.isInstanceOf[PolyTypeCompleter]!
+ override val typeParams = completer.typeParams
+
+ val tree = completer.tree
+
+ override def complete(sym: Symbol): Unit = {
+ completer.complete(sym)
+ }
+ }
+
// Can we relax these restrictions? For motivation, see
// test/files/pos/depmet_implicit_oopsla_session_2.scala
// neg/depmet_try_implicit.scala
@@ -1881,18 +2036,18 @@ trait Namers extends MethodSynthesis {
* bugs waiting to be reported? If not, why not? When exactly do we need to
* call this method?
*/
- def companionSymbolOf(original: Symbol, ctx: Context): Symbol = {
+ def companionSymbolOf(original: Symbol, ctx: Context): Symbol = if (original == NoSymbol) NoSymbol else {
val owner = original.owner
// SI-7264 Force the info of owners from previous compilation runs.
// Doing this generally would trigger cycles; that's what we also
// use the lower-level scan through the current Context as a fall back.
if (!currentRun.compiles(owner)) owner.initialize
- original.companionSymbol orElse {
- ctx.lookup(original.name.companionName, owner).suchThat(sym =>
- (original.isTerm || sym.hasModuleFlag) &&
- (sym isCoDefinedWith original)
- )
- }
+
+ if (original.isModuleClass) original.sourceModule
+ else if (!owner.isTerm && owner.hasCompleteInfo)
+ original.companionSymbol
+ else
+ ctx.lookupCompanionInIncompleteOwner(original)
}
/** A version of `Symbol#linkedClassOfClass` that works with local companions, ala `companionSymbolOf`. */
diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala
index 39cd610b1c..ab6837ec01 100644
--- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala
@@ -9,6 +9,7 @@ package typechecker
import symtab.Flags._
import scala.collection.mutable
import scala.reflect.ClassTag
+import PartialFunction.{ cond => when }
/**
* @author Lukas Rytz
@@ -468,8 +469,7 @@ trait NamesDefaults { self: Analyzer =>
else {
// isClass also works for methods in objects, owner is the ModuleClassSymbol
if (param.owner.owner.isClass) {
- // .toInterface: otherwise we get the method symbol of the impl class
- param.owner.owner.toInterface.info.member(defGetterName)
+ param.owner.owner.info.member(defGetterName)
} else {
// the owner of the method is another method. find the default
// getter in the context.
@@ -551,64 +551,75 @@ trait NamesDefaults { self: Analyzer =>
}
}
- /**
- * Removes name assignments from args. Additionally, returns an array mapping
- * argument indices from call-site-order to definition-site-order.
+ /** Removes name assignments from args. Additionally, returns an array mapping
+ * argument indices from call-site-order to definition-site-order.
*
- * Verifies that names are not specified twice, positional args don't appear
- * after named ones.
+ * Verifies that names are not specified twice, and positional args don't appear after named ones.
*/
def removeNames(typer: Typer)(args: List[Tree], params: List[Symbol]): (List[Tree], Array[Int]) = {
implicit val context0 = typer.context
- // maps indices from (order written by user) to (order of definition)
- val argPos = Array.fill(args.length)(-1)
- var positionalAllowed = true
- val namelessArgs = mapWithIndex(args) { (arg, argIndex) =>
- arg match {
- case arg @ AssignOrNamedArg(Ident(name), rhs) =>
- def matchesName(param: Symbol) = !param.isSynthetic && (
- (param.name == name) || (param.deprecatedParamName match {
- case Some(`name`) =>
- context0.deprecationWarning(arg.pos, param,
- s"the parameter name $name has been deprecated. Use ${param.name} instead.")
- true
- case _ => false
- })
- )
- val paramPos = params indexWhere matchesName
- if (paramPos == -1) {
- if (positionalAllowed) {
- argPos(argIndex) = argIndex
- // prevent isNamed from being true when calling doTypedApply recursively,
- // treat the arg as an assignment of type Unit
- Assign(arg.lhs, rhs) setPos arg.pos
- }
- else UnknownParameterNameNamesDefaultError(arg, name)
- }
- else if (argPos contains paramPos) {
+ def matchesName(param: Symbol, name: Name, argIndex: Int) = {
+ def warn(msg: String, since: String) = context0.deprecationWarning(args(argIndex).pos, param, msg, since)
+ def checkDeprecation(anonOK: Boolean) =
+ when (param.deprecatedParamName) {
+ case Some(`name`) => true
+ case Some(nme.NO_NAME) => anonOK
+ }
+ def version = param.deprecatedParamVersion.getOrElse("")
+ def since = if (version.isEmpty) version else s" (since $version)"
+ def checkName = {
+ val res = param.name == name
+ if (res && checkDeprecation(true)) warn(s"naming parameter $name is deprecated$since.", version)
+ res
+ }
+ def checkAltName = {
+ val res = checkDeprecation(false)
+ if (res) warn(s"the parameter name $name is deprecated$since: use ${param.name} instead", version)
+ res
+ }
+ !param.isSynthetic && (checkName || checkAltName)
+ }
+ // argPos maps indices from (order written by user) to (order of definition)
+ val argPos = Array.fill(args.length)(-1)
+ val namelessArgs = {
+ var positionalAllowed = true
+ def stripNamedArg(arg: AssignOrNamedArg, argIndex: Int): Tree = {
+ val AssignOrNamedArg(Ident(name), rhs) = arg
+ params indexWhere (p => matchesName(p, name, argIndex)) match {
+ case -1 if positionalAllowed =>
+ // prevent isNamed from being true when calling doTypedApply recursively,
+ // treat the arg as an assignment of type Unit
+ Assign(arg.lhs, rhs) setPos arg.pos
+ case -1 =>
+ UnknownParameterNameNamesDefaultError(arg, name)
+ case paramPos if argPos contains paramPos =>
val existingArgIndex = argPos.indexWhere(_ == paramPos)
- val otherName = args(paramPos) match {
- case AssignOrNamedArg(Ident(oName), rhs) if oName != name => Some(oName)
- case _ => None
+ val otherName = Some(args(paramPos)) collect {
+ case AssignOrNamedArg(Ident(oName), _) if oName != name => oName
}
DoubleParamNamesDefaultError(arg, name, existingArgIndex+1, otherName)
- } else if (isAmbiguousAssignment(typer, params(paramPos), arg))
+ case paramPos if isAmbiguousAssignment(typer, params(paramPos), arg) =>
AmbiguousReferenceInNamesDefaultError(arg, name)
- else {
- // if the named argument is on the original parameter
- // position, positional after named is allowed.
- if (argIndex != paramPos)
- positionalAllowed = false
- argPos(argIndex) = paramPos
+ case paramPos if paramPos != argIndex =>
+ positionalAllowed = false // named arg is not in original parameter order: require names after this
+ argPos(argIndex) = paramPos // fix up the arg position
rhs
- }
- case _ =>
- argPos(argIndex) = argIndex
- if (positionalAllowed) arg
- else PositionalAfterNamedNamesDefaultError(arg)
+ case _ => rhs
+ }
+ }
+ mapWithIndex(args) {
+ case (arg: AssignOrNamedArg, argIndex) =>
+ val t = stripNamedArg(arg, argIndex)
+ if (!t.isErroneous && argPos(argIndex) < 0) argPos(argIndex) = argIndex
+ t
+ case (arg, argIndex) =>
+ if (positionalAllowed) {
+ argPos(argIndex) = argIndex
+ arg
+ } else
+ PositionalAfterNamedNamesDefaultError(arg)
}
}
-
(namelessArgs, argPos)
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala
index f90e61ff92..cd0c292d90 100644
--- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala
@@ -79,6 +79,7 @@ trait PatternTypers {
// do not update the symbol if the tree's symbol's type does not define an unapply member
// (e.g. since it's some method that returns an object with an unapply member)
val fun = inPlaceAdHocOverloadingResolution(fun0)(hasUnapplyMember)
+ val canElide = treeInfo.isQualifierSafeToElide(fun)
val caseClass = companionSymbolOf(fun.tpe.typeSymbol.sourceModule, context)
val member = unapplyMember(fun.tpe)
def resultType = (fun.tpe memberType member).finalResultType
@@ -94,7 +95,7 @@ trait PatternTypers {
// Dueling test cases: pos/overloaded-unapply.scala, run/case-class-23.scala, pos/t5022.scala
// A case class with 23+ params has no unapply method.
// A case class constructor may be overloaded with unapply methods in the companion.
- if (caseClass.isCase && !member.isOverloaded)
+ if (canElide && caseClass.isCase && !member.isOverloaded)
logResult(s"convertToCaseConstructor($fun, $caseClass, pt=$pt)")(convertToCaseConstructor(fun, caseClass, pt))
else if (!reallyExists(member))
CaseClassConstructorError(fun, s"${fun.symbol} is not a case class, nor does it have an unapply/unapplySeq member")
@@ -122,7 +123,7 @@ trait PatternTypers {
}
private def boundedArrayType(bound: Type): Type = {
- val tparam = context.owner freshExistential "" setInfo (TypeBounds upper bound)
+ val tparam = context.owner.freshExistential("", 0) setInfo (TypeBounds upper bound)
newExistentialType(tparam :: Nil, arrayType(tparam.tpe_*))
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
index 0b44566108..86a1d3f2e4 100644
--- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
@@ -6,15 +6,17 @@
package scala.tools.nsc
package typechecker
-import symtab.Flags._
-import scala.collection.{ mutable, immutable }
-import transform.InfoTransform
-import scala.collection.mutable.ListBuffer
import scala.language.postfixOps
+
+import scala.collection.mutable
+import scala.collection.mutable.ListBuffer
import scala.tools.nsc.settings.ScalaVersion
-import scala.tools.nsc.settings.AnyScalaVersion
import scala.tools.nsc.settings.NoScalaVersion
+import symtab.Flags._
+import transform.Transform
+
+
/** <p>
* Post-attribution checking and transformation.
* </p>
@@ -41,42 +43,20 @@ import scala.tools.nsc.settings.NoScalaVersion
*
* @todo Check whether we always check type parameter bounds.
*/
-abstract class RefChecks extends InfoTransform with scala.reflect.internal.transform.RefChecks {
+abstract class RefChecks extends Transform {
val global: Global // need to repeat here because otherwise last mixin defines global as
// SymbolTable. If we had DOT this would not be an issue
import global._
import definitions._
- import typer.{typed, typedOperator, atOwner}
+ import typer.typed
/** the following two members override abstract members in Transform */
val phaseName: String = "refchecks"
- override def phaseNewFlags: Long = lateMETHOD
def newTransformer(unit: CompilationUnit): RefCheckTransformer =
new RefCheckTransformer(unit)
- override def changesBaseClasses = false
-
- override def transformInfo(sym: Symbol, tp: Type): Type = {
- // !!! This is a sketchy way to do things.
- // It would be better to replace the module symbol with a method symbol
- // rather than creating this module/method hybrid which must be special
- // cased all over the place. Look for the call sites which use(d) some
- // variation of "isMethod && !isModule", which to an observer looks like
- // a nonsensical condition. (It is now "isModuleNotMethod".)
- if (sym.isModule && !sym.isStatic) {
- sym setFlag lateMETHOD | STABLE
- // Note that this as far as we can see it works equally well
- // to set the METHOD flag here and dump lateMETHOD, but it does
- // mean that under separate compilation the typer will see
- // modules as methods (albeit stable ones with singleton types.)
- // So for now lateMETHOD lives while we try to convince ourselves
- // we can live without it or deliver that info some other way.
- log(s"Stabilizing module method for ${sym.fullLocationString}")
- }
- super.transformInfo(sym, tp)
- }
val toJavaRepeatedParam = new SubstSymMap(RepeatedParamClass -> JavaRepeatedParamClass)
val toScalaRepeatedParam = new SubstSymMap(JavaRepeatedParamClass -> RepeatedParamClass)
@@ -86,17 +66,24 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
if (sym.hasAccessBoundary) "" + sym.privateWithin.name else ""
)
- def overridesTypeInPrefix(tp1: Type, tp2: Type, prefix: Type): Boolean = (tp1.dealiasWiden, tp2.dealiasWiden) match {
+ def overridesTypeInPrefix(tp1: Type, tp2: Type, prefix: Type, isModuleOverride: Boolean): Boolean = (tp1.dealiasWiden, tp2.dealiasWiden) match {
case (MethodType(List(), rtp1), NullaryMethodType(rtp2)) =>
rtp1 <:< rtp2
case (NullaryMethodType(rtp1), MethodType(List(), rtp2)) =>
rtp1 <:< rtp2
- case (TypeRef(_, sym, _), _) if sym.isModuleClass =>
- overridesTypeInPrefix(NullaryMethodType(tp1), tp2, prefix)
+
+ // all this module business would be so much simpler if we moduled^w modelled a module as a class and an accessor, like we do for fields
+ case (TypeRef(_, sym, _), _) if sym.isModuleClass =>
+ overridesTypeInPrefix(NullaryMethodType(tp1), tp2, prefix, isModuleOverride)
+ case (_, TypeRef(_, sym, _)) if sym.isModuleClass =>
+ overridesTypeInPrefix(tp1, NullaryMethodType(tp2), prefix, isModuleOverride)
+
case _ =>
def classBoundAsSeen(tp: Type) = tp.typeSymbol.classBound.asSeenFrom(prefix, tp.typeSymbol.owner)
-
- (tp1 <:< tp2) || ( // object override check
+ (tp1 <:< tp2) || isModuleOverride && (
+ // Object override check. This requires that both the overridden and the overriding member are object
+ // definitions. The overriding module type is allowed to replace the original one with the same name
+ // as long as it conform to the original non-singleton type.
tp1.typeSymbol.isModuleClass && tp2.typeSymbol.isModuleClass && {
val cb1 = classBoundAsSeen(tp1)
val cb2 = classBoundAsSeen(tp2)
@@ -108,6 +95,15 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
)
}
+ private val separatelyCompiledScalaSuperclass = perRunCaches.newAnyRefMap[Symbol, Unit]()
+ final def isSeparatelyCompiledScalaSuperclass(sym: Symbol) = if (globalPhase.refChecked){
+ separatelyCompiledScalaSuperclass.contains(sym)
+ } else {
+ // conservative approximation in case someone in pre-refchecks phase asks for `exitingFields(someClass.info)`
+ // and we haven't run the refchecks tree transform which populates `separatelyCompiledScalaSuperclass`
+ false
+ }
+
class RefCheckTransformer(unit: CompilationUnit) extends Transformer {
var localTyper: analyzer.Typer = typer
@@ -168,12 +164,12 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
// This has become noisy with implicit classes.
if (settings.warnPolyImplicitOverload && settings.developer) {
- clazz.info.decls filter (x => x.isImplicit && x.typeParams.nonEmpty) foreach { sym =>
+ clazz.info.decls.foreach(sym => if (sym.isImplicit && sym.typeParams.nonEmpty) {
// implicit classes leave both a module symbol and a method symbol as residue
val alts = clazz.info.decl(sym.name).alternatives filterNot (_.isModule)
if (alts.size > 1)
alts foreach (x => reporter.warning(x.pos, "parameterized overloaded implicit methods are not visible as view bounds"))
- }
+ })
}
}
@@ -294,16 +290,29 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
def infoString(sym: Symbol) = infoString0(sym, sym.owner != clazz)
def infoStringWithLocation(sym: Symbol) = infoString0(sym, true)
- def infoString0(sym: Symbol, showLocation: Boolean) = {
- val sym1 = analyzer.underlyingSymbol(sym)
- sym1.toString() +
+ def infoString0(member: Symbol, showLocation: Boolean) = {
+ val underlying = // not using analyzer.underlyingSymbol(member) because we should get rid of it
+ if (!(member hasFlag ACCESSOR)) member
+ else member.accessed match {
+ case field if field.exists => field
+ case _ if member.isSetter => member.getterIn(member.owner)
+ case _ => member
+ }
+
+ def memberInfo =
+ self.memberInfo(underlying) match {
+ case getterTp if underlying.isGetter => getterTp.resultType
+ case tp => tp
+ }
+
+ underlying.toString() +
(if (showLocation)
- sym1.locationString +
- (if (sym1.isAliasType) ", which equals "+self.memberInfo(sym1)
- else if (sym1.isAbstractType) " with bounds"+self.memberInfo(sym1)
- else if (sym1.isModule) ""
- else if (sym1.isTerm) " of type "+self.memberInfo(sym1)
- else "")
+ underlying.locationString +
+ (if (underlying.isAliasType) s", which equals $memberInfo"
+ else if (underlying.isAbstractType) s" with bounds$memberInfo"
+ else if (underlying.isModule) ""
+ else if (underlying.isTerm) s" of type $memberInfo"
+ else "")
else "")
}
@@ -314,10 +323,8 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
import pair._
val member = low
val other = high
- def memberTp = lowType
- def otherTp = highType
- debuglog("Checking validity of %s overriding %s".format(member.fullLocationString, other.fullLocationString))
+// debuglog(s"Checking validity of ${member.fullLocationString} overriding ${other.fullLocationString}")
def noErrorType = !pair.isErroneous
def isRootOrNone(sym: Symbol) = sym != null && sym.isRoot || sym == NoSymbol
@@ -342,9 +349,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
analyzer.foundReqMsg(member.tpe, other.tpe)
else ""
- "overriding %s;\n %s %s%s".format(
- infoStringWithLocation(other), infoString(member), msg, addendum
- )
+ s"overriding ${infoStringWithLocation(other)};\n ${infoString(member)} $msg$addendum"
}
def emitOverrideError(fullmsg: String) {
if (member.owner == clazz) reporter.error(member.pos, fullmsg)
@@ -421,7 +426,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
overrideError("cannot be used here - classes can only override abstract types")
} else if (other.isEffectivelyFinal) { // (1.2)
overrideError("cannot override final member")
- } else if (!other.isDeferredOrJavaDefault && !other.hasFlag(JAVA_DEFAULTMETHOD) && !member.isAnyOverride && !member.isSynthetic) { // (*)
+ } else if (!other.isDeferred && !member.isAnyOverride && !member.isSynthetic) { // (*)
// (*) Synthetic exclusion for (at least) default getters, fixes SI-5178. We cannot assign the OVERRIDE flag to
// the default getter: one default getter might sometimes override, sometimes not. Example in comment on ticket.
if (isNeitherInClass && !(other.owner isSubClass member.owner))
@@ -435,9 +440,11 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
} else if (other.isAbstractOverride && other.isIncompleteIn(clazz) && !member.isAbstractOverride) {
overrideError("needs `abstract override' modifiers")
}
- else if (member.isAnyOverride && (other hasFlag ACCESSOR) && other.accessed.isVariable && !other.accessed.isLazy) {
- // !?! this is not covered by the spec. We need to resolve this either by changing the spec or removing the test here.
- // !!! is there a !?! convention? I'm !!!ing this to make sure it turns up on my searches.
+ else if (member.isAnyOverride && (other hasFlag ACCESSOR) && !(other hasFlag STABLE | DEFERRED)) {
+ // The check above used to look at `field` == `other.accessed`, ensuring field.isVariable && !field.isLazy,
+ // which I think is identical to the more direct `!(other hasFlag STABLE)` (given that `other` is a method).
+ // Also, we're moving away from (looking at) underlying fields (vals in traits no longer have them, to begin with)
+ // TODO: this is not covered by the spec. We need to resolve this either by changing the spec or removing the test here.
if (!settings.overrideVars)
overrideError("cannot override a mutable variable")
}
@@ -450,9 +457,9 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
} else if (other.isStable && !member.isStable) { // (1.4)
overrideError("needs to be a stable, immutable value")
} else if (member.isValue && member.isLazy &&
- other.isValue && !other.isSourceMethod && !other.isDeferred && !other.isLazy) {
+ other.isValue && other.hasFlag(STABLE) && !(other.isDeferred || other.isLazy)) {
overrideError("cannot override a concrete non-lazy value")
- } else if (other.isValue && other.isLazy && !other.isSourceMethod && !other.isDeferred &&
+ } else if (other.isValue && other.isLazy &&
member.isValue && !member.isLazy) {
overrideError("must be declared lazy to override a concrete lazy value")
} else if (other.isDeferred && member.isTermMacro && member.extendedOverriddenSymbols.forall(_.isDeferred)) { // (1.9)
@@ -463,7 +470,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
checkOverrideTypes()
checkOverrideDeprecated()
if (settings.warnNullaryOverride) {
- if (other.paramss.isEmpty && !member.paramss.isEmpty) {
+ if (other.paramss.isEmpty && !member.paramss.isEmpty && !member.isJavaDefined) {
reporter.warning(member.pos, "non-nullary method overrides nullary method")
}
}
@@ -518,7 +525,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
}
def checkOverrideTerm() {
other.cookJavaRawInfo() // #2454
- if (!overridesTypeInPrefix(lowType, highType, rootType)) { // 8
+ if (!overridesTypeInPrefix(lowType, highType, rootType, low.isModuleOrModuleClass && high.isModuleOrModuleClass)) { // 8
overrideTypeError()
explainTypes(lowType, highType)
}
@@ -543,10 +550,12 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
}
def checkOverrideDeprecated() {
- if (other.hasDeprecatedOverridingAnnotation && !member.ownerChain.exists(x => x.isDeprecated || x.hasBridgeAnnotation)) {
- val suffix = other.deprecatedOverridingMessage map (": " + _) getOrElse ""
- val msg = s"overriding ${other.fullLocationString} is deprecated$suffix"
- currentRun.reporting.deprecationWarning(member.pos, other, msg)
+ if (other.hasDeprecatedOverridingAnnotation && !(member.hasDeprecatedOverridingAnnotation || member.ownerChain.exists(x => x.isDeprecated || x.hasBridgeAnnotation))) {
+ val version = other.deprecatedOverridingVersion.getOrElse("")
+ val since = if (version.isEmpty) version else s" (since $version)"
+ val message = other.deprecatedOverridingMessage map (msg => s": $msg") getOrElse ""
+ val report = s"overriding ${other.fullLocationString} is deprecated$since$message"
+ currentRun.reporting.deprecationWarning(member.pos, other, report, version)
}
}
}
@@ -604,10 +613,10 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
def checkNoAbstractMembers(): Unit = {
// Avoid spurious duplicates: first gather any missing members.
def memberList = clazz.info.nonPrivateMembersAdmitting(VBRIDGE)
- val (missing, rest) = memberList partition (m => m.isDeferredNotJavaDefault && !ignoreDeferred(m))
+ val (missing, rest) = memberList partition (m => m.isDeferred && !ignoreDeferred(m))
// Group missing members by the name of the underlying symbol,
// to consolidate getters and setters.
- val grouped = missing groupBy (sym => analyzer.underlyingSymbol(sym).name)
+ val grouped = missing groupBy (_.name.getterName)
val missingMethods = grouped.toList flatMap {
case (name, syms) =>
if (syms exists (_.isSetter)) syms filterNot (_.isGetter)
@@ -645,19 +654,20 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
for (member <- missing) {
def undefined(msg: String) = abstractClassError(false, infoString(member) + " is not defined" + msg)
- val underlying = analyzer.underlyingSymbol(member)
+ val underlying = analyzer.underlyingSymbol(member) // TODO: don't use this method
// Give a specific error message for abstract vars based on why it fails:
// It could be unimplemented, have only one accessor, or be uninitialized.
- if (underlying.isVariable) {
- val isMultiple = grouped.getOrElse(underlying.name, Nil).size > 1
+ val groupedAccessors = grouped.getOrElse(member.name.getterName, Nil)
+ val isMultiple = groupedAccessors.size > 1
+ if (groupedAccessors.exists(_.isSetter) || (member.isGetter && !isMultiple && member.setterIn(member.owner).exists)) {
// If both getter and setter are missing, squelch the setter error.
if (member.isSetter && isMultiple) ()
else undefined(
if (member.isSetter) "\n(Note that an abstract var requires a setter in addition to the getter)"
else if (member.isGetter && !isMultiple) "\n(Note that an abstract var requires a getter in addition to the setter)"
- else analyzer.abstractVarMessage(member)
+ else "\n(Note that variables need to be initialized to be defined)"
)
}
else if (underlying.isMethod) {
@@ -851,6 +861,8 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
// println("validate base type "+tp)
val baseClass = tp.typeSymbol
if (baseClass.isClass) {
+ if (!baseClass.isTrait && !baseClass.isJavaDefined && !currentRun.compiles(baseClass) && !separatelyCompiledScalaSuperclass.contains(baseClass))
+ separatelyCompiledScalaSuperclass.update(baseClass, ())
val index = clazz.info.baseTypeIndex(baseClass)
if (index >= 0) {
if (seenTypes(index) forall (tp1 => !(tp1 <:< tp)))
@@ -917,17 +929,11 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
var index = -1
for (stat <- stats) {
index = index + 1
- def enterSym(sym: Symbol) = if (sym.isLocalToBlock) {
- currentLevel.scope.enter(sym)
- symIndex(sym) = index
- }
stat match {
- case DefDef(_, _, _, _, _, _) if stat.symbol.isLazy =>
- enterSym(stat.symbol)
- case ClassDef(_, _, _, _) | DefDef(_, _, _, _, _, _) | ModuleDef(_, _, _) | ValDef(_, _, _, _) =>
- //assert(stat.symbol != NoSymbol, stat);//debug
- enterSym(stat.symbol.lazyAccessorOrSelf)
+ case _ : MemberDef if stat.symbol.isLocalToBlock =>
+ currentLevel.scope.enter(stat.symbol)
+ symIndex(stat.symbol) = index
case _ =>
}
}
@@ -1095,7 +1101,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
// better to have lubbed and lost
def warnIfLubless(): Unit = {
val common = global.lub(List(actual.tpe, receiver.tpe))
- if (ObjectTpe <:< common && !(ObjectTpe <:< actual.tpe && ObjectTpe <:< receiver.tpe))
+ if (ObjectTpe <:< common && !(ObjectTpe <:< actual.tpe) && !(ObjectTpe <:< receiver.tpe))
unrelatedTypes()
}
// warn if actual has a case parent that is not same as receiver's;
@@ -1122,27 +1128,21 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
}
/** Sensibility check examines flavors of equals. */
def checkSensible(pos: Position, fn: Tree, args: List[Tree]) = fn match {
- case Select(qual, name @ (nme.EQ | nme.NE | nme.eq | nme.ne)) if args.length == 1 && isObjectOrAnyComparisonMethod(fn.symbol) && !currentOwner.isSynthetic =>
+ case Select(qual, name @ (nme.EQ | nme.NE | nme.eq | nme.ne)) if args.length == 1 && isObjectOrAnyComparisonMethod(fn.symbol) && (!currentOwner.isSynthetic || currentOwner.isAnonymousFunction) =>
checkSensibleEquals(pos, qual, name, fn.symbol, args.head)
case _ =>
}
- // SI-6276 warn for `def foo = foo` or `val bar: X = bar`, which come up more frequently than you might think.
- def checkInfiniteLoop(valOrDef: ValOrDefDef) {
- def callsSelf = valOrDef.rhs match {
- case t @ (Ident(_) | Select(This(_), _)) =>
- t hasSymbolWhich (_.accessedOrSelf == valOrDef.symbol)
- case _ => false
+ // SI-6276 warn for trivial recursion, such as `def foo = foo` or `val bar: X = bar`, which come up more frequently than you might think.
+ // TODO: Move to abide rule. Also, this does not check that the def is final or not overridden, for example
+ def checkInfiniteLoop(sym: Symbol, rhs: Tree): Unit =
+ if (!sym.isValueParameter && sym.paramss.isEmpty) {
+ rhs match {
+ case t@(Ident(_) | Select(This(_), _)) if t hasSymbolWhich (_.accessedOrSelf == sym) =>
+ reporter.warning(rhs.pos, s"${sym.fullLocationString} does nothing other than call itself recursively")
+ case _ =>
+ }
}
- val trivialInfiniteLoop = (
- !valOrDef.isErroneous
- && !valOrDef.symbol.isValueParameter
- && valOrDef.symbol.paramss.isEmpty
- && callsSelf
- )
- if (trivialInfiniteLoop)
- reporter.warning(valOrDef.rhs.pos, s"${valOrDef.symbol.fullLocationString} does nothing other than call itself recursively")
- }
// Transformation ------------------------------------------------------------
@@ -1150,11 +1150,14 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
def toConstructor(pos: Position, tpe: Type): Tree = {
val rtpe = tpe.finalResultType
assert(rtpe.typeSymbol hasFlag CASE, tpe)
- localTyper.typedOperator {
+ val tree = localTyper.typedOperator {
atPos(pos) {
Select(New(TypeTree(rtpe)), rtpe.typeSymbol.primaryConstructor)
}
}
+ checkUndesiredProperties(rtpe.typeSymbol, tree.pos)
+ checkUndesiredProperties(rtpe.typeSymbol.primaryConstructor, tree.pos)
+ tree
}
override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = {
@@ -1167,74 +1170,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
finally popLevel()
}
- /** Eliminate ModuleDefs. In all cases the ModuleDef (carrying a module symbol) is
- * replaced with a ClassDef (carrying the corresponding module class symbol) with additional
- * trees created as follows:
- *
- * 1) A statically reachable object (either top-level or nested only in objects) receives
- * no additional trees.
- * 2) An inner object which matches an existing member (e.g. implements an interface)
- * receives an accessor DefDef to implement the interface.
- * 3) An inner object otherwise receives a private ValDef which declares a module var
- * (the field which holds the module class - it has a name like Foo$module) and an
- * accessor for that field. The instance is created lazily, on first access.
- */
- private def eliminateModuleDefs(moduleDef: Tree): List[Tree] = exitingRefchecks {
- val ModuleDef(_, _, impl) = moduleDef
- val module = moduleDef.symbol
- val moduleClass = module.moduleClass
- val site = module.owner
- val moduleName = module.name.toTermName
- // The typer doesn't take kindly to seeing this ClassDef; we have to
- // set NoType so it will be ignored.
- val cdef = ClassDef(moduleClass, impl) setType NoType
-
- // This code is related to the fix of SI-9375, which stops adding `readResolve` methods to
- // non-static (nested) modules. Before the fix, the method would cause the module accessor
- // to become notPrivate. To prevent binary changes in the 2.11.x branch, we mimic that behavior.
- // There is a bit of code duplication between here and SyntheticMethods. We cannot call
- // makeNotPrivate already in SyntheticMethod: that is during type checking, and not all references
- // are resolved yet, so we cannot rename a definition. This code doesn't exist in the 2.12.x branch.
- def hasConcreteImpl(name: Name) = moduleClass.info.member(name).alternatives exists (m => !m.isDeferred)
- val hadReadResolveBeforeSI9375 = moduleClass.isSerializable && !hasConcreteImpl(nme.readResolve)
- if (hadReadResolveBeforeSI9375)
- moduleClass.sourceModule.makeNotPrivate(moduleClass.sourceModule.owner)
-
- // Create the module var unless the immediate owner is a class and
- // the module var already exists there. See SI-5012, SI-6712.
- def findOrCreateModuleVar() = {
- val vsym = (
- if (site.isTerm) NoSymbol
- else site.info decl nme.moduleVarName(moduleName)
- )
- vsym orElse (site newModuleVarSymbol module)
- }
- def newInnerObject() = {
- // Create the module var unless it is already in the module owner's scope.
- // The lookup is on module.enclClass and not module.owner lest there be a
- // nullary method between us and the class; see SI-5012.
- val moduleVar = findOrCreateModuleVar()
- val rhs = gen.newModule(module, moduleVar.tpe)
- val body = if (site.isTrait) rhs else gen.mkAssignAndReturn(moduleVar, rhs)
- val accessor = DefDef(module, body.changeOwner(moduleVar -> module))
-
- ValDef(moduleVar) :: accessor :: Nil
- }
- def matchingInnerObject() = {
- val newFlags = (module.flags | STABLE) & ~MODULE
- val newInfo = NullaryMethodType(moduleClass.tpe)
- val accessor = site.newMethod(moduleName, module.pos, newFlags) setInfoAndEnter newInfo
- DefDef(accessor, Select(This(site), module)) :: Nil
- }
- val newTrees = cdef :: (
- if (module.isStatic)
- if (module.isOverridingSymbol) matchingInnerObject() else Nil
- else
- newInnerObject()
- )
- transformTrees(newTrees map localTyper.typedPos(moduleDef.pos))
- }
def transformStat(tree: Tree, index: Int): List[Tree] = tree match {
case t if treeInfo.isSelfConstrCall(t) =>
@@ -1245,15 +1181,14 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
debuglog("refsym = " + currentLevel.refsym)
reporter.error(currentLevel.refpos, "forward reference not allowed from self constructor invocation")
}
- case ModuleDef(_, _, _) => eliminateModuleDefs(tree)
case ValDef(_, _, _, _) =>
val tree1 = transform(tree) // important to do before forward reference check
if (tree1.symbol.isLazy) tree1 :: Nil
else {
- val lazySym = tree.symbol.lazyAccessorOrSelf
- if (lazySym.isLocalToBlock && index <= currentLevel.maxindex) {
+ val sym = tree.symbol
+ if (sym.isLocalToBlock && index <= currentLevel.maxindex) {
debuglog("refsym = " + currentLevel.refsym)
- reporter.error(currentLevel.refpos, "forward reference extends over definition of " + lazySym)
+ reporter.error(currentLevel.refpos, "forward reference extends over definition of " + sym)
}
tree1 :: Nil
}
@@ -1421,7 +1356,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
currentRun.reporting.deprecationWarning(
tree.pos,
symbol,
- s"${symbol.toString} overrides concrete, non-deprecated symbol(s): ${concrOvers.map(_.name.decode).mkString(", ")}")
+ s"${symbol.toString} overrides concrete, non-deprecated symbol(s): ${concrOvers.map(_.name.decode).mkString(", ")}", "")
}
}
private def isRepeatedParamArg(tree: Tree) = currentApplication match {
@@ -1476,17 +1411,26 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
transformTrees(annots flatMap (_.args))
}
+ def checkIsElisible(sym: Symbol) = if (sym ne null) sym.elisionLevel.foreach { level =>
+ if (!sym.isMethod || sym.isAccessor || sym.isLazy || sym.isDeferred)
+ reporter.error(sym.pos, s"${sym.name}: Only methods can be marked @elidable.")
+ }
+ if (settings.isScala213) checkIsElisible(tree.symbol)
+
tree match {
case m: MemberDef =>
val sym = m.symbol
applyChecks(sym.annotations)
- // validate implicitNotFoundMessage
- analyzer.ImplicitNotFoundMsg.check(sym) foreach { warn =>
- reporter.warning(tree.pos, f"Invalid implicitNotFound message for ${sym}%s${sym.locationString}%s:%n$warn")
- }
+
+ def messageWarning(name: String)(warn: String) =
+ reporter.warning(tree.pos, f"Invalid $name message for ${sym}%s${sym.locationString}%s:%n$warn")
+
+ // validate implicitNotFoundMessage and implicitAmbiguousMessage
+ analyzer.ImplicitNotFoundMsg.check(sym) foreach messageWarning("implicitNotFound")
+ analyzer.ImplicitAmbiguousMsg.check(sym) foreach messageWarning("implicitAmbiguous")
case tpt@TypeTree() =>
- if(tpt.original != null) {
+ if (tpt.original != null) {
tpt.original foreach {
case dc@TypeTreeWithDeferredRefCheck() =>
applyRefchecksToAnnotations(dc.check()) // #2416
@@ -1518,25 +1462,35 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
)
}
- sym.isSourceMethod &&
+ sym.name == nme.apply &&
+ !(sym hasFlag STABLE) && // ???
sym.isCase &&
- sym.name == nme.apply &&
isClassTypeAccessible(tree) &&
!tree.tpe.finalResultType.typeSymbol.primaryConstructor.isLessAccessibleThan(tree.symbol)
}
private def transformCaseApply(tree: Tree) = {
+ def loop(t: Tree): Unit = t match {
+ case Ident(_) =>
+ checkUndesiredProperties(t.symbol, t.pos)
+ case Select(qual, _) =>
+ checkUndesiredProperties(t.symbol, t.pos)
+ loop(qual)
+ case _ =>
+ }
+
tree foreach {
case i@Ident(_) =>
enterReference(i.pos, i.symbol) // SI-5390 need to `enterReference` for `a` in `a.B()`
case _ =>
}
+ loop(tree)
toConstructor(tree.pos, tree.tpe)
}
private def transformApply(tree: Apply): Tree = tree match {
case Apply(
- Select(qual, nme.filter | nme.withFilter),
+ Select(qual, nme.withFilter),
List(Function(
List(ValDef(_, pname, tpt, _)),
Match(_, CaseDef(pat1, _, _) :: _))))
@@ -1643,13 +1597,12 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
// inside annotations.
applyRefchecksToAnnotations(tree)
var result: Tree = tree match {
- case DefDef(_, _, _, _, _, EmptyTree) if sym hasAnnotation NativeAttr =>
- sym resetFlag DEFERRED
- transform(deriveDefDef(tree)(_ => typed(gen.mkSysErrorCall("native method stub"))))
-
- case ValDef(_, _, _, _) | DefDef(_, _, _, _, _, _) =>
+ // NOTE: a val in a trait is now a DefDef, with the RHS being moved to an Assign in Constructors
+ case tree: ValOrDefDef =>
checkDeprecatedOvers(tree)
- checkInfiniteLoop(tree.asInstanceOf[ValOrDefDef])
+ if (!tree.isErroneous)
+ checkInfiniteLoop(tree.symbol, tree.rhs)
+
if (settings.warnNullaryUnit)
checkNullaryMethodReturnType(sym)
if (settings.warnInaccessible) {
@@ -1657,10 +1610,22 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
checkAccessibilityOfReferencedTypes(tree)
}
tree match {
- case dd: DefDef => checkByNameRightAssociativeDef(dd)
- case _ =>
+ case dd: DefDef =>
+ checkByNameRightAssociativeDef(dd)
+
+ if (sym hasAnnotation NativeAttr) {
+ if (sym.owner.isTrait) {
+ reporter.error(tree.pos, "A trait cannot define a native method.")
+ tree
+ } else if (dd.rhs == EmptyTree) {
+ // pretend it had a stub implementation
+ sym resetFlag DEFERRED
+ deriveDefDef(dd)(_ => typed(gen.mkSysErrorCall("native method stub")))
+ } else tree
+ } else tree
+
+ case _ => tree
}
- tree
case Template(parents, self, body) =>
localTyper = localTyper.atOwner(tree, currentOwner)
@@ -1668,7 +1633,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
checkOverloadedRestrictions(currentOwner, currentOwner)
// SI-7870 default getters for constructors live in the companion module
checkOverloadedRestrictions(currentOwner, currentOwner.companionModule)
- val bridges = addVarargBridges(currentOwner)
+ val bridges = addVarargBridges(currentOwner) // TODO: do this during uncurry?
checkAllOverrides(currentOwner)
checkAnyValSubclass(currentOwner)
if (currentOwner.isDerivedValueClass)
@@ -1694,7 +1659,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
case tp @ ExistentialType(tparams, tpe) =>
existentialParams ++= tparams
case ann: AnnotatedType if ann.hasAnnotation(UncheckedBoundsClass) =>
- // SI-7694 Allow code synthetizers to disable checking of bounds for TypeTrees based on inferred LUBs
+ // SI-7694 Allow code synthesizers to disable checking of bounds for TypeTrees based on inferred LUBs
// which might not conform to the constraints.
skipBounds = true
case tp: TypeRef =>
@@ -1786,7 +1751,8 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
}
result match {
case ClassDef(_, _, _, _)
- | TypeDef(_, _, _, _) =>
+ | TypeDef(_, _, _, _)
+ | ModuleDef(_, _, _) =>
if (result.symbol.isLocalToBlock || result.symbol.isTopLevel)
varianceValidator.traverse(result)
case tt @ TypeTree() if tt.original != null =>
diff --git a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala
index e0d96df062..57906cfe0a 100644
--- a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala
@@ -146,7 +146,28 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT
val intermediateClasses = clazz.info.baseClasses.tail.takeWhile(_ != sym.owner)
intermediateClasses.map(sym.overridingSymbol).find(s => s.isDeferred && !s.isAbstractOverride && !s.owner.isTrait).foreach {
absSym =>
- reporter.error(sel.pos, s"${sym.fullLocationString} cannot be directly accessed from ${clazz} because ${absSym.owner} redeclares it as abstract")
+ reporter.error(sel.pos, s"${sym.fullLocationString} cannot be directly accessed from $clazz because ${absSym.owner} redeclares it as abstract")
+ }
+ } else {
+ // SD-143: a call super[T].m that resolves to A.m cannot be translated to correct bytecode if
+ // - A is a class (not a trait / interface), but not the direct superclass. Invokespecial
+ // would select an overriding method in the direct superclass, rather than A.m.
+ // We allow this if there are statically no intervening overrides.
+ // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial
+ // - A is a java-defined interface and not listed as direct parent of the class. In this
+ // case, `invokespecial A.m` would be invalid.
+ def hasClassOverride(member: Symbol, subclass: Symbol): Boolean = {
+ if (subclass == ObjectClass || subclass == member.owner) false
+ else if (member.overridingSymbol(subclass) != NoSymbol) true
+ else hasClassOverride(member, subclass.superClass)
+ }
+ val owner = sym.owner
+ if (mix != tpnme.EMPTY && !owner.isTrait && owner != clazz.superClass && hasClassOverride(sym, clazz.superClass)) {
+ reporter.error(sel.pos,
+ s"cannot emit super call: the selected $sym is declared in $owner, which is not the direct superclass of $clazz.\n" +
+ s"An unqualified super call (super.${sym.name}) would be allowed.")
+ } else if (owner.isInterface && owner.isJavaDefined && !clazz.parentSymbols.contains(owner)) {
+ reporter.error(sel.pos, s"unable to emit super call unless interface ${owner.name} (which declares $sym) is directly extended by $clazz.")
}
}
@@ -287,17 +308,18 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT
val result = (localTyper.typedPos(tree.pos) {
Select(Super(qual, tpnme.EMPTY) setPos qual.pos, sym.alias)
}).asInstanceOf[Select]
- debuglog("alias replacement: " + tree + " ==> " + result); //debug
+ debuglog(s"alias replacement: $sym --> ${sym.alias} / $tree ==> $result"); //debug
localTyper.typed(gen.maybeMkAsInstanceOf(transformSuperSelect(result), sym.tpe, sym.alias.tpe, beforeRefChecks = true))
} else {
/*
* A trait which extends a class and accesses a protected member
* of that class cannot implement the necessary accessor method
- * because its implementation is in an implementation class (e.g.
- * Foo$class) which inherits nothing, and jvm access restrictions
- * require the call site to be in an actual subclass. So non-trait
- * classes inspect their ancestors for any such situations and
- * generate the accessors. See SI-2296.
+ * because jvm access restrictions require the call site to be
+ * in an actual subclass, and an interface cannot extend a class.
+ * So, non-trait classes inspect their ancestors for any such situations
+ * and generate the accessors. See SI-2296.
+ *
+ * TODO: anything we can improve here now that a trait compiles 1:1 to an interface?
*/
// FIXME - this should be unified with needsProtectedAccessor, but some
// subtlety which presently eludes me is foiling my attempts.
@@ -387,7 +409,7 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT
val savedValid = validCurrentOwner
if (owner.isClass) validCurrentOwner = true
val savedLocalTyper = localTyper
- localTyper = localTyper.atOwner(tree, if (owner.isModule) owner.moduleClass else owner)
+ localTyper = localTyper.atOwner(tree, if (owner.isModuleNotMethod) owner.moduleClass else owner)
typers = typers updated (owner, localTyper)
val result = super.atOwner(tree, owner)(trans)
localTyper = savedLocalTyper
diff --git a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala
index 9cc0fc4c59..7943187f35 100644
--- a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala
@@ -6,11 +6,13 @@
package scala.tools.nsc
package typechecker
-import scala.collection.{ mutable, immutable }
-import symtab.Flags._
-import scala.collection.mutable.ListBuffer
import scala.language.postfixOps
+import scala.collection.mutable
+import scala.collection.mutable.ListBuffer
+
+import symtab.Flags._
+
/** Synthetic method implementations for case classes and case objects.
*
* Added to all case classes/objects:
@@ -87,33 +89,18 @@ trait SyntheticMethods extends ast.TreeDSL {
def accessors = clazz.caseFieldAccessors
val arity = accessors.size
- // If this is ProductN[T1, T2, ...], accessorLub is the lub of T1, T2, ..., .
- // !!! Hidden behind -Xexperimental due to bummer type inference bugs.
- // Refining from Iterator[Any] leads to types like
- //
- // Option[Int] { def productIterator: Iterator[String] }
- //
- // appearing legitimately, but this breaks invariant places
- // like Tags and Arrays which are not robust and infer things
- // which they shouldn't.
- val accessorLub = (
- if (settings.Xexperimental) {
- global.lub(accessors map (_.tpe.finalResultType)) match {
- case RefinedType(parents, decls) if !decls.isEmpty => intersectionType(parents)
- case tp => tp
- }
- }
- else AnyTpe
- )
def forwardToRuntime(method: Symbol): Tree =
forwardMethod(method, getMember(ScalaRunTimeModule, (method.name prepend "_")))(mkThis :: _)
- def callStaticsMethod(name: String)(args: Tree*): Tree = {
- val method = termMember(RuntimeStaticsModule, name)
+ def callStaticsMethodName(name: TermName)(args: Tree*): Tree = {
+ val method = RuntimeStaticsModule.info.member(name)
Apply(gen.mkAttributedRef(method), args.toList)
}
+ def callStaticsMethod(name: String)(args: Tree*): Tree =
+ callStaticsMethodName(newTermName(name))(args: _*)
+
// Any concrete member, including private
def hasConcreteImpl(name: Name) =
clazz.info.member(name).alternatives exists (m => !m.isDeferred)
@@ -125,8 +112,8 @@ trait SyntheticMethods extends ast.TreeDSL {
}
}
def productIteratorMethod = {
- createMethod(nme.productIterator, iteratorOfType(accessorLub))(_ =>
- gen.mkMethodCall(ScalaRunTimeModule, nme.typedProductIterator, List(accessorLub), List(mkThis))
+ createMethod(nme.productIterator, iteratorOfType(AnyTpe))(_ =>
+ gen.mkMethodCall(ScalaRunTimeModule, nme.typedProductIterator, List(AnyTpe), List(mkThis))
)
}
@@ -247,7 +234,7 @@ trait SyntheticMethods extends ast.TreeDSL {
List(
Product_productPrefix -> (() => constantNullary(nme.productPrefix, clazz.name.decode)),
Product_productArity -> (() => constantNullary(nme.productArity, arity)),
- Product_productElement -> (() => perElementMethod(nme.productElement, accessorLub)(mkThisSelect)),
+ Product_productElement -> (() => perElementMethod(nme.productElement, AnyTpe)(mkThisSelect)),
Product_iterator -> (() => productIteratorMethod),
Product_canEqual -> (() => canEqualMethod)
// This is disabled pending a reimplementation which doesn't add any
@@ -262,10 +249,10 @@ trait SyntheticMethods extends ast.TreeDSL {
case BooleanClass => If(Ident(sym), Literal(Constant(1231)), Literal(Constant(1237)))
case IntClass => Ident(sym)
case ShortClass | ByteClass | CharClass => Select(Ident(sym), nme.toInt)
- case LongClass => callStaticsMethod("longHash")(Ident(sym))
- case DoubleClass => callStaticsMethod("doubleHash")(Ident(sym))
- case FloatClass => callStaticsMethod("floatHash")(Ident(sym))
- case _ => callStaticsMethod("anyHash")(Ident(sym))
+ case LongClass => callStaticsMethodName(nme.longHash)(Ident(sym))
+ case DoubleClass => callStaticsMethodName(nme.doubleHash)(Ident(sym))
+ case FloatClass => callStaticsMethodName(nme.floatHash)(Ident(sym))
+ case _ => callStaticsMethodName(nme.anyHash)(Ident(sym))
}
}
@@ -355,16 +342,18 @@ trait SyntheticMethods extends ast.TreeDSL {
}
for ((m, impl) <- methods ; if shouldGenerate(m)) yield impl()
}
- def extras = (
+ def extras = {
if (needsReadResolve) {
// Aha, I finally decoded the original comment.
// This method should be generated as private, but apparently if it is, then
// it is name mangled afterward. (Wonder why that is.) So it's only protected.
// For sure special methods like "readResolve" should not be mangled.
- List(createMethod(nme.readResolve, Nil, ObjectTpe)(m => { m setFlag PRIVATE ; REF(clazz.sourceModule) }))
+ List(createMethod(nme.readResolve, Nil, ObjectTpe)(m => {
+ m setFlag PRIVATE; REF(clazz.sourceModule)
+ }))
}
else Nil
- )
+ }
try impls ++ extras
catch { case _: TypeError if reporter.hasErrors => Nil }
@@ -382,7 +371,14 @@ trait SyntheticMethods extends ast.TreeDSL {
for (ddef @ DefDef(_, _, _, _, _, _) <- templ.body ; if isRewrite(ddef.symbol)) {
val original = ddef.symbol
- val newAcc = deriveMethod(ddef.symbol, name => context.unit.freshTermName(name + "$")) { newAcc =>
+ val i = original.owner.caseFieldAccessors.indexOf(original)
+ def freshAccessorName = {
+ devWarning(s"Unable to find $original among case accessors of ${original.owner}: ${original.owner.caseFieldAccessors}")
+ context.unit.freshTermName(original.name + "$")
+ }
+ def nameSuffixedByParamIndex = original.name.append(nme.CASE_ACCESSOR + "$" + i).toTermName
+ val newName = if (i < 0) freshAccessorName else nameSuffixedByParamIndex
+ val newAcc = deriveMethod(ddef.symbol, name => newName) { newAcc =>
newAcc.makePublic
newAcc resetFlag (ACCESSOR | PARAMACCESSOR | OVERRIDE)
ddef.rhs.duplicate
diff --git a/src/compiler/scala/tools/nsc/typechecker/Tags.scala b/src/compiler/scala/tools/nsc/typechecker/Tags.scala
index 56127f4026..e29451f379 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Tags.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Tags.scala
@@ -13,16 +13,7 @@ trait Tags {
private val runDefinitions = currentRun.runDefinitions
private def resolveTag(pos: Position, taggedTp: Type, allowMaterialization: Boolean) = enteringTyper {
- def wrapper (tree: => Tree): Tree = if (allowMaterialization) (context.withMacrosEnabled[Tree](tree)) else (context.withMacrosDisabled[Tree](tree))
- wrapper(inferImplicit(
- EmptyTree,
- taggedTp,
- reportAmbiguous = true,
- isView = false,
- context,
- saveAmbiguousDivergent = true,
- pos
- ).tree)
+ context.withMacros(enabled = allowMaterialization) { inferImplicitByType(taggedTp, context, pos).tree }
}
/** Finds in scope or materializes a ClassTag.
diff --git a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala
index e8db8309f1..50743a922a 100644
--- a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala
@@ -7,8 +7,6 @@ package scala.tools.nsc
package typechecker
import scala.collection.mutable
-import mutable.ListBuffer
-import util.returning
import scala.reflect.internal.util.shortClassOfInstance
import scala.reflect.internal.util.StringOps._
@@ -94,7 +92,7 @@ abstract class TreeCheckers extends Analyzer {
def latest = maps.head._2
val defSyms = symbolTreeMap[DefTree]()
val newSyms = mutable.HashSet[Symbol]()
- val movedMsgs = new ListBuffer[String]
+ val movedMsgs = mutable.ListBuffer[String]()
def sortedNewSyms = newSyms.toList.distinct sortBy (_.name.toString)
def record(tree: Tree) {
@@ -113,10 +111,8 @@ abstract class TreeCheckers extends Analyzer {
newSyms += sym
else if (prevTrees exists (t => (t eq tree) || (t.symbol == sym)))
()
- else if (prevTrees exists (_.symbol.owner == sym.owner.implClass))
- errorFn("Noticed " + ownerstr(sym) + " moving to implementation class.")
else {
- val s1 = (prevTrees map wholetreestr).sorted.distinct
+ val s1 = (prevTrees map wholetreestr).distinct.sorted
val s2 = wholetreestr(tree)
if (s1 contains s2) ()
else movedMsgs += ("\n** %s moved:\n** Previously:\n%s\n** Currently:\n%s".format(ownerstr(sym), s1 mkString ", ", s2))
diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala
index 5f2643cb25..cd1dd18768 100644
--- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala
@@ -11,6 +11,7 @@ import scala.collection.mutable.ListBuffer
import scala.util.control.Exception.ultimately
import symtab.Flags._
import PartialFunction._
+import scala.annotation.tailrec
/** An interface to enable higher configurability of diagnostic messages
* regarding type errors. This is barely a beginning as error messages are
@@ -32,7 +33,7 @@ import PartialFunction._
* @version 1.0
*/
trait TypeDiagnostics {
- self: Analyzer =>
+ self: Analyzer with StdAttachments =>
import global._
import definitions._
@@ -78,6 +79,8 @@ trait TypeDiagnostics {
prefix + name.decode
}
+ private def atBounded(t: Tree) = t.hasAttachment[AtBoundIdentifierAttachment.type]
+
/** Does the positioned line assigned to t1 precede that of t2?
*/
def posPrecedes(p1: Position, p2: Position) = p1.isDefined && p2.isDefined && p1.line < p2.line
@@ -97,7 +100,7 @@ trait TypeDiagnostics {
/** An explanatory note to be added to error messages
* when there's a problem with abstract var defs */
def abstractVarMessage(sym: Symbol): String =
- if (underlyingSymbol(sym).isVariable)
+ if (sym.isSetter || sym.isGetter && sym.setterIn(sym.owner).exists)
"\n(Note that variables need to be initialized to be defined)"
else ""
@@ -116,13 +119,13 @@ trait TypeDiagnostics {
*/
final def exampleTuplePattern(names: List[Name]): String = {
val arity = names.length
- val varPatterNames: Option[List[String]] = sequence(names map {
+ val varPatternNames: Option[List[String]] = sequence(names map {
case name if nme.isVariableName(name) => Some(name.decode)
case _ => None
})
def parenthesize(a: String) = s"($a)"
def genericParams = (Seq("param1") ++ (if (arity > 2) Seq("...") else Nil) ++ Seq(s"param$arity"))
- parenthesize(varPatterNames.getOrElse(genericParams).mkString(", "))
+ parenthesize(varPatternNames.getOrElse(genericParams).mkString(", "))
}
def alternatives(tree: Tree): List[Type] = tree.tpe match {
@@ -133,12 +136,14 @@ trait TypeDiagnostics {
alternatives(tree) map (x => " " + methodTypeErrorString(x)) mkString ("", " <and>\n", "\n")
/** The symbol which the given accessor represents (possibly in part).
- * This is used for error messages, where we want to speak in terms
- * of the actual declaration or definition, not in terms of the generated setters
- * and getters.
- */
+ * This is used for error messages, where we want to speak in terms
+ * of the actual declaration or definition, not in terms of the generated setters
+ * and getters.
+ *
+ * TODO: is it wise to create new symbols simply to generate error message? is this safe in interactive/resident mode?
+ */
def underlyingSymbol(member: Symbol): Symbol =
- if (!member.hasAccessorFlag) member
+ if (!member.hasAccessorFlag || member.accessed == NoSymbol) member
else if (!member.isDeferred) member.accessed
else {
val getter = if (member.isSetter) member.getterIn(member.owner) else member
@@ -272,19 +277,54 @@ trait TypeDiagnostics {
if (AnyRefTpe <:< req) notAnyRefMessage(found) else ""
}
+ def finalOwners(tpe: Type): Boolean = (tpe.prefix == NoPrefix) || recursivelyFinal(tpe)
+
+ @tailrec
+ final def recursivelyFinal(tpe: Type): Boolean = {
+ val prefix = tpe.prefix
+ if (prefix != NoPrefix) {
+ if (prefix.typeSymbol.isFinal) {
+ recursivelyFinal(prefix)
+ } else {
+ false
+ }
+ } else {
+ true
+ }
+ }
+
// TODO - figure out how to avoid doing any work at all
// when the message will never be seen. I though context.reportErrors
// being false would do that, but if I return "<suppressed>" under
// that condition, I see it.
def foundReqMsg(found: Type, req: Type): String = {
- def baseMessage = (
- ";\n found : " + found.toLongString + existentialContext(found) + explainAlias(found) +
- "\n required: " + req + existentialContext(req) + explainAlias(req)
- )
- ( withDisambiguation(Nil, found, req)(baseMessage)
- + explainVariance(found, req)
- + explainAnyVsAnyRef(found, req)
- )
+ val foundWiden = found.widen
+ val reqWiden = req.widen
+ val sameNamesDifferentPrefixes =
+ foundWiden.typeSymbol.name == reqWiden.typeSymbol.name &&
+ foundWiden.prefix.typeSymbol != reqWiden.prefix.typeSymbol
+ val easilyMistakable =
+ sameNamesDifferentPrefixes &&
+ !req.typeSymbol.isConstant &&
+ finalOwners(foundWiden) && finalOwners(reqWiden) &&
+ !found.typeSymbol.isTypeParameterOrSkolem && !req.typeSymbol.isTypeParameterOrSkolem
+
+ if (easilyMistakable) {
+ val longestNameLength = foundWiden.nameAndArgsString.length max reqWiden.nameAndArgsString.length
+ val paddedFoundName = foundWiden.nameAndArgsString.padTo(longestNameLength, ' ')
+ val paddedReqName = reqWiden.nameAndArgsString.padTo(longestNameLength, ' ')
+ ";\n found : " + (paddedFoundName + s" (in ${found.prefix.typeSymbol.fullNameString}) ") + explainAlias(found) +
+ "\n required: " + (paddedReqName + s" (in ${req.prefix.typeSymbol.fullNameString}) ") + explainAlias(req)
+ } else {
+ def baseMessage = {
+ ";\n found : " + found.toLongString + existentialContext(found) + explainAlias(found) +
+ "\n required: " + req + existentialContext(req) + explainAlias(req)
+ }
+ (withDisambiguation(Nil, found, req)(baseMessage)
+ + explainVariance(found, req)
+ + explainAnyVsAnyRef(found, req)
+ )
+ }
}
def typePatternAdvice(sym: Symbol, ptSym: Symbol) = {
@@ -313,17 +353,12 @@ trait TypeDiagnostics {
def restoreName() = sym.name = savedName
def modifyName(f: String => String) = sym setName newTypeName(f(sym.name.toString))
- /** Prepend java.lang, scala., or Predef. if this type originated
- * in one of those.
- */
- def qualifyDefaultNamespaces() = {
- val intersect = Set(trueOwner, aliasOwner) intersect UnqualifiedOwners
- if (intersect.nonEmpty && tp.typeSymbolDirect.name == tp.typeSymbol.name) preQualify()
- }
-
// functions to manipulate the name
def preQualify() = modifyName(trueOwner.fullName + "." + _)
- def postQualify() = if (!(postQualifiedWith contains trueOwner)) { postQualifiedWith ::= trueOwner; modifyName(_ + "(in " + trueOwner + ")") }
+ def postQualify() = if (!(postQualifiedWith contains trueOwner)) {
+ postQualifiedWith ::= trueOwner
+ modifyName(s => s"$s(in $trueOwner)")
+ }
def typeQualify() = if (sym.isTypeParameterOrSkolem) postQualify()
def nameQualify() = if (trueOwner.isPackageClass) preQualify() else postQualify()
@@ -412,12 +447,6 @@ trait TypeDiagnostics {
if (td1 string_== td2)
tds foreach (_.nameQualify())
- // If they have the same simple name, and either of them is in the
- // scala package or predef, qualify with scala so it is not confusing why
- // e.g. java.util.Iterator and Iterator are different types.
- if (td1 name_== td2)
- tds foreach (_.qualifyDefaultNamespaces())
-
// If they still print identically:
// a) If they are type parameters with different owners, append (in <owner>)
// b) Failing that, the best we can do is append "(some other)" to the latter.
@@ -440,13 +469,18 @@ trait TypeDiagnostics {
context.warning(pos, "imported `%s' is permanently hidden by definition of %s".format(hidden, defn.fullLocationString))
object checkUnused {
- val ignoreNames: Set[TermName] = Set(TermName("readResolve"), TermName("readObject"), TermName("writeObject"), TermName("writeReplace"))
+ val ignoreNames: Set[TermName] = Set(
+ "readResolve", "readObject", "writeObject", "writeReplace"
+ ).map(TermName(_))
class UnusedPrivates extends Traverser {
val defnTrees = ListBuffer[MemberDef]()
val targets = mutable.Set[Symbol]()
val setVars = mutable.Set[Symbol]()
val treeTypes = mutable.Set[Type]()
+ val atBounds = mutable.Set[Symbol]()
+ val params = mutable.Set[Symbol]()
+ val patvars = mutable.Set[Symbol]()
def defnSymbols = defnTrees.toList map (_.symbol)
def localVars = defnSymbols filter (t => t.isLocalToBlock && t.isVar)
@@ -466,20 +500,39 @@ trait TypeDiagnostics {
)
override def traverse(t: Tree): Unit = {
+ val sym = t.symbol
t match {
- case t: MemberDef if qualifies(t.symbol) => defnTrees += t
- case t: RefTree if t.symbol ne null => targets += t.symbol
+ case m: MemberDef if qualifies(t.symbol) =>
+ defnTrees += m
+ t match {
+ case DefDef(mods@_, name@_, tparams@_, vparamss, tpt@_, rhs@_) if !sym.isAbstract && !sym.isDeprecated && !sym.isMacro =>
+ if (sym.isPrimaryConstructor)
+ for (cpa <- sym.owner.constrParamAccessors if cpa.isPrivateLocal) params += cpa
+ else if (sym.isSynthetic && sym.isImplicit) return
+ else if (!sym.isConstructor)
+ for (vs <- vparamss) params ++= vs.map(_.symbol)
+ case _ =>
+ }
+ case CaseDef(pat, guard@_, rhs@_) if settings.warnUnusedPatVars
+ => pat.foreach {
+ // TODO don't warn in isDefinedAt of $anonfun
+ case b @ Bind(n, _) if !atBounded(b) && n != nme.DEFAULT_CASE => patvars += b.symbol
+ case _ =>
+ }
+ case _: RefTree if sym ne null => targets += sym
case Assign(lhs, _) if lhs.symbol != null => setVars += lhs.symbol
+ case Bind(_, _) if atBounded(t) => atBounds += sym
case _ =>
}
// Only record type references which don't originate within the
// definition of the class being referenced.
if (t.tpe ne null) {
- for (tp <- t.tpe ; if !treeTypes(tp) && !currentOwner.ownerChain.contains(tp.typeSymbol)) {
+ for (tp <- t.tpe if !treeTypes(tp) && !currentOwner.ownerChain.contains(tp.typeSymbol)) {
tp match {
case NoType | NoPrefix =>
case NullaryMethodType(_) =>
case MethodType(_, _) =>
+ case SingleType(_, _) =>
case _ =>
log(s"$tp referenced from $currentOwner")
treeTypes += tp
@@ -499,55 +552,127 @@ trait TypeDiagnostics {
&& (m.isPrivate || m.isLocalToBlock)
&& !(treeTypes.exists(tp => tp exists (t => t.typeSymbolDirect == m)))
)
+ def isSyntheticWarnable(sym: Symbol) = (
+ sym.isDefaultGetter
+ )
+
def isUnusedTerm(m: Symbol): Boolean = (
- (m.isTerm)
- && (m.isPrivate || m.isLocalToBlock)
+ m.isTerm
+ && (!m.isSynthetic || isSyntheticWarnable(m))
+ && ((m.isPrivate && !(m.isConstructor && m.owner.isAbstract)) || m.isLocalToBlock)
&& !targets(m)
&& !(m.name == nme.WILDCARD) // e.g. val _ = foo
- && !ignoreNames(m.name.toTermName) // serialization methods
+ && (m.isValueParameter || !ignoreNames(m.name.toTermName)) // serialization methods
&& !isConstantType(m.info.resultType) // subject to constant inlining
&& !treeTypes.exists(_ contains m) // e.g. val a = new Foo ; new a.Bar
+ //&& !(m.isVal && m.info.resultType =:= typeOf[Unit]) // Unit val is uninteresting
)
- def unusedTypes = defnTrees.toList filter (t => isUnusedType(t.symbol))
- def unusedTerms = defnTrees.toList filter (v => isUnusedTerm(v.symbol))
+ def isUnusedParam(m: Symbol): Boolean = (
+ isUnusedTerm(m)
+ && !m.isDeprecated
+ && !m.owner.isDefaultGetter
+ && !(m.isParamAccessor && (
+ m.owner.isImplicit ||
+ targets.exists(s => s.isParameter
+ && s.name == m.name && s.owner.isConstructor && s.owner.owner == m.owner) // exclude ctor params
+ ))
+ )
+ def sympos(s: Symbol): Int =
+ if (s.pos.isDefined) s.pos.point else if (s.isTerm) s.asTerm.referenced.pos.point else -1
+ def treepos(t: Tree): Int =
+ if (t.pos.isDefined) t.pos.point else sympos(t.symbol)
+
+ def unusedTypes = defnTrees.toList.filter(t => isUnusedType(t.symbol)).sortBy(treepos)
+ def unusedTerms = {
+ val all = defnTrees.toList.filter(v => isUnusedTerm(v.symbol))
+
+ // filter out setters if already warning for getter, indicated by position.
+ // also documentary names in patterns.
+ all.filterNot(v =>
+ v.symbol.isSetter && all.exists(g => g.symbol.isGetter && g.symbol.pos.point == v.symbol.pos.point)
+ || atBounds.exists(x => v.symbol.pos.point == x.pos.point)
+ ).sortBy(treepos)
+ }
// local vars which are never set, except those already returned in unused
- def unsetVars = localVars filter (v => !setVars(v) && !isUnusedTerm(v))
+ def unsetVars = localVars.filter(v => !setVars(v) && !isUnusedTerm(v)).sortBy(sympos)
+ def unusedParams = params.toList.filter(isUnusedParam).sortBy(sympos)
+ def inDefinedAt(p: Symbol) = p.owner.isMethod && p.owner.name == nme.isDefinedAt && p.owner.owner.isAnonymousFunction
+ def unusedPatVars = patvars.toList.filter(p => isUnusedTerm(p) && !inDefinedAt(p)).sortBy(sympos)
}
- def apply(unit: CompilationUnit) = {
+ private def warningsEnabled: Boolean = {
+ val ss = settings
+ import ss._
+ warnUnusedPatVars || warnUnusedPrivates || warnUnusedLocals || warnUnusedParams || warnUnusedImplicits
+ }
+
+ def apply(unit: CompilationUnit): Unit = if (warningsEnabled) {
val p = new UnusedPrivates
- p traverse unit.body
- val unused = p.unusedTerms
- unused foreach { defn: DefTree =>
- val sym = defn.symbol
- val pos = (
- if (defn.pos.isDefined) defn.pos
- else if (sym.pos.isDefined) sym.pos
- else sym match {
- case sym: TermSymbol => sym.referenced.pos
- case _ => NoPosition
+ p.traverse(unit.body)
+ if (settings.warnUnusedLocals || settings.warnUnusedPrivates) {
+ for (defn: DefTree <- p.unusedTerms) {
+ val sym = defn.symbol
+ val pos = (
+ if (defn.pos.isDefined) defn.pos
+ else if (sym.pos.isDefined) sym.pos
+ else sym match {
+ case sym: TermSymbol => sym.referenced.pos
+ case _ => NoPosition
+ }
+ )
+ val why = if (sym.isPrivate) "private" else "local"
+ val what = (
+ if (sym.isDefaultGetter) "default argument"
+ else if (sym.isConstructor) "constructor"
+ else if (
+ sym.isVar
+ || sym.isGetter && (sym.accessed.isVar || (sym.owner.isTrait && !sym.hasFlag(STABLE)))
+ ) s"var ${sym.name.getterName.decoded}"
+ else if (
+ sym.isVal
+ || sym.isGetter && (sym.accessed.isVal || (sym.owner.isTrait && sym.hasFlag(STABLE)))
+ || sym.isLazy
+ ) s"val ${sym.name.decoded}"
+ else if (sym.isSetter) s"setter of ${sym.name.getterName.decoded}"
+ else if (sym.isMethod) s"method ${sym.name.decoded}"
+ else if (sym.isModule) s"object ${sym.name.decoded}"
+ else "term"
+ )
+ reporter.warning(pos, s"$why $what in ${sym.owner} is never used")
+ }
+ for (v <- p.unsetVars) {
+ reporter.warning(v.pos, s"local var ${v.name} in ${v.owner} is never set: consider using immutable val")
+ }
+ for (t <- p.unusedTypes) {
+ val sym = t.symbol
+ val wrn = if (sym.isPrivate) settings.warnUnusedPrivates else settings.warnUnusedLocals
+ if (wrn) {
+ val why = if (sym.isPrivate) "private" else "local"
+ reporter.warning(t.pos, s"$why ${sym.fullLocationString} is never used")
}
- )
- val why = if (sym.isPrivate) "private" else "local"
- val what = (
- if (sym.isDefaultGetter) "default argument"
- else if (sym.isConstructor) "constructor"
- else if (sym.isVar || sym.isGetter && sym.accessed.isVar) "var"
- else if (sym.isVal || sym.isGetter && sym.accessed.isVal || sym.isLazy) "val"
- else if (sym.isSetter) "setter"
- else if (sym.isMethod) "method"
- else if (sym.isModule) "object"
- else "term"
- )
- reporter.warning(pos, s"$why $what in ${sym.owner} is never used")
+ }
}
- p.unsetVars foreach { v =>
- reporter.warning(v.pos, s"local var ${v.name} in ${v.owner} is never set - it could be a val")
+ if (settings.warnUnusedPatVars) {
+ for (v <- p.unusedPatVars)
+ reporter.warning(v.pos, s"pattern var ${v.name} in ${v.owner} is never used; `${v.name}@_' suppresses this warning")
}
- p.unusedTypes foreach { t =>
- val sym = t.symbol
- val why = if (sym.isPrivate) "private" else "local"
- reporter.warning(t.pos, s"$why ${sym.fullLocationString} is never used")
+ if (settings.warnUnusedParams || settings.warnUnusedImplicits) {
+ def classOf(s: Symbol): Symbol = if (s.isClass || s == NoSymbol) s else classOf(s.owner)
+ def isImplementation(m: Symbol): Boolean = {
+ val opc = new overridingPairs.Cursor(classOf(m))
+ opc.iterator.exists(pair => pair.low == m)
+ }
+ def isConvention(p: Symbol): Boolean = {
+ (p.name.decoded == "args" && p.owner.isMethod && p.owner.name.decoded == "main") ||
+ (p.tpe =:= typeOf[scala.Predef.DummyImplicit])
+ }
+ def warnable(s: Symbol) = (
+ (settings.warnUnusedParams || s.isImplicit)
+ && !isImplementation(s.owner)
+ && !isConvention(s)
+ )
+ for (s <- p.unusedParams if warnable(s))
+ reporter.warning(s.pos, s"parameter $s in ${s.owner} is never used")
}
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index ac0a653626..69bf5fdef7 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -13,11 +13,12 @@ package scala
package tools.nsc
package typechecker
-import scala.collection.{mutable, immutable}
-import scala.reflect.internal.util.{ BatchSourceFile, Statistics, shortClassOfInstance, ListOfNil }
+import scala.collection.{immutable, mutable}
+import scala.reflect.internal.util.{ListOfNil, Statistics}
import mutable.ListBuffer
import symtab.Flags._
import Mode._
+import scala.reflect.macros.whitebox
// Suggestion check whether we can do without priming scopes with symbols of outer scopes,
// like the IDE does.
@@ -105,12 +106,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// that are turned private by typedBlock
private final val SYNTHETIC_PRIVATE = TRANS_FLAG
- private final val InterpolatorCodeRegex = """\$\{.*?\}""".r
+ private final val InterpolatorCodeRegex = """\$\{\s*(.*?)\s*\}""".r
private final val InterpolatorIdentRegex = """\$[$\w]+""".r // note that \w doesn't include $
abstract class Typer(context0: Context) extends TyperDiagnostics with Adaptation with Tag with PatternTyper with TyperContextErrors {
import context0.unit
- import typeDebug.{ ptTree, ptBlock, ptLine, inGreen, inRed }
+ import typeDebug.ptTree
import TyperErrorGen._
val runDefinitions = currentRun.runDefinitions
import runDefinitions._
@@ -128,6 +129,22 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
def canTranslateEmptyListToNil = true
def missingSelectErrorTree(tree: Tree, qual: Tree, name: Name): Tree = tree
+ // used to exempt synthetic accessors (i.e. those that are synthesized by the compiler to access a field)
+ // from skolemization because there's a weird bug that causes spurious type mismatches
+ // (it seems to have something to do with existential abstraction over values
+ // https://github.com/scala/scala-dev/issues/165
+ // when we're past typer, lazy accessors are synthetic, but before they are user-defined
+ // to make this hack less hacky, we could rework our flag assignment to allow for
+ // requiring both the ACCESSOR and the SYNTHETIC bits to trigger the exemption
+ private def isSyntheticAccessor(sym: Symbol) = sym.isAccessor && (!sym.isLazy || isPastTyper)
+
+ // when type checking during erasure, generate erased types in spots that aren't transformed by erasure
+ // (it erases in TypeTrees, but not in, e.g., the type a Function node)
+ def phasedAppliedType(sym: Symbol, args: List[Type]) = {
+ val tp = appliedType(sym, args)
+ if (phase.erasedTypes) erasure.specialScalaErasure(tp) else tp
+ }
+
def typedDocDef(docDef: DocDef, mode: Mode, pt: Type): Tree =
typed(docDef.definition, mode, pt)
@@ -151,7 +168,9 @@ 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.isErroneous && {paramFailed = true; true})) SearchFailure else inferImplicit(fun, paramTp, context.reportErrors, isView = false, context)
+ val res =
+ if (paramFailed || (paramTp.isErroneous && {paramFailed = true; true})) SearchFailure
+ else inferImplicitFor(paramTp, fun, context, reportAmbiguous = context.reportErrors)
argResultsBuff += res
if (res.isSuccess) {
@@ -187,14 +206,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
!from.isError
&& !to.isError
&& context.implicitsEnabled
- && (inferView(context.tree, from, to, reportAmbiguous = false, saveErrors = true) != EmptyTree)
+ && (inferView(context.tree, from, to, reportAmbiguous = false) != EmptyTree)
// SI-8230 / SI-8463 We'd like to change this to `saveErrors = false`, but can't.
// For now, we can at least pass in `context.tree` rather then `EmptyTree` so as
// to avoid unpositioned type errors.
)
- def inferView(tree: Tree, from: Type, to: Type, reportAmbiguous: Boolean): Tree =
- inferView(tree, from, to, reportAmbiguous, saveErrors = true)
/** Infer an implicit conversion (`view`) between two types.
* @param tree The tree which needs to be converted.
@@ -207,25 +224,23 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* during the inference of a view be put into the original buffer.
* False iff we don't care about them.
*/
- def inferView(tree: Tree, from: Type, to: Type, reportAmbiguous: Boolean, saveErrors: Boolean): Tree = {
- debuglog("infer view from "+from+" to "+to)//debug
- if (isPastTyper) EmptyTree
- else from match {
- case MethodType(_, _) => EmptyTree
- case OverloadedType(_, _) => EmptyTree
- case PolyType(_, _) => EmptyTree
- case _ =>
- def wrapImplicit(from: Type): Tree = {
- val result = inferImplicit(tree, functionType(from.withoutAnnotations :: Nil, to), reportAmbiguous, isView = true, context, saveAmbiguousDivergent = saveErrors)
- if (result.subst != EmptyTreeTypeSubstituter) {
- result.subst traverse tree
- notifyUndetparamsInferred(result.subst.from, result.subst.to)
- }
- result.tree
- }
- wrapImplicit(from) orElse wrapImplicit(byNameType(from))
+ def inferView(tree: Tree, from: Type, to: Type, reportAmbiguous: Boolean = true, saveErrors: Boolean = true): Tree =
+ if (isPastTyper || from.isInstanceOf[MethodType] || from.isInstanceOf[OverloadedType] || from.isInstanceOf[PolyType]) EmptyTree
+ else {
+ debuglog(s"Inferring view from $from to $to for $tree (reportAmbiguous= $reportAmbiguous, saveErrors=$saveErrors)")
+
+ val fromNoAnnot = from.withoutAnnotations
+ val result = inferImplicitView(fromNoAnnot, to, tree, context, reportAmbiguous, saveErrors) match {
+ case fail if fail.isFailure => inferImplicitView(byNameType(fromNoAnnot), to, tree, context, reportAmbiguous, saveErrors)
+ case ok => ok
+ }
+
+ if (result.subst != EmptyTreeTypeSubstituter) {
+ result.subst traverse tree
+ notifyUndetparamsInferred(result.subst.from, result.subst.to)
+ }
+ result.tree
}
- }
import infer._
@@ -239,6 +254,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
var context = context0
def context1 = context
+ // for use with silent type checking to when we can't have results with undetermined type params
+ // note that this captures the context var
+ val isMonoContext = (_: Any) => context.undetparams.isEmpty
+
def dropExistential(tp: Type): Type = tp match {
case ExistentialType(tparams, tpe) =>
new SubstWildcardMap(tparams).apply(tp)
@@ -542,7 +561,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
val qual = typedQualifier { atPos(tree.pos.makeTransparent) {
tree match {
- case Ident(_) => Ident(rootMirror.getPackageObjectWithMember(pre, sym))
+ case Ident(_) =>
+ val packageObject =
+ if (!sym.isOverloaded && sym.owner.isModuleClass) sym.owner.sourceModule // historical optimization, perhaps no longer needed
+ else pre.typeSymbol.packageObject
+ Ident(packageObject)
case Select(qual, _) => Select(qual, nme.PACKAGEkw)
case SelectFromTypeTree(qual, _) => Select(qual, nme.PACKAGEkw)
}
@@ -721,7 +744,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
featureTrait.owner.ownerChain.takeWhile(_ != languageFeatureModule.moduleClass).reverse
val featureName = (nestedOwners map (_.name + ".")).mkString + featureTrait.name
def action(): Boolean = {
- def hasImport = inferImplicit(EmptyTree: Tree, featureTrait.tpe, reportAmbiguous = true, isView = false, context).isSuccess
+ def hasImport = inferImplicitByType(featureTrait.tpe, context).isSuccess
def hasOption = settings.language contains featureName
val OK = hasImport || hasOption
if (!OK) {
@@ -760,7 +783,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
else FunctionClass(numVparams)
}
- if (samSym.exists && samSym.owner != correspondingFunctionSymbol) // don't treat Functions as SAMs
+ if (samSym.exists && tp.typeSymbol != correspondingFunctionSymbol) // don't treat Functions as SAMs
wildcardExtrapolation(normalize(tp memberInfo samSym))
else NoType
}
@@ -798,7 +821,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* (11) Widen numeric literals to their expected type, if necessary
* (12) When in mode EXPRmode, convert E to { E; () } if expected type is scala.Unit.
* (13) When in mode EXPRmode, apply AnnotationChecker conversion if expected type is annotated.
- * (14) When in mode EXPRmode, apply a view
+ * (14) When in mode EXPRmode, do SAM conversion
+ * (15) When in mode EXPRmode, apply a view
* If all this fails, error
*/
protected def adapt(tree: Tree, mode: Mode, pt: Type, original: Tree = EmptyTree): Tree = {
@@ -840,11 +864,24 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
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
- // we pass `EmptyTree` as the `original`. intended? added in 2009 (53d98e7d42) by martin.
- tree1 setType pluginsTyped(tree1.tpe, this, tree1, mode, pt)
- if (tree1.isEmpty) tree1 else adapt(tree1, mode, pt, EmptyTree)
+ // SO-10066 Need to patch the enclosing tree in the context to make translation of Dynamic
+ // work during fallback typechecking below.
+ val resetContext: Context = {
+ object substResetForOriginal extends Transformer {
+ override def transform(tree: Tree): Tree = {
+ if (tree eq original) resetTree
+ else super.transform(tree)
+ }
+ }
+ context.make(substResetForOriginal.transform(context.tree))
+ }
+ typerWithLocalContext(resetContext) { typer1 =>
+ val tree1 = typer1.typed(resetTree, mode)
+ // Q: `typed` already calls `pluginsTyped` and `adapt`. the only difference here is that
+ // we pass `EmptyTree` as the `original`. intended? added in 2009 (53d98e7d42) by martin.
+ tree1 setType pluginsTyped(tree1.tpe, typer1, tree1, mode, pt)
+ if (tree1.isEmpty) tree1 else typer1.adapt(tree1, mode, pt, EmptyTree)
+ }
}
)
else
@@ -858,9 +895,32 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case Block(_, tree1) => tree1.symbol
case _ => tree.symbol
}
- if (!meth.isConstructor && (isFunctionType(pt) || samOf(pt).exists)) { // (4.2)
+
+ def cantAdapt =
+ if (context.implicitsEnabled) MissingArgsForMethodTpeError(tree, meth)
+ else setError(tree)
+
+ // constructors do not eta-expand
+ if (meth.isConstructor) cantAdapt
+ // (4.2) eta-expand method value when function or sam type is expected
+ else if (isFunctionType(pt) || (!mt.params.isEmpty && samOf(pt).exists)) {
+ // SI-9536 `!mt.params.isEmpty &&`: for backwards compatibility with 2.11,
+ // we don't adapt a zero-arg method value to a SAM
+ // In 2.13, we won't do any eta-expansion for zero-arg method values, but we should deprecate first
+
debuglog(s"eta-expanding $tree: ${tree.tpe} to $pt")
checkParamsConvertible(tree, tree.tpe)
+
+ // SI-7187 eta-expansion of zero-arg method value is deprecated, switch order of (4.3) and (4.2) in 2.13
+ def isExplicitEtaExpansion = original match {
+ case Typed(_, Function(Nil, EmptyTree)) => true // tree shape for `f _`
+ case _ => false
+ }
+ if (mt.params.isEmpty && !isExplicitEtaExpansion) {
+ currentRun.reporting.deprecationWarning(tree.pos, NoSymbol,
+ s"Eta-expansion of zero-argument method values is deprecated. Did you intend to write ${Apply(tree, Nil)}?", "2.12.0")
+ }
+
val tree0 = etaExpand(context.unit, tree, this)
// #2624: need to infer type arguments for eta expansion of a polymorphic method
@@ -874,12 +934,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
else
typed(tree0, mode, pt)
}
- else if (!meth.isConstructor && mt.params.isEmpty) // (4.3)
- adapt(typed(Apply(tree, Nil) setPos tree.pos), mode, pt, original)
- else if (context.implicitsEnabled)
- MissingArgsForMethodTpeError(tree, meth)
- else
- setError(tree)
+ // (4.3) apply to empty argument list -- TODO 2.13: move this one case up to avoid eta-expanding at arity 0
+ else if (mt.params.isEmpty) adapt(typed(Apply(tree, Nil) setPos tree.pos), mode, pt, original)
+ else cantAdapt
}
def adaptType(): Tree = {
@@ -928,24 +985,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
def insertApply(): Tree = {
assert(!context.inTypeConstructorAllowed, mode) //@M
val adapted = adaptToName(tree, nme.apply)
- def stabilize0(pre: Type): Tree = stabilize(adapted, pre, MonoQualifierModes, WildcardType)
-
- // TODO reconcile the overlap between Typers#stablize and TreeGen.stabilize
- val qual = adapted match {
- case This(_) =>
- gen.stabilize(adapted)
- case Ident(_) =>
- val owner = adapted.symbol.owner
- val pre =
- if (owner.isPackageClass) owner.thisType
- else if (owner.isClass) context.enclosingSubClassContext(owner).prefix
- else NoPrefix
- stabilize0(pre)
- case Select(qualqual, _) =>
- stabilize0(qualqual.tpe)
- case other =>
- other
- }
+ val qual = gen.stabilize(adapted)
typedPos(tree.pos, mode, pt) {
Select(qual setPos tree.pos.makeTransparent, nme.apply)
}
@@ -1020,72 +1060,75 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
}
- def fallbackAfterVanillaAdapt(): Tree = {
- def isPopulatedPattern = {
- if ((tree.symbol ne null) && tree.symbol.isModule)
- inferModulePattern(tree, pt)
-
- isPopulated(tree.tpe, approximateAbstracts(pt))
+ def adaptExprNotFunMode(): Tree = {
+ def lastTry(err: AbsTypeError = null): Tree = {
+ debuglog("error tree = " + tree)
+ if (settings.debug && settings.explaintypes) explainTypes(tree.tpe, pt)
+ if (err ne null) context.issue(err)
+ if (tree.tpe.isErroneous || pt.isErroneous) setError(tree)
+ else adaptMismatchedSkolems()
}
- if (mode.inPatternMode && isPopulatedPattern)
- return tree
- val tree1 = constfold(tree, pt) // (10) (11)
- if (tree1.tpe <:< pt)
- return adapt(tree1, mode, pt, original)
+ // TODO: should we even get to fallbackAfterVanillaAdapt for an ill-typed tree?
+ if (mode.typingExprNotFun && !tree.tpe.isErroneous) {
+ @inline def tpdPos(transformed: Tree) = typedPos(tree.pos, mode, pt)(transformed)
+ @inline def tpd(transformed: Tree) = typed(transformed, mode, pt)
- if (mode.typingExprNotFun) {
- // The <: Any requirement inhibits attempts to adapt continuation types
- // to non-continuation types.
- if (tree.tpe <:< AnyTpe) pt.dealias match {
- case TypeRef(_, UnitClass, _) => // (12)
- 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 (!isPastTyper && settings.warnNumericWiden)
- context.warning(tree.pos, "implicit numeric widening")
- return typedPos(tree.pos, mode, pt)(Select(tree, "to" + sym.name))
- case _ =>
+ @inline def warnValueDiscard(): Unit = if (!isPastTyper && settings.warnValueDiscard) {
+ def isThisTypeResult = (tree, tree.tpe) match {
+ case (Apply(Select(receiver, _), _), SingleType(_, sym)) => sym == receiver.symbol
+ case _ => false
+ }
+ if (!isThisTypeResult) context.warning(tree.pos, "discarded non-Unit value")
}
- if (pt.dealias.annotations.nonEmpty && canAdaptAnnotations(tree, this, mode, pt)) // (13)
- return typed(adaptAnnotations(tree, this, mode, pt), mode, pt)
-
- if (hasUndets)
- return instantiate(tree, mode, pt)
-
- if (context.implicitsEnabled && !pt.isError && !tree.isErrorTyped) {
- // (14); the condition prevents chains of views
- debuglog("inferring view from " + tree.tpe + " to " + pt)
- inferView(tree, tree.tpe, pt, reportAmbiguous = true) match {
- case EmptyTree =>
- case coercion =>
- def msg = "inferred view from " + tree.tpe + " to " + pt + " = " + coercion + ":" + coercion.tpe
- if (settings.logImplicitConv)
- context.echo(tree.pos, msg)
-
- debuglog(msg)
- val silentContext = context.makeImplicit(context.ambiguousErrors)
- val res = newTyper(silentContext).typed(
- new ApplyImplicitView(coercion, List(tree)) setPos tree.pos, mode, pt)
- silentContext.reporter.firstError match {
- case Some(err) => context.issue(err)
- case None => return res
+ @inline def warnNumericWiden(): Unit =
+ if (!isPastTyper && settings.warnNumericWiden) context.warning(tree.pos, "implicit numeric widening")
+
+ // The <: Any requirement inhibits attempts to adapt continuation types to non-continuation types.
+ val anyTyped = tree.tpe <:< AnyTpe
+
+ pt.dealias match {
+ case TypeRef(_, UnitClass, _) if anyTyped => // (12)
+ warnValueDiscard() ; tpdPos(gen.mkUnitBlock(tree))
+ case TypeRef(_, numValueCls, _) if anyTyped && isNumericValueClass(numValueCls) && isNumericSubType(tree.tpe, pt) => // (10) (11)
+ warnNumericWiden() ; tpdPos(Select(tree, s"to${numValueCls.name}"))
+ case dealiased if dealiased.annotations.nonEmpty && canAdaptAnnotations(tree, this, mode, pt) => // (13)
+ tpd(adaptAnnotations(tree, this, mode, pt))
+ case _ =>
+ if (hasUndets) instantiate(tree, mode, pt)
+ else {
+ // (14) sam conversion
+ // TODO: figure out how to avoid partially duplicating typedFunction (samMatchingFunction)
+ // Could we infer the SAM type, assign it to the tree and add the attachment,
+ // all in one fell swoop at the end of typedFunction?
+ val samAttach = inferSamType(tree, pt, mode)
+
+ if (samAttach.samTp ne NoType) tree.setType(samAttach.samTp).updateAttachment(samAttach)
+ else { // (15) implicit view application
+ val coercion =
+ if (context.implicitsEnabled) inferView(tree, tree.tpe, pt)
+ else EmptyTree
+ if (coercion ne EmptyTree) {
+ def msg = s"inferred view from ${tree.tpe} to $pt via $coercion: ${coercion.tpe}"
+ if (settings.logImplicitConv) context.echo(tree.pos, msg)
+ else debuglog(msg)
+
+ val viewApplied = new ApplyImplicitView(coercion, List(tree)) setPos tree.pos
+ val silentContext = context.makeImplicit(context.ambiguousErrors)
+ val typedView = newTyper(silentContext).typed(viewApplied, mode, pt)
+
+ silentContext.reporter.firstError match {
+ case None => typedView
+ case Some(err) => lastTry(err)
+ }
+ } else lastTry()
}
- }
+ }
}
- }
-
- debuglog("error tree = " + tree)
- if (settings.debug && settings.explaintypes)
- explainTypes(tree.tpe, pt)
-
- if (tree.tpe.isErroneous || pt.isErroneous)
- setError(tree)
- else
- adaptMismatchedSkolems()
+ } else lastTry()
}
+
def vanillaAdapt(tree: Tree) = {
def applyPossible = {
def applyMeth = member(adaptToName(tree, nme.apply), nme.apply)
@@ -1119,8 +1162,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
else if (tree.tpe <:< pt)
tree
- else
- fallbackAfterVanillaAdapt()
+ else if (mode.inPatternMode && { inferModulePattern(tree, pt); isPopulated(tree.tpe, approximateAbstracts(pt)) })
+ tree
+ else {
+ val constFolded = constfold(tree, pt)
+ if (constFolded.tpe <:< pt) adapt(constFolded, mode, pt, original) // set stage for (0)
+ else adaptExprNotFunMode() // (10) -- (15)
+ }
}
// begin adapt
@@ -1139,7 +1187,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
adapt(tree setType restpe, mode, pt, original)
case TypeRef(_, ByNameParamClass, arg :: Nil) if mode.inExprMode => // (2)
adapt(tree setType arg, mode, pt, original)
- case tp if mode.typingExprNotLhs && isExistentialType(tp) =>
+ case tp if mode.typingExprNotLhs && isExistentialType(tp) && !isSyntheticAccessor(context.owner) =>
adapt(tree setType tp.dealias.skolemizeExistential(context.owner, tree), mode, pt, original)
case PolyType(tparams, restpe) if mode.inNone(TAPPmode | PATTERNmode) && !context.inTypeConstructorAllowed => // (3)
// assert((mode & HKmode) == 0) //@M a PolyType in HKmode represents an anonymous type function,
@@ -1185,7 +1233,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val savedUndetparams = context.undetparams
silent(_.instantiate(tree, mode, UnitTpe)) orElse { _ =>
context.undetparams = savedUndetparams
- val valueDiscard = atPos(tree.pos)(Block(List(instantiate(tree, mode, WildcardType)), Literal(Constant(()))))
+ val valueDiscard = atPos(tree.pos)(gen.mkUnitBlock(instantiate(tree, mode, WildcardType)))
typed(valueDiscard, mode, UnitTpe)
}
}
@@ -1246,7 +1294,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* If no conversion is found, return `qual` unchanged.
*
*/
- def adaptToArguments(qual: Tree, name: Name, args: List[Tree], pt: Type, reportAmbiguous: Boolean, saveErrors: Boolean): Tree = {
+ def adaptToArguments(qual: Tree, name: Name, args: List[Tree], pt: Type, reportAmbiguous: Boolean = true, saveErrors: Boolean = true): Tree = {
def doAdapt(restpe: Type) =
//util.trace("adaptToArgs "+qual+", name = "+name+", argtpes = "+(args map (_.tpe))+", pt = "+pt+" = ")
adaptToMember(qual, HasMethodMatching(name, args map (_.tpe), restpe), reportAmbiguous, saveErrors)
@@ -1262,7 +1310,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* a method `name`. If that's ambiguous try taking arguments into
* account using `adaptToArguments`.
*/
- def adaptToMemberWithArgs(tree: Tree, qual: Tree, name: Name, mode: Mode, reportAmbiguous: Boolean, saveErrors: Boolean): Tree = {
+ def adaptToMemberWithArgs(tree: Tree, qual: Tree, name: Name, mode: Mode, reportAmbiguous: Boolean = true, saveErrors: Boolean = true): Tree = {
def onError(reportError: => Tree): Tree = context.tree match {
case Apply(tree1, args) if (tree1 eq tree) && args.nonEmpty =>
( silent (_.typedArgs(args.map(_.duplicate), mode))
@@ -1697,9 +1745,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (!isPastTyper && psym.hasDeprecatedInheritanceAnnotation &&
!sameSourceFile && !context.owner.ownerChain.exists(x => x.isDeprecated || x.hasBridgeAnnotation)) {
- val suffix = psym.deprecatedInheritanceMessage map (": " + _) getOrElse ""
- val msg = s"inheritance from ${psym.fullLocationString} is deprecated$suffix"
- context.deprecationWarning(parent.pos, psym, msg)
+ val version = psym.deprecatedInheritanceVersion.getOrElse("")
+ val since = if (version.isEmpty) version else s" (since $version)"
+ val message = psym.deprecatedInheritanceMessage.map(msg => s": $msg").getOrElse("")
+ val report = s"inheritance from ${psym.fullLocationString} is deprecated$since$message"
+ context.deprecationWarning(parent.pos, psym, report, version)
}
val parentTypeOfThis = parent.tpe.dealias.typeOfThis
@@ -1737,17 +1787,21 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
classinfo.parents map (_.instantiateTypeParams(List(tparam), List(AnyRefTpe))),
classinfo.decls,
clazz)
- clazz.setInfo {
- clazz.info match {
- case PolyType(tparams, _) => PolyType(tparams, newinfo)
- case _ => newinfo
- }
- }
+ updatePolyClassInfo(clazz, newinfo)
FinitaryError(tparam)
}
}
}
+ private def updatePolyClassInfo(clazz: Symbol, newinfo: ClassInfoType): clazz.type = {
+ clazz.setInfo {
+ clazz.info match {
+ case PolyType(tparams, _) => PolyType(tparams, newinfo)
+ case _ => newinfo
+ }
+ }
+ }
+
def typedClassDef(cdef: ClassDef): Tree = {
val clazz = cdef.symbol
val typedMods = typedModifiers(cdef.mods)
@@ -1856,10 +1910,30 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// please FIXME: uncommenting this line breaks everything
// val templ = treeCopy.Template(templ0, templ0.body, templ0.self, templ0.parents)
val clazz = context.owner
+
+ val parentTypes = parents1.map(_.tpe)
+
+ // The parents may have been normalized by typedParentTypes.
+ // We must update the info as well, or we won't find the super constructor for our now-first parent class
+ // Consider `class C ; trait T extends C ; trait U extends T`
+ // `U`'s info will start with parent `T`, but `typedParentTypes` will return `List(C, T)` (`== parents1`)
+ // now, the super call in the primary ctor will fail to find `C`'s ctor, since it bases its search on
+ // `U`'s info, not the trees.
+ //
+ // For correctness and performance, we restrict this rewrite to anonymous classes,
+ // as others have their parents in order already (it seems!), and we certainly
+ // don't want to accidentally rewire superclasses for e.g. the primitive value classes.
+ //
+ // TODO: Find an example of a named class needing this rewrite, I tried but couldn't find one.
+ if (clazz.isAnonymousClass && clazz.info.parents != parentTypes) {
+// println(s"updating parents of $clazz from ${clazz.info.parents} to $parentTypes")
+ updatePolyClassInfo(clazz, ClassInfoType(parentTypes, clazz.info.decls, clazz))
+ }
+
clazz.annotations.map(_.completeInfo())
if (templ.symbol == NoSymbol)
templ setSymbol clazz.newLocalDummy(templ.pos)
- val self1 = templ.self match {
+ val self1 = (templ.self: @unchecked) match {
case vd @ ValDef(_, _, tpt, EmptyTree) =>
val tpt1 = checkNoEscaping.privates(
clazz.thisSym,
@@ -1897,11 +1971,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (!phase.erasedTypes && !clazz.info.resultType.isError) // @S: prevent crash for duplicated type members
checkFinitary(clazz.info.resultType.asInstanceOf[ClassInfoType])
- val body2 = {
- val body2 =
- if (isPastTyper || reporter.hasErrors) body1
- else body1 flatMap rewrappingWrapperTrees(namer.addDerivedTrees(Typer.this, _))
- val primaryCtor = treeInfo.firstConstructor(body2)
+ val bodyWithPrimaryCtor = {
+ val primaryCtor = treeInfo.firstConstructor(body1)
val primaryCtor1 = primaryCtor match {
case DefDef(_, _, _, _, _, Block(earlyVals :+ global.pendingSuperCall, unit)) =>
val argss = superArgs(parents1.head) getOrElse Nil
@@ -1910,10 +1981,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
deriveDefDef(primaryCtor)(block => Block(earlyVals :+ superCall, unit) setPos pos) setPos pos
case _ => primaryCtor
}
- body2 mapConserve { case `primaryCtor` => primaryCtor1; case stat => stat }
+ body1 mapConserve { case `primaryCtor` => primaryCtor1; case stat => stat }
}
- val body3 = typedStats(body2, templ.symbol)
+ val body3 = typedStats(bodyWithPrimaryCtor, templ.symbol)
if (clazz.info.firstParent.typeSymbol == AnyValClass)
validateDerivedValueClass(clazz, body3)
@@ -1956,13 +2027,20 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// use typedValDef instead. this version is called after creating a new context for the ValDef
private def typedValDefImpl(vdef: ValDef) = {
val sym = vdef.symbol.initialize
- val typedMods = typedModifiers(vdef.mods)
+ val typedMods = if (nme.isLocalName(sym.name) && sym.isPrivateThis && !vdef.mods.isPrivateLocal) {
+ // SI-10009 This tree has been given a field symbol by `enterGetterSetter`, patch up the
+ // modifiers accordingly so that we can survive resetAttrs and retypechecking.
+ // Similarly, we use `sym.name` rather than `vdef.name` below to use the local name.
+ typedModifiers(vdef.mods.copy(flags = sym.flags, privateWithin = tpnme.EMPTY))
+ } else typedModifiers(vdef.mods)
sym.annotations.map(_.completeInfo())
val tpt1 = checkNoEscaping.privates(sym, typedType(vdef.tpt))
checkNonCyclic(vdef, tpt1)
- if (sym.hasAnnotation(definitions.VolatileAttr) && !sym.isMutable)
+ // allow trait accessors: it's the only vehicle we have to hang on to annotations that must be passed down to
+ // the field that's mixed into a subclass
+ if (sym.hasAnnotation(definitions.VolatileAttr) && !((sym hasFlag MUTABLE | LAZY) || (sym hasFlag ACCESSOR) && sym.owner.isTrait))
VolatileValueError(vdef)
val rhs1 =
@@ -1979,7 +2057,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
override def matches(sym: Symbol, sym1: Symbol) =
if (sym.isSkolem) matches(sym.deSkolemize, sym1)
else if (sym1.isSkolem) matches(sym, sym1.deSkolemize)
- else super[SubstTypeMap].matches(sym, sym1)
+ else super.matches(sym, sym1)
}
// allow defaults on by-name parameters
if (sym hasFlag BYNAMEPARAM)
@@ -1989,7 +2067,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
} else tpt1.tpe
transformedOrTyped(vdef.rhs, EXPRmode | BYVALmode, tpt2)
}
- treeCopy.ValDef(vdef, typedMods, vdef.name, tpt1, checkDead(rhs1)) setType NoType
+ treeCopy.ValDef(vdef, typedMods, sym.name, tpt1, checkDead(rhs1)) setType NoType
}
/** Enter all aliases of local parameter accessors.
@@ -2028,35 +2106,39 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case _ =>
(call, Nil)
}
- val (superConstr, superArgs) = decompose(rhs)
- assert(superConstr.symbol ne null, superConstr)//debug
- def superClazz = superConstr.symbol.owner
- def superParamAccessors = superClazz.constrParamAccessors
// associate superclass paramaccessors with their aliases
- if (superConstr.symbol.isPrimaryConstructor && !superClazz.isJavaDefined && sameLength(superParamAccessors, superArgs)) {
- for ((superAcc, superArg @ Ident(name)) <- superParamAccessors zip superArgs) {
- if (mexists(vparamss)(_.symbol == superArg.symbol)) {
- val alias = (
- superAcc.initialize.alias
- orElse (superAcc getterIn superAcc.owner)
- filter (alias => superClazz.info.nonPrivateMember(alias.name) == alias)
- )
- if (alias.exists && !alias.accessed.isVariable && !isRepeatedParamType(alias.accessed.info)) {
- val ownAcc = clazz.info decl name suchThat (_.isParamAccessor) match {
- case acc if !acc.isDeferred && acc.hasAccessorFlag => acc.accessed
- case acc => acc
- }
- ownAcc match {
- case acc: TermSymbol if !acc.isVariable && !isByNameParamType(acc.info) =>
- debuglog(s"$acc has alias ${alias.fullLocationString}")
- acc setAlias alias
- case _ =>
+ val (superConstr, superArgs) = decompose(rhs)
+ if (superConstr.symbol.isPrimaryConstructor) {
+ val superClazz = superConstr.symbol.owner
+ if (!superClazz.isJavaDefined) {
+ val superParamAccessors = superClazz.constrParamAccessors
+ if (sameLength(superParamAccessors, superArgs)) {
+ for ((superAcc, superArg@Ident(name)) <- superParamAccessors zip superArgs) {
+ if (mexists(vparamss)(_.symbol == superArg.symbol)) {
+ val alias = (
+ superAcc.initialize.alias
+ orElse (superAcc getterIn superAcc.owner)
+ filter (alias => superClazz.info.nonPrivateMember(alias.name) == alias)
+ )
+ if (alias.exists && !alias.accessed.isVariable && !isRepeatedParamType(alias.accessed.info)) {
+ val ownAcc = clazz.info decl name suchThat (_.isParamAccessor) match {
+ case acc if !acc.isDeferred && acc.hasAccessorFlag => acc.accessed
+ case acc => acc
+ }
+ ownAcc match {
+ case acc: TermSymbol if !acc.isVariable && !isByNameParamType(acc.info) =>
+ debuglog(s"$acc has alias ${alias.fullLocationString}")
+ acc setAlias alias
+ case _ =>
+ }
+ }
}
}
}
}
}
+
pending.foreach(ErrorUtils.issueTypeError)
}
@@ -2158,6 +2240,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
def typedDefDef(ddef: DefDef): DefDef = {
+ // an accessor's type completer may mutate a type inside `ddef` (`== context.unit.synthetics(ddef.symbol)`)
+ // concretely: it sets the setter's parameter type or the getter's return type (when derived from a valdef with empty tpt)
val meth = ddef.symbol.initialize
reenterTypeParams(ddef.tparams)
@@ -2167,7 +2251,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (!isPastTyper && meth.isPrimaryConstructor) {
for (vparams <- ddef.vparamss; vd <- vparams) {
if (vd.mods.isParamAccessor) {
- namer.validateParam(vd)
+ vd.symbol setAnnotations (vd.symbol.annotations filter AnnotationInfo.mkFilter(ParamTargetClass, defaultRetention = true))
}
}
}
@@ -2203,9 +2287,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
transformedOrTyped(ddef.rhs, EXPRmode, tpt1.tpe)
}
- if (meth.isClassConstructor && !isPastTyper && !meth.owner.isSubClass(AnyValClass)) {
- // At this point in AnyVal there is no supercall, which will blow up
- // in computeParamAliases; there's nothing to be computed for Anyval anyway.
+ if (meth.isClassConstructor && !isPastTyper && !meth.owner.isSubClass(AnyValClass) && !meth.isJava) {
+ // There are no supercalls for AnyVal or constructors from Java sources, which
+ // would blow up in computeParamAliases; there's nothing to be computed for them
+ // anyway.
if (meth.isPrimaryConstructor)
computeParamAliases(meth.owner, vparamss1, rhs1)
else
@@ -2223,7 +2308,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val allParams = meth.paramss.flatten
for (p <- allParams) {
for (n <- p.deprecatedParamName) {
- if (allParams.exists(p1 => p1.name == n || (p != p1 && p1.deprecatedParamName.exists(_ == n))))
+ if (allParams.exists(p1 => p != p1 && (p1.name == n || p1.deprecatedParamName.exists(_ == n))))
DeprecatedParamNameError(p, n)
}
}
@@ -2319,7 +2404,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
for (stat <- block.stats) enterLabelDef(stat)
if (phaseId(currentPeriod) <= currentRun.typerPhase.id) {
- // This is very tricky stuff, because we are navigating the Skylla and Charybdis of
+ // This is very tricky stuff, because we are navigating the Scylla and Charybdis of
// anonymous classes and what to return from them here. On the one hand, we cannot admit
// every non-private member of an anonymous class as a part of the structural type of the
// enclosing block. This runs afoul of the restriction that a structural type may not
@@ -2351,31 +2436,49 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
// The block is an anonymous class definitions/instantiation pair
// -> members that are hidden by the type of the block are made private
- val toHide = (
- classDecls filter (member =>
- member.isTerm
- && member.isPossibleInRefinement
- && member.isPublic
- && !matchesVisibleMember(member)
- ) map (member => member
- resetFlag (PROTECTED | LOCAL)
- setFlag (PRIVATE | SYNTHETIC_PRIVATE)
- setPrivateWithin NoSymbol
- )
- )
- syntheticPrivates ++= toHide
+ classDecls foreach { toHide =>
+ if (toHide.isTerm
+ && toHide.isPossibleInRefinement
+ && toHide.isPublic
+ && !matchesVisibleMember(toHide)) {
+ (toHide
+ resetFlag (PROTECTED | LOCAL)
+ setFlag (PRIVATE | SYNTHETIC_PRIVATE)
+ setPrivateWithin NoSymbol)
+
+ syntheticPrivates += toHide
+ }
+ }
+
case _ =>
}
}
- val stats1 = if (isPastTyper) block.stats else
- block.stats.flatMap(stat => stat match {
- case vd@ValDef(_, _, _, _) if vd.symbol.isLazy =>
- namer.addDerivedTrees(Typer.this, vd)
- case _ => stat::Nil
- })
- val stats2 = typedStats(stats1, context.owner)
+ val statsTyped = typedStats(block.stats, context.owner, warnPure = false)
val expr1 = typed(block.expr, mode &~ (FUNmode | QUALmode), pt)
- treeCopy.Block(block, stats2, expr1)
+
+ // sanity check block for unintended expr placement
+ if (!isPastTyper) {
+ val (count, result0, adapted) =
+ expr1 match {
+ case Block(expr :: Nil, Literal(Constant(()))) => (1, expr, true)
+ case Literal(Constant(())) => (0, EmptyTree, false)
+ case _ => (1, EmptyTree, false)
+ }
+ def checkPure(t: Tree, supple: Boolean): Unit =
+ if (treeInfo.isPureExprForWarningPurposes(t)) {
+ val msg = "a pure expression does nothing in statement position"
+ val parens = if (statsTyped.length + count > 1) "multiline expressions might require enclosing parentheses" else ""
+ val discard = if (adapted) "; a value can be silently discarded when Unit is expected" else ""
+ val text =
+ if (supple) s"${parens}${discard}"
+ else if (!parens.isEmpty) s"${msg}; ${parens}" else msg
+ context.warning(t.pos, text)
+ }
+ statsTyped.foreach(checkPure(_, supple = false))
+ if (result0.nonEmpty) checkPure(result0, supple = true)
+ }
+
+ treeCopy.Block(block, statsTyped, expr1)
.setType(if (treeInfo.isExprSafeToInline(block)) expr1.tpe else expr1.tpe.deconst)
} finally {
// enable escaping privates checking from the outside and recycle
@@ -2464,7 +2567,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// TODO: add fallback __match sentinel to predef
val matchStrategy: Tree =
- if (!(settings.Xexperimental && context.isNameInScope(vpmName._match))) null // fast path, avoiding the next line if there's no __match to be seen
+ if (!(settings.Yvirtpatmat && context.isNameInScope(vpmName._match))) null // fast path, avoiding the next line if there's no __match to be seen
else newTyper(context.makeImplicit(reportAmbiguousErrors = false)).silent(_.typed(Ident(vpmName._match)), reportAmbiguousErrors = false) orElse (_ => null)
if (matchStrategy ne null) // virtualize
@@ -2576,8 +2679,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// the default uses applyOrElse's first parameter since the scrut's type has been widened
val match_ = {
- val defaultCase = methodBodyTyper.typedCase(
- mkDefaultCase(methodBodyTyper.typed1(REF(default) APPLY (REF(x)), mode, B1.tpe).setType(B1.tpe)), argTp, B1.tpe)
+ val cdef = mkDefaultCase(methodBodyTyper.typed1(REF(default) APPLY (REF(x)), mode, B1.tpe).setType(B1.tpe))
+ val List(defaultCase) = methodBodyTyper.typedCases(List(cdef), argTp, B1.tpe)
treeCopy.Match(match0, match0.selector, match0.cases :+ defaultCase)
}
match_ setType B1.tpe
@@ -2705,187 +2808,99 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
}
- /** Synthesize and type check the implementation of a type with a Single Abstract Method
- *
- * `{ (p1: T1, ..., pN: TN) => body } : S`
- *
- * expands to (where `S` is the expected type that defines a single abstract method named `apply`)
- *
- * `{
- * def apply$body(p1: T1, ..., pN: TN): T = body
- * new S {
- * def apply(p1: T1', ..., pN: TN'): T' = apply$body(p1,..., pN)
- * }
- * }`
- *
- * If 'T' is not fully defined, it is inferred by type checking
- * `apply$body` without a result type before type checking the block.
- * The method's inferred result type is used instead of `T`. [See test/files/pos/sammy_poly.scala]
- *
- * The `apply` method is identified by the argument `sam`; `S` corresponds to the argument `samClassTp`,
- * and `resPt` is derived from `samClassTp` -- it may be fully defined, or not...
- * If it is not fully defined, we derive `samClassTpFullyDefined` by inferring any unknown type parameters.
- *
- * The types T1' ... TN' and T' are derived from the method signature of the sam method,
- * as seen from the fully defined `samClassTpFullyDefined`.
- *
- * The function's body is put in a method outside of the class definition to enforce scoping.
- * S's members should not be in scope in `body`.
- *
- * The restriction on implicit arguments (neither S's constructor, nor sam may take an implicit argument list),
- * is largely to keep the implementation of type inference (the computation of `samClassTpFullyDefined`) simple.
- *
- * NOTE: it would be nicer to not have to type check `apply$body` separately when `T` is not fully defined.
- * However T must be fully defined before we type the instantiation, as it'll end up as a parent type,
- * which must be fully defined. Would be nice to have some kind of mechanism to insert type vars in a block of code,
- * and have the instantiation of the first occurrence propagate to the rest of the block.
- *
- * TODO: by-name params
- * scala> trait LazySink { def accept(a: => Any): Unit }
- * defined trait LazySink
- *
- * scala> val f: LazySink = (a) => (a, a)
- * f: LazySink = $anonfun$1@1fb26910
- *
- * scala> f(println("!"))
- * <console>:10: error: LazySink does not take parameters
- * f(println("!"))
- * ^
- *
- * scala> f.accept(println("!"))
- * !
- * !
- */
- def synthesizeSAMFunction(sam: Symbol, fun: Function, resPt: Type, samClassTp: Type, mode: Mode): Tree = {
- // assert(fun.vparams forall (vp => isFullyDefined(vp.tpt.tpe))) -- by construction, as we take them from sam's info
- val sampos = fun.pos
-
- // if the expected sam type is fully defined, use it for the method's result type
- // otherwise, NoType, so that type inference will determine the method's result type
- // resPt is syntactically contained in samClassTp, so if the latter is fully defined, so is the former
- // ultimately, we want to fully define samClassTp as it is used as the superclass of our anonymous class
- val samDefTp = if (isFullyDefined(resPt)) resPt else NoType
- val bodyName = newTermName(sam.name + "$body")
-
- // `def '${sam.name}\$body'($p1: $T1, ..., $pN: $TN): $resPt = $body`
- val samBodyDef =
- DefDef(NoMods,
- bodyName,
- Nil,
- List(fun.vparams.map(_.duplicate)), // must duplicate as we're also using them for `samDef`
- TypeTree(samDefTp) setPos sampos.focus,
- fun.body)
-
- // If we need to enter the sym for the body def before type checking the block,
- // we'll create a nested context, as explained below.
- var nestedTyper = this
-
- // Type check body def before classdef to fully determine samClassTp (if necessary).
- // As `samClassTp` determines a parent type for the class,
- // we can't type check `block` in one go unless `samClassTp` is fully defined.
- val samClassTpFullyDefined =
- if (isFullyDefined(samClassTp)) samClassTp
+ /** Synthesize and type check the implementation of a type with a Single Abstract Method.
+ *
+ * Based on a type checked Function node `{ (p1: T1, ..., pN: TN) => body } : S`
+ * where `S` is the expected type that defines a single abstract method (call it `apply` for the example),
+ * that has signature `(p1: T1', ..., pN: TN'): T'`, synthesize the instantiation of the following anonymous class
+ *
+ * ```
+ * new S {
+ * def apply$body(p1: T1, ..., pN: TN): T = body
+ * def apply(p1: T1', ..., pN: TN'): T' = apply$body(p1,..., pN)
+ * }
+ * ```
+ *
+ * The `apply` method is identified by the argument `sam`; `S` corresponds to the argument `pt`,
+ * If `pt` is not fully defined, we derive `samClassTpFullyDefined` by inferring any unknown type parameters.
+ *
+ * The types T1' ... TN' and T' are derived from the method signature of the sam method,
+ * as seen from the fully defined `samClassTpFullyDefined`.
+ *
+ * The function's body is put in a (static) method in the class definition to enforce scoping.
+ * S's members should not be in scope in `body`. (Putting it in the block outside the class runs into implementation problems described below)
+ *
+ * The restriction on implicit arguments (neither S's constructor, nor sam may take an implicit argument list),
+ * is to keep the implementation of type inference (the computation of `samClassTpFullyDefined`) simple.
+ *
+ * Impl notes:
+ * - `fun` has a FunctionType, but the expected type `pt` is some SAM type -- let's remedy that
+ * - `fun` is fully attributed, so we'll have to wrangle some symbols into shape (owner change, vparam syms)
+ * - after experimentation, it works best to type check function literals fully first and then adapt to a sam type,
+ * as opposed to a sam-specific code paths earlier on in type checking (in typedFunction).
+ * For one, we want to emit the same bytecode regardless of whether the expected
+ * function type is a built-in FunctionN or some SAM type
+ *
+ */
+ def inferSamType(fun: Tree, pt: Type, mode: Mode): SAMFunction = {
+ val sam =
+ if (fun.isInstanceOf[Function] && !isFunctionType(pt)) {
+ // TODO: can we ensure there's always a SAMFunction attachment, instead of looking up the sam again???
+ // seems like overloading complicates things?
+ val sam = samOf(pt)
+ if (samMatchesFunctionBasedOnArity(sam, fun.asInstanceOf[Function].vparams)) sam
+ else NoSymbol
+ } else NoSymbol
+
+ def fullyDefinedMeetsExpectedFunTp(pt: Type): Boolean = isFullyDefined(pt) && {
+ val samMethType = pt memberInfo sam
+ fun.tpe <:< functionType(samMethType.paramTypes, samMethType.resultType)
+ }
+
+ SAMFunction(
+ if (!sam.exists) NoType
+ else if (fullyDefinedMeetsExpectedFunTp(pt)) pt
else try {
- // This creates a symbol for samBodyDef with a type completer that'll be triggered immediately below.
- // The symbol is entered in the same scope used for the block below, and won't thus be reentered later.
- // It has to be a new scope, though, or we'll "get ambiguous reference to overloaded definition" [pos/sammy_twice.scala]
- // makeSilent: [pos/nonlocal-unchecked.scala -- when translation all functions to sams]
- val nestedCtx = enterSym(context.makeNewScope(context.tree, context.owner).makeSilent(), samBodyDef)
- nestedTyper = newTyper(nestedCtx)
-
- // NOTE: this `samBodyDef.symbol.info` runs the type completer set up by the enterSym above
- val actualSamType = samBodyDef.symbol.info
+ val samClassSym = pt.typeSymbol
// we're trying to fully define the type arguments for this type constructor
- val samTyCon = samClassTp.typeSymbol.typeConstructor
+ val samTyCon = samClassSym.typeConstructor
// the unknowns
- val tparams = samClassTp.typeSymbol.typeParams
+ val tparams = samClassSym.typeParams
// ... as typevars
- val tvars = tparams map freshVar
-
- // 1. Recover partial information:
- // - derive a type from samClassTp that has the corresponding tparams for type arguments that aren't fully defined
- // - constrain typevars to be equal to type args that are fully defined
- val samClassTpMoreDefined = appliedType(samTyCon,
- (samClassTp.typeArgs, tparams, tvars).zipped map {
- case (a, _, tv) if isFullyDefined(a) => tv =:= a; a
- case (_, p, _) => p.typeConstructor
- })
-
- // the method type we're expecting the synthesized sam to have, based on the expected sam type,
- // where fully defined type args to samClassTp have been preserved,
- // with the unknown args replaced by their corresponding type param
- val expectedSamType = samClassTpMoreDefined.memberInfo(sam)
+ val tvars = tparams map freshVar
- // 2. make sure the body def's actual type (formals and result) conforms to
- // sam's expected type (in terms of the typevars that represent the sam's class's type params)
- actualSamType <:< expectedSamType.substituteTypes(tparams, tvars)
+ val ptVars = appliedType(samTyCon, tvars)
- // solve constraints tracked by tvars
- val targs = solvedTypes(tvars, tparams, tparams map varianceInType(sam.info), upper = false, lubDepth(sam.info :: Nil))
+ // carry over info from pt
+ ptVars <:< pt
- debuglog(s"sam infer: $samClassTp --> ${appliedType(samTyCon, targs)} by $actualSamType <:< $expectedSamType --> $targs for $tparams")
+ val samInfoWithTVars = ptVars.memberInfo(sam)
- // a fully defined samClassTp
- appliedType(samTyCon, targs)
- } catch {
- case _: NoInstance | _: TypeError =>
- devWarning(sampos, s"Could not define type $samClassTp using ${samBodyDef.symbol.rawInfo} <:< ${samClassTp memberInfo sam} (for $sam)")
- samClassTp
- }
-
- // what's the signature of the method that we should actually be overriding?
- val samMethTp = samClassTpFullyDefined memberInfo sam
- // Before the mutation, `tp <:< vpar.tpt.tpe` should hold.
- // TODO: error message when this is not the case, as the expansion won't type check
- // - Ti' <:< Ti and T <: T' must hold for the samDef body to type check
- val funArgTps = foreach2(samMethTp.paramTypes, fun.vparams)((tp, vpar) => vpar.tpt setType tp)
-
- // `final override def ${sam.name}($p1: $T1', ..., $pN: $TN'): ${samMethTp.finalResultType} = ${sam.name}\$body'($p1, ..., $pN)`
- val samDef =
- DefDef(Modifiers(FINAL | OVERRIDE | SYNTHETIC),
- sam.name.toTermName,
- Nil,
- List(fun.vparams),
- TypeTree(samMethTp.finalResultType) setPos sampos.focus,
- Apply(Ident(bodyName), fun.vparams map gen.paramToArg)
- )
+ // use function type subtyping, not method type subtyping (the latter is invariant in argument types)
+ fun.tpe <:< functionType(samInfoWithTVars.paramTypes, samInfoWithTVars.finalResultType)
- val serializableParentAddendum =
- if (typeIsSubTypeOfSerializable(samClassTp)) Nil
- else List(TypeTree(SerializableTpe))
-
- val classDef =
- ClassDef(Modifiers(FINAL), tpnme.ANON_FUN_NAME, tparams = Nil,
- gen.mkTemplate(
- parents = TypeTree(samClassTpFullyDefined) :: serializableParentAddendum,
- self = noSelfType,
- constrMods = NoMods,
- vparamss = ListOfNil,
- body = List(samDef),
- superPos = sampos.focus
- )
- )
+ val variances = tparams map varianceInType(sam.info)
- // type checking the whole block, so that everything is packaged together nicely
- // and we don't have to create any symbols by hand
- val block =
- nestedTyper.typedPos(sampos, mode, samClassTpFullyDefined) {
- Block(
- samBodyDef,
- classDef,
- Apply(Select(New(Ident(tpnme.ANON_FUN_NAME)), nme.CONSTRUCTOR), Nil)
- )
- }
+ // solve constraints tracked by tvars
+ val targs = solvedTypes(tvars, tparams, variances, upper = false, lubDepth(sam.info :: Nil))
- // TODO: improve error reporting -- when we're in silent mode (from `silent(_.doTypedApply(tree, fun, args, mode, pt)) orElse onError`)
- // the errors in the function don't get out...
- if (block exists (_.isErroneous))
- context.error(fun.pos, s"Could not derive subclass of $samClassTp\n (with SAM `def $sam$samMethTp`)\n based on: $fun.")
+ debuglog(s"sam infer: $pt --> ${appliedType(samTyCon, targs)} by ${fun.tpe} <:< $samInfoWithTVars --> $targs for $tparams")
- classDef.symbol addAnnotation SerialVersionUIDAnnotation
- block
+ val ptFullyDefined = appliedType(samTyCon, targs)
+ if (ptFullyDefined <:< pt && fullyDefinedMeetsExpectedFunTp(ptFullyDefined)) {
+ debuglog(s"sam fully defined expected type: $ptFullyDefined from $pt for ${fun.tpe}")
+ ptFullyDefined
+ } else {
+ debuglog(s"Could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)")
+ NoType
+ }
+ } catch {
+ case e@(_: NoInstance | _: TypeError) =>
+ debuglog(s"Error during SAM synthesis: could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)\n$e")
+ NoType
+ }, sam)
}
/** Type check a function literal.
@@ -2895,16 +2910,19 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* - a type with a Single Abstract Method (under -Xexperimental for now).
*/
private def typedFunction(fun: Function, mode: Mode, pt: Type): Tree = {
- val numVparams = fun.vparams.length
+ val vparams = fun.vparams
+ val numVparams = vparams.length
val FunctionSymbol =
if (numVparams > definitions.MaxFunctionArity) NoSymbol
else FunctionClass(numVparams)
+ val ptSym = pt.typeSymbol
+
/* The Single Abstract Member of pt, unless pt is the built-in function type of the expected arity,
* as `(a => a): Int => Int` should not (yet) get the sam treatment.
*/
val sam =
- if (pt.typeSymbol == FunctionSymbol) NoSymbol
+ if (ptSym == NoSymbol || ptSym == FunctionSymbol || ptSym == PartialFunctionClass) NoSymbol
else samOf(pt)
/* The SAM case comes first so that this works:
@@ -2912,79 +2930,101 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* (a => a): MyFun
*
* Note that the arity of the sam must correspond to the arity of the function.
+ * TODO: handle vararg sams?
*/
- val samViable = sam.exists && sameLength(sam.info.params, fun.vparams)
- val ptNorm = if (samViable) samToFunctionType(pt, sam) else pt
+ val ptNorm =
+ if (samMatchesFunctionBasedOnArity(sam, vparams)) samToFunctionType(pt, sam)
+ else pt
val (argpts, respt) =
ptNorm baseType FunctionSymbol match {
case TypeRef(_, FunctionSymbol, args :+ res) => (args, res)
- case _ => (fun.vparams map (_ => if (pt == ErrorType) ErrorType else NoType), WildcardType)
+ case _ => (vparams map (if (pt == ErrorType) (_ => ErrorType) else (_ => NoType)), WildcardType)
}
- if (!FunctionSymbol.exists)
- MaxFunctionArityError(fun)
- else if (argpts.lengthCompare(numVparams) != 0)
- WrongNumberOfParametersError(fun, argpts)
+ if (!FunctionSymbol.exists) MaxFunctionArityError(fun)
+ else if (argpts.lengthCompare(numVparams) != 0) WrongNumberOfParametersError(fun, argpts)
else {
- var issuedMissingParameterTypeError = false
- foreach2(fun.vparams, argpts) { (vparam, argpt) =>
+ val paramsMissingType = mutable.ArrayBuffer.empty[ValDef] //.sizeHint(numVparams) probably useless, since initial size is 16 and max fun arity is 22
+ // first, try to define param types from expected function's arg types if needed
+ foreach2(vparams, argpts) { (vparam, argpt) =>
if (vparam.tpt.isEmpty) {
- val vparamType =
- if (isFullyDefined(argpt)) argpt
- else {
- fun match {
- case etaExpansion(vparams, fn, args) =>
- silent(_.typed(fn, mode.forFunMode, pt)) filter (_ => context.undetparams.isEmpty) map { fn1 =>
- // if context.undetparams is not empty, the function was polymorphic,
- // so we need the missing arguments to infer its type. See #871
- //println("typing eta "+fun+":"+fn1.tpe+"/"+context.undetparams)
- val ftpe = normalize(fn1.tpe) baseType FunctionClass(numVparams)
- if (isFunctionType(ftpe) && isFullyDefined(ftpe))
- return typedFunction(fun, mode, ftpe)
- }
- case _ =>
- }
- MissingParameterTypeError(fun, vparam, pt, withTupleAddendum = !issuedMissingParameterTypeError)
- issuedMissingParameterTypeError = true
- ErrorType
- }
- vparam.tpt.setType(vparamType)
+ if (isFullyDefined(argpt)) vparam.tpt setType argpt
+ else paramsMissingType += vparam
+
if (!vparam.tpt.pos.isDefined) vparam.tpt setPos vparam.pos.focus
}
}
- fun.body match {
- // translate `x => x match { <cases> }` : PartialFunction to
- // `new PartialFunction { def applyOrElse(x, default) = x match { <cases> } def isDefinedAt(x) = ... }`
- case Match(sel, cases) if (sel ne EmptyTree) && (pt.typeSymbol == PartialFunctionClass) =>
- // go to outer context -- must discard the context that was created for the Function since we're discarding the function
- // thus, its symbol, which serves as the current context.owner, is not the right owner
- // you won't know you're using the wrong owner until lambda lift crashes (unless you know better than to use the wrong owner)
- val outerTyper = newTyper(context.outer)
- val p = fun.vparams.head
- if (p.tpt.tpe == null) p.tpt setType outerTyper.typedType(p.tpt).tpe
+ // If we're typing `(a1: T1, ..., aN: TN) => m(a1,..., aN)`, where some Ti are not fully defined,
+ // type `m` directly (undoing eta-expansion of method m) to determine the argument types.
+ // This tree is the result from one of:
+ // - manual eta-expansion with named arguments (x => f(x));
+ // - wildcard-style eta expansion (`m(_, _,)`);
+ // - instantiateToMethodType adapting a tree of method type to a function type using etaExpand.
+ //
+ // Note that method values are a separate thing (`m _`): they have the idiosyncratic shape
+ // of `Typed(expr, Function(Nil, EmptyTree))`
+ val ptUnrollingEtaExpansion =
+ if (paramsMissingType.nonEmpty && pt != ErrorType) fun.body match {
+ // we can compare arguments and parameters by name because there cannot be a binder between
+ // the function's valdefs and the Apply's arguments
+ case Apply(meth, args) if (vparams corresponds args) { case (p, Ident(name)) => p.name == name case _ => false } =>
+ // We're looking for a method (as indicated by FUNmode in the silent typed below),
+ // so let's make sure our expected type is a MethodType
+ val methArgs = NoSymbol.newSyntheticValueParams(argpts map { case NoType => WildcardType case tp => tp })
+ silent(_.typed(meth, mode.forFunMode, MethodType(methArgs, respt))) filter (isMonoContext) map { methTyped =>
+ // if context.undetparams is not empty, the method was polymorphic,
+ // so we need the missing arguments to infer its type. See #871
+ val funPt = normalize(methTyped.tpe) baseType FunctionClass(numVparams)
+ // println(s"typeUnEtaExpanded $meth : ${methTyped.tpe} --> normalized: $funPt")
+
+ // If we are sure this function type provides all the necessary info, so that we won't have
+ // any undetermined argument types, go ahead an recurse below (`typedFunction(fun, mode, ptUnrollingEtaExpansion)`)
+ // and rest assured we won't end up right back here (and keep recursing)
+ if (isFunctionType(funPt) && funPt.typeArgs.iterator.take(numVparams).forall(isFullyDefined)) funPt
+ else null
+ } orElse { _ => null }
+ case _ => null
+ } else null
+
+
+ if (ptUnrollingEtaExpansion ne null) typedFunction(fun, mode, ptUnrollingEtaExpansion)
+ else {
+ // we ran out of things to try, missing parameter types are an irrevocable error
+ var issuedMissingParameterTypeError = false
+ paramsMissingType.foreach { vparam =>
+ vparam.tpt setType ErrorType
+ MissingParameterTypeError(fun, vparam, pt, withTupleAddendum = !issuedMissingParameterTypeError)
+ issuedMissingParameterTypeError = true
+ }
- outerTyper.synthesizePartialFunction(p.name, p.pos, paramSynthetic = false, fun.body, mode, pt)
+ fun.body match {
+ // translate `x => x match { <cases> }` : PartialFunction to
+ // `new PartialFunction { def applyOrElse(x, default) = x match { <cases> } def isDefinedAt(x) = ... }`
+ case Match(sel, cases) if (sel ne EmptyTree) && (pt.typeSymbol == PartialFunctionClass) =>
+ // go to outer context -- must discard the context that was created for the Function since we're discarding the function
+ // thus, its symbol, which serves as the current context.owner, is not the right owner
+ // you won't know you're using the wrong owner until lambda lift crashes (unless you know better than to use the wrong owner)
+ val outerTyper = newTyper(context.outer)
+ val p = vparams.head
+ if (p.tpt.tpe == null) p.tpt setType outerTyper.typedType(p.tpt).tpe
- // Use synthesizeSAMFunction to expand `(p1: T1, ..., pN: TN) => body`
- // to an instance of the corresponding anonymous subclass of `pt`.
- case _ if samViable =>
- newTyper(context.outer).synthesizeSAMFunction(sam, fun, respt, pt, mode)
+ outerTyper.synthesizePartialFunction(p.name, p.pos, paramSynthetic = false, fun.body, mode, pt)
- // regular Function
- case _ =>
- val vparamSyms = fun.vparams map { vparam =>
- enterSym(context, vparam)
- if (context.retyping) context.scope enter vparam.symbol
- vparam.symbol
- }
- val vparams = fun.vparams mapConserve typedValDef
- val formals = vparamSyms map (_.tpe)
- val body1 = typed(fun.body, respt)
- val restpe = packedType(body1, fun.symbol).deconst.resultType
- val funtpe = appliedType(FunctionSymbol, formals :+ restpe: _*)
+ case _ =>
+ val vparamSyms = vparams map { vparam =>
+ enterSym(context, vparam)
+ if (context.retyping) context.scope enter vparam.symbol
+ vparam.symbol
+ }
+ val vparamsTyped = vparams mapConserve typedValDef
+ val formals = vparamSyms map (_.tpe)
+ val body1 = typed(fun.body, respt)
+ val restpe = packedType(body1, fun.symbol).deconst.resultType
+ val funtpe = phasedAppliedType(FunctionSymbol, formals :+ restpe)
- treeCopy.Function(fun, vparams, body1) setType funtpe
+ treeCopy.Function(fun, vparamsTyped, body1) setType funtpe
+ }
}
}
}
@@ -3012,99 +3052,124 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case _ => log("unhandled import: "+imp+" in "+unit); imp
}
- def typedStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = {
+ def typedStats(stats: List[Tree], exprOwner: Symbol, warnPure: Boolean = true): List[Tree] = {
val inBlock = exprOwner == context.owner
def includesTargetPos(tree: Tree) =
tree.pos.isRange && context.unit.exists && (tree.pos includes context.unit.targetPos)
val localTarget = stats exists includesTargetPos
- def typedStat(stat: Tree): Tree = {
- if (context.owner.isRefinementClass && !treeInfo.isDeclarationOrTypeDef(stat))
- OnlyDeclarationsError(stat)
- else
- stat match {
- case imp @ Import(_, _) =>
- imp.symbol.initialize
- if (!imp.symbol.isError) {
- context = context.make(imp)
- typedImport(imp)
- } else EmptyTree
- case _ =>
- if (localTarget && !includesTargetPos(stat)) {
- // skip typechecking of statements in a sequence where some other statement includes
- // the targetposition
- stat
- } else {
- val localTyper = if (inBlock || (stat.isDef && !stat.isInstanceOf[LabelDef])) {
- this
- } else newTyper(context.make(stat, exprOwner))
- // XXX this creates a spurious dead code warning if an exception is thrown
- // in a constructor, even if it is the only thing in the constructor.
- val result = checkDead(localTyper.typedByValueExpr(stat))
-
- if (treeInfo.isSelfOrSuperConstrCall(result)) {
- context.inConstructorSuffix = true
- if (treeInfo.isSelfConstrCall(result) && result.symbol.pos.pointOrElse(0) >= exprOwner.enclMethod.pos.pointOrElse(0))
- ConstructorsOrderError(stat)
- }
-
- if (!isPastTyper && treeInfo.isPureExprForWarningPurposes(result)) context.warning(stat.pos,
- "a pure expression does nothing in statement position; " +
- "you may be omitting necessary parentheses"
- )
- result
- }
+ def typedStat(stat: Tree): Tree = stat match {
+ case s if context.owner.isRefinementClass && !treeInfo.isDeclarationOrTypeDef(s) => OnlyDeclarationsError(s)
+ case imp @ Import(_, _) =>
+ imp.symbol.initialize
+ if (!imp.symbol.isError) {
+ context = context.make(imp)
+ typedImport(imp)
+ } else EmptyTree
+ // skip typechecking of statements in a sequence where some other statement includes the targetposition
+ case s if localTarget && !includesTargetPos(s) => s
+ case _ =>
+ val localTyper = if (inBlock || (stat.isDef && !stat.isInstanceOf[LabelDef])) this
+ else newTyper(context.make(stat, exprOwner))
+ // XXX this creates a spurious dead code warning if an exception is thrown
+ // in a constructor, even if it is the only thing in the constructor.
+ val result = checkDead(localTyper.typedByValueExpr(stat))
+
+ if (treeInfo.isSelfOrSuperConstrCall(result)) {
+ context.inConstructorSuffix = true
+ if (treeInfo.isSelfConstrCall(result)) {
+ if (result.symbol == exprOwner.enclMethod)
+ ConstructorRecursesError(stat)
+ else if (result.symbol.pos.pointOrElse(0) >= exprOwner.enclMethod.pos.pointOrElse(0))
+ ConstructorsOrderError(stat)
+ }
+ }
+ if (warnPure && !isPastTyper && treeInfo.isPureExprForWarningPurposes(result)) {
+ val msg = "a pure expression does nothing in statement position"
+ val clause = if (stats.lengthCompare(1) > 0) "; multiline expressions may require enclosing parentheses" else ""
+ context.warning(stat.pos, s"${msg}${clause}")
}
+ result
}
- /* 'accessor' and 'accessed' are so similar it becomes very difficult to
- * follow the logic, so I renamed one to something distinct.
- */
+ // TODO: adapt to new trait field encoding, figure out why this exemption is made
+ // 'accessor' and 'accessed' are so similar it becomes very difficult to
+ //follow the logic, so I renamed one to something distinct.
def accesses(looker: Symbol, accessed: Symbol) = accessed.isLocalToThis && (
- (accessed.isParamAccessor)
- || (looker.hasAccessorFlag && !accessed.hasAccessorFlag && accessed.isPrivate)
- )
+ (accessed.isParamAccessor)
+ || (looker.hasAccessorFlag && !accessed.hasAccessorFlag && accessed.isPrivate)
+ )
- def checkNoDoubleDefs: Unit = {
- val scope = if (inBlock) context.scope else context.owner.info.decls
+ def checkNoDoubleDefs(scope: Scope): Unit = {
var e = scope.elems
while ((e ne null) && e.owner == scope) {
var e1 = scope.lookupNextEntry(e)
while ((e1 ne null) && e1.owner == scope) {
- if (!accesses(e.sym, e1.sym) && !accesses(e1.sym, e.sym) &&
- (e.sym.isType || inBlock || (e.sym.tpe matches e1.sym.tpe)))
- // default getters are defined twice when multiple overloads have defaults. an
- // error for this is issued in RefChecks.checkDefaultsInOverloaded
- if (!e.sym.isErroneous && !e1.sym.isErroneous && !e.sym.hasDefault &&
- !e.sym.hasAnnotation(BridgeClass) && !e1.sym.hasAnnotation(BridgeClass)) {
- log("Double definition detected:\n " +
- ((e.sym.getClass, e.sym.info, e.sym.ownerChain)) + "\n " +
- ((e1.sym.getClass, e1.sym.info, e1.sym.ownerChain)))
-
- DefDefinedTwiceError(e.sym, e1.sym)
- scope.unlink(e1) // need to unlink to avoid later problems with lub; see #2779
- }
- e1 = scope.lookupNextEntry(e1)
+ val sym = e.sym
+ val sym1 = e1.sym
+
+ /** From the spec (refchecks checks other conditions regarding erasing to the same type and default arguments):
+ *
+ * A block expression [... its] statement sequence may not contain two definitions or
+ * declarations that bind the same name --> `inBlock`
+ *
+ * It is an error if a template directly defines two matching members.
+ *
+ * A member definition $M$ _matches_ a member definition $M'$, if $M$ and $M'$ bind the same name,
+ * and one of following holds:
+ * 1. Neither $M$ nor $M'$ is a method definition.
+ * 2. $M$ and $M'$ define both monomorphic methods with equivalent argument types.
+ * 3. $M$ defines a parameterless method and $M'$ defines a method with an empty parameter list `()` or _vice versa_.
+ * 4. $M$ and $M'$ define both polymorphic methods with equal number of argument types $\overline T$, $\overline T'$
+ * and equal numbers of type parameters $\overline t$, $\overline t'$, say,
+ * and $\overline T' = [\overline t'/\overline t]\overline T$.
+ */
+ if (!(accesses(sym, sym1) || accesses(sym1, sym)) // TODO: does this purely defer errors until later?
+ && (inBlock || !(sym.isMethod || sym1.isMethod) || (sym.tpe matches sym1.tpe))
+ // default getters are defined twice when multiple overloads have defaults.
+ // The error for this is deferred until RefChecks.checkDefaultsInOverloaded
+ && (!sym.isErroneous && !sym1.isErroneous && !sym.hasDefault &&
+ !sym.hasAnnotation(BridgeClass) && !sym1.hasAnnotation(BridgeClass))) {
+ log("Double definition detected:\n " +
+ ((sym.getClass, sym.info, sym.ownerChain)) + "\n " +
+ ((sym1.getClass, sym1.info, sym1.ownerChain)))
+
+ DefDefinedTwiceError(sym, sym1)
+ scope.unlink(e1) // need to unlink to avoid later problems with lub; see #2779
+ }
+ e1 = scope.lookupNextEntry(e1)
}
e = e.next
}
}
- def addSynthetics(stats: List[Tree]): List[Tree] = {
- val scope = if (inBlock) context.scope else context.owner.info.decls
+ def addSynthetics(stats: List[Tree], scope: Scope): List[Tree] = {
var newStats = new ListBuffer[Tree]
var moreToAdd = true
- val retractErroneousSynthetics = settings.isScala212
-
while (moreToAdd) {
val initElems = scope.elems
// SI-5877 The decls of a package include decls of the package object. But we don't want to add
// the corresponding synthetics to the package class, only to the package object class.
- def shouldAdd(sym: Symbol) =
- inBlock || !context.isInPackageObject(sym, context.owner)
+ // SI-6734 Locality test below is meaningless if we're not even in the correct tree.
+ // For modules that are synthetic case companions, check that case class is defined here.
+ def shouldAdd(sym: Symbol): Boolean = {
+ def shouldAddAsModule: Boolean =
+ sym.moduleClass.attachments.get[ClassForCaseCompanionAttachment] match {
+ case Some(att) =>
+ val cdef = att.caseClass
+ stats.exists {
+ case t @ ClassDef(_, _, _, _) => t.symbol == cdef.symbol // cdef ne t
+ case _ => false
+ }
+ case _ => true
+ }
+
+ (!sym.isModule || shouldAddAsModule) && (inBlock || !context.isInPackageObject(sym, context.owner))
+ }
for (sym <- scope)
- for (tree <- context.unit.synthetics get sym if shouldAdd(sym)) { // OPT: shouldAdd is usually true. Call it here, rather than in the outer loop
+ // OPT: shouldAdd is usually true. Call it here, rather than in the outer loop
+ for (tree <- context.unit.synthetics.get(sym) if shouldAdd(sym)) {
// if the completer set the IS_ERROR flag, retract the stat (currently only used by applyUnapplyMethodCompleter)
- if (!(retractErroneousSynthetics && sym.initialize.hasFlag(IS_ERROR)))
+ if (!sym.initialize.hasFlag(IS_ERROR))
newStats += typedStat(tree) // might add even more synthetics to the scope
context.unit.synthetics -= sym
}
@@ -3132,6 +3197,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case (ClassDef(cmods, cname, _, _), DefDef(dmods, dname, _, _, _, _)) =>
cmods.isImplicit && dmods.isImplicit && cname.toTermName == dname
+ // ValDef and Accessor
+ case (ValDef(_, cname, _, _), DefDef(_, dname, _, _, _, _)) =>
+ cname.getterName == dname.getterName
+
case _ => false
}
@@ -3150,11 +3219,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val stats1 = stats mapConserve typedStat
if (phase.erasedTypes) stats1
else {
+ val scope = if (inBlock) context.scope else context.owner.info.decls
+
// As packages are open, it doesn't make sense to check double definitions here. Furthermore,
- // it is expensive if the package is large. Instead, such double defininitions are checked in `Namers.enterInScope`
+ // it is expensive if the package is large. Instead, such double definitions are checked in `Namers.enterInScope`
if (!context.owner.isPackageClass)
- checkNoDoubleDefs
- addSynthetics(stats1)
+ checkNoDoubleDefs(scope)
+
+ addSynthetics(stats1, scope)
}
}
@@ -3218,6 +3290,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// less expensive than including them in inferMethodAlternative (see below).
def shapeType(arg: Tree): Type = arg match {
case Function(vparams, body) =>
+ // No need for phasedAppliedType, as we don't get here during erasure --
+ // overloading resolution happens during type checking.
+ // During erasure, the condition above (fun.symbol.isOverloaded) is false.
functionType(vparams map (_ => AnyTpe), shapeType(body))
case AssignOrNamedArg(Ident(name), rhs) =>
NamedType(name, shapeType(rhs))
@@ -3254,40 +3329,74 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
val fun = preSelectOverloaded(fun0)
+ val argslen = args.length
fun.tpe match {
case OverloadedType(pre, alts) =>
def handleOverloaded = {
val undetparams = context.undetparams
+
+ def funArgTypes(tpAlts: List[(Type, Symbol)]) = tpAlts.map { case (tp, alt) =>
+ val relTp = tp.asSeenFrom(pre, alt.owner)
+ val argTps = functionOrSamArgTypes(relTp)
+ //println(s"funArgTypes $argTps from $relTp")
+ argTps.map(approximateAbstracts)
+ }
+
+ def functionProto(argTpWithAlt: List[(Type, Symbol)]): Type =
+ try functionType(funArgTypes(argTpWithAlt).transpose.map(lub), WildcardType)
+ catch { case _: IllegalArgumentException => WildcardType }
+
+ // To propagate as much information as possible to typedFunction, which uses the expected type to
+ // infer missing parameter types for Function trees that we're typing as arguments here,
+ // we expand the parameter types for all alternatives to the expected argument length,
+ // then transpose to get a list of alternative argument types (push down the overloading to the arguments).
+ // Thus, for each `arg` in `args`, the corresponding `argPts` in `altArgPts` is a list of expected types
+ // for `arg`. Depending on which overload is picked, only one of those expected types must be met, but
+ // we're in the process of figuring that out, so we'll approximate below by normalizing them to function types
+ // and lubbing the argument types (we treat SAM and FunctionN types equally, but non-function arguments
+ // do not receive special treatment: they are typed under WildcardType.)
+ val altArgPts =
+ if (settings.isScala212 && args.exists(treeInfo.isFunctionMissingParamType))
+ try alts.map(alt => formalTypes(alt.info.paramTypes, argslen).map(ft => (ft, alt))).transpose // do least amount of work up front
+ catch { case _: IllegalArgumentException => args.map(_ => Nil) } // fail safe in case formalTypes fails to align to argslen
+ else args.map(_ => Nil) // will type under argPt == WildcardType
+
val (args1, argTpes) = context.savingUndeterminedTypeParams() {
val amode = forArgMode(fun, mode)
- def typedArg0(tree: Tree) = typedArg(tree, amode, BYVALmode, WildcardType)
- args.map {
- case arg @ AssignOrNamedArg(Ident(name), rhs) =>
- // named args: only type the righthand sides ("unknown identifier" errors otherwise)
- // the assign is untyped; that's ok because we call doTypedApply
- val typedRhs = typedArg0(rhs)
- val argWithTypedRhs = treeCopy.AssignOrNamedArg(arg, arg.lhs, typedRhs)
-
- // TODO: SI-8197/SI-4592: check whether this named argument could be interpreted as an assign
+
+ map2(args, altArgPts) { (arg, argPtAlts) =>
+ def typedArg0(tree: Tree) = {
+ // if we have an overloaded HOF such as `(f: Int => Int)Int <and> (f: Char => Char)Char`,
+ // and we're typing a function like `x => x` for the argument, try to collapse
+ // the overloaded type into a single function type from which `typedFunction`
+ // can derive the argument type for `x` in the function literal above
+ val argPt =
+ if (argPtAlts.nonEmpty && treeInfo.isFunctionMissingParamType(tree)) functionProto(argPtAlts)
+ else WildcardType
+
+ val argTyped = typedArg(tree, amode, BYVALmode, argPt)
+ (argTyped, argTyped.tpe.deconst)
+ }
+
+ arg match {
+ // SI-8197/SI-4592 call for checking whether this named argument could be interpreted as an assign
// infer.checkNames must not use UnitType: it may not be a valid assignment, or the setter may return another type from Unit
- //
- // var typedAsAssign = true
- // val argTyped = silent(_.typedArg(argWithTypedRhs, amode, BYVALmode, WildcardType)) orElse { errors =>
- // typedAsAssign = false
- // argWithTypedRhs
- // }
- //
- // TODO: add an assignmentType field to NamedType, equal to:
- // assignmentType = if (typedAsAssign) argTyped.tpe else NoType
-
- (argWithTypedRhs, NamedType(name, typedRhs.tpe.deconst))
- case arg @ treeInfo.WildcardStarArg(repeated) =>
- val arg1 = typedArg0(arg)
- (arg1, RepeatedType(arg1.tpe.deconst))
- case arg =>
- val arg1 = typedArg0(arg)
- (arg1, arg1.tpe.deconst)
+ // TODO: just make it an error to refer to a non-existent named arg, as it's far more likely to be
+ // a typo than an assignment passed as an argument
+ case AssignOrNamedArg(lhs@Ident(name), rhs) =>
+ // named args: only type the righthand sides ("unknown identifier" errors otherwise)
+ // the assign is untyped; that's ok because we call doTypedApply
+ typedArg0(rhs) match {
+ case (rhsTyped, tp) => (treeCopy.AssignOrNamedArg(arg, lhs, rhsTyped), NamedType(name, tp))
+ }
+ case treeInfo.WildcardStarArg(_) =>
+ typedArg0(arg) match {
+ case (argTyped, tp) => (argTyped, RepeatedType(tp))
+ }
+ case _ =>
+ typedArg0(arg)
+ }
}.unzip
}
if (context.reporter.hasErrors)
@@ -3308,7 +3417,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// governed by a) the argument types and b) the expected type
val args1 = typedArgs(args, forArgMode(fun, mode))
val pts = args1.map(_.tpe.deconst)
- val clone = fun.symbol.cloneSymbol
+ val clone = fun.symbol.cloneSymbol.withoutAnnotations
val cloneParams = pts map (pt => clone.newValueParameter(currentUnit.freshTermName()).setInfo(pt))
val resultType = if (isFullyDefined(pt)) pt else ObjectTpe
clone.modifyInfo(mt => copyMethodType(mt, cloneParams, resultType))
@@ -3318,32 +3427,31 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case mt @ MethodType(params, _) =>
val paramTypes = mt.paramTypes
// repeat vararg as often as needed, remove by-name
- val argslen = args.length
val formals = formalTypes(paramTypes, argslen)
- /* Try packing all arguments into a Tuple and apply `fun`
- * to that. This is the last thing which is tried (after
- * default arguments)
+ /* Try packing all arguments into a Tuple and apply `fun` to that.
+ * This is the last thing which is tried (after default arguments).
*/
- def tryTupleApply: Tree = {
- if (eligibleForTupleConversion(paramTypes, argslen) && !phase.erasedTypes) {
+ def tryTupleApply: Tree =
+ if (phase.erasedTypes || !eligibleForTupleConversion(paramTypes, argslen)) EmptyTree
+ else {
val tupleArgs = List(atPos(tree.pos.makeTransparent)(gen.mkTuple(args)))
// expected one argument, but got 0 or >1 ==> try applying to tuple
// the inner "doTypedApply" does "extractUndetparams" => restore when it fails
val savedUndetparams = context.undetparams
- silent(_.doTypedApply(tree, fun, tupleArgs, mode, pt)) map { t =>
- // Depending on user options, may warn or error here if
- // a Unit or tuple was inserted.
- val keepTree = (
- !mode.typingExprNotFun // why? introduced in 4e488a60, doc welcome
- || t.symbol == null // ditto
- || checkValidAdaptation(t, args)
- )
- if (keepTree) t else EmptyTree
- } orElse { _ => context.undetparams = savedUndetparams ; EmptyTree }
+ // May warn or error if a Unit or tuple was inserted.
+ def validate(t: Tree): Tree = {
+ // regardless of typer's mode
+ val invalidAdaptation = t.symbol != null && !checkValidAdaptation(t, args)
+ // only bail if we're typing an expression (and not inside another application)
+ if (invalidAdaptation && mode.typingExprNotFun) EmptyTree else t
+ }
+ def reset(errors: Seq[AbsTypeError]): Tree = {
+ context.undetparams = savedUndetparams
+ EmptyTree
+ }
+ silent(_.doTypedApply(tree, fun, tupleArgs, mode, pt)).map(validate).orElse(reset)
}
- else EmptyTree
- }
/* Treats an application which uses named or default arguments.
* Also works if names + a vararg used: when names are used, the vararg
@@ -3363,7 +3471,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// #2064
duplErrorTree(WrongNumberOfArgsError(tree, fun))
} else if (lencmp > 0) {
- tryTupleApply orElse duplErrorTree(TooManyArgsNamesDefaultsError(tree, fun))
+ tryTupleApply orElse duplErrorTree {
+ val (namelessArgs, argPos) = removeNames(Typer.this)(args, params)
+ TooManyArgsNamesDefaultsError(tree, fun, formals, args, namelessArgs, argPos)
+ }
} else if (lencmp == 0) {
// we don't need defaults. names were used, so this application is transformed
// into a block (@see transformNamedApplication in NamesDefaults)
@@ -3427,7 +3538,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val lencmp2 = compareLengths(allArgs, formals)
if (!sameLength(allArgs, args) && callToCompanionConstr(context, funSym)) {
- duplErrorTree(ModuleUsingCompanionClassDefaultArgsErrror(tree))
+ duplErrorTree(ModuleUsingCompanionClassDefaultArgsError(tree))
} else if (lencmp2 > 0) {
removeNames(Typer.this)(allArgs, params) // #3818
duplErrTree
@@ -3663,7 +3774,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val annType = annTpt.tpe
finish(
- if (typedFun.isErroneous)
+ if (typedFun.isErroneous || annType == null)
ErroneousAnnotation
else if (annType.typeSymbol isNonBottomSubClass ClassfileAnnotationClass) {
// annotation to be saved as java classfile annotation
@@ -3672,10 +3783,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
reportAnnotationError(MultipleArgumentListForAnnotationError(ann))
}
else {
- val annScope = annType.decls
- .filter(sym => sym.isMethod && !sym.isConstructor && sym.isJavaDefined)
+ val annScopeJava =
+ if (isJava) annType.decls.filter(sym => sym.isMethod && !sym.isConstructor && sym.isJavaDefined)
+ else EmptyScope // annScopeJava is only used if isJava
+
val names = mutable.Set[Symbol]()
- names ++= (if (isJava) annScope.iterator
+ names ++= (if (isJava) annScopeJava.iterator
else typedFun.tpe.params.iterator)
def hasValue = names exists (_.name == nme.value)
@@ -3686,7 +3799,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val nvPairs = args map {
case arg @ AssignOrNamedArg(Ident(name), rhs) =>
- val sym = if (isJava) annScope.lookup(name)
+ val sym = if (isJava) annScopeJava.lookup(name)
else findSymbol(typedFun.tpe.params)(_.name == name)
if (sym == NoSymbol) {
reportAnnotationError(UnknownAnnotationNameError(arg, name))
@@ -3742,7 +3855,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
if (annType.typeSymbol == DeprecatedAttr && argss.flatten.size < 2)
- context.deprecationWarning(ann.pos, DeprecatedAttr, "@deprecated now takes two arguments; see the scaladoc.")
+ context.deprecationWarning(ann.pos, DeprecatedAttr, "@deprecated now takes two arguments; see the scaladoc.", "2.11.0")
if ((typedAnn.tpe == null) || typedAnn.tpe.isErroneous) ErroneousAnnotation
else annInfo(typedAnn)
@@ -4140,6 +4253,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
ann setType arg1.tpe.withAnnotation(annotInfo)
}
val atype = ann.tpe
+ // For `f(): @inline/noinline` callsites, add the InlineAnnotatedAttachment. TypeApplys
+ // are eliminated by erasure, so add it to the underlying function in this case.
+ def setInlineAttachment(t: Tree, att: InlineAnnotatedAttachment): Unit = t match {
+ case TypeApply(fun, _) => setInlineAttachment(fun, att)
+ case _ => t.updateAttachment(att)
+ }
+ if (atype.hasAnnotation(definitions.ScalaNoInlineClass)) setInlineAttachment(arg1, NoInlineCallsiteAttachment)
+ else if (atype.hasAnnotation(definitions.ScalaInlineClass)) setInlineAttachment(arg1, InlineCallsiteAttachment)
Typed(arg1, resultingTypeTree(atype)) setPos tree.pos setType atype
}
}
@@ -4148,7 +4269,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val name = tree.name
val body = tree.body
name match {
- case name: TypeName => assert(body == EmptyTree, context.unit + " typedBind: " + name.debugString + " " + body + " " + body.getClass)
+ case name: TypeName =>
+ assert(body == EmptyTree, s"${context.unit} typedBind: ${name.debugString} ${body} ${body.getClass}")
val sym =
if (tree.symbol != NoSymbol) tree.symbol
else {
@@ -4231,7 +4353,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// if (varsym.isVariable ||
// // setter-rewrite has been done above, so rule out methods here, but, wait a minute, why are we assigning to non-variables after erasure?!
// (phase.erasedTypes && varsym.isValue && !varsym.isMethod)) {
- if (varsym.isVariable || varsym.isValue && phase.erasedTypes) {
+ if (varsym.isVariable || varsym.isValue && phase.assignsFields) {
val rhs1 = typedByValueExpr(rhs, lhs1.tpe)
treeCopy.Assign(tree, lhs1, checkDead(rhs1)) setType UnitTpe
}
@@ -4291,7 +4413,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (pt.typeSymbol == PartialFunctionClass)
synthesizePartialFunction(newTermName(context.unit.fresh.newName("x")), tree.pos, paramSynthetic = true, tree, mode, pt)
else {
- val arity = if (isFunctionType(pt)) pt.dealiasWiden.typeArgs.length - 1 else 1
+ val arity = functionArityFromType(pt) match { case -1 => 1 case arity => arity } // SI-8429: consider sam and function type equally in determining function arity
+
val params = for (i <- List.range(0, arity)) yield
atPos(tree.pos.focusStart) {
ValDef(Modifiers(PARAM | SYNTHETIC),
@@ -4366,8 +4489,9 @@ 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.getterIn(sym.owner) != NoSymbol) sym.getterIn(sym.owner)
- else sym.lazyAccessorOrSelf
+ val sym1 =
+ if (sym.owner.isClass && sym.getterIn(sym.owner) != NoSymbol) sym.getterIn(sym.owner)
+ else sym
val pre = if (sym1.owner.isClass) sym1.owner.thisType else NoPrefix
intersectionType(List(tp, singleType(pre, sym1)))
case _ => tp
@@ -4391,31 +4515,43 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
treeCopy.New(tree, tpt1).setType(tp)
}
- def functionTypeWildcard(tree: Tree, arity: Int): Type = {
- val tp = functionType(List.fill(arity)(WildcardType), WildcardType)
- if (tp == NoType) MaxFunctionArityError(tree)
- tp
- }
-
- def typedEta(expr1: Tree): Tree = expr1.tpe match {
- case TypeRef(_, ByNameParamClass, _) =>
- val expr2 = Function(List(), expr1) setPos expr1.pos
- new ChangeOwnerTraverser(context.owner, expr2.symbol).traverse(expr2)
- typed1(expr2, mode, pt)
- case NullaryMethodType(restpe) =>
- val expr2 = Function(List(), expr1) setPos expr1.pos
- new ChangeOwnerTraverser(context.owner, expr2.symbol).traverse(expr2)
- typed1(expr2, mode, pt)
- case PolyType(_, MethodType(formals, _)) =>
- if (isFunctionType(pt)) expr1
- else adapt(expr1, mode, functionTypeWildcard(expr1, formals.length))
- case MethodType(formals, _) =>
- if (isFunctionType(pt)) expr1
- else adapt(expr1, mode, functionTypeWildcard(expr1, formals.length))
+ def functionTypeWildcard(arity: Int): Type =
+ functionType(List.fill(arity)(WildcardType), WildcardType)
+
+ def checkArity(tree: Tree)(tp: Type): tp.type = tp match {
+ case NoType => MaxFunctionArityError(tree); tp
+ case _ => tp
+ }
+
+
+ /** Eta expand an expression like `m _`, where `m` denotes a method or a by-name argument
+ *
+ * The spec says:
+ * The expression `$e$ _` is well-formed if $e$ is of method type or if $e$ is a call-by-name parameter.
+ * (1) If $e$ is a method with parameters, `$e$ _` represents $e$ converted to a function type
+ * by [eta expansion](#eta-expansion).
+ * (2) If $e$ is a parameterless method or call-by-name parameter of type `=>$T$`, `$e$ _` represents
+ * the function of type `() => $T$`, which evaluates $e$ when it is applied to the empty parameterlist `()`.
+ */
+ def typedEta(methodValue: Tree, original: Tree): Tree = methodValue.tpe match {
+ case tp@(MethodType(_, _) | PolyType(_, MethodType(_, _))) => // (1)
+ val formals = tp.params
+ if (isFunctionType(pt) || samMatchesFunctionBasedOnArity(samOf(pt), formals)) methodValue
+ else adapt(methodValue, mode, checkArity(methodValue)(functionTypeWildcard(formals.length)), original)
+
+ case TypeRef(_, ByNameParamClass, _) | NullaryMethodType(_) => // (2)
+ val pos = methodValue.pos
+ // must create it here to change owner (normally done by typed's typedFunction)
+ val funSym = context.owner.newAnonymousFunctionValue(pos)
+ new ChangeOwnerTraverser(context.owner, funSym) traverse methodValue
+
+ typed(Function(List(), methodValue) setSymbol funSym setPos pos, mode, pt)
+
case ErrorType =>
- expr1
+ methodValue
+
case _ =>
- UnderscoreEtaError(expr1)
+ UnderscoreEtaError(methodValue)
}
def tryTypedArgs(args: List[Tree], mode: Mode): Option[List[Tree]] = {
@@ -4459,7 +4595,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case Annotated(_, r) => treesInResult(r)
case If(_, t, e) => treesInResult(t) ++ treesInResult(e)
case Try(b, catches, _) => treesInResult(b) ++ catches
- case Typed(r, Function(Nil, EmptyTree)) => treesInResult(r)
+ case Typed(r, Function(Nil, EmptyTree)) => treesInResult(r) // a method value
case Select(qual, name) => treesInResult(qual)
case Apply(fun, args) => treesInResult(fun) ++ args.flatMap(treesInResult)
case TypeApply(fun, args) => treesInResult(fun) ++ args.flatMap(treesInResult)
@@ -4478,7 +4614,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
tryTypedArgs(args, forArgMode(fun, mode)) match {
case Some(args1) if !args1.exists(arg => arg.exists(_.isErroneous)) =>
val qual1 =
- if (!pt.isError) adaptToArguments(qual, name, args1, pt, reportAmbiguous = true, saveErrors = true)
+ if (!pt.isError) adaptToArguments(qual, name, args1, pt)
else qual
if (qual1 ne qual) {
val tree1 = Apply(Select(qual1, name) setPos fun.pos, args1) setPos tree.pos
@@ -4534,19 +4670,20 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val qual1 = typedQualifier(qual)
if (treeInfo.isVariableOrGetter(qual1)) {
if (Statistics.canEnable) Statistics.stopTimer(failedOpEqNanos, opeqStart)
- val erred = qual1.isErroneous || args.exists(_.isErroneous)
+ val erred = qual1.exists(_.isErroneous) || args.exists(_.isErroneous)
if (erred) reportError(error) else {
val convo = convertToAssignment(fun, qual1, name, args)
silent(op = _.typed1(convo, mode, pt)) match {
case SilentResultValue(t) => t
- case err: SilentTypeError => reportError(SilentTypeError(advice1(convo, error.errors, err), error.warnings))
+ case err: SilentTypeError => reportError(
+ SilentTypeError(advice1(convo, error.errors, err), error.warnings)
+ )
}
}
- }
- else {
+ } else {
if (Statistics.canEnable) Statistics.stopTimer(failedApplyNanos, appStart)
val Apply(Select(qual2, _), args2) = tree
- val erred = qual2.isErroneous || args2.exists(_.isErroneous)
+ val erred = qual2.exists(_.isErroneous) || args2.exists(_.isErroneous)
reportError {
if (erred) error else SilentTypeError(advice2(error.errors), error.warnings)
}
@@ -4667,10 +4804,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
def findMixinSuper(site: Type): Type = {
var ps = site.parents filter (_.typeSymbol.name == mix)
if (ps.isEmpty)
- ps = site.parents filter (_.typeSymbol.toInterface.name == mix)
+ ps = site.parents filter (_.typeSymbol.name == mix)
if (ps.isEmpty) {
debuglog("Fatal: couldn't find site " + site + " in " + site.parents.map(_.typeSymbol.name))
- if (phase.erasedTypes && context.enclClass.owner.isImplClass) {
+ if (phase.erasedTypes && context.enclClass.owner.isTrait) {
// the reference to super class got lost during erasure
restrictionError(tree.pos, unit, "traits may not select fields or methods from super[C] where C is a class")
ErrorType
@@ -4702,6 +4839,16 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (isStableContext(tree, mode, pt)) tree setType clazz.thisType else tree
}
+
+ // For Java, instance and static members are in the same scope, but we put the static ones in the companion object
+ // so, when we can't find a member in the class scope, check the companion
+ def inCompanionForJavaStatic(pre: Type, cls: Symbol, name: Name): Symbol =
+ if (!(context.unit.isJava && cls.isClass && !cls.isModuleClass)) NoSymbol else {
+ val companion = companionSymbolOf(cls, context)
+ if (!companion.exists) NoSymbol
+ else member(gen.mkAttributedRef(pre, companion), name) // assert(res.isStatic, s"inCompanionForJavaStatic($pre, $cls, $name) = $res ${res.debugFlagString}")
+ }
+
/* Attribute a selection where `tree` is `qual.name`.
* `qual` is already attributed.
*/
@@ -4728,12 +4875,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
dyna.wrapErrors(t, (_.typed1(t, mode, pt)))
}
- val sym = tree.symbol orElse member(qual, name) orElse {
+ val sym = tree.symbol orElse member(qual, name) orElse inCompanionForJavaStatic(qual.tpe.prefix, qual.symbol, name) orElse {
// symbol not found? --> try to convert implicitly to a type that does have the required
// member. Added `| PATTERNmode` to allow enrichment in patterns (so we can add e.g., an
// xml member to StringContext, which in turn has an unapply[Seq] method)
if (name != nme.CONSTRUCTOR && mode.inAny(EXPRmode | PATTERNmode)) {
- val qual1 = adaptToMemberWithArgs(tree, qual, name, mode, reportAmbiguous = true, saveErrors = true)
+ val qual1 = adaptToMemberWithArgs(tree, qual, name, mode)
if ((qual1 ne qual) && !qual1.isErrorTyped)
return typed(treeCopy.Select(tree, qual1, name), mode, pt)
}
@@ -4820,16 +4967,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
}
- // temporarily use `filter` as an alternative for `withFilter`
- def tryWithFilterAndFilter(tree: Select, qual: Tree): Tree = {
- def warn(sym: Symbol) = context.deprecationWarning(tree.pos, sym, s"`withFilter' method does not yet exist on ${qual.tpe.widen}, using `filter' method instead")
- silent(_ => typedSelect(tree, qual, nme.withFilter)) orElse { _ =>
- silent(_ => typed1(Select(qual, nme.filter) setPos tree.pos, mode, pt)) match {
- case SilentResultValue(res) => warn(res.symbol) ; res
- case SilentTypeError(err) => WithFilterError(tree, err)
- }
- }
- }
def typedSelectOrSuperCall(tree: Select) = tree match {
case Select(qual @ Super(_, _), nme.CONSTRUCTOR) =>
// the qualifier type of a supercall constructor is its first parent class
@@ -4843,10 +4980,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
else
UnstableTreeError(qualTyped)
)
- val tree1 = name match {
- case nme.withFilter if !settings.future => tryWithFilterAndFilter(tree, qualStableOrError)
- case _ => typedSelect(tree, qualStableOrError, name)
- }
+ val tree1 = typedSelect(tree, qualStableOrError, name)
def sym = tree1.symbol
if (tree.isInstanceOf[PostfixSelect])
checkFeature(tree.pos, PostfixOpsFeature, name.decode)
@@ -5127,11 +5261,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// because `expr` might contain nested macro calls (see SI-6673)
//
// Note: apparently `Function(Nil, EmptyTree)` is the secret parser marker
- // which means trailing underscore.
+ // which means trailing underscore -- denoting a method value. See makeMethodValue in TreeBuilder.
case Typed(expr, Function(Nil, EmptyTree)) =>
typed1(suppressMacroExpansion(expr), mode, pt) match {
case macroDef if treeInfo.isMacroApplication(macroDef) => MacroEtaError(macroDef)
- case exprTyped => typedEta(checkDead(exprTyped))
+ case methodValue => typedEta(checkDead(methodValue), tree)
}
case Typed(expr, tpt) =>
val tpt1 = typedType(tpt, mode) // type the ascribed type first
@@ -5222,17 +5356,23 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case MethodType(p :: _, _) => p.isImplicit // implicit method requires no args
case _ => true // catches all others including NullaryMethodType
}
- def isPlausible(m: Symbol) = m.alternatives exists (m => requiresNoArgs(m.info))
+ def isPlausible(m: Symbol) = !m.isPackage && m.alternatives.exists(x => requiresNoArgs(x.info))
def maybeWarn(s: String): Unit = {
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
+ val suspiciousExprs = InterpolatorCodeRegex findAllMatchIn s
def suspiciousIdents = InterpolatorIdentRegex findAllIn s map (s => suspiciousSym(TermName(s drop 1)))
-
- if (suspiciousExpr.nonEmpty)
- warn("detected an interpolated expression") // "${...}"
- else
+ def isCheapIdent(expr: String) = (Character.isJavaIdentifierStart(expr.charAt(0)) &&
+ expr.tail.forall(Character.isJavaIdentifierPart))
+ def warnableExpr(expr: String) = !expr.isEmpty && (!isCheapIdent(expr) || isPlausible(suspiciousSym(TermName(expr))))
+
+ if (suspiciousExprs.nonEmpty) {
+ val exprs = (suspiciousExprs map (_ group 1)).toList
+ // short-circuit on leading ${}
+ if (!exprs.head.isEmpty && exprs.exists(warnableExpr))
+ warn("detected an interpolated expression") // "${...}"
+ } else
suspiciousIdents find isPlausible foreach (sym => warn(s"detected interpolated identifier `$$${sym.name}`")) // "$id"
}
lit match {
@@ -5256,7 +5396,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (refTyped.isErrorTyped) {
setError(tree)
} else {
- tree setType refTyped.tpe.resultType
+ tree setType refTyped.tpe.resultType.deconst
if (refTyped.isErrorTyped || treeInfo.admitsTypeSelection(refTyped)) tree
else UnstableTreeError(tree)
}
@@ -5444,6 +5584,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (!isPastTyper)
signalDone(context.asInstanceOf[analyzer.Context], tree, result)
+ if (mode.inPatternMode && !mode.inPolyMode && result.isType)
+ PatternMustBeValue(result, pt)
+
result
}
@@ -5535,10 +5678,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// as a compromise, context.enrichmentEnabled tells adaptToMember to go ahead and enrich,
// but arbitrary conversions (in adapt) are disabled
// TODO: can we achieve the pattern matching bit of the string interpolation SIP without this?
- typingInPattern(context.withImplicitsDisabledAllowEnrichment(typed(tree, PATTERNmode, pt))) match {
- case tpt if tpt.isType => PatternMustBeValue(tpt, pt); tpt
- case pat => pat
- }
+ typingInPattern(context.withImplicitsDisabledAllowEnrichment(typed(tree, PATTERNmode, pt)))
}
/** Types a (fully parameterized) type tree */
@@ -5613,7 +5753,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
def reportWarning(inferredType: Type) = {
val explanation = s"inference of $inferredType from macro impl's c.Expr[$inferredType] is deprecated and is going to stop working in 2.12"
- context.deprecationWarning(ddef.pos, ddef.symbol, s"$commonMessage ($explanation)")
+ context.deprecationWarning(ddef.pos, ddef.symbol, s"$commonMessage ($explanation)", "2.12.0")
}
computeMacroDefTypeFromMacroImplRef(ddef, rhs1) match {
case ErrorType => ErrorType
diff --git a/src/compiler/scala/tools/nsc/typechecker/TypersTracking.scala b/src/compiler/scala/tools/nsc/typechecker/TypersTracking.scala
index 37fbb73b85..f2911fb98b 100644
--- a/src/compiler/scala/tools/nsc/typechecker/TypersTracking.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/TypersTracking.scala
@@ -6,9 +6,6 @@
package scala.tools.nsc
package typechecker
-import scala.collection.mutable
-import scala.reflect.internal.util.{ BatchSourceFile, Statistics }
-import mutable.ListBuffer
import Mode._
trait TypersTracking {
diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
index 22fb0728e6..c13257f6ec 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
@@ -24,7 +24,7 @@ trait Unapplies extends ast.TreeDSL {
private def unapplyParamName = nme.x_0
private def caseMods = Modifiers(SYNTHETIC | CASE)
- // In the typeCompleter (templateSig) of a case class (resp it's module),
+ // In the typeCompleter (templateSig) of a case class (resp its module),
// synthetic `copy` (reps `apply`, `unapply`) methods are added. To compute
// their signatures, the corresponding ClassDef is needed. During naming (in
// `enterClassDef`), the case class ClassDef is added as an attachment to the
@@ -128,6 +128,7 @@ trait Unapplies extends ast.TreeDSL {
*/
def factoryMeth(mods: Modifiers, name: TermName, cdef: ClassDef): DefDef = {
val tparams = constrTparamsInvariant(cdef)
+
val cparamss = constrParamss(cdef)
def classtpe = classType(cdef, tparams)
atPos(cdef.pos.focus)(
diff --git a/src/compiler/scala/tools/nsc/util/ClassFileLookup.scala b/src/compiler/scala/tools/nsc/util/ClassFileLookup.scala
deleted file mode 100644
index 4451651229..0000000000
--- a/src/compiler/scala/tools/nsc/util/ClassFileLookup.scala
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (c) 2014 Contributor. All rights reserved.
- */
-package scala.tools.nsc.util
-
-import scala.tools.nsc.io.AbstractFile
-import java.net.URL
-
-/**
- * Simple interface that allows us to abstract over how class file lookup is performed
- * in different classpath representations.
- */
-// TODO at the end, after the possible removal of the old classpath representation, this class shouldn't be generic
-// T should be just changed to AbstractFile
-trait ClassFileLookup[T] {
- def findClassFile(name: String): Option[AbstractFile]
-
- /**
- * It returns both classes from class file and source files (as our base ClassRepresentation).
- * So note that it's not so strictly related to findClassFile.
- */
- def findClass(name: String): Option[ClassRepresentation[T]]
-
- /**
- * A sequence of URLs representing this classpath.
- */
- def asURLs: Seq[URL]
-
- /** The whole classpath in the form of one String.
- */
- def asClassPathString: String
-
- // for compatibility purposes
- @deprecated("Use asClassPathString instead of this one", "2.11.5")
- def asClasspathString: String = asClassPathString
-
- /** The whole sourcepath in the form of one String.
- */
- def asSourcePathString: String
-}
-
-/**
- * Represents classes which can be loaded with a ClassfileLoader and/or SourcefileLoader.
- */
-// TODO at the end, after the possible removal of the old classpath implementation, this class shouldn't be generic
-// T should be just changed to AbstractFile
-trait ClassRepresentation[T] {
- def binary: Option[T]
- def source: Option[AbstractFile]
-
- def name: String
-}
-
-object ClassRepresentation {
- def unapply[T](classRep: ClassRepresentation[T]): Option[(Option[T], Option[AbstractFile])] =
- Some((classRep.binary, classRep.source))
-}
diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala
index 2811520b67..f286cfe246 100644
--- a/src/compiler/scala/tools/nsc/util/ClassPath.scala
+++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala
@@ -7,28 +7,61 @@
package scala.tools.nsc
package util
-import io.{ AbstractFile, Directory, File, Jar }
+import io.{AbstractFile, Directory, File, Jar}
import java.net.MalformedURLException
import java.net.URL
import java.util.regex.PatternSyntaxException
-import scala.collection.{ mutable, immutable }
-import scala.reflect.internal.util.StringOps.splitWhere
-import scala.tools.nsc.classpath.FileUtils
import File.pathSeparator
-import FileUtils.endsClass
-import FileUtils.endsScalaOrJava
import Jar.isJarOrZip
-/** <p>
- * This module provides star expansion of '-classpath' option arguments, behaves the same as
- * java, see [[http://docs.oracle.com/javase/6/docs/technotes/tools/windows/classpath.html]]
- * </p>
- *
- * @author Stepan Koltsov
- */
+/**
+ * A representation of the compiler's class- or sourcepath.
+ */
+trait ClassPath {
+ import scala.tools.nsc.classpath._
+ def asURLs: Seq[URL]
+
+ /** Empty string represents root package */
+ private[nsc] def packages(inPackage: String): Seq[PackageEntry]
+ private[nsc] def classes(inPackage: String): Seq[ClassFileEntry]
+ private[nsc] def sources(inPackage: String): Seq[SourceFileEntry]
+
+ /** Allows to get entries for packages and classes merged with sources possibly in one pass. */
+ private[nsc] def list(inPackage: String): ClassPathEntries
+
+ /**
+ * It returns both classes from class file and source files (as our base ClassRepresentation).
+ * So note that it's not so strictly related to findClassFile.
+ */
+ def findClass(className: String): Option[ClassRepresentation] = {
+ // A default implementation which should be overridden, if we can create the more efficient
+ // solution for a given type of ClassPath
+ val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className)
+
+ val foundClassFromClassFiles = classes(pkg).find(_.name == simpleClassName)
+ def findClassInSources = sources(pkg).find(_.name == simpleClassName)
+
+ foundClassFromClassFiles orElse findClassInSources
+ }
+ def findClassFile(className: String): Option[AbstractFile]
+
+ def asClassPathStrings: Seq[String]
+
+ /** The whole classpath in the form of one String.
+ */
+ def asClassPathString: String = ClassPath.join(asClassPathStrings: _*)
+ // for compatibility purposes
+ @deprecated("use asClassPathString instead of this one", "2.11.5")
+ def asClasspathString: String = asClassPathString
+
+ /** The whole sourcepath in the form of one String.
+ */
+ def asSourcePathString: String
+}
+
object ClassPath {
- import scala.language.postfixOps
+ val RootPackage = ""
/** Expand single path entry */
private def expandS(pattern: String): List[String] = {
@@ -36,14 +69,14 @@ object ClassPath {
/* Get all subdirectories, jars, zips out of a directory. */
def lsDir(dir: Directory, filt: String => Boolean = _ => true) =
- dir.list filter (x => filt(x.name) && (x.isDirectory || isJarOrZip(x))) map (_.path) toList
+ dir.list.filter(x => filt(x.name) && (x.isDirectory || isJarOrZip(x))).map(_.path).toList
if (pattern == "*") lsDir(Directory("."))
else if (pattern endsWith wildSuffix) lsDir(Directory(pattern dropRight 2))
else if (pattern contains '*') {
try {
val regexp = ("^" + pattern.replaceAllLiterally("""\*""", """.*""") + "$").r
- lsDir(Directory(pattern).parent, regexp findFirstIn _ isDefined)
+ lsDir(Directory(pattern).parent, regexp.findFirstIn(_).isDefined)
}
catch { case _: PatternSyntaxException => List(pattern) }
}
@@ -51,7 +84,7 @@ object ClassPath {
}
/** Split classpath using platform-dependent path separator */
- def split(path: String): List[String] = (path split pathSeparator).toList filterNot (_ == "") distinct
+ def split(path: String): List[String] = (path split pathSeparator).toList.filterNot(_ == "").distinct
/** Join classpath using platform-dependent path separator */
def join(paths: String*): String = paths filterNot (_ == "") mkString pathSeparator
@@ -68,9 +101,10 @@ object ClassPath {
def expandDir(extdir: String): List[String] = {
AbstractFile getDirectory extdir match {
case null => Nil
- case dir => dir filter (_.isClassContainer) map (x => new java.io.File(dir.file, x.name) getPath) toList
+ case dir => dir.filter(_.isClassContainer).map(x => new java.io.File(dir.file, x.name).getPath).toList
}
}
+
/** Expand manifest jar classpath entries: these are either urls, or paths
* relative to the location of the jar.
*/
@@ -88,309 +122,30 @@ object ClassPath {
try Some(new URL(spec))
catch { case _: MalformedURLException => None }
- /** A class modeling aspects of a ClassPath which should be
- * propagated to any classpaths it creates.
- */
- abstract class ClassPathContext[T] extends classpath.ClassPathFactory[ClassPath[T]] {
- /** A filter which can be used to exclude entities from the classpath
- * based on their name.
- */
- def isValidName(name: String): Boolean = true
-
- /** Filters for assessing validity of various entities.
- */
- def validClassFile(name: String) = endsClass(name) && isValidName(name)
- def validPackage(name: String) = (name != "META-INF") && (name != "") && (name.charAt(0) != '.')
- def validSourceFile(name: String) = endsScalaOrJava(name)
-
- /** From the representation to its identifier.
- */
- def toBinaryName(rep: T): String
-
- def sourcesInPath(path: String): List[ClassPath[T]] =
- for (file <- expandPath(path, expandStar = false) ; dir <- Option(AbstractFile getDirectory file)) yield
- new SourcePath[T](dir, this)
- }
-
def manifests: List[java.net.URL] = {
- import scala.collection.convert.WrapAsScala.enumerationAsScalaIterator
- Thread.currentThread().getContextClassLoader()
- .getResources("META-INF/MANIFEST.MF")
- .filter(_.getProtocol == "jar").toList
+ import scala.collection.JavaConverters._
+ val resources = Thread.currentThread().getContextClassLoader().getResources("META-INF/MANIFEST.MF")
+ resources.asScala.filter(_.getProtocol == "jar").toList
}
- class JavaContext extends ClassPathContext[AbstractFile] {
- def toBinaryName(rep: AbstractFile) = {
- val name = rep.name
- assert(endsClass(name), name)
- FileUtils.stripClassExtension(name)
- }
+ @deprecated("shim for sbt's compiler interface", since = "2.12.0")
+ sealed abstract class ClassPathContext
- def newClassPath(dir: AbstractFile) = new DirectoryClassPath(dir, this)
- }
-
- object DefaultJavaContext extends JavaContext
-
- /** From the source file to its identifier.
- */
- def toSourceName(f: AbstractFile): String = FileUtils.stripSourceExtension(f.name)
+ @deprecated("shim for sbt's compiler interface", since = "2.12.0")
+ sealed abstract class JavaContext
}
-import ClassPath._
-
-/**
- * Represents a package which contains classes and other packages
- */
-abstract class ClassPath[T] extends ClassFileLookup[T] {
- /**
- * The short name of the package (without prefix)
- */
+trait ClassRepresentation {
def name: String
-
- /**
- * A String representing the origin of this classpath element, if known.
- * For example, the path of the directory or jar.
- */
- def origin: Option[String] = None
-
- /** Info which should be propagated to any sub-classpaths.
- */
- def context: ClassPathContext[T]
-
- /** Lists of entities.
- */
- def classes: IndexedSeq[ClassRepresentation[T]]
- def packages: IndexedSeq[ClassPath[T]]
- def sourcepaths: IndexedSeq[AbstractFile]
-
- /** The entries this classpath is composed of. In class `ClassPath` it's just the singleton list containing `this`.
- * Subclasses such as `MergedClassPath` typically return lists with more elements.
- */
- def entries: IndexedSeq[ClassPath[T]] = IndexedSeq(this)
-
- /** Merge classpath of `platform` and `urls` into merged classpath */
- def mergeUrlsIntoClassPath(urls: URL*): MergedClassPath[T] = {
- // Collect our new jars/directories and add them to the existing set of classpaths
- val allEntries =
- (entries ++
- urls.map(url => context.newClassPath(io.AbstractFile.getURL(url)))
- ).distinct
-
- // Combine all of our classpaths (old and new) into one merged classpath
- new MergedClassPath(allEntries, context)
- }
-
- /**
- * Represents classes which can be loaded with a ClassfileLoader and/or SourcefileLoader.
- */
- case class ClassRep(binary: Option[T], source: Option[AbstractFile]) extends ClassRepresentation[T] {
- def name: String = binary match {
- case Some(x) => context.toBinaryName(x)
- case _ =>
- assert(source.isDefined)
- toSourceName(source.get)
- }
- }
-
- /** Filters for assessing validity of various entities.
- */
- def validClassFile(name: String) = context.validClassFile(name)
- def validPackage(name: String) = context.validPackage(name)
- def validSourceFile(name: String) = context.validSourceFile(name)
-
- /**
- * Find a ClassRep given a class name of the form "package.subpackage.ClassName".
- * Does not support nested classes on .NET
- */
- override def findClass(name: String): Option[ClassRepresentation[T]] =
- splitWhere(name, _ == '.', doDropIndex = true) match {
- case Some((pkg, rest)) =>
- val rep = packages find (_.name == pkg) flatMap (_ findClass rest)
- rep map {
- case x: ClassRepresentation[T] => x
- case x => throw new FatalError("Unexpected ClassRep '%s' found searching for name '%s'".format(x, name))
- }
- case _ =>
- classes find (_.name == name)
- }
-
- override def findClassFile(name: String): Option[AbstractFile] =
- findClass(name) match {
- case Some(ClassRepresentation(Some(x: AbstractFile), _)) => Some(x)
- case _ => None
- }
-
- override def asSourcePathString: String = sourcepaths.mkString(pathSeparator)
-
- def sortString = join(split(asClassPathString).sorted: _*)
- override def equals(that: Any) = that match {
- case x: ClassPath[_] => this.sortString == x.sortString
- case _ => false
- }
- override def hashCode = sortString.hashCode()
-}
-
-/**
- * A Classpath containing source files
- */
-class SourcePath[T](dir: AbstractFile, val context: ClassPathContext[T]) extends ClassPath[T] {
- import FileUtils.AbstractFileOps
-
- def name = dir.name
- override def origin = dir.underlyingSource map (_.path)
- def asURLs = dir.toURLs()
- def asClassPathString = dir.path
- val sourcepaths: IndexedSeq[AbstractFile] = IndexedSeq(dir)
-
- private def traverse() = {
- val classBuf = immutable.Vector.newBuilder[ClassRep]
- val packageBuf = immutable.Vector.newBuilder[SourcePath[T]]
- dir foreach { f =>
- if (!f.isDirectory && validSourceFile(f.name))
- classBuf += ClassRep(None, Some(f))
- else if (f.isDirectory && validPackage(f.name))
- packageBuf += new SourcePath[T](f, context)
- }
- (packageBuf.result(), classBuf.result())
- }
-
- lazy val (packages, classes) = traverse()
- override def toString() = "sourcepath: "+ dir.toString()
-}
-
-/**
- * A directory (or a .jar file) containing classfiles and packages
- */
-class DirectoryClassPath(val dir: AbstractFile, val context: ClassPathContext[AbstractFile]) extends ClassPath[AbstractFile] {
- import FileUtils.AbstractFileOps
-
- def name = dir.name
- override def origin = dir.underlyingSource map (_.path)
- def asURLs = dir.toURLs(default = Seq(new URL(name)))
- def asClassPathString = dir.path
- val sourcepaths: IndexedSeq[AbstractFile] = IndexedSeq()
-
- // calculates (packages, classes) in one traversal.
- private def traverse() = {
- val classBuf = immutable.Vector.newBuilder[ClassRep]
- val packageBuf = immutable.Vector.newBuilder[DirectoryClassPath]
- dir foreach {
- f =>
- // Optimization: We assume the file was not changed since `dir` called
- // `Path.apply` and categorized existent files as `Directory`
- // or `File`.
- val isDirectory = f match {
- case pf: io.PlainFile => pf.givenPath match {
- case _: io.Directory => true
- case _: io.File => false
- case _ => f.isDirectory
- }
- case _ =>
- f.isDirectory
- }
- if (!isDirectory && validClassFile(f.name))
- classBuf += ClassRep(Some(f), None)
- else if (isDirectory && validPackage(f.name))
- packageBuf += new DirectoryClassPath(f, context)
- }
- (packageBuf.result(), classBuf.result())
- }
-
- lazy val (packages, classes) = traverse()
- override def toString() = "directory classpath: "+ origin.getOrElse("?")
+ def binary: Option[AbstractFile]
+ def source: Option[AbstractFile]
}
-class DeltaClassPath[T](original: MergedClassPath[T], subst: Map[ClassPath[T], ClassPath[T]])
-extends MergedClassPath[T](original.entries map (e => subst getOrElse (e, e)), original.context) {
- // not sure we should require that here. Commented out for now.
- // require(subst.keySet subsetOf original.entries.toSet)
- // We might add specialized operations for computing classes packages here. Not sure it's worth it.
-}
-
-/**
- * A classpath unifying multiple class- and sourcepath entries.
- */
-class MergedClassPath[T](
- override val entries: IndexedSeq[ClassPath[T]],
- val context: ClassPathContext[T])
-extends ClassPath[T] {
-
- def this(entries: TraversableOnce[ClassPath[T]], context: ClassPathContext[T]) =
- this(entries.toIndexedSeq, context)
-
- def name = entries.head.name
- def asURLs = (entries flatMap (_.asURLs)).toList
- lazy val sourcepaths: IndexedSeq[AbstractFile] = entries flatMap (_.sourcepaths)
-
- override def origin = Some(entries map (x => x.origin getOrElse x.name) mkString ("Merged(", ", ", ")"))
- override def asClassPathString: String = join(entries map (_.asClassPathString) : _*)
-
- lazy val classes: IndexedSeq[ClassRepresentation[T]] = {
- var count = 0
- val indices = mutable.HashMap[String, Int]()
- val cls = new mutable.ArrayBuffer[ClassRepresentation[T]](1024)
-
- for (e <- entries; c <- e.classes) {
- val name = c.name
- if (indices contains name) {
- val idx = indices(name)
- val existing = cls(idx)
-
- if (existing.binary.isEmpty && c.binary.isDefined)
- cls(idx) = ClassRep(binary = c.binary, source = existing.source)
- if (existing.source.isEmpty && c.source.isDefined)
- cls(idx) = ClassRep(binary = existing.binary, source = c.source)
- }
- else {
- indices(name) = count
- cls += c
- count += 1
- }
- }
- cls.toIndexedSeq
- }
-
- lazy val packages: IndexedSeq[ClassPath[T]] = {
- var count = 0
- val indices = mutable.HashMap[String, Int]()
- val pkg = new mutable.ArrayBuffer[ClassPath[T]](256)
-
- for (e <- entries; p <- e.packages) {
- val name = p.name
- if (indices contains name) {
- val idx = indices(name)
- pkg(idx) = addPackage(pkg(idx), p)
- }
- else {
- indices(name) = count
- pkg += p
- count += 1
- }
- }
- pkg.toIndexedSeq
- }
+@deprecated("shim for sbt's compiler interface", since = "2.12.0")
+sealed abstract class DirectoryClassPath
- private def addPackage(to: ClassPath[T], pkg: ClassPath[T]) = {
- val newEntries: IndexedSeq[ClassPath[T]] = to match {
- case cp: MergedClassPath[_] => cp.entries :+ pkg
- case _ => IndexedSeq(to, pkg)
- }
- new MergedClassPath[T](newEntries, context)
- }
+@deprecated("shim for sbt's compiler interface", since = "2.12.0")
+sealed abstract class MergedClassPath
- def show() {
- println("ClassPath %s has %d entries and results in:\n".format(name, entries.size))
- asClassPathString split ':' foreach (x => println(" " + x))
- }
-
- override def toString() = "merged classpath "+ entries.mkString("(", "\n", ")")
-}
-
-/**
- * The classpath when compiling with target:jvm. Binary files (classfiles) are represented
- * as AbstractFile. nsc.io.ZipArchive is used to view zip/jar archives as directories.
- */
-class JavaClassPath(
- containers: IndexedSeq[ClassPath[AbstractFile]],
- context: JavaContext)
-extends MergedClassPath[AbstractFile](containers, context) { }
+@deprecated("shim for sbt's compiler interface", since = "2.12.0")
+sealed abstract class JavaClassPath
diff --git a/src/compiler/scala/tools/nsc/util/StackTracing.scala b/src/compiler/scala/tools/nsc/util/StackTracing.scala
index fa4fe29f28..c6749a13f3 100644
--- a/src/compiler/scala/tools/nsc/util/StackTracing.scala
+++ b/src/compiler/scala/tools/nsc/util/StackTracing.scala
@@ -8,7 +8,7 @@ private[util] trait StackTracing extends Any {
/** Format a stack trace, returning the prefix consisting of frames that satisfy
* a given predicate.
- * The format is similar to the typical case described in the JavaDoc
+ * The format is similar to the typical case described in the Javadoc
* for [[java.lang.Throwable#printStackTrace]].
* If a stack trace is truncated, it will be followed by a line of the form
* `... 3 elided`, by analogy to the lines `... 3 more` which indicate
@@ -19,25 +19,18 @@ private[util] trait StackTracing extends Any {
def stackTracePrefixString(e: Throwable)(p: StackTraceElement => Boolean): String = {
import collection.mutable.{ ArrayBuffer, ListBuffer }
import compat.Platform.EOL
- import util.Properties.isJavaAtLeast
-
- val sb = ListBuffer.empty[String]
type TraceRelation = String
val Self = new TraceRelation("")
val CausedBy = new TraceRelation("Caused by: ")
val Suppressed = new TraceRelation("Suppressed: ")
- val suppressable = isJavaAtLeast("1.7")
-
- def clazz(e: Throwable) = e.getClass.getName
+ def clazz(e: Throwable): String = e.getClass.getName
def because(e: Throwable): String = e.getCause match { case null => null ; case c => header(c) }
def msg(e: Throwable): String = e.getMessage match { case null => because(e) ; case s => s }
def txt(e: Throwable): String = msg(e) match { case null => "" ; case s => s": $s" }
def header(e: Throwable): String = s"${clazz(e)}${txt(e)}"
- val indent = "\u0020\u0020"
-
val seen = new ArrayBuffer[Throwable](16)
def unseen(t: Throwable) = {
def inSeen = seen exists (_ eq t)
@@ -46,28 +39,25 @@ private[util] trait StackTracing extends Any {
interesting
}
+ val sb = ListBuffer.empty[String]
+
+ // format the stack trace, skipping the shared trace
def print(e: Throwable, r: TraceRelation, share: Array[StackTraceElement], indents: Int): Unit = if (unseen(e)) {
val trace = e.getStackTrace
- val frames = (
- if (share.nonEmpty) {
- val spare = share.reverseIterator
- val trimmed = trace.reverse dropWhile (spare.hasNext && spare.next == _)
- trimmed.reverse
- } else trace
- )
- val prefix = frames takeWhile p
- val margin = indent * indents
- val indented = margin + indent
+ val frames = if (share.isEmpty) trace else {
+ val spare = share.reverseIterator
+ val trimmed = trace.reverse dropWhile (spare.hasNext && spare.next == _)
+ trimmed.reverse
+ }
+ val prefix = frames takeWhile p
+ val margin = " " * indents
+ val indent = margin + " "
sb append s"${margin}${r}${header(e)}"
- prefix foreach (f => sb append s"${indented}at $f")
- if (frames.size < trace.size) sb append s"$indented... ${trace.size - frames.size} more"
- if (r == Self && prefix.size < frames.size) sb append s"$indented... ${frames.size - prefix.size} elided"
+ prefix foreach (f => sb append s"${margin} at $f")
+ if (frames.size < trace.size) sb append s"${margin} ... ${trace.size - frames.size} more"
+ if (r == Self && prefix.size < frames.size) sb append s"${margin} ... ${frames.size - prefix.size} elided"
print(e.getCause, CausedBy, trace, indents)
- if (suppressable) {
- import scala.language.reflectiveCalls
- type Suppressing = { def getSuppressed(): Array[Throwable] }
- for (s <- e.asInstanceOf[Suppressing].getSuppressed) print(s, Suppressed, frames, indents + 1)
- }
+ e.getSuppressed foreach (t => print(t, Suppressed, frames, indents + 1))
}
print(e, Self, share = Array.empty, indents = 0)
diff --git a/src/compiler/scala/tools/nsc/util/package.scala b/src/compiler/scala/tools/nsc/util/package.scala
index bd95fdbb50..80e82c85d8 100644
--- a/src/compiler/scala/tools/nsc/util/package.scala
+++ b/src/compiler/scala/tools/nsc/util/package.scala
@@ -89,7 +89,7 @@ package object util {
implicit class StackTraceOps(private val e: Throwable) extends AnyVal with StackTracing {
/** Format the stack trace, returning the prefix consisting of frames that satisfy
* a given predicate.
- * The format is similar to the typical case described in the JavaDoc
+ * The format is similar to the typical case described in the Javadoc
* for [[java.lang.Throwable#printStackTrace]].
* If a stack trace is truncated, it will be followed by a line of the form
* `... 3 elided`, by analogy to the lines `... 3 more` which indicate
diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala
index 8fed53c89f..dc26c93066 100644
--- a/src/compiler/scala/tools/reflect/FastTrack.scala
+++ b/src/compiler/scala/tools/reflect/FastTrack.scala
@@ -4,7 +4,6 @@ package reflect
import scala.reflect.reify.Taggers
import scala.tools.nsc.typechecker.{ Analyzer, Macros }
import scala.reflect.runtime.Macros.currentMirror
-import scala.reflect.api.Universe
import scala.reflect.quasiquotes.{ Quasiquotes => QuasiquoteImpls }
/** Optimizes system macro expansions by hardwiring them directly to their implementations
diff --git a/src/compiler/scala/tools/reflect/FormatInterpolator.scala b/src/compiler/scala/tools/reflect/FormatInterpolator.scala
index b445f1e2bb..857b733f59 100644
--- a/src/compiler/scala/tools/reflect/FormatInterpolator.scala
+++ b/src/compiler/scala/tools/reflect/FormatInterpolator.scala
@@ -6,7 +6,7 @@ import scala.reflect.internal.util.Position
import scala.PartialFunction.cond
import scala.util.matching.Regex.Match
-import java.util.{ Formatter, Formattable, IllegalFormatException }
+import java.util.Formattable
abstract class FormatInterpolator {
val c: Context
@@ -19,7 +19,6 @@ abstract class FormatInterpolator {
@inline private def truly(body: => Unit): Boolean = { body ; true }
@inline private def falsely(body: => Unit): Boolean = { body ; false }
- private def fail(msg: String) = c.abort(c.enclosingPosition, msg)
private def bail(msg: String) = global.abort(msg)
def interpolate: Tree = c.macroApplication match {
@@ -93,8 +92,8 @@ abstract class FormatInterpolator {
case '\n' => "\\n"
case '\f' => "\\f"
case '\r' => "\\r"
- case '\"' => "${'\"'}" /* avoid lint warn */ +
- " or a triple-quoted literal \"\"\"with embedded \" or \\u0022\"\"\"" // $" in future
+ case '\"' => "$" /* avoid lint warn */ +
+ "{'\"'} or a triple-quoted literal \"\"\"with embedded \" or \\u0022\"\"\""
case '\'' => "'"
case '\\' => """\\"""
case x => "\\u%04x" format x
@@ -117,7 +116,7 @@ abstract class FormatInterpolator {
c.error(errPoint, msg("unsupported"))
s0
} else {
- currentRun.reporting.deprecationWarning(errPoint, msg("deprecated"))
+ currentRun.reporting.deprecationWarning(errPoint, msg("deprecated"), "2.11.0")
try StringContext.treatEscapes(s0) catch escapeHatch
}
}
@@ -262,7 +261,7 @@ abstract class FormatInterpolator {
def goodFlags = {
val badFlags = flags map (_ filterNot (okFlags contains _))
for (bf <- badFlags; f <- bf) badFlag(f, s"Illegal flag '$f'")
- badFlags.getOrElse("").isEmpty
+ badFlags.getOrElse("").isEmpty
}
def goodIndex = {
if (index.nonEmpty && hasFlag('<'))
@@ -281,7 +280,7 @@ abstract class FormatInterpolator {
) orElse Some(variants(0))
}
object Conversion {
- import SpecifierGroups.{ Spec, CC, Width }
+ import SpecifierGroups.{ Spec, CC }
def apply(m: Match, p: Position, n: Int): Option[Conversion] = {
def badCC(msg: String) = {
val dk = new ErrorXn(m, p)
diff --git a/src/compiler/scala/tools/reflect/ReflectGlobal.scala b/src/compiler/scala/tools/reflect/ReflectGlobal.scala
index e30d1ed7cd..b80524df2b 100644
--- a/src/compiler/scala/tools/reflect/ReflectGlobal.scala
+++ b/src/compiler/scala/tools/reflect/ReflectGlobal.scala
@@ -30,8 +30,7 @@ class ReflectGlobal(currentSettings: Settings, reporter: Reporter, override val
override def transformedType(sym: Symbol) =
postErasure.transformInfo(sym,
erasure.transformInfo(sym,
- uncurry.transformInfo(sym,
- refChecks.transformInfo(sym, sym.info))))
+ uncurry.transformInfo(sym, sym.info)))
override def isCompilerUniverse = true
diff --git a/src/compiler/scala/tools/reflect/ReflectMain.scala b/src/compiler/scala/tools/reflect/ReflectMain.scala
index 8d8418945a..7d82910699 100644
--- a/src/compiler/scala/tools/reflect/ReflectMain.scala
+++ b/src/compiler/scala/tools/reflect/ReflectMain.scala
@@ -5,12 +5,12 @@ import scala.reflect.internal.util.ScalaClassLoader
import scala.tools.nsc.Driver
import scala.tools.nsc.Global
import scala.tools.nsc.Settings
-import scala.tools.util.PathResolverFactory
+import scala.tools.util.PathResolver
object ReflectMain extends Driver {
private def classloaderFromSettings(settings: Settings) = {
- val classPathURLs = PathResolverFactory.create(settings).resultAsURLs
+ val classPathURLs = new PathResolver(settings).resultAsURLs
ScalaClassLoader.fromURLs(classPathURLs, getClass.getClassLoader)
}
diff --git a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala
index 47c88f2c00..9c4d521336 100644
--- a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala
+++ b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala
@@ -3,13 +3,12 @@ package tools
package reflect
import scala.tools.cmd.CommandLineParser
-import scala.tools.nsc.Global
import scala.tools.nsc.reporters._
import scala.tools.nsc.CompilerCommand
import scala.tools.nsc.io.{AbstractFile, VirtualDirectory}
import scala.reflect.internal.util.AbstractFileClassLoader
import scala.reflect.internal.Flags._
-import scala.reflect.internal.util.{BatchSourceFile, NoSourceFile, NoFile}
+import scala.reflect.internal.util.NoSourceFile
import java.lang.{Class => jClass}
import scala.compat.Platform.EOL
import scala.reflect.NameTransformer
@@ -118,13 +117,15 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
def transformDuringTyper(expr: Tree, mode: scala.reflect.internal.Mode, withImplicitViewsDisabled: Boolean, withMacrosDisabled: Boolean)(transform: (analyzer.Typer, Tree) => Tree): Tree = {
def withWrapping(tree: Tree)(op: Tree => Tree) = if (mode == TERMmode) wrappingIntoTerm(tree)(op) else op(tree)
- withWrapping(verify(expr))(expr1 => {
+ withWrapping(verify(expr)) { expr =>
// need to extract free terms, because otherwise you won't be able to typecheck macros against something that contains them
- val exprAndFreeTerms = extractFreeTerms(expr1, wrapFreeTermRefs = false)
- var expr2 = exprAndFreeTerms._1
- val freeTerms = exprAndFreeTerms._2
- val dummies = freeTerms.map{ case (freeTerm, name) => ValDef(NoMods, name, TypeTree(freeTerm.info), Select(Ident(PredefModule), newTermName("$qmark$qmark$qmark"))) }.toList
- expr2 = Block(dummies, expr2)
+ val (extracted, freeTerms) = extractFreeTerms(expr, wrapFreeTermRefs = false)
+ val exprBound = {
+ val binders = freeTerms.toList.map { case (freeTerm, name) =>
+ ValDef(NoMods, name, TypeTree(freeTerm.info), Select(Ident(PredefModule), newTermName("$qmark$qmark$qmark")))
+ }
+ Block(binders, extracted)
+ }
// !!! Why is this is in the empty package? If it's only to make
// it inaccessible then please put it somewhere designed for that
@@ -132,26 +133,29 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
// [Eugene] how can we implement that?
val ownerClass = rootMirror.EmptyPackageClass.newClassSymbol(newTypeName("<expression-owner>"))
build.setInfo(ownerClass, ClassInfoType(List(ObjectTpe), newScope, ownerClass))
- val owner = ownerClass.newLocalDummy(expr2.pos)
- val currentTyper = analyzer.newTyper(analyzer.rootContext(NoCompilationUnit, EmptyTree).make(expr2, owner))
- val withImplicitFlag = if (!withImplicitViewsDisabled) (currentTyper.context.withImplicitsEnabled[Tree] _) else (currentTyper.context.withImplicitsDisabled[Tree] _)
- val withMacroFlag = if (!withMacrosDisabled) (currentTyper.context.withMacrosEnabled[Tree] _) else (currentTyper.context.withMacrosDisabled[Tree] _)
- def withContext (tree: => Tree) = withImplicitFlag(withMacroFlag(tree))
+ val owner = ownerClass.newLocalDummy(exprBound.pos)
+ val currentTyper = analyzer.newTyper(analyzer.rootContext(NoCompilationUnit, EmptyTree).make(exprBound, owner))
+ import currentTyper.{context => currCtx}
val run = new Run
run.symSource(ownerClass) = NoAbstractFile // need to set file to something different from null, so that currentRun.defines works
phase = run.typerPhase // need to set a phase to something <= typerPhase, otherwise implicits in typedSelect will be disabled
globalPhase = run.typerPhase // amazing... looks like phase and globalPhase are different things, so we need to set them separately
- currentTyper.context.initRootContext() // need to manually set context mode, otherwise typer.silent will throw exceptions
+ currCtx.initRootContext() // need to manually set context mode, otherwise typer.silent will throw exceptions
reporter.reset()
- val expr3 = withContext(transform(currentTyper, expr2))
- var (dummies1, result) = expr3 match {
- case Block(dummies, result) => ((dummies, result))
- case result => ((Nil, result))
- }
+ val (binders, transformed) =
+ currCtx.withImplicits(enabled = !withImplicitViewsDisabled) {
+ currCtx.withMacros(enabled = !withMacrosDisabled) {
+ transform(currentTyper, exprBound)
+ }
+ } match {
+ case Block(binders, transformed) => (binders, transformed)
+ case transformed => (Nil, transformed)
+ }
+
val invertedIndex = freeTerms map (_.swap)
- result = new Transformer {
+ val indexed = new Transformer {
override def transform(tree: Tree): Tree =
tree match {
case Ident(name: TermName) if invertedIndex contains name =>
@@ -159,10 +163,10 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
case _ =>
super.transform(tree)
}
- }.transform(result)
- new TreeTypeSubstituter(dummies1 map (_.symbol), dummies1 map (dummy => SingleType(NoPrefix, invertedIndex(dummy.symbol.name.toTermName)))).traverse(result)
- result
- })
+ }.transform(transformed)
+ new TreeTypeSubstituter(binders map (_.symbol), binders map (b => SingleType(NoPrefix, invertedIndex(b.symbol.name.toTermName)))).traverse(indexed)
+ indexed
+ }
}
def typecheck(expr: Tree, pt: Type, mode: scala.reflect.internal.Mode, silent: Boolean, withImplicitViewsDisabled: Boolean, withMacrosDisabled: Boolean): Tree =
diff --git a/src/compiler/scala/tools/reflect/WrappedProperties.scala b/src/compiler/scala/tools/reflect/WrappedProperties.scala
index 523287fc66..348d000d15 100644
--- a/src/compiler/scala/tools/reflect/WrappedProperties.scala
+++ b/src/compiler/scala/tools/reflect/WrappedProperties.scala
@@ -30,9 +30,10 @@ trait WrappedProperties extends PropertiesTrait {
def systemProperties: List[(String, String)] = {
import scala.collection.JavaConverters._
wrap {
+ // SI-7269,7775 Avoid `ConcurrentModificationException` and nulls if another thread modifies properties
val props = System.getProperties
- // SI-7269 Be careful to avoid `ConcurrentModificationException` if another thread modifies the properties map
- props.stringPropertyNames().asScala.toList.map(k => (k, props.get(k).asInstanceOf[String]))
+ val it = props.stringPropertyNames().asScala.iterator map (k => (k, props getProperty k)) filter (_._2 ne null)
+ it.toList
} getOrElse Nil
}
}
diff --git a/src/compiler/scala/tools/util/PathResolver.scala b/src/compiler/scala/tools/util/PathResolver.scala
index 8e5b1e0a5c..f845656980 100644
--- a/src/compiler/scala/tools/util/PathResolver.scala
+++ b/src/compiler/scala/tools/util/PathResolver.scala
@@ -10,14 +10,10 @@ package util
import java.net.URL
import scala.tools.reflect.WrappedProperties.AccessControl
import scala.tools.nsc.Settings
-import scala.tools.nsc.util.{ ClassFileLookup, ClassPath, JavaClassPath }
-import scala.reflect.io.{ File, Directory, Path, AbstractFile }
-import scala.reflect.runtime.ReflectionUtils
-import ClassPath.{ JavaContext, DefaultJavaContext, join, split }
+import scala.tools.nsc.util.ClassPath
+import scala.reflect.io.{Directory, File, Path}
import PartialFunction.condOpt
-import scala.language.postfixOps
-import scala.tools.nsc.classpath.{ AggregateFlatClassPath, ClassPathFactory, FlatClassPath, FlatClassPathFactory }
-import scala.tools.nsc.settings.ClassPathRepresentationType
+import scala.tools.nsc.classpath._
// Loosely based on the draft specification at:
// https://wiki.scala-lang.org/display/SIW/Classpath
@@ -42,7 +38,7 @@ object PathResolver {
}
/** pretty print class path */
- def ppcp(s: String) = split(s) match {
+ def ppcp(s: String) = ClassPath.split(s) match {
case Nil => ""
case Seq(x) => x
case xs => xs.mkString(EOL, EOL, "")
@@ -52,7 +48,7 @@ object PathResolver {
*/
object Environment {
private def searchForBootClasspath =
- systemProperties find (_._1 endsWith ".boot.class.path") map (_._2) getOrElse ""
+ systemProperties collectFirst { case (k, v) if k endsWith ".boot.class.path" => v } getOrElse ""
/** Environment variables which java pays attention to so it
* seems we do as well.
@@ -166,19 +162,6 @@ object PathResolver {
|}""".asLines
}
- // used in PathResolver constructor
- private object NoImplClassJavaContext extends JavaContext {
- override def isValidName(name: String): Boolean =
- !ReflectionUtils.scalacShouldntLoadClassfile(name)
- }
-
- @deprecated("This method is no longer used be scalap and will be deleted", "2.11.5")
- def fromPathString(path: String, context: JavaContext = DefaultJavaContext): JavaClassPath = {
- val s = new Settings()
- s.classpath.value = path
- new PathResolver(s, context).result
- }
-
/** With no arguments, show the interesting values in Environment and Defaults.
* If there are arguments, show those in Calculated as if those options had been
* given to a scala runner.
@@ -190,28 +173,19 @@ object PathResolver {
} else {
val settings = new Settings()
val rest = settings.processArguments(args.toList, processAll = false)._2
- val pr = PathResolverFactory.create(settings)
+ val pr = new PathResolver(settings)
println("COMMAND: 'scala %s'".format(args.mkString(" ")))
println("RESIDUAL: 'scala %s'\n".format(rest.mkString(" ")))
pr.result match {
- case cp: JavaClassPath =>
- cp.show()
- case cp: AggregateFlatClassPath =>
+ case cp: AggregateClassPath =>
println(s"ClassPath has ${cp.aggregates.size} entries and results in:\n${cp.asClassPathStrings}")
}
}
}
-trait PathResolverResult {
- def result: ClassFileLookup[AbstractFile]
-
- def resultAsURLs: Seq[URL] = result.asURLs
-}
-
-abstract class PathResolverBase[BaseClassPathType <: ClassFileLookup[AbstractFile], ResultClassPathType <: BaseClassPathType]
-(settings: Settings, classPathFactory: ClassPathFactory[BaseClassPathType])
- extends PathResolverResult {
+final class PathResolver(settings: Settings) {
+ private val classPathFactory = new ClassPathFactory(settings)
import PathResolver.{ AsLines, Defaults, ppcp }
@@ -254,22 +228,13 @@ abstract class PathResolverBase[BaseClassPathType <: ClassFileLookup[AbstractFil
* TODO: we should refactor this as a separate -bootstrap option to have a clean implementation, no? */
def sourcePath = if (!settings.isScaladoc) cmdLineOrElse("sourcepath", Defaults.scalaSourcePath) else ""
- /** Against my better judgment, giving in to martin here and allowing
- * CLASSPATH to be used automatically. So for the user-specified part
- * of the classpath:
- *
- * - If -classpath or -cp is given, it is that
- * - Otherwise, if CLASSPATH is set, it is that
- * - If neither of those, then "." is used.
- */
- def userClassPath =
- if (!settings.classpath.isDefault) settings.classpath.value
- else sys.env.getOrElse("CLASSPATH", ".")
+ def userClassPath = settings.classpath.value // default is specified by settings and can be overridden there
import classPathFactory._
// Assemble the elements!
- def basis = List[Traversable[BaseClassPathType]](
+ def basis = List[Traversable[ClassPath]](
+ JrtClassPath.apply(), // 0. The Java 9 classpath (backed by the jrt:/ virtual system, if available)
classesInPath(javaBootClassPath), // 1. The Java bootstrap class path.
contentsOfDirsInPath(javaExtDirs), // 2. The Java extension class path.
classesInExpandedPath(javaUserClassPath), // 3. The Java application class path.
@@ -300,7 +265,7 @@ abstract class PathResolverBase[BaseClassPathType <: ClassFileLookup[AbstractFil
import PathResolver.MkLines
- def result: ResultClassPathType = {
+ def result: ClassPath = {
val cp = computeResult()
if (settings.Ylogcp) {
Console print f"Classpath built from ${settings.toConciseString} %n"
@@ -313,37 +278,11 @@ abstract class PathResolverBase[BaseClassPathType <: ClassFileLookup[AbstractFil
cp
}
+ def resultAsURLs: Seq[URL] = result.asURLs
+
@deprecated("Use resultAsURLs instead of this one", "2.11.5")
def asURLs: List[URL] = resultAsURLs.toList
- protected def computeResult(): ResultClassPathType
+ private def computeResult(): ClassPath = AggregateClassPath(containers.toIndexedSeq)
}
-class PathResolver(settings: Settings, context: JavaContext)
- extends PathResolverBase[ClassPath[AbstractFile], JavaClassPath](settings, context) {
-
- def this(settings: Settings) =
- this(settings,
- if (settings.YnoLoadImplClass) PathResolver.NoImplClassJavaContext
- else DefaultJavaContext)
-
- override protected def computeResult(): JavaClassPath =
- new JavaClassPath(containers.toIndexedSeq, context)
-}
-
-class FlatClassPathResolver(settings: Settings, flatClassPathFactory: ClassPathFactory[FlatClassPath])
- extends PathResolverBase[FlatClassPath, AggregateFlatClassPath](settings, flatClassPathFactory) {
-
- def this(settings: Settings) = this(settings, new FlatClassPathFactory(settings))
-
- override protected def computeResult(): AggregateFlatClassPath = AggregateFlatClassPath(containers.toIndexedSeq)
-}
-
-object PathResolverFactory {
-
- def create(settings: Settings): PathResolverResult =
- settings.YclasspathImpl.value match {
- case ClassPathRepresentationType.Flat => new FlatClassPathResolver(settings)
- case ClassPathRepresentationType.Recursive => new PathResolver(settings)
- }
-}
diff --git a/src/compiler/scala/tools/util/VerifyClass.scala b/src/compiler/scala/tools/util/VerifyClass.scala
index 3c203e1cf2..14888e25b4 100644
--- a/src/compiler/scala/tools/util/VerifyClass.scala
+++ b/src/compiler/scala/tools/util/VerifyClass.scala
@@ -31,7 +31,7 @@ object VerifyClass {
if (name endsWith ".jar") checkClassesInJar(name, cl)
else checkClassesInDir(name, cl)
- /** Attempts to load all classes on the classpath defined in the args string array. This method is meant to be used via reflection from tools like SBT or Ant. */
+ /** Attempts to load all classes on the classpath defined in the args string array. This method is meant to be used via reflection from tools like sbt or Ant. */
def run(args: Array[String]): java.util.Map[String, String] = {
val urls = args.map(Path.apply).map(_.toFile.toURI.toURL).toArray
println("As urls: " + urls.mkString(","))