diff options
57 files changed, 1177 insertions, 428 deletions
diff --git a/scripts/jobs/integrate/bootstrap b/scripts/jobs/integrate/bootstrap index ed1e05251a..7c045ae918 100755 --- a/scripts/jobs/integrate/bootstrap +++ b/scripts/jobs/integrate/bootstrap @@ -430,7 +430,7 @@ removeExistingBuilds() { local scalaLangModules=`curl -s $storageApiUrl/org/scala-lang | jq -r '.children | .[] | "org/scala-lang" + .uri' | grep -v actors-migration` for module in $scalaLangModules; do - local artifacts=`curl -s $storageApiUrl/$module | jq -r ".children | .[] | select(.uri | contains(\"$SCALA_VER\")) | .uri"` + local artifacts=`curl -s $storageApiUrl/$module | jq -r ".children | .[] | select(.uri | endswith(\"$SCALA_VER\")) | .uri"` for artifact in $artifacts; do echo "Deleting $releaseTempRepoUrl$module$artifact" curl -s --netrc-file $netrcFile -X DELETE $releaseTempRepoUrl$module$artifact diff --git a/spec/01-lexical-syntax.md b/spec/01-lexical-syntax.md index e4764c10dc..78f1a1a408 100644 --- a/spec/01-lexical-syntax.md +++ b/spec/01-lexical-syntax.md @@ -55,7 +55,7 @@ plainid ::= upper idrest | varid | op id ::= plainid - | ‘`’ stringLiteral ‘`’ + | ‘`’ { charNoBackQuoteOrNewline | UnicodeEscape | charEscapeSeq } ‘`’ idrest ::= {letter | digit} [‘_’ op] ``` diff --git a/spec/13-syntax-summary.md b/spec/13-syntax-summary.md index dd042824f4..be5cc1324e 100644 --- a/spec/13-syntax-summary.md +++ b/spec/13-syntax-summary.md @@ -38,7 +38,7 @@ plainid ::= upper idrest | varid | op id ::= plainid - | ‘`’ stringLiteral ‘`’ + | ‘`’ { charNoBackQuoteOrNewline | UnicodeEscape | charEscapeSeq } ‘`’ idrest ::= {letter | digit} [‘_’ op] integerLiteral ::= (decimalNumeral | hexNumeral) [‘L’ | ‘l’] diff --git a/spec/_layouts/default.yml b/spec/_layouts/default.yml index 06d8c1c118..983d4e56ae 100644 --- a/spec/_layouts/default.yml +++ b/spec/_layouts/default.yml @@ -15,9 +15,9 @@ } }); </script> - <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script> + <script type="text/javascript" src="//cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script> <script src="//code.jquery.com/jquery-2.1.3.min.js"></script> - <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.2/styles/default.min.css"> + <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.2/styles/default.min.css"> <!-- need to use include to see value of page.chapter variable --> <style type="text/css"> {% include numbering.css %} diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index d651d523a8..9c9be4eb4d 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -95,6 +95,9 @@ 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 def classPath: ClassPath = platform.classPath diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 151926b8e7..121091fe4f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -123,10 +123,19 @@ abstract class BTypes { * has the method. */ val indyLambdaImplMethods: mutable.AnyRefMap[InternalName, mutable.LinkedHashSet[asm.Handle]] = recordPerRunCache(mutable.AnyRefMap()) - def addIndyLambdaImplMethod(hostClass: InternalName, handle: Seq[asm.Handle]): Unit = { + 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 + indyLambdaImplMethods.getOrElseUpdate(hostClass, mutable.LinkedHashSet()) --= handle } + def getIndyLambdaImplMethods(hostClass: InternalName): Iterable[asm.Handle] = { indyLambdaImplMethods.getOrNull(hostClass) match { case null => Nil diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index edb75514e8..f7ee36c1ba 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -37,7 +37,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val coreBTypes = new CoreBTypesProxy[this.type](this) import coreBTypes._ - val byteCodeRepository: ByteCodeRepository[this.type] = new ByteCodeRepository(global.classPath, this) + val byteCodeRepository: ByteCodeRepository[this.type] = new ByteCodeRepository(global.optimizerClassPath(global.classPath), this) val localOpt: LocalOpt[this.type] = new LocalOpt(this) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala index 72a371cabc..e6ae073a2a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala @@ -185,53 +185,61 @@ object BackendReporting { def name: String def descriptor: String - def calleeMethodSig = BackendReporting.methodSignature(calleeDeclarationClass, name, descriptor) - - override def toString = this match { - case IllegalAccessInstruction(_, _, _, callsiteClass, instruction) => - s"The callee $calleeMethodSig contains the instruction ${AsmUtils.textify(instruction)}" + - s"\nthat would cause an IllegalAccessError when inlined into class $callsiteClass." - - case IllegalAccessCheckFailed(_, _, _, callsiteClass, instruction, cause) => - s"Failed to check if $calleeMethodSig can be safely inlined to $callsiteClass without causing an IllegalAccessError. Checking instruction ${AsmUtils.textify(instruction)} failed:\n" + cause + /** Either the callee or the callsite is annotated @inline */ + def annotatedInline: Boolean - 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.optWarnings.contains(settings.optWarningsChoices.anyInlineFailed) - - 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 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 e0fd77bb54..9c0dfb0ee2 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -137,7 +137,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { Callee( callee = method, calleeDeclarationClass = declarationClassBType, - safeToInline = safeToInline, + isStaticallyResolved = isStaticallyResolved, sourceFilePath = sourceFilePath, annotatedInline = annotatedInline, annotatedNoInline = annotatedNoInline, @@ -256,7 +256,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { /** * Just a named tuple used as return type of `analyzeCallsite`. */ - private case class CallsiteInfo(safeToInline: Boolean, sourceFilePath: Option[String], + private case class CallsiteInfo(isStaticallyResolved: Boolean, sourceFilePath: Option[String], annotatedInline: Boolean, annotatedNoInline: Boolean, samParamTypes: IntMap[ClassBType], warning: Option[CalleeInfoWarning]) @@ -293,7 +293,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { // 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 + 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) } @@ -301,22 +301,13 @@ class CallGraph[BT <: BTypes](val btypes: BT) { 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). CallsiteInfo( - safeToInline = - inlinerHeuristics.canInlineFromSource(calleeSourceFilePath) && - isStaticallyResolved && // (1) - !isAbstract && - !BytecodeUtils.isConstructor(calleeMethodNode) && - !BytecodeUtils.isNativeMethod(calleeMethodNode) && - !BytecodeUtils.hasCallerSensitiveAnnotation(calleeMethodNode), - sourceFilePath = calleeSourceFilePath, - annotatedInline = methodInlineInfo.annotatedInline, - annotatedNoInline = methodInlineInfo.annotatedNoInline, - samParamTypes = samParamTypes(calleeMethodNode, receiverType), - warning = warning) + 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) @@ -353,6 +344,10 @@ class CallGraph[BT <: BTypes](val btypes: BT) { */ 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}" + @@ -378,8 +373,7 @@ 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 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 @@ -387,11 +381,17 @@ class CallGraph[BT <: BTypes](val btypes: BT) { * gathering the information about this callee. */ final case class Callee(callee: MethodNode, calleeDeclarationClass: btypes.ClassBType, - safeToInline: Boolean, sourceFilePath: Option[String], + isStaticallyResolved: Boolean, sourceFilePath: Option[String], annotatedInline: Boolean, annotatedNoInline: Boolean, samParamTypes: IntMap[btypes.ClassBType], calleeInfoWarning: Option[CalleeInfoWarning]) { 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 } /** 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 35ee5ba13d..2fca8991ab 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala @@ -359,7 +359,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { Callee( callee = bodyMethodNode, calleeDeclarationClass = bodyDeclClassType, - safeToInline = inlinerHeuristics.canInlineFromSource(sourceFilePath), + isStaticallyResolved = true, sourceFilePath = sourceFilePath, annotatedInline = false, annotatedNoInline = false, @@ -392,7 +392,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { // (x: T) => ??? has return type Nothing$, and an ATHROW is added (see fixLoadedNothingOrNullValue). unreachableCodeEliminated -= ownerMethod - if (hasAdaptedImplMethod(closureInit) && inliner.canInlineBody(bodyMethodCallsite).isEmpty) + if (hasAdaptedImplMethod(closureInit) && inliner.canInlineCallsite(bodyMethodCallsite).isEmpty) inliner.inlineCallsite(bodyMethodCallsite) } 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 64638ca34d..b9f593a4d8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -25,49 +25,110 @@ class Inliner[BT <: BTypes](val btypes: BT) { import inlinerHeuristics._ import backendUtils._ - case class InlineLog(request: InlineRequest, sizeBefore: Int, sizeAfter: Int, sizeInlined: Int, warning: Option[CannotInlineWarning]) - var inlineLog: List[InlineLog] = Nil + 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)) + } - def runInliner(): Unit = { - for (request <- collectAndOrderInlineRequests) { - val Right(callee) = request.callsite.callee // collectAndOrderInlineRequests returns callsites with a known callee + // indexed by callsite method + private val logs = mutable.Map.empty[MethodNode, mutable.LinkedHashSet[InlineLog]] - // TODO: if the request has downstream requests, create a snapshot to which we could roll back in case some downstream callsite cannot be inlined - // (Needs to revert modifications to the callee method, but also the call graph) - // (This assumes that inlining a request only makes sense if its downstream requests are satisfied - sync with heuristics!) + private var upstream: InlineLogSuccess = _ + private var isTopLevel = true - val warnings = inline(request) - for (warning <- warnings) { - if ((callee.annotatedInline && btypes.compilerSettings.optWarningEmitAtInlineFailed) || 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.callsite.callsitePosition, msg) - } + 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() } } - if (compilerSettings.YoptLogInline.isSetByUser) { - val methodPrefix = { val p = compilerSettings.YoptLogInline.value; if (p == "_") "" else p } - val byCallsiteMethod = inlineLog.groupBy(_.request.callsite.callsiteMethod).toList.sortBy(_._2.head.request.callsite.callsiteClass.internalName) - for ((m, mLogs) <- byCallsiteMethod) { - val initialSize = mLogs.minBy(_.sizeBefore).sizeBefore - val firstLog = mLogs.head - val methodName = s"${firstLog.request.callsite.callsiteClass.internalName}.${m.name}" - if (methodName.startsWith(methodPrefix)) { - println(s"Inlining into $methodName (initially $initialSize instructions, ultimately ${m.instructions.size}):") - val byCallee = mLogs.groupBy(_.request.callsite.callee.get).toList.sortBy(_._2.length).reverse - for ((c, cLogs) <- byCallee) { - val first = cLogs.head - if (first.warning.isEmpty) { - val num = if (cLogs.tail.isEmpty) "" else s" ${cLogs.length} times" - println(s" - Inlined ${c.calleeDeclarationClass.internalName}.${c.callee.name} (${first.sizeInlined} instructions)$num: ${first.request.reason}") - } else - println(s" - Failed to inline ${c.calleeDeclarationClass.internalName}.${c.callee.name} (${first.request.reason}): ${first.warning.get}") - } - println() - } + 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 = { + for (request <- collectAndOrderInlineRequests) { + 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() } /** @@ -221,26 +282,79 @@ class Inliner[BT <: BTypes](val btypes: BT) { 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()) + + 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 + } + } + } + + 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): List[CannotInlineWarning] = canInlineBody(request.callsite) match { - case Some(w) => - if (compilerSettings.YoptLogInline.isSetByUser) { - val size = request.callsite.callsiteMethod.instructions.size - inlineLog ::= InlineLog(request, size, size, 0, Some(w)) + 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)) + } + warnings } - List(w) - case None => - val sizeBefore = request.callsite.callsiteMethod.instructions.size - inlineCallsite(request.callsite) - if (compilerSettings.YoptLogInline.isSetByUser) - inlineLog ::= InlineLog(request, sizeBefore, request.callsite.callsiteMethod.instructions.size, request.callsite.callee.get.callee.instructions.size, None) - val postRequests = request.post.flatMap(adaptPostRequestForMainCallsite(_, request.callsite)) - postRequests flatMap inline + } + + def inlinedByPost(insns: List[AbstractInsnNode]): Boolean = + insns.nonEmpty && insns.forall(ins => request.post.exists(_.callsite.callsiteInstruction == ins)) + + 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) + } } /** @@ -253,7 +367,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { * @return A map associating instruction nodes of the callee with the corresponding cloned * instruction in the callsite method. */ - def inlineCallsite(callsite: Callsite): Unit = { + 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} @@ -380,6 +494,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { clonedInstructions.insert(postCallLabel, retVarLoad) } + undo.saveMethodState(callsiteMethod) + callsiteMethod.instructions.insert(callsiteInstruction, clonedInstructions) callsiteMethod.instructions.remove(callsiteInstruction) @@ -406,7 +522,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, math.max(stackHeightAtNullCheck, maxStackOfInlinedCode)) - addIndyLambdaImplMethod(callsiteClass.internalName, targetHandles) + val added = addIndyLambdaImplMethod(callsiteClass.internalName, targetHandles) + undo { removeIndyLambdaImplMethod(callsiteClass.internalName, added) } callGraph.addIfMissing(callee, calleeDeclarationClass) @@ -426,8 +543,13 @@ class Inliner[BT <: BTypes](val btypes: BT) { argInfos = argInfos, callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight ) - originalCallsite.inlinedClones += ClonedCallsite(newCallsite, callsite) + 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 => @@ -440,10 +562,14 @@ class Inliner[BT <: BTypes](val btypes: BT) { 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 @@ -467,10 +593,10 @@ class Inliner[BT <: BTypes](val btypes: BT) { 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)) + Some(SynchronizedMethod(calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated)) } else if (isStrictfpMethod(callsiteMethod) != isStrictfpMethod(callee)) { Some(StrictfpMismatch( - calleeDeclarationClass.internalName, callee.name, callee.desc, + calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated, callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) } else None @@ -486,9 +612,14 @@ class Inliner[BT <: BTypes](val btypes: BT) { * 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. * - * @return `Some(message)` if inlining cannot be performed, `None` otherwise + * 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 canInlineBody(callsite: Callsite): Option[CannotInlineWarning] = { + def canInlineCallsite(callsite: Callsite): Option[(CannotInlineWarning, List[AbstractInsnNode])] = { import callsite.{callsiteInstruction, callsiteMethod, callsiteClass, callsiteStackHeight} val Right(callsiteCallee) = callsite.callee import callsiteCallee.{callee, calleeDeclarationClass} @@ -519,23 +650,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)) + 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)) } } @@ -624,13 +762,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 +898,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 index 79e74f3eb7..4744cb9ab1 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala @@ -7,17 +7,18 @@ 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.{MethodInsnNode, MethodNode} +import scala.tools.asm.tree.{AbstractInsnNode, MethodInsnNode, MethodNode} import scala.tools.nsc.backend.jvm.BTypes.InternalName -import scala.tools.nsc.backend.jvm.BackendReporting.OptimizerWarning +import scala.tools.nsc.backend.jvm.BackendReporting.{CalleeNotFinal, OptimizerWarning} class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { import bTypes._ import callGraph._ - case class InlineRequest(callsite: Callsite, post: List[InlineRequest], reason: String) { + 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}") } @@ -41,30 +42,18 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { compilingMethods.map(methodNode => { var requests = Set.empty[InlineRequest] callGraph.callsites(methodNode).valuesIterator foreach { - case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, sourceFilePath, calleeAnnotatedInline, _, _, callsiteWarning)), _, _, _, pos, _, _) => + case callsite @ Callsite(_, _, _, Right(Callee(callee, _, _, _, _, _, _, callsiteWarning)), _, _, _, pos, _, _) => inlineRequest(callsite, requests) match { case Some(Right(req)) => requests += req - case Some(Left(w)) => - if ((calleeAnnotatedInline && bTypes.compilerSettings.optWarningEmitAtInlineFailed) || w.emitWarning(compilerSettings)) { - val annotWarn = if (calleeAnnotatedInline) " is annotated @inline but" else "" - val msg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)}$annotWarn could not be inlined:\n$w" - backendReporting.inlinerWarning(callsite.callsitePosition, msg) + + case Some(Left(w)) => + if (w.emitWarning(compilerSettings)) { + backendReporting.inlinerWarning(callsite.callsitePosition, w.toString) } case None => - if (canInlineFromSource(sourceFilePath) && calleeAnnotatedInline && !callsite.annotatedNoInline && bTypes.compilerSettings.optWarningEmitAtInlineFailed) { - // 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 -opt-warning flag). - def initMsg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)} is annotated @inline but cannot be inlined" - def warnMsg = callsiteWarning.map(" Possible reason:\n" + _).getOrElse("") - 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 (callsiteWarning.isDefined && callsiteWarning.get.emitWarning(compilerSettings)) { - // when annotatedInline is false, and there is some warning, the callsite metadata is possibly incomplete. + 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, _, _) => @@ -75,6 +64,46 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { }).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`). @@ -90,81 +119,89 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { * `Some(Right)` if the callsite should be and can be inlined */ def inlineRequest(callsite: Callsite, selectedRequestsForCallee: Set[InlineRequest]): Option[Either[OptimizerWarning, InlineRequest]] = { - val callee = callsite.callee.get - def requestIfCanInline(callsite: Callsite, reason: String): Either[OptimizerWarning, InlineRequest] = inliner.earlyCanInlineCheck(callsite) match { - case Some(w) => Left(w) - case None => - val callee = callsite.callee.get - val postInlineRequest: List[InlineRequest] = callee.calleeDeclarationClass.isInterface match { - case Right(true) => - // Treat the pair of trait interface method and static method as one for the purposes of inlining: - // if we inline invokeinterface, invoke the invokestatic, too. - val calls = callee.callee.instructions.iterator().asScala.filter(BytecodeUtils.isCall).take(2).toList - calls match { - case List(x: MethodInsnNode) if x.getOpcode == Opcodes.INVOKESTATIC && x.name == (callee.callee.name + "$") => - callGraph.addIfMissing(callee.callee, callee.calleeDeclarationClass) - val maybeNodeToCallsite1 = callGraph.findCallSite(callee.callee, x) - maybeNodeToCallsite1.toList.flatMap(x => requestIfCanInline(x, reason).right.toOption) - case _ => - Nil - - } - case _ => Nil - } - - Right(InlineRequest(callsite, postInlineRequest, reason)) - + 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 attempty 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))) + } } - compilerSettings.YoptInlineHeuristics.value match { - case "everything" => - if (callee.safeToInline) { + // 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 - Some(requestIfCanInline(callsite, reason)) - } - else None + requestIfCanInline(callsite, reason) - case "at-inline-annotated" => - if (callee.safeToInline && callee.annotatedInline) { - val reason = if (compilerSettings.YoptLogInline.isSetByUser) { - val what = if (callee.safeToInline) "callee" else "callsite" + 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`" - } else null - Some(requestIfCanInline(callsite, reason)) - } - else None + } + if (callsite.isInlineAnnotated && !callsite.isNoInlineAnnotated) requestIfCanInline(callsite, reason) + else None - case "default" => - if (callee.safeToInline && !callee.annotatedNoInline && !callsite.annotatedNoInline) { - def shouldInlineHO = callee.samParamTypes.nonEmpty && (callee.samParamTypes exists { - case (index, _) => callsite.argInfos.contains(index) - }) - if (callee.annotatedInline || callsite.annotatedInline || shouldInlineHO) { - val reason = if (compilerSettings.YoptLogInline.isSetByUser) { - if (callee.annotatedInline || callsite.annotatedInline) { - val what = if (callee.safeToInline) "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) + 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" } - s"the callee is a higher-order method, ${argInfos.mkString(", ")}" + samInfo(i, sam.internalName.split('/').last, argKind) } - } else null - Some(requestIfCanInline(callsite, reason)) + 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 - } else None + } } } diff --git a/src/compiler/scala/tools/nsc/classpath/VirtualDirectoryClassPath.scala b/src/compiler/scala/tools/nsc/classpath/VirtualDirectoryClassPath.scala index 8df0c3743d..6fefaf0da0 100644 --- a/src/compiler/scala/tools/nsc/classpath/VirtualDirectoryClassPath.scala +++ b/src/compiler/scala/tools/nsc/classpath/VirtualDirectoryClassPath.scala @@ -1,9 +1,11 @@ package scala.tools.nsc.classpath import scala.tools.nsc.util.ClassRepresentation -import scala.reflect.io.{Path, PlainFile, VirtualDirectory, AbstractFile} +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 { @@ -11,7 +13,7 @@ case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath wi protected def emptyFiles: Array[AbstractFile] = Array.empty protected def getSubDir(packageDirName: String): Option[AbstractFile] = - Option(dir.lookupName(packageDirName, directory = true)) + 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 @@ -27,10 +29,8 @@ case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath wi 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 PlainFile(Path(s"$dir/$relativePath.class")) - if (classFile.exists) Some(classFile) - else None + val relativePath = FileUtils.dirPath(className) + ".class" + Option(AbstractFileClassLoader.lookupPath(dir)(relativePath split '/', directory = false)) } private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage) diff --git a/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala b/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala index a1923ead21..a0bba46398 100644 --- a/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala +++ b/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala @@ -332,7 +332,7 @@ trait AccessorSynthesis extends Transform with ast.TreeDSL { val isUnit = isUnitGetter(lazyAccessor) val selectVar = if (isUnit) UNIT else Select(thisRef, lazyVar) - val storeRes = if (isUnit) rhsAtSlowDef else Assign(selectVar, rhsAtSlowDef) + val storeRes = if (isUnit) rhsAtSlowDef else Assign(selectVar, fields.castHack(rhsAtSlowDef, lazyVar.info)) def needsInit = mkTest(lazyAccessor) val doInit = Block(List(storeRes), mkSetFlag(lazyAccessor)) diff --git a/src/compiler/scala/tools/nsc/transform/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala index 0fe7a82b15..b09223110a 100644 --- a/src/compiler/scala/tools/nsc/transform/Fields.scala +++ b/src/compiler/scala/tools/nsc/transform/Fields.scala @@ -510,6 +510,16 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor 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) @@ -596,15 +606,6 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // synth trees for accessors/fields and trait setters when they are mixed into a class def fieldsAndAccessors(clazz: Symbol): List[Tree] = { - // scala/scala-dev#219 - // 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 cast(tree: Tree, pt: Type) = gen.mkAsInstanceOf(tree, pt) // 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. @@ -615,14 +616,14 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // 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)(cast(Select(This(clazz), getter.asTerm.referenced), getter.info.resultType)) + 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)(cast(Select(This(clazz), fieldSel), getter.info.resultType)) + case fieldSel => mkAccessor(getter)(castHack(Select(This(clazz), fieldSel), getter.info.resultType)) } } @@ -636,7 +637,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor 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), cast(Ident(setter.firstParam), fieldSel.info))) + mkAccessor(setter)(Assign(Select(This(clazz), fieldSel), castHack(Ident(setter.firstParam), fieldSel.info))) } } @@ -657,7 +658,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor val selectSuper = Select(Super(This(clazz), tpnme.EMPTY), getter.name) val lazyVar = lazyVarOf(getter) - val rhs = cast(Apply(selectSuper, Nil), lazyVar.info) + val rhs = castHack(Apply(selectSuper, Nil), lazyVar.info) synthAccessorInClass.expandLazyClassMember(lazyVar, getter, rhs) } @@ -708,7 +709,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor val transformedRhs = atOwner(statSym)(transform(rhs)) if (rhs == EmptyTree) mkAccessor(statSym)(EmptyTree) - else if (currOwner.isTrait) mkAccessor(statSym)(transformedRhs) + 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? diff --git a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala index 4ae97ce281..cb3759e5fa 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala @@ -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/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index d11417192d..0f257d3717 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -132,7 +132,11 @@ trait MethodSynthesis { // 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) diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala index 1df3449ce6..cd0c292d90 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala @@ -123,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/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index c89a410334..de72d9feed 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.{ Statistics, 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. @@ -2020,7 +2021,12 @@ 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)) @@ -2055,7 +2061,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. diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 715ba0d4f3..669a018f10 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -1189,7 +1189,8 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") case Nil => entered.isEmpty && matchCount > 0 case head :: tail => val enteredAlternatives = Set(entered, entered.capitalize) - head.inits.filter(_.length <= entered.length).exists(init => + val n = (head, entered).zipped.count {case (c, e) => c == e || (c.isUpper && c == e.toUpper)} + head.take(n).inits.exists(init => enteredAlternatives.exists(entered => lenientMatch(entered.stripPrefix(init), tail, matchCount + (if (init.isEmpty) 0 else 1)) ) diff --git a/src/library/scala/collection/mutable/HashTable.scala b/src/library/scala/collection/mutable/HashTable.scala index a6a6e1e432..776eafaccc 100644 --- a/src/library/scala/collection/mutable/HashTable.scala +++ b/src/library/scala/collection/mutable/HashTable.scala @@ -360,14 +360,14 @@ trait HashTable[A, Entry >: Null <: HashEntry[A, Entry]] extends HashTable.HashU protected def elemEquals(key1: A, key2: A): Boolean = (key1 == key2) - // Note: - // we take the most significant bits of the hashcode, not the lower ones - // this is of crucial importance when populating the table in parallel - protected final def index(hcode: Int) = { + /** + * Note: we take the most significant bits of the hashcode, not the lower ones + * this is of crucial importance when populating the table in parallel + */ + protected final def index(hcode: Int): Int = { val ones = table.length - 1 - val improved = improve(hcode, seedvalue) - val shifted = (improved >> (32 - java.lang.Integer.bitCount(ones))) & ones - shifted + val exponent = Integer.numberOfLeadingZeros(ones) + (improve(hcode, seedvalue) >>> exponent) & ones } protected def initWithContents(c: HashTable.Contents[A, Entry]) = { @@ -411,58 +411,23 @@ private[collection] object HashTable { protected def elemHashCode(key: KeyType) = key.## - protected final def improve(hcode: Int, seed: Int) = { - /* Murmur hash - * m = 0x5bd1e995 - * r = 24 - * note: h = seed = 0 in mmix - * mmix(h,k) = k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; */ - // var k = hcode * 0x5bd1e995 - // k ^= k >> 24 - // k *= 0x5bd1e995 - // k - - /* Another fast multiplicative hash - * by Phil Bagwell - * - * Comment: - * Multiplication doesn't affect all the bits in the same way, so we want to - * multiply twice, "once from each side". - * It would be ideal to reverse all the bits after the first multiplication, - * however, this is more costly. We therefore restrict ourselves only to - * reversing the bytes before final multiplication. This yields a slightly - * worse entropy in the lower 8 bits, but that can be improved by adding: - * - * `i ^= i >> 6` - * - * For performance reasons, we avoid this improvement. - * */ - val i= scala.util.hashing.byteswap32(hcode) - - /* Jenkins hash - * for range 0-10000, output has the msb set to zero */ - // var h = hcode + (hcode << 12) - // h ^= (h >> 22) - // h += (h << 4) - // h ^= (h >> 9) - // h += (h << 10) - // h ^= (h >> 2) - // h += (h << 7) - // h ^= (h >> 12) - // h - - /* OLD VERSION - * quick, but bad for sequence 0-10000 - little entropy in higher bits - * since 2003 */ - // var h: Int = hcode + ~(hcode << 9) - // h = h ^ (h >>> 14) - // h = h + (h << 4) - // h ^ (h >>> 10) - - // the rest of the computation is due to SI-5293 - val rotation = seed % 32 - val rotated = (i >>> rotation) | (i << (32 - rotation)) - rotated + /** + * Defer to a high-quality hash in [[scala.util.hashing]]. + * The goal is to distribute across bins as well as possible even if a hash code has low entropy at some bits. + * <p/> + * OLD VERSION - quick, but bad for sequence 0-10000 - little entropy in higher bits - since 2003 + * {{{ + * var h: Int = hcode + ~(hcode << 9) + * h = h ^ (h >>> 14) + * h = h + (h << 4) + * h ^ (h >>> 10) + * }}} + * the rest of the computation is due to SI-5293 + */ + protected final def improve(hcode: Int, seed: Int): Int = { + val hash = scala.util.hashing.byteswap32(hcode) + val shift = seed & ((1 << 5) - 1) + (hash >>> shift) | (hash << (32 - shift)) } } diff --git a/src/library/scala/inline.scala b/src/library/scala/inline.scala index f6d7c7569e..f188ccab07 100644 --- a/src/library/scala/inline.scala +++ b/src/library/scala/inline.scala @@ -23,7 +23,7 @@ package scala * def t2 = f2(1) // not inlined * def t3 = f3(1) // may be inlined (heuristics) * def t4 = f1(1): @noinline // not inlined (override at callsite) - * def t5 = f2(1): @inline // not inlined (cannot override the @noinline at f2's definition) + * def t5 = f2(1): @inline // inlined if possible (override at callsite) * def t6 = f3(1): @inline // inlined if possible * def t7 = f3(1): @noinline // not inlined * } diff --git a/src/library/scala/noinline.scala b/src/library/scala/noinline.scala index 0cd5ef9f64..6c21ed667d 100644 --- a/src/library/scala/noinline.scala +++ b/src/library/scala/noinline.scala @@ -23,7 +23,7 @@ package scala * def t2 = f2(1) // not inlined * def t3 = f3(1) // may be inlined (heuristics) * def t4 = f1(1): @noinline // not inlined (override at callsite) - * def t5 = f2(1): @inline // not inlined (cannot override the @noinline at f2's definition) + * def t5 = f2(1): @inline // inlined if possible (override at callsite) * def t6 = f3(1): @inline // inlined if possible * def t7 = f3(1): @noinline // not inlined * } diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 56b6dc078d..0da153349a 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -34,9 +34,13 @@ trait Symbols extends api.Symbols { self: SymbolTable => def recursionTable = _recursionTable def recursionTable_=(value: immutable.Map[Symbol, Int]) = _recursionTable = value + @deprecated("Global existential IDs no longer used", "2.12.1") private var existentialIds = 0 + @deprecated("Global existential IDs no longer used", "2.12.1") protected def nextExistentialId() = { existentialIds += 1; existentialIds } - protected def freshExistentialName(suffix: String) = newTypeName("_" + nextExistentialId() + suffix) + @deprecated("Use overload that accepts an id", "2.12.1") + protected def freshExistentialName(suffix: String): TypeName = freshExistentialName(suffix, nextExistentialId()) + protected def freshExistentialName(suffix: String, id: Int): TypeName = newTypeName("_" + id + suffix) // Set the fields which point companions at one another. Returns the module. def connectModuleToClass(m: ModuleSymbol, moduleClass: ClassSymbol): ModuleSymbol = { @@ -440,8 +444,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => def newGADTSkolem(name: TypeName, origin: Symbol, info: Type): TypeSkolem = newTypeSkolemSymbol(name, origin, origin.pos, origin.flags & ~(EXISTENTIAL | PARAM) | GADT_SKOLEM_FLAGS) setInfo info + @deprecated("Use overload that accepts an id", "2.12.1") final def freshExistential(suffix: String): TypeSymbol = newExistential(freshExistentialName(suffix), pos) + final def freshExistential(suffix: String, id: Int): TypeSymbol = + newExistential(freshExistentialName(suffix, id), pos) /** Type skolems are type parameters ''seen from the inside'' * Assuming a polymorphic method m[T], its type is a PolyType which has a TypeParameter @@ -488,7 +495,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => * often to the point of never. */ def newStubSymbol(name: Name, missingMessage: String, isPackage: Boolean = false): Symbol = name match { - case n: TypeName => if (isPackage) new StubPackageClassSymbol(this, n, missingMessage) else new StubClassSymbol(this, n, missingMessage) + case n: TypeName => new StubClassSymbol(this, n, missingMessage) case _ => new StubTermSymbol(this, name.toTermName, missingMessage) } @@ -3423,7 +3430,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => override def companionSymbol = fail(NoSymbol) } class StubClassSymbol(owner0: Symbol, name0: TypeName, val missingMessage: String) extends ClassSymbol(owner0, owner0.pos, name0) with StubSymbol - class StubPackageClassSymbol(owner0: Symbol, name0: TypeName, val missingMessage: String) extends PackageClassSymbol(owner0, owner0.pos, name0) with StubSymbol class StubTermSymbol(owner0: Symbol, name0: TermName, val missingMessage: String) extends TermSymbol(owner0, owner0.pos, name0) with StubSymbol trait FreeSymbol extends Symbol { diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index 61937958dd..1aef30819a 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -480,7 +480,8 @@ abstract class TreeInfo { } map { dd => val DefDef(dmods, dname, _, _, _, drhs) = dd // get access flags from DefDef - val vdMods = (vmods &~ Flags.AccessFlags) | (dmods & Flags.AccessFlags).flags + val defDefMask = Flags.AccessFlags | OVERRIDE | IMPLICIT | DEFERRED + val vdMods = (vmods &~ defDefMask) | (dmods & defDefMask).flags // for most cases lazy body should be taken from accessor DefDef val vdRhs = if (vmods.isLazy) lazyValDefRhs(drhs) else vrhs copyValDef(vd)(mods = vdMods, name = dname, rhs = vdRhs) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index aa30c4a4c8..dc35053835 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -4478,6 +4478,7 @@ trait Types debuglog(s"transposed irregular matrix!? tps=$tps argss=$argss") NoType case Some(argsst) => + var capturedParamIds = 0 val args = map2(sym.typeParams, argsst) { (tparam, as0) => val as = as0.distinct if (as.size == 1) as.head @@ -4499,8 +4500,10 @@ trait Types else { // Martin: I removed this, because incomplete. Not sure there is a good way to fix it. For the moment we // just err on the conservative side, i.e. with a bound that is too high. // if(!(tparam.info.bounds contains tparam)) //@M can't deal with f-bounds, see #2251 + capturedParamIds += 1 + val capturedParamId = capturedParamIds - val qvar = commonOwner(as) freshExistential "" setInfo TypeBounds(g, l) + val qvar = commonOwner(as).freshExistential("", capturedParamId) setInfo TypeBounds(g, l) capturedParams += qvar qvar.tpe } diff --git a/src/reflect/scala/reflect/internal/pickling/UnPickler.scala b/src/reflect/scala/reflect/internal/pickling/UnPickler.scala index 6dea184826..fe1de91662 100644 --- a/src/reflect/scala/reflect/internal/pickling/UnPickler.scala +++ b/src/reflect/scala/reflect/internal/pickling/UnPickler.scala @@ -216,19 +216,6 @@ abstract class UnPickler { } adjust(decl) } - def nestedObjectSymbol: Symbol = { - // If the owner is overloaded (i.e. a method), it's not possible to select the - // right member, so return NoSymbol. This can only happen when unpickling a tree. - // the "case Apply" in readTree() takes care of selecting the correct alternative - // after parsing the arguments. - if (owner.isOverloaded) - return NoSymbol - - if (tag == EXTMODCLASSref) { - owner.info.decl(nme.moduleVarName(name.toTermName)) - } - NoSymbol - } def moduleAdvice(missing: String): String = { val module = @@ -255,20 +242,18 @@ abstract class UnPickler { // symbols are read from outside: for instance when checking the children // of a class. See #1722. fromName(nme.expandedName(name.toTermName, owner)) orElse { - // (3) Try as a nested object symbol. - nestedObjectSymbol orElse { - // (4) Call the mirror's "missing" hook. - adjust(mirrorThatLoaded(owner).missingHook(owner, name)) orElse { - // (5) Create a stub symbol to defer hard failure a little longer. - val advice = moduleAdvice(s"${owner.fullName}.$name") - val missingMessage = - s"""|missing or invalid dependency detected while loading class file '$filename'. - |Could not access ${name.longString} in ${owner.kindString} ${owner.fullName}, - |because it (or its dependencies) are missing. Check your build definition for - |missing or conflicting dependencies. (Re-run with `-Ylog-classpath` to see the problematic classpath.) - |A full rebuild may help if '$filename' was compiled against an incompatible version of ${owner.fullName}.$advice""".stripMargin - owner.newStubSymbol(name, missingMessage) - } + // (3) Call the mirror's "missing" hook. + adjust(mirrorThatLoaded(owner).missingHook(owner, name)) orElse { + // (4) Create a stub symbol to defer hard failure a little longer. + val advice = moduleAdvice(s"${owner.fullName}.$name") + val missingMessage = + s"""|missing or invalid dependency detected while loading class file '$filename'. + |Could not access ${name.longString} in ${owner.kindString} ${owner.fullName}, + |because it (or its dependencies) are missing. Check your build definition for + |missing or conflicting dependencies. (Re-run with `-Ylog-classpath` to see the problematic classpath.) + |A full rebuild may help if '$filename' was compiled against an incompatible version of ${owner.fullName}.$advice""".stripMargin + val stubName = if (tag == EXTref) name else name.toTypeName + owner.newStubSymbol(stubName, missingMessage) } } } @@ -392,9 +377,7 @@ abstract class UnPickler { def readThisType(): Type = { val sym = readSymbolRef() match { - case stub: StubSymbol if !stub.isClass => - // SI-8502 This allows us to create a stub for a unpickled reference to `missingPackage.Foo`. - stub.owner.newStubSymbol(stub.name.toTypeName, stub.missingMessage, isPackage = true) + case stub: StubSymbol => stub.setFlag(PACKAGE) case sym => sym } ThisType(sym) diff --git a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala index ba4f2bec4b..08219c0634 100644 --- a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala +++ b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala @@ -512,6 +512,8 @@ private[internal] trait TypeMaps { && isBaseClassOfEnclosingClass(sym.owner) ) + private var capturedThisIds= 0 + private def nextCapturedThisId() = { capturedThisIds += 1; capturedThisIds } /** Creates an existential representing a type parameter which appears * in the prefix of a ThisType. */ @@ -519,7 +521,7 @@ private[internal] trait TypeMaps { capturedParams find (_.owner == clazz) match { case Some(p) => p.tpe case _ => - val qvar = clazz freshExistential nme.SINGLETON_SUFFIX setInfo singletonBounds(pre) + val qvar = clazz.freshExistential(nme.SINGLETON_SUFFIX, nextCapturedThisId()) setInfo singletonBounds(pre) _capturedParams ::= qvar debuglog(s"Captured This(${clazz.fullNameString}) seen from $seenFromPrefix: ${qvar.defString}") qvar.tpe diff --git a/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala b/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala index b5030460b8..3cede1b3c5 100644 --- a/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala +++ b/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala @@ -12,6 +12,20 @@ import java.security.cert.Certificate import java.security.{ ProtectionDomain, CodeSource } import java.util.{ Collections => JCollections, Enumeration => JEnumeration } +object AbstractFileClassLoader { + // should be a method on AbstractFile, but adding in `internal.util._` for now as we're in a minor release + private[scala] final def lookupPath(base: AbstractFile)(pathParts: Seq[String], directory: Boolean): AbstractFile = { + var file: AbstractFile = base + for (dirPart <- pathParts.init) { + file = file.lookupName(dirPart, directory = true) + if (file == null) + return null + } + + file.lookupName(pathParts.last, directory = directory) + } +} + /** A class loader that loads files from a [[scala.reflect.io.AbstractFile]]. * * @author Lex Spoon @@ -25,19 +39,7 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) else s"${name.replace('.', '/')}.class" protected def findAbstractFile(name: String): AbstractFile = { - var file: AbstractFile = root - val pathParts = name split '/' - - for (dirPart <- pathParts.init) { - file = file.lookupName(dirPart, directory = true) - if (file == null) - return null - } - - file.lookupName(pathParts.last, directory = false) match { - case null => null - case file => file - } + AbstractFileClassLoader.lookupPath(root)(name split '/', directory = false) } protected def dirNameToPath(name: String): String = diff --git a/src/reflect/scala/reflect/runtime/SynchronizedSymbols.scala b/src/reflect/scala/reflect/runtime/SynchronizedSymbols.scala index 237afa082b..4e7ddda54e 100644 --- a/src/reflect/scala/reflect/runtime/SynchronizedSymbols.scala +++ b/src/reflect/scala/reflect/runtime/SynchronizedSymbols.scala @@ -10,7 +10,9 @@ private[reflect] trait SynchronizedSymbols extends internal.Symbols { self: Symb private lazy val atomicIds = new java.util.concurrent.atomic.AtomicInteger(0) override protected def nextId() = atomicIds.incrementAndGet() + @deprecated("Global existential IDs no longer used", "2.12.1") private lazy val atomicExistentialIds = new java.util.concurrent.atomic.AtomicInteger(0) + @deprecated("Global existential IDs no longer used", "2.12.1") override protected def nextExistentialId() = atomicExistentialIds.incrementAndGet() private lazy val _recursionTable = mkThreadLocalStorage(immutable.Map.empty[Symbol, Int]) diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala index 65f2c95f73..99acc34811 100644 --- a/src/repl/scala/tools/nsc/interpreter/IMain.scala +++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala @@ -889,7 +889,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends } class ClassBasedWrapper extends Wrapper { - def preambleHeader = "class %s extends _root_.java.io.Serializable { " + def preambleHeader = "sealed class %s extends _root_.java.io.Serializable { " /** Adds an object that instantiates the outer wrapping class. */ def postamble = s""" diff --git a/src/repl/scala/tools/nsc/interpreter/ReplGlobal.scala b/src/repl/scala/tools/nsc/interpreter/ReplGlobal.scala index cf055e0758..0bb9eb6a0b 100644 --- a/src/repl/scala/tools/nsc/interpreter/ReplGlobal.scala +++ b/src/repl/scala/tools/nsc/interpreter/ReplGlobal.scala @@ -6,6 +6,9 @@ package scala.tools.nsc package interpreter +import scala.tools.nsc.backend.JavaPlatform +import scala.tools.nsc.classpath.{AggregateClassPath, ClassPathFactory} +import scala.tools.nsc.util.ClassPath import typechecker.Analyzer /** A layer on top of Global so I can guarantee some extra @@ -31,4 +34,14 @@ trait ReplGlobal extends Global { new util.AbstractFileClassLoader(virtualDirectory, loader) {} } } + + override def optimizerClassPath(base: ClassPath): ClassPath = { + settings.outputDirs.getSingleOutput match { + case None => base + case Some(out) => + // Make bytecode of previous lines available to the inliner + val replOutClasspath = ClassPathFactory.newClassPath(settings.outputDirs.getSingleOutput.get, settings) + AggregateClassPath.createAggregate(platform.classPath, replOutClasspath) + } + } } diff --git a/test/benchmarks/src/main/scala/scala/BitManipulationBenchmark.scala b/test/benchmarks/src/main/scala/scala/BitManipulationBenchmark.scala new file mode 100644 index 0000000000..23e303ede0 --- /dev/null +++ b/test/benchmarks/src/main/scala/scala/BitManipulationBenchmark.scala @@ -0,0 +1,170 @@ +package scala.collection + +import org.openjdk.jmh.annotations._ +import org.openjdk.jmh.infra._ +import org.openjdk.jmh.runner.IterationType +import benchmark._ +import java.util.concurrent.TimeUnit + +@BenchmarkMode(Array(Mode.AverageTime)) +@Fork(2) +@Threads(1) +@Warmup(iterations = 10) +@Measurement(iterations = 10) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +class BitManipulationBenchmark { + val powersOfTwo = Array(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824) + + ////////////////////////////////////////////// + + @Benchmark def withIntegerBitCount(bh: Blackhole) { + for (v <- powersOfTwo) { + val leadingZeros = withIntegerBitCount(v) + // assert (leadingZeros == withLoop(v), s"$leadingZeros != ${withLoop(v)} ($v)") + bh.consume(leadingZeros) + } + } + + private def withIntegerBitCount(v: Int) = Integer.SIZE - Integer.bitCount(v - 1) + + ////////////////////////////////////////////// + + @Benchmark def withIntegerNumberOfLeadingZeros(bh: Blackhole) { + for (v <- powersOfTwo) { + val leadingZeros = withIntegerNumberOfLeadingZeros(v) + // assert (leadingZeros == withLoop(v), s"$leadingZeros != ${withLoop(v)} ($v)") + bh.consume(leadingZeros) + } + } + + private def withIntegerNumberOfLeadingZeros(v: Int) = Integer.numberOfLeadingZeros(v - 1) + + ////////////////////////////////////////////// + + @Benchmark def withLoop(bh: Blackhole) { + for (v <- powersOfTwo) { + val leadingZeros = withLoop(v) + bh.consume(leadingZeros) + } + } + + private def withLoop(v: Int): Int = { + var r = Integer.SIZE + var copy = v >> 1 + while (copy != 0) { + r -= 1 + copy = copy >> 1 + } + r + } + + ////////////////////////////////////////////// + + @Benchmark def withMatch(bh: Blackhole) { + for (v <- powersOfTwo) { + val leadingZeros = withMatch(v) + // assert (leadingZeros == withLoop(v), s"$leadingZeros != ${withLoop(v)} ($v)") + bh.consume(leadingZeros) + } + } + + private def withMatch(i: Int) = i match { + case 1 => 32 + case 2 => 31 + case 4 => 30 + case 8 => 29 + case 16 => 28 + case 32 => 27 + case 64 => 26 + case 128 => 25 + case 256 => 24 + case 512 => 23 + case 1024 => 22 + case 2048 => 21 + case 4096 => 20 + case 8192 => 19 + case 16384 => 18 + case 32768 => 17 + case 65536 => 16 + case 131072 => 15 + case 262144 => 14 + case 524288 => 13 + case 1048576 => 12 + case 2097152 => 11 + case 4194304 => 10 + case 8388608 => 9 + case 16777216 => 8 + case 33554432 => 7 + case 67108864 => 6 + case 134217728 => 5 + case 268435456 => 4 + case 536870912 => 3 + case 1073741824 => 2 + } + + + ////////////////////////////////////////////// + + @Benchmark def with2DeBruijn(bh: Blackhole) { + for (v <- powersOfTwo) { + val leadingZeros = with2DeBruijn(v) + // assert (leadingZeros == withLoop(v), s"$leadingZeros != ${withLoop(v)} ($v)") + bh.consume(leadingZeros) + } + } + + // https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn + private val multiplyDeBruijnBitPosition2 = Array(32, 31, 4, 30, 3, 18, 8, 29, 2, 10, 12, 17, 7, 15, 28, 24, 1, 5, 19, 9, 11, 13, 16, 25, 6, 20, 14, 26, 21, 27, 22, 23) + + private def with2DeBruijn(v: Int) = multiplyDeBruijnBitPosition2((v * 0x077CB531) >>> 27) + + + ////////////////////////////////////////////// + + @Benchmark def withBinSearch(bh: Blackhole) { + for (v <- powersOfTwo) { + val leadingZeros = withBinSearch(v) + // assert (leadingZeros == withLoop(v), s"$leadingZeros != ${withLoop(v)} ($v)") + bh.consume(leadingZeros) + } + } + + private def withBinSearch(v: Int) = + if (v < 65536) if (v < 256) if (v < 16) if (v < 4) if (v == 1) 32 else 31 + else if (v == 4) 30 else 29 + else if (v < 64) if (v == 16) 28 else 27 + else if (v == 64) 26 else 25 + else if (v < 4096) if (v < 1024) if (v == 256) 24 else 23 + else if (v == 1024) 22 else 21 + else if (v < 16384) if (v == 4096) 20 else 19 + else if (v == 16384) 18 else 17 + else if (v < 16777216) if (v < 1048576) if (v < 262144) if (v == 65536) 16 else 15 + else if (v == 262144) 14 else 13 + else if (v < 4194304) if (v == 1048576) 12 else 11 + else if (v == 4194304) 10 else 9 + else if (v < 268435456) if (v < 67108864) if (v == 16777216) 8 else 7 + else if (v == 67108864) 6 else 5 + else if (v < 1073741824) if (v == 268435456) 4 else 3 + else if (v == 1073741824) 2 else 1 + + ////////////////////////////////////////////// + + @Benchmark def withSumBinSearch(bh: Blackhole) { + for (v <- powersOfTwo) { + val leadingZeros = withSumBinSearch(v) + // assert(leadingZeros == withLoop(v), s"$leadingZeros != ${withLoop(v)} ($v)") + bh.consume(leadingZeros) + } + } + + private def withSumBinSearch(v: Int): Int = { + var exponent = Integer.SIZE + var remaining = v + if (remaining >= 65536) { remaining >>>= 16; exponent = 16 } + if (remaining >= 256) { remaining >>>= 8; exponent -= 8 } + if (remaining >= 16) { remaining >>>= 4; exponent -= 4 } + if (remaining >= 4) { remaining >>>= 2; exponent -= 2 } + if (remaining >= 2) exponent - 1 else exponent + } +}
\ No newline at end of file diff --git a/test/files/neg/sabin2.check b/test/files/neg/sabin2.check index aa0e8f734c..cd6fde4608 100644 --- a/test/files/neg/sabin2.check +++ b/test/files/neg/sabin2.check @@ -1,6 +1,6 @@ sabin2.scala:22: error: type mismatch; found : Test.Base#T - required: _5.T where val _5: Test.Base + required: _1.T where val _1: Test.Base a.set(b.get()) // Error ^ one error found diff --git a/test/files/neg/sealed-final-neg.check b/test/files/neg/sealed-final-neg.check index e135f38f8b..5e47c69ed8 100644 --- a/test/files/neg/sealed-final-neg.check +++ b/test/files/neg/sealed-final-neg.check @@ -1,7 +1,9 @@ -sealed-final-neg.scala:17: warning: neg1/Foo::bar(I)I is annotated @inline but cannot be inlined: the method is not final and may be overridden. +sealed-final-neg.scala:17: warning: neg1/Foo::bar(I)I is annotated @inline but could not be inlined: +The method is not final and may be overridden. def f = Foo.mkFoo() bar 10 ^ -sealed-final-neg.scala:37: warning: neg2/Foo::bar(I)I is annotated @inline but cannot be inlined: the method is not final and may be overridden. +sealed-final-neg.scala:37: warning: neg2/Foo::bar(I)I is annotated @inline but could not be inlined: +The method is not final and may be overridden. def f = Foo.mkFoo() bar 10 ^ error: No warnings can be incurred under -Xfatal-warnings. diff --git a/test/files/neg/t0764.check b/test/files/neg/t0764.check index 830278e715..0c7cff1e1e 100644 --- a/test/files/neg/t0764.check +++ b/test/files/neg/t0764.check @@ -1,5 +1,5 @@ t0764.scala:13: error: type mismatch; - found : Node{type T = _2.type} where val _2: Node{type T = NextType} + found : Node{type T = _1.type} where val _1: Node{type T = NextType} required: Node{type T = Main.this.AType} (which expands to) Node{type T = Node{type T = NextType}} new Main[AType]( (value: AType).prepend ) diff --git a/test/files/neg/t1010.check b/test/files/neg/t1010.check index 2cc8f9d986..d412d8ac1e 100644 --- a/test/files/neg/t1010.check +++ b/test/files/neg/t1010.check @@ -1,6 +1,6 @@ t1010.scala:14: error: type mismatch; found : MailBox#Message - required: _3.in.Message where val _3: Actor + required: _1.in.Message where val _1: Actor unstable.send(msg) // in.Message becomes unstable.Message, but that's ok since Message is a concrete type member ^ one error found diff --git a/test/files/neg/t5120.check b/test/files/neg/t5120.check index 34d4ebde31..b6a3cb96aa 100644 --- a/test/files/neg/t5120.check +++ b/test/files/neg/t5120.check @@ -6,7 +6,7 @@ t5120.scala:11: error: type mismatch; t5120.scala:25: error: type mismatch; found : Thread required: h.T - (which expands to) _2 + (which expands to) _1 List(str, num).foreach(h => h.f1 = new Thread()) ^ two errors found diff --git a/test/files/neg/t6829.check b/test/files/neg/t6829.check index 274094f791..5ccd531be1 100644 --- a/test/files/neg/t6829.check +++ b/test/files/neg/t6829.check @@ -1,6 +1,6 @@ t6829.scala:35: error: type mismatch; found : AgentSimulation.this.state.type (with underlying type G#State) - required: _9.State + required: _1.State lazy val actions: Map[G#Agent,G#Action] = agents.map(a => a -> a.chooseAction(state)).toMap ^ t6829.scala:45: error: trait AgentSimulation takes type parameters @@ -17,32 +17,32 @@ t6829.scala:49: error: not found: value nextState ^ t6829.scala:50: error: type mismatch; found : s.type (with underlying type Any) - required: _30.State where val _30: G + required: _1.State where val _1: G val r = rewards(agent).r(s,a,s2) ^ t6829.scala:50: error: type mismatch; found : a.type (with underlying type Any) - required: _30.Action where val _30: G + required: _1.Action where val _1: G val r = rewards(agent).r(s,a,s2) ^ t6829.scala:50: error: type mismatch; found : s2.type (with underlying type Any) - required: _30.State where val _30: G + required: _1.State where val _1: G val r = rewards(agent).r(s,a,s2) ^ t6829.scala:51: error: type mismatch; found : s.type (with underlying type Any) - required: _25.State + required: _1.State agent.learn(s,a,s2,r): G#Agent ^ t6829.scala:51: error: type mismatch; found : a.type (with underlying type Any) - required: _25.Action + required: _1.Action agent.learn(s,a,s2,r): G#Agent ^ t6829.scala:51: error: type mismatch; found : s2.type (with underlying type Any) - required: _25.State + required: _1.State agent.learn(s,a,s2,r): G#Agent ^ t6829.scala:53: error: not found: value nextState diff --git a/test/files/pos/sd268.scala b/test/files/pos/sd268.scala new file mode 100644 index 0000000000..8839651501 --- /dev/null +++ b/test/files/pos/sd268.scala @@ -0,0 +1,17 @@ +class Context(val v : AnyRef) + +trait AbidePlugin { + val someVal = "" + + val x = null.asInstanceOf[Context { val v : someVal.type }] // CRASH + lazy val y = null.asInstanceOf[Context { val v : someVal.type }] // CRASH + var z = null.asInstanceOf[Context { val v : someVal.type }] // CRASH +} + +class C { + val someVal = "" + + val x = null.asInstanceOf[Context { val v : someVal.type }] + lazy val y = null.asInstanceOf[Context { val v : someVal.type }] // CRASH + var z = null.asInstanceOf[Context { val v : someVal.type }] +} diff --git a/test/files/pos/t10009.scala b/test/files/pos/t10009.scala new file mode 100644 index 0000000000..7cd96f0f3d --- /dev/null +++ b/test/files/pos/t10009.scala @@ -0,0 +1,6 @@ +class C { + def c(a: Any, b: Any*) = a +} +object Test { + new C().c(b = new { val x = 42 }, a = 0) +} diff --git a/test/files/run/repl-inline.check b/test/files/run/repl-inline.check new file mode 100644 index 0000000000..db729a67dd --- /dev/null +++ b/test/files/run/repl-inline.check @@ -0,0 +1,11 @@ +warning: there was one deprecation warning (since 2.11.0); re-run with -deprecation for details +callerOfCaller: String +g: String +h: String +g: String +h: String +callerOfCaller: String +g: String +h: String +g: String +h: String diff --git a/test/files/run/repl-inline.scala b/test/files/run/repl-inline.scala new file mode 100644 index 0000000000..260ed28a4f --- /dev/null +++ b/test/files/run/repl-inline.scala @@ -0,0 +1,27 @@ +import scala.tools.nsc._ + +object Test { + val testCode = + """ +def callerOfCaller = Thread.currentThread.getStackTrace.drop(2).head.getMethodName +def g = callerOfCaller +def h = g +assert(h == "g", h) +@inline def g = callerOfCaller +def h = g +assert(h == "h", h) + """ + + def main(args: Array[String]) { + def test(f: Settings => Unit): Unit = { + val settings = new Settings() + settings.processArgumentString("-opt:l:classpath") + f(settings) + settings.usejavacp.value = true + val repl = new interpreter.IMain(settings) + testCode.linesIterator.foreach(repl.interpret(_)) + } + test(_ => ()) + test(_.Yreplclassbased.value = true) + } +} diff --git a/test/files/run/sd275-java/A.java b/test/files/run/sd275-java/A.java new file mode 100644 index 0000000000..b293cf6dab --- /dev/null +++ b/test/files/run/sd275-java/A.java @@ -0,0 +1,5 @@ +package sample; +public class A { + public void irrelevant(p1.p2.p3.DeleteMe arg) {} + public static class A_Inner {} +} diff --git a/test/files/run/sd275-java/DeleteMe.java b/test/files/run/sd275-java/DeleteMe.java new file mode 100644 index 0000000000..ccff2951d0 --- /dev/null +++ b/test/files/run/sd275-java/DeleteMe.java @@ -0,0 +1,4 @@ +package p1.p2.p3; + +public class DeleteMe {} + diff --git a/test/files/run/sd275-java/LeaveMe.java b/test/files/run/sd275-java/LeaveMe.java new file mode 100644 index 0000000000..cb58f0080f --- /dev/null +++ b/test/files/run/sd275-java/LeaveMe.java @@ -0,0 +1,3 @@ +package p1; + +public class LeaveMe {} diff --git a/test/files/run/sd275-java/Test.scala b/test/files/run/sd275-java/Test.scala new file mode 100644 index 0000000000..84187527d2 --- /dev/null +++ b/test/files/run/sd275-java/Test.scala @@ -0,0 +1,39 @@ +import scala.tools.partest._ +import java.io.File + +object Test extends StoreReporterDirectTest { + def code = ??? + + def compileCode(code: String) = { + val classpath = List(sys.props("partest.lib"), testOutput.path) mkString sys.props("path.separator") + compileString(newCompiler("-cp", classpath, "-d", testOutput.path))(code) + } + + def show(): Unit = { + deletePackage("p1/p2/p3") + deletePackage("p1/p2") + + compileCode(""" +package sample + +class Test { + final class Inner extends A.A_Inner { + def foo = 42 + } + + def test = new Inner().foo +} + """) + assert(storeReporter.infos.isEmpty, storeReporter.infos.mkString("\n")) + } + + def deletePackage(name: String) { + val directory = new File(testOutput.path, name) + for (f <- directory.listFiles()) { + assert(f.getName.endsWith(".class")) + assert(f.delete()) + } + assert(directory.listFiles().isEmpty) + assert(directory.delete()) + } +} diff --git a/test/files/run/sd275.scala b/test/files/run/sd275.scala new file mode 100644 index 0000000000..8cdee3ae15 --- /dev/null +++ b/test/files/run/sd275.scala @@ -0,0 +1,60 @@ +import scala.tools.partest._ +import java.io.File + +object Test extends StoreReporterDirectTest { + def code = ??? + + def compileCode(code: String) = { + val classpath = List(sys.props("partest.lib"), testOutput.path) mkString sys.props("path.separator") + compileString(newCompiler("-cp", classpath, "-d", testOutput.path))(code) + } + + def show(): Unit = { + compileCode(""" +package sample { + + class A1 { + def irrelevant: p1.p2.p3.DeleteMe = null + } + object A1 { + class A1_Inner + } +} + +package p1 { + class LeaveMe + package p2 { + package p3 { + class DeleteMe + } + } +} + """) + assert(filteredInfos.isEmpty, filteredInfos) + deletePackage("p1/p2/p3") + deletePackage("p1/p2") + + compileCode(""" +package sample + +class Test { + final class Inner extends A1.A1_Inner { + def foo = 42 + } + + def test = new Inner().foo +} + """) + assert(storeReporter.infos.isEmpty, storeReporter.infos.mkString("\n")) // Included a MissingRequirementError before. + } + + def deletePackage(name: String) { + val directory = new File(testOutput.path, name) + for (f <- directory.listFiles()) { + assert(f.getName.endsWith(".class")) + assert(f.delete()) + } + assert(directory.listFiles().isEmpty) + assert(directory.delete()) + } +} diff --git a/test/files/run/t10009.scala b/test/files/run/t10009.scala new file mode 100644 index 0000000000..2a318752f1 --- /dev/null +++ b/test/files/run/t10009.scala @@ -0,0 +1,28 @@ +import scala.reflect.runtime.currentMirror +import scala.reflect.runtime.universe._ +import scala.tools.reflect.ToolBox + +object Test { + def test(code: String, log: Boolean = false) { + val tb = currentMirror.mkToolBox() + val tree = tb.parse(code) + val typed = tb.typecheck(tree) + if (log) { + println("=" * 80) + println(typed) + } + val untyped = tb.untypecheck(typed) + if (log) println(untyped) + val retyped = tb.typecheck(untyped) + if (log) println(retyped) + } + def main(args: Array[String]): Unit = { + test("{ class a { val x = 42 }; new a }") // failed + test("{ trait a { val x = 42 }; new a {} }") // worked + test("{ abstract class a { val x: Int } }") // worked + test("{ abstract class a { val x: Int }; new a { val x = 42 } }") // failed + test("{ class a { private val x = 42 }; new a }") // failed + test("{ class a { protected val x = 42 }; new a { x } }") // failed + test("{ class a { protected[a] val x = 42 }; new a }") // failed + } +}
\ No newline at end of file diff --git a/test/files/run/t7747-repl.check b/test/files/run/t7747-repl.check index 621a70205e..ab37da5722 100644 --- a/test/files/run/t7747-repl.check +++ b/test/files/run/t7747-repl.check @@ -246,12 +246,12 @@ scala> case class Bingo() defined class Bingo scala> List(BippyBups(), PuppyPups(), Bingo()) // show -class $read extends _root_.java.io.Serializable { +sealed class $read extends _root_.java.io.Serializable { def <init>() = { super.<init>; () }; - class $iw extends _root_.java.io.Serializable { + sealed class $iw extends _root_.java.io.Serializable { def <init>() = { super.<init>; () @@ -262,7 +262,7 @@ class $read extends _root_.java.io.Serializable { import $line45.$read.INSTANCE.$iw.$iw.PuppyPups; import $line46.$read.INSTANCE.$iw.$iw.Bingo; import $line46.$read.INSTANCE.$iw.$iw.Bingo; - class $iw extends _root_.java.io.Serializable { + sealed class $iw extends _root_.java.io.Serializable { def <init>() = { super.<init>; () diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala index 80fbba133e..a74e73afc9 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -72,7 +72,7 @@ class CallGraphTest extends BytecodeTesting { | @noinline def f5 = try { 0 } catch { case _: Throwable => 1 } | @noinline final def f6 = try { 0 } catch { case _: Throwable => 1 } | - | @inline @noinline def f7 = try { 0 } catch { case _: Throwable => 1 } + | @inline @noinline def f7 = try { 0 } catch { case _: Throwable => 1 } // no warning, @noinline takes precedence |} |class D extends C { | @inline override def f1 = try { 0 } catch { case _: Throwable => 1 } @@ -91,18 +91,17 @@ class CallGraphTest extends BytecodeTesting { // The callGraph.callsites map is indexed by instructions of those ClassNodes. val ok = Set( - "D::f1()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // only one warning for D.f1: C.f1 is not annotated @inline - "C::f3()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // only one warning for C.f3: D.f3 does not have @inline (and it would also be safe to inline) - "C::f7()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // two warnings (the error message mentions C.f7 even if the receiver type is D, because f7 is inherited from C) - "operand stack at the callsite in Test::t1(LC;)I contains more values", - "operand stack at the callsite in Test::t2(LD;)I contains more values") + "D::f1()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.", // only one warning for D.f1: C.f1 is not annotated @inline + "C::f3()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.", // only one warning for C.f3: D.f3 does not have @inline (and it would also be safe to inline) + "C::f4()I is annotated @inline but could not be inlined:\nThe operand stack at the callsite in Test::t1(LC;)I contains more values", + "C::f4()I is annotated @inline but could not be inlined:\nThe operand stack at the callsite in Test::t2(LD;)I contains more values") var msgCount = 0 val checkMsg = (m: StoreReporter#Info) => { msgCount += 1 ok exists (m.msg contains _) } val List(cCls, cMod, dCls, testCls) = compile(code, checkMsg) - assert(msgCount == 6, msgCount) + assert(msgCount == 4, msgCount) val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = getAsmMethods(cCls, _.startsWith("f")) val List(df1, df3) = getAsmMethods(dCls, _.startsWith("f")) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala index 95b47f7d04..b1aa27fd27 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala @@ -35,9 +35,9 @@ class InlineWarningTest extends BytecodeTesting { """.stripMargin var count = 0 val warns = Set( - "C::m1()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", - "T::m2()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", - "D::m2()I is annotated @inline but cannot be inlined: the method is not final and may be overridden") + "C::m1()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.", + "T::m2()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.", + "D::m2()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.") compileToBytes(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)}) assert(count == 4, count) } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala index 3e0b889e9c..bf9da0f48f 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala @@ -20,7 +20,7 @@ class InlinerIllegalAccessTest extends BytecodeTesting { import global.genBCode.bTypes._ def addToRepo(cls: List[ClassNode]): Unit = for (c <- cls) byteCodeRepository.add(c, None) - def assertEmpty(ins: Option[AbstractInsnNode]) = for (i <- ins) + def assertEmpty(ins: List[AbstractInsnNode]) = for (i <- ins) throw new AssertionError(textify(i)) @Test @@ -28,7 +28,7 @@ class InlinerIllegalAccessTest extends BytecodeTesting { val code = """package a { | private class C { // the Scala compiler makes all classes public - | def f1 = new C // NEW a/C + | def f1 = new C // NEW a/C, INVOKESPECIAL a/C.<init> ()V | def f2 = new Array[C](0) // ANEWARRAY a/C | def f3 = new Array[Array[C]](0) // ANEWARRAY [La/C; | } @@ -46,9 +46,9 @@ class InlinerIllegalAccessTest extends BytecodeTesting { val methods = cClass.methods.asScala.filter(_.name(0) == 'f').toList - def check(classNode: ClassNode, test: Option[AbstractInsnNode] => Unit) = { + def check(classNode: ClassNode, test: List[AbstractInsnNode] => Unit) = { for (m <- methods) - test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(cClass.name), classBTypeFromParsedClassfile(classNode.name)).map(_._1)) + test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(cClass.name), classBTypeFromParsedClassfile(classNode.name)).right.get) } check(cClass, assertEmpty) @@ -65,7 +65,11 @@ class InlinerIllegalAccessTest extends BytecodeTesting { check(cClass, assertEmpty) check(dClass, assertEmpty) // accessing a private class in the same package is OK check(eClass, { - case Some(ti: TypeInsnNode) if Set("a/C", "[La/C;")(ti.desc) => () + case (ti: TypeInsnNode) :: is if Set("a/C", "[La/C;")(ti.desc) => + is match { + case List(mi: MethodInsnNode) => assert(mi.owner == "a/C" && mi.name == "<init>") + case Nil => + } // MatchError otherwise }) } @@ -141,12 +145,12 @@ class InlinerIllegalAccessTest extends BytecodeTesting { val List(rbD, rcD, rfD, rgD) = dCl.methods.asScala.toList.filter(_.name(0) == 'r').sortBy(_.name) - def check(method: MethodNode, decl: ClassNode, dest: ClassNode, test: Option[AbstractInsnNode] => Unit): Unit = { - test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(decl.name), classBTypeFromParsedClassfile(dest.name)).map(_._1)) + def check(method: MethodNode, decl: ClassNode, dest: ClassNode, test: List[AbstractInsnNode] => Unit): Unit = { + test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(decl.name), classBTypeFromParsedClassfile(dest.name)).right.get) } - val cOrDOwner = (_: Option[AbstractInsnNode] @unchecked) match { - case Some(mi: MethodInsnNode) if Set("a/C", "a/D")(mi.owner) => () + val cOrDOwner = (_: List[AbstractInsnNode] @unchecked) match { + case List(mi: MethodInsnNode) if Set("a/C", "a/D")(mi.owner) => () // MatchError otherwise } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala index 5362585642..9b1609a130 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala @@ -31,7 +31,7 @@ class InlinerSeparateCompilationTest { |} """.stripMargin - val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + val warn = "T::f()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden." val List(c, o, oMod, t) = compileClassesSeparately(List(codeA, codeB), args + " -opt-warnings", _.msg contains warn) assertInvoke(getMethod(c, "t1"), "T", "f") assertNoInvoke(getMethod(c, "t2")) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala index a844c20a7f..7be88816d5 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -67,7 +67,7 @@ class InlinerTest extends BytecodeTesting { def canInlineTest(code: String, mod: ClassNode => Unit = _ => ()): Option[OptimizerWarning] = { val cs = gMethAndFCallsite(code, mod)._2 - inliner.earlyCanInlineCheck(cs) orElse inliner.canInlineBody(cs) + inliner.earlyCanInlineCheck(cs) orElse inliner.canInlineCallsite(cs).map(_._1) } def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): MethodNode = { @@ -199,8 +199,8 @@ class InlinerTest extends BytecodeTesting { val List(c, d) = compile(code) val hMeth = getAsmMethod(d, "h") val gCall = getCallsite(hMeth, "g") - val r = inliner.canInlineBody(gCall) - assert(r.nonEmpty && r.get.isInstanceOf[IllegalAccessInstruction], r) + val r = inliner.canInlineCallsite(gCall) + assert(r.nonEmpty && r.get._1.isInstanceOf[IllegalAccessInstruction], r) } @Test @@ -340,7 +340,7 @@ class InlinerTest extends BytecodeTesting { val fMeth = getAsmMethod(c, "f") val call = getCallsite(fMeth, "lowestOneBit") - val warning = inliner.canInlineBody(call) + val warning = inliner.canInlineCallsite(call) assert(warning.isEmpty, warning) inliner.inline(InlineRequest(call, Nil, null)) @@ -475,7 +475,7 @@ class InlinerTest extends BytecodeTesting { | def t2 = this.f |} """.stripMargin - val warn = "::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + val warn = "::f()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden." var count = 0 val List(c, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn}) assert(count == 2, count) @@ -513,7 +513,7 @@ class InlinerTest extends BytecodeTesting { | def t3(t: T) = t.f // no inlining here |} """.stripMargin - val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + val warn = "T::f()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden." var count = 0 val List(c, oMirror, oModule, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn}) assert(count == 1, count) @@ -617,7 +617,7 @@ class InlinerTest extends BytecodeTesting { |} """.stripMargin - val warning = "T1::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + val warning = "T1::f()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden." var count = 0 val List(ca, cb, t1, t2a, t2b) = compile(code, allowMessage = i => {count += 1; i.msg contains warning}) assert(count == 4, count) // see comments, f is not inlined 4 times @@ -698,7 +698,7 @@ class InlinerTest extends BytecodeTesting { | def t1(c: C) = c.foo |} """.stripMargin - val warn = "C::foo()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + val warn = "C::foo()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden." var c = 0 compile(code, allowMessage = i => {c += 1; i.msg contains warn}) assert(c == 1, c) @@ -762,7 +762,7 @@ class InlinerTest extends BytecodeTesting { |} """.stripMargin - val List(c, t, u) = compile(code, allowMessage = _.msg contains "i()I is annotated @inline but cannot be inlined") + val List(c, t, u) = compile(code, allowMessage = _.msg contains "::i()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.") val m1 = getMethod(c, "m1") assertInvoke(m1, "T", "a") assertInvoke(m1, "T", "b") @@ -969,7 +969,7 @@ class InlinerTest extends BytecodeTesting { val gCall = getCallsite(hMeth, "g") val hCall = getCallsite(iMeth, "h") - val warning = inliner.canInlineBody(gCall) + val warning = inliner.canInlineCallsite(gCall) assert(warning.isEmpty, warning) inliner.inline(InlineRequest(hCall, @@ -1053,7 +1053,7 @@ class InlinerTest extends BytecodeTesting { | def t1 = f1(1) // inlined | def t2 = f2(1) // not inlined | def t3 = f1(1): @noinline // not inlined - | def t4 = f2(1): @inline // not inlined (cannot override the def-site @noinline) + | def t4 = f2(1): @inline // inlined | def t5 = f3(1): @inline // inlined | def t6 = f3(1): @noinline // not inlined | @@ -1067,7 +1067,7 @@ class InlinerTest extends BytecodeTesting { assertNoInvoke(getMethod(c, "t1")) assertInvoke(getMethod(c, "t2"), "C", "f2") assertInvoke(getMethod(c, "t3"), "C", "f1") - assertInvoke(getMethod(c, "t4"), "C", "f2") + assertNoInvoke(getMethod(c, "t4")) assertNoInvoke(getMethod(c, "t5")) assertInvoke(getMethod(c, "t6"), "C", "f3") assertNoInvoke(getMethod(c, "t7")) @@ -1469,8 +1469,8 @@ class InlinerTest extends BytecodeTesting { |class C extends T1 with T2 """.stripMargin val List(c, t1, t2) = compile(code, allowMessage = _ => true) - // the forwarder C.f is inlined, so there's no invocation - assertSameSummary(getMethod(c, "f"), List(ICONST_1, IRETURN)) + // we never inline into mixin forwarders, see scala-dev#259 + assertInvoke(getMethod(c, "f"), "T2", "f$") } @Test @@ -1622,4 +1622,135 @@ class InlinerTest extends BytecodeTesting { ("oneLastMethodWithVeryVeryLongNam_yetAnotherMethodWithVeryVeryLong_oneMoreMethodWithVeryVeryLongNam_anotherMethodWithVeryVeryLongNam_param",10), ("oneLastMethodWithVeryVery_yetAnotherMethodWithVeryV_oneMoreMethodWithVeryVery_anotherMethodWithVeryVery_methodWithVeryVeryLongNam_param",11))) } + + @Test + def sd259(): Unit = { + // - trait methods are not inlined into their static super accessors, and also not into mixin forwarders. + // - inlining an invocation of a mixin forwarder also inlines the static accessor and the trait method body. + val code = + """trait T { + | def m1a = 1 + | final def m1b = 1 + | + | @inline def m2a = 2 + | @inline final def m2b = 2 + | + | def m3a(f: Int => Int) = f(1) + | final def m3b(f: Int => Int) = f(1) + |} + |final class A extends T + |class C { + | def t1(t: T) = t.m1a + | def t2(t: T) = t.m1b + | def t3(t: T) = t.m2a + | def t4(t: T) = t.m2b + | def t5(t: T) = t.m3a(x => x) + | def t6(t: T) = t.m3b(x => x) + | + | def t7(a: A) = a.m1a + | def t8(a: A) = a.m1b + | def t9(a: A) = a.m2a + | def t10(a: A) = a.m2b + | def t11(a: A) = a.m3a(x => x) + | def t12(a: A) = a.m3b(x => x) + |} + """.stripMargin + val warn = "T::m2a()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden." + var count = 0 + val List(a, c, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn}) + assert(count == 1) + + assertInvoke(getMethod(t, "m1a$"), "T", "m1a") + assertInvoke(getMethod(t, "m1b$"), "T", "m1b") + assertInvoke(getMethod(t, "m2a$"), "T", "m2a") + assertInvoke(getMethod(t, "m2b$"), "T", "m2b") + assertInvoke(getMethod(t, "m3a$"), "T", "m3a") + assertInvoke(getMethod(t, "m3b$"), "T", "m3b") + + assertInvoke(getMethod(a, "m1a"), "T", "m1a$") + assertInvoke(getMethod(a, "m1b"), "T", "m1b$") + assertInvoke(getMethod(a, "m2a"), "T", "m2a$") + assertInvoke(getMethod(a, "m2b"), "T", "m2b$") + assertInvoke(getMethod(a, "m3a"), "T", "m3a$") + assertInvoke(getMethod(a, "m3b"), "T", "m3b$") + + assertInvoke(getMethod(c, "t1"), "T", "m1a") + assertInvoke(getMethod(c, "t2"), "T", "m1b") + + assertInvoke(getMethod(c, "t3"), "T", "m2a") // could not inline + assertNoInvoke(getMethod(c, "t4")) + + assertInvoke(getMethod(c, "t5"), "T", "m3a") // could not inline + assertInvoke(getMethod(c, "t6"), "C", "$anonfun$t6$1") // both forwarders inlined, closure eliminated + + assertInvoke(getMethod(c, "t7"), "A", "m1a") + assertInvoke(getMethod(c, "t8"), "A", "m1b") + + assertNoInvoke(getMethod(c, "t9")) + assertNoInvoke(getMethod(c, "t10")) + + assertInvoke(getMethod(c, "t11"), "C", "$anonfun$t11$1") // both forwarders inlined, closure eliminated + assertInvoke(getMethod(c, "t12"), "C", "$anonfun$t12$1") // both forwarders inlined, closure eliminated + } + + @Test + def sd259b(): Unit = { + val code = + """trait T { + | def get = 1 + | @inline final def m = try { get } catch { case _: Throwable => 1 } + |} + |class A extends T + |class C { + | def t(a: A) = 1 + a.m // cannot inline a try block onto a non-empty stack + |} + """.stripMargin + val warn = + """T::m()I is annotated @inline but could not be inlined: + |The operand stack at the callsite in C::t(LA;)I contains more values than the + |arguments expected by the callee T::m()I. These values would be discarded + |when entering an exception handler declared in the inlined method.""".stripMargin + val List(a, c, t) = compile(code, allowMessage = _.msg contains warn) + + // inlinig of m$ is rolled back, because <invokespecial T.m> is not legal in class C. + assertInvoke(getMethod(c, "t"), "T", "m$") + } + + @Test + def sd259c(): Unit = { + val code = + """trait T { + | def bar = 1 + | @inline final def m = { + | def impl = bar // private, non-static method + | impl + | } + |} + |class A extends T + |class C { + | def t(a: A) = a.m + |} + """.stripMargin + val warn = + """T::m()I is annotated @inline but could not be inlined: + |The callee T::m()I contains the instruction INVOKESPECIAL T.impl$1 ()I + |that would cause an IllegalAccessError when inlined into class C.""".stripMargin + val List(a, c, t) = compile(code, allowMessage = _.msg contains warn) + assertInvoke(getMethod(c, "t"), "T", "m$") + } + + @Test + def sd259d(): Unit = { + val code = + """trait T { + | @inline final def m = 1 + |} + |class C extends T { + | def t = super.m // inline call to T.m$ here, we're not in the mixin forwarder C.m + |} + """.stripMargin + val List(c, t) = compileClasses(code) + assertNoInvoke(getMethod(c, "t")) + assertInvoke(getMethod(c, "m"), "T", "m$") + } } diff --git a/test/junit/scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala b/test/junit/scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala new file mode 100644 index 0000000000..234f575b79 --- /dev/null +++ b/test/junit/scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.classpath + +import org.junit.Assert._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +import scala.reflect.io.VirtualDirectory + + +@RunWith(classOf[JUnit4]) +class VirtualDirectoryClassPathTest { + + @Test + def virtualDirectoryClassPath_findClassFile(): Unit = { + val base = new VirtualDirectory("base", None) + val p1 = base subdirectoryNamed "p1" + val p1_Test_class = p1.fileNamed("Test.class") + val p2 = base subdirectoryNamed "p2" + val p3 = p2 subdirectoryNamed "p3" + val p4 = p3 subdirectoryNamed "p4" + val p4_Test1_class = p4.fileNamed("Test.class") + val classPath = VirtualDirectoryClassPath(base) + + assertEquals(Some(p1_Test_class), classPath.findClassFile("p1/Test")) + + assertEquals(None, classPath.findClassFile("p1/DoesNotExist")) + assertEquals(None, classPath.findClassFile("DoesNotExist")) + assertEquals(None, classPath.findClassFile("p2")) + assertEquals(None, classPath.findClassFile("p2/DoesNotExist")) + assertEquals(None, classPath.findClassFile("p4/DoesNotExist")) + + assertEquals(List("p1", "p2"), classPath.packages("").toList.map(_.name).sorted) + assertEquals(List(), classPath.packages("p1").toList.map(_.name).sorted) + assertEquals(List("p2.p3"), classPath.packages("p2").toList.map(_.name).sorted) + assertEquals(List("p2.p3.p4"), classPath.packages("p2.p3").toList.map(_.name).sorted) + } +} diff --git a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala index 78ebb7cf9c..a216b319a8 100644 --- a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala +++ b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala @@ -1,10 +1,11 @@ package scala.tools.nsc.interpreter -import java.io.{StringWriter, PrintWriter} +import java.io.{PrintWriter, StringWriter} import org.junit.Assert.assertEquals import org.junit.Test +import scala.reflect.internal.util.BatchSourceFile import scala.tools.nsc.Settings class CompletionTest { @@ -174,6 +175,24 @@ class CompletionTest { checkExact(completer, "case class D(a: Int, b: Int) { this.a")("a", "asInstanceOf") } + @Test + def replGeneratedCodeDeepPackages(): Unit = { + val intp = newIMain() + val completer = new PresentationCompilerCompleter(intp) + intp.compileSources(new BatchSourceFile("<paste>", "package p1.p2.p3; object Ping { object Pong }")) + checkExact(completer, "p1.p2.p")("p3") + checkExact(completer, "p1.p2.p3.P")("Ping") + checkExact(completer, "p1.p2.p3.Ping.Po")("Pong") + } + + @Test + def performanceOfLenientMatch(): Unit = { + val intp = newIMain() + val completer = new PresentationCompilerCompleter(intp) + val ident: String = "thisIsAReallyLongMethodNameWithManyManyManyManyChunks" + checkExact(completer, s"($ident: Int) => tia")(ident) + } + def checkExact(completer: PresentationCompilerCompleter, before: String, after: String = "")(expected: String*): Unit = { assertEquals(expected.toSet, completer.complete(before, after).candidates.toSet) } |