diff options
64 files changed, 1036 insertions, 432 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 a7880c72d7..e58d2d3b43 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/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 25475515aa..92accaf9dd 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -1278,5 +1278,4 @@ abstract class Erasure extends InfoTransform } private class TypeRefAttachment(val tpe: TypeRef) - class TypeParamVarargsAttachment(val typeParamRef: Type) } 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/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index c171050bbd..84f47c1caa 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -1049,7 +1049,8 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } } debuglog(s"specialized overload $om for ${overriding.name.decode} in ${pp(env)}: ${om.info}") - if (overriding.isAbstractOverride) om.setFlag(ABSOVERRIDE) + om.setFlag(overriding.flags & (ABSOVERRIDE | SYNCHRONIZED)) + om.withAnnotations(overriding.annotations.filter(_.symbol == ScalaStrictFPAttr)) typeEnv(om) = env addConcreteSpecMethod(overriding) if (overriding.isDeferred) { // abstract override diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index d8fa7b58e8..ea3c7da014 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -27,6 +27,8 @@ import scala.reflect.internal.util.ListOfNil * - for every repeated Scala parameter `x: T*' --> x: Seq[T]. * - for every repeated Java parameter `x: T...' --> x: Array[T], except: * if T is an unbounded abstract type, replace --> x: Array[Object] + * - for every method defining repeated parameters annotated with @varargs, generate + * a synthetic Java-style vararg method * - for every argument list that corresponds to a repeated Scala parameter * (a_1, ..., a_n) => (Seq(a_1, ..., a_n)) * - for every argument list that corresponds to a repeated Java parameter @@ -44,6 +46,8 @@ import scala.reflect.internal.util.ListOfNil * def liftedTry$1 = try { x_i } catch { .. } * meth(x_1, .., liftedTry$1(), .. ) * } + * - remove calls to elidable methods and replace their bodies with NOPs when elide-below + * requires it */ /*</export> */ abstract class UnCurry extends InfoTransform @@ -577,7 +581,13 @@ abstract class UnCurry extends InfoTransform case None => literalRhsIfConst } ) - addJavaVarargsForwarders(dd, flatdd) + // Only class members can reasonably be called from Java due to name mangling. + // Additionally, the Uncurry info transformer only adds a forwarder symbol to class members, + // since the other symbols are not part of the ClassInfoType (see reflect.internal.transform.UnCurry) + if (dd.symbol.owner.isClass) + addJavaVarargsForwarders(dd, flatdd) + else + flatdd case tree: Try => if (tree.catches exists (cd => !treeInfo.isCatchCase(cd))) @@ -739,68 +749,32 @@ abstract class UnCurry extends InfoTransform if (!hasRepeated) reporter.error(dd.symbol.pos, "A method without repeated parameters cannot be annotated with the `varargs` annotation.") } - /* Called during post transform, after the method argument lists have been flattened. - * It looks for the method in the `repeatedParams` map, and generates a Java-style + /** + * Called during post transform, after the method argument lists have been flattened. + * It looks for the forwarder symbol in the symbol attachments and generates a Java-style * varargs forwarder. + * + * @note The Java-style varargs method symbol is generated in the Uncurry info transformer. If the + * symbol can't be found this method reports a warning and carries on. + * @see [[scala.reflect.internal.transform.UnCurry]] */ private def addJavaVarargsForwarders(dd: DefDef, flatdd: DefDef): DefDef = { if (!dd.symbol.hasAnnotation(VarargsClass) || !enteringUncurry(mexists(dd.symbol.paramss)(sym => definitions.isRepeatedParamType(sym.tpe)))) return flatdd - val forwSym = currentClass.newMethod(dd.name.toTermName, dd.pos, VARARGS | SYNTHETIC | flatdd.symbol.flags & ~DEFERRED) - - val isRepeated = enteringUncurry(dd.symbol.info.paramss.flatten.map(sym => definitions.isRepeatedParamType(sym.tpe))) - - val oldPs = flatdd.symbol.paramss.head - - // see comment in method toArrayType below - val arrayTypesMappedToObject = mutable.Map.empty[Symbol, Type] - - val forwTpe = { - val (oldTps, tps) = dd.symbol.tpe match { - case PolyType(oldTps, _) => - val newTps = oldTps.map(_.cloneSymbol(forwSym)) - (oldTps, newTps) - - case _ => (Nil, Nil) - } - - def toArrayType(tp: Type, newParam: Symbol): Type = { - val arg = elementType(SeqClass, tp) - val elem = if (arg.typeSymbol.isTypeParameterOrSkolem && !(arg <:< AnyRefTpe)) { - // To prevent generation of an `Object` parameter from `Array[T]` parameter later - // as this would crash the Java compiler which expects an `Object[]` array for varargs - // e.g. def foo[T](a: Int, b: T*) - // becomes def foo[T](a: Int, b: Array[Object]) - // instead of def foo[T](a: Int, b: Array[T]) ===> def foo[T](a: Int, b: Object) - // - // In order for the forwarder method to type check we need to insert a cast: - // def foo'[T'](a: Int, b: Array[Object]) = foo[T'](a, wrapRefArray(b).asInstanceOf[Seq[T']]) - // The target element type for that cast (T') is stored in the `arrayTypesMappedToObject` map. - val originalArg = arg.substSym(oldTps, tps) - arrayTypesMappedToObject(newParam) = originalArg - // Store the type parameter that was replaced by Object to emit the correct generic signature - newParam.updateAttachment(new erasure.TypeParamVarargsAttachment(originalArg)) - ObjectTpe - } else - arg - arrayType(elem) + val forwSym: Symbol = { + currentClass.info // make sure the info is up to date, so the varargs forwarder symbol has been generated + flatdd.symbol.attachments.get[VarargsSymbolAttachment] match { + case Some(VarargsSymbolAttachment(sym)) => sym + case None => + reporter.warning(dd.pos, s"Could not generate Java varargs forwarder for ${flatdd.symbol}. Please file a bug.") + return flatdd } - - val ps = map2(oldPs, isRepeated)((oldParam, isRep) => { - val newParam = oldParam.cloneSymbol(forwSym) - val tp = if (isRep) toArrayType(oldParam.tpe, newParam) else oldParam.tpe - newParam.setInfo(tp) - }) - - val resTp = dd.symbol.tpe_*.finalResultType.substSym(oldPs, ps) - val mt = MethodType(ps, resTp) - val r = if (tps.isEmpty) mt else PolyType(tps, mt) - r.substSym(oldTps, tps) } - forwSym.setInfo(forwTpe) - val newPs = forwTpe.params + val newPs = forwSym.tpe.params + val isRepeated = enteringUncurry(dd.symbol.info.paramss.flatten.map(sym => definitions.isRepeatedParamType(sym.tpe))) + val oldPs = flatdd.symbol.paramss.head val theTyper = typer.atOwner(dd, currentClass) val forwTree = theTyper.typedPos(dd.pos) { @@ -809,8 +783,8 @@ abstract class UnCurry extends InfoTransform else { val parTp = elementType(ArrayClass, param.tpe) val wrap = gen.mkWrapArray(Ident(param), parTp) - arrayTypesMappedToObject.get(param) match { - case Some(tp) => gen.mkCast(wrap, seqType(tp)) + param.attachments.get[TypeParamVarargsAttachment] match { + case Some(TypeParamVarargsAttachment(tp)) => gen.mkCast(wrap, seqType(tp)) case _ => wrap } } @@ -821,13 +795,12 @@ abstract class UnCurry extends InfoTransform } // check if the method with that name and those arguments already exists in the template - currentClass.info.member(forwSym.name).alternatives.find(s => s != forwSym && s.tpe.matches(forwSym.tpe)) match { - case Some(s) => reporter.error(dd.symbol.pos, - "A method with a varargs annotation produces a forwarder method with the same signature " - + s.tpe + " as an existing method.") + enteringUncurry(currentClass.info.member(forwSym.name).alternatives.find(s => s != forwSym && s.tpe.matches(forwSym.tpe))) match { + case Some(s) => + reporter.error(dd.symbol.pos, + s"A method with a varargs annotation produces a forwarder method with the same signature ${s.tpe} as an existing method.") case None => // enter symbol into scope - currentClass.info.decls enter forwSym addNewMember(forwTree) } 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/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index c73ea54c3d..8f0625e58c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -60,7 +60,7 @@ trait Contexts { self: Analyzer => private lazy val allImportInfos = mutable.Map[CompilationUnit, List[ImportInfo]]() withDefaultValue Nil - def warnUnusedImports(unit: CompilationUnit) = { + def warnUnusedImports(unit: CompilationUnit) = if (!unit.isJava) { for (imps <- allImportInfos.remove(unit)) { for (imp <- imps.reverse.distinct) { val used = allUsedSelectors(imp) 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 cca6f280e3..78533bdfc5 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/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/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala index 78f360409d..fd8f51cfb1 100644 --- a/src/reflect/scala/reflect/internal/StdAttachments.scala +++ b/src/reflect/scala/reflect/internal/StdAttachments.scala @@ -78,4 +78,7 @@ trait StdAttachments { case object OuterArgCanBeElided extends PlainAttachment case object UseInvokeSpecial extends PlainAttachment + + /** An attachment carrying information between uncurry and erasure */ + case class TypeParamVarargsAttachment(val typeParamRef: Type) } diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 56b6dc078d..5077169525 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 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 f8679616d1..5e1bf37b80 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -4487,6 +4487,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 @@ -4508,8 +4509,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/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/transform/UnCurry.scala b/src/reflect/scala/reflect/internal/transform/UnCurry.scala index a50084f40d..3918723b5c 100644 --- a/src/reflect/scala/reflect/internal/transform/UnCurry.scala +++ b/src/reflect/scala/reflect/internal/transform/UnCurry.scala @@ -4,6 +4,7 @@ package internal package transform import Flags._ +import scala.collection.mutable trait UnCurry { @@ -11,6 +12,12 @@ trait UnCurry { import global._ import definitions._ + /** + * The synthetic Java vararg method symbol corresponding to a Scala vararg method + * annotated with @varargs. + */ + case class VarargsSymbolAttachment(varargMethod: Symbol) + /** Note: changing tp.normalize to tp.dealias in this method leads to a single * test failure: run/t5688.scala, where instead of the expected output * Vector(ta, tb, tab) @@ -65,18 +72,74 @@ trait UnCurry { def apply(tp0: Type): Type = { val tp = expandAlias(tp0) tp match { - case ClassInfoType(parents, decls, clazz) => + case ClassInfoType(parents, decls, clazz) if !clazz.isJavaDefined => val parents1 = parents mapConserve uncurry - if (parents1 eq parents) tp - else ClassInfoType(parents1, decls, clazz) // @MAT normalize in decls?? + val varargOverloads = mutable.ListBuffer.empty[Symbol] + + // Not using `hasAnnotation` here because of dreaded cyclic reference errors: + // it may happen that VarargsClass has not been initialized yet and we get here + // while processing one of its superclasses (such as java.lang.Object). Since we + // don't need the more precise `matches` semantics, we only check the symbol, which + // is anyway faster and safer + for (decl <- decls if decl.annotations.exists(_.symbol == VarargsClass)) { + if (mexists(decl.paramss)(sym => definitions.isRepeatedParamType(sym.tpe))) { + varargOverloads += varargForwarderSym(clazz, decl, exitingPhase(phase)(decl.info)) + } + } + if ((parents1 eq parents) && varargOverloads.isEmpty) tp + else { + val newDecls = decls.cloneScope + varargOverloads.foreach(newDecls.enter) + ClassInfoType(parents1, newDecls, clazz) + } // @MAT normalize in decls?? + case PolyType(_, _) => mapOver(tp) + case _ => tp } } } + private def varargForwarderSym(currentClass: Symbol, origSym: Symbol, newInfo: Type): Symbol = { + val forwSym = origSym.cloneSymbol(currentClass, VARARGS | SYNTHETIC | origSym.flags & ~DEFERRED, origSym.name.toTermName).withoutAnnotations + + // we are using `origSym.info`, which contains the type *before* the transformation + // so we still see repeated parameter types (uncurry replaces them with Seq) + val isRepeated = origSym.info.paramss.flatten.map(sym => definitions.isRepeatedParamType(sym.tpe)) + val oldPs = newInfo.paramss.head + def toArrayType(tp: Type, newParam: Symbol): Type = { + val arg = elementType(SeqClass, tp) + val elem = if (arg.typeSymbol.isTypeParameterOrSkolem && !(arg <:< AnyRefTpe)) { + // To prevent generation of an `Object` parameter from `Array[T]` parameter later + // as this would crash the Java compiler which expects an `Object[]` array for varargs + // e.g. def foo[T](a: Int, b: T*) + // becomes def foo[T](a: Int, b: Array[Object]) + // instead of def foo[T](a: Int, b: Array[T]) ===> def foo[T](a: Int, b: Object) + // + // In order for the forwarder method to type check we need to insert a cast: + // def foo'[T'](a: Int, b: Array[Object]) = foo[T'](a, wrapRefArray(b).asInstanceOf[Seq[T']]) + // The target element type for that cast (T') is stored in the TypeParamVarargsAttachment +// val originalArg = arg.substSym(oldTps, tps) + // Store the type parameter that was replaced by Object to emit the correct generic signature + newParam.updateAttachment(new TypeParamVarargsAttachment(arg)) + ObjectTpe + } else + arg + arrayType(elem) + } + + foreach2(forwSym.paramss.flatten, isRepeated)((p, isRep) => + if (isRep) { + p.setInfo(toArrayType(p.info, p)) + } + ) + + origSym.updateAttachment(VarargsSymbolAttachment(forwSym)) + forwSym + } + /** - return symbol's transformed type, * - if symbol is a def parameter with transformed type T, return () => T * 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/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index b74ccb9177..dbafbfc6ba 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -47,6 +47,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => this.InlineCallsiteAttachment this.OuterArgCanBeElided this.UseInvokeSpecial + this.TypeParamVarargsAttachment this.noPrint this.typeDebug this.Range @@ -458,6 +459,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => definitions.ScalaValueClassesNoUnit definitions.ScalaValueClasses + uncurry.VarargsSymbolAttachment uncurry.DesugaredParameterType erasure.GenericArray erasure.scalaErasure 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/files/jvm/varargs-separate-bytecode.check b/test/files/jvm/varargs-separate-bytecode.check new file mode 100644 index 0000000000..1507cd48c5 --- /dev/null +++ b/test/files/jvm/varargs-separate-bytecode.check @@ -0,0 +1 @@ +Found vararg overload for method create
\ No newline at end of file diff --git a/test/files/jvm/varargs-separate-bytecode/AbstractProps_1.scala b/test/files/jvm/varargs-separate-bytecode/AbstractProps_1.scala new file mode 100644 index 0000000000..5dfb8d1a9e --- /dev/null +++ b/test/files/jvm/varargs-separate-bytecode/AbstractProps_1.scala @@ -0,0 +1,8 @@ +package foo + +import scala.annotation.varargs + +trait AbstractProps { + @varargs + def create(x: String, y: Int*): AbstractProps = null +} diff --git a/test/files/jvm/varargs-separate-bytecode/Props_2.scala b/test/files/jvm/varargs-separate-bytecode/Props_2.scala new file mode 100644 index 0000000000..3fc09586fc --- /dev/null +++ b/test/files/jvm/varargs-separate-bytecode/Props_2.scala @@ -0,0 +1,3 @@ +import foo.AbstractProps + +class Props extends AbstractProps
\ No newline at end of file diff --git a/test/files/jvm/varargs-separate-bytecode/Test.scala b/test/files/jvm/varargs-separate-bytecode/Test.scala new file mode 100644 index 0000000000..a666de7f39 --- /dev/null +++ b/test/files/jvm/varargs-separate-bytecode/Test.scala @@ -0,0 +1,15 @@ +import scala.collection.JavaConverters._ +import scala.tools.asm +import scala.tools.asm.Opcodes +import scala.tools.partest.BytecodeTest + +object Test extends BytecodeTest { + def show: Unit = { + val classNode = loadClassNode("Props") + val methods = classNode.methods.iterator().asScala.filter( m => m.name == "create") + + for (m <- methods if (m.access & Opcodes.ACC_VARARGS) > 0) { + println(s"Found vararg overload for method ${m.name}") + } + } +} 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/reflection-mem-typecheck.scala b/test/files/run/reflection-mem-typecheck.scala deleted file mode 100644 index 93ec1c937a..0000000000 --- a/test/files/run/reflection-mem-typecheck.scala +++ /dev/null @@ -1,28 +0,0 @@ -import scala.tools.partest.MemoryTest - -trait A { type T <: A } -trait B { type T <: B } - -object Test extends MemoryTest { - lazy val tb = { - import scala.reflect.runtime.universe._ - import scala.reflect.runtime.{currentMirror => cm} - import scala.tools.reflect.ToolBox - cm.mkToolBox() - } - - // I'm not sure this is a great way to test for memory leaks, - // since we're also testing how good the JVM's GC is, and this is not easily reproduced between machines/over time - override def maxDelta = 12 - override def calcsPerIter = 8 - override def calc() { - var snippet = """ - trait A { type T <: A } - trait B { type T <: B } - def foo[T](x: List[T]) = x - foo(List(new A {}, new B {})) - """.trim - snippet = snippet + "\n" + (List.fill(50)(snippet.split("\n").last) mkString "\n") - tb.typecheck(tb.parse(snippet)) - } -}
\ No newline at end of file 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/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/t5125b.check b/test/files/run/t5125b.check index ddbf908f04..29b438a2d6 100644 --- a/test/files/run/t5125b.check +++ b/test/files/run/t5125b.check @@ -5,3 +5,6 @@ public void C2.f(scala.collection.Seq) public void C2$C3.f(java.lang.String[]) public void C2$C3.f(scala.collection.Seq) public void C4.f(scala.collection.Seq) +private void C5.f(int,int[]) +private void C5.f(int,scala.collection.Seq) +public void C5.f(scala.collection.Seq) diff --git a/test/files/run/t5125b.scala b/test/files/run/t5125b.scala index 149c49e213..60ab1d9792 100644 --- a/test/files/run/t5125b.scala +++ b/test/files/run/t5125b.scala @@ -23,6 +23,17 @@ class C4 { } } +class C5 { + def f(values: String*) = println("Calling C5.f(): " + values) + @scala.annotation.varargs + private def f(v: Int, values: Int*) = println("Calling C5.f(): " + values) + + def method(): Unit = { + @scala.annotation.varargs + def f(values: String*) = println("Calling C5.<locally>.f(): " + values) + } +} + object Test extends App { def check(c: Class[_]) { val methodName = "f" @@ -34,4 +45,5 @@ object Test extends App { check(classOf[C2]) check(classOf[C2#C3]) check(classOf[C4]) + check(classOf[C5]) } 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/files/run/t9814.scala b/test/files/run/t9814.scala new file mode 100644 index 0000000000..3aef3928f6 --- /dev/null +++ b/test/files/run/t9814.scala @@ -0,0 +1,28 @@ +import java.lang.reflect.Modifier + +import scala.annotation.strictfp + +class Foo extends (() => Unit) { + def apply(): Unit = synchronized { + // we're in a specialized subclass + assert(Thread.currentThread.getStackTrace.apply(1).getMethodName == "apply$mcV$sp") + assert(Thread.holdsLock(this)) + } +} + +class Bar extends (() => Unit) { + @strictfp def apply(): Unit = synchronized { + // we're in a specialized subclass + assert(Thread.currentThread.getStackTrace.apply(1).getMethodName == "apply$mcV$sp") + assert(Thread.holdsLock(this)) + } +} + +object Test { + def main(args: Array[String]): Unit = { + new Foo().apply() + + val m = classOf[Bar].getDeclaredMethod("apply$mcV$sp") + assert(Modifier.isStrict(m.getModifiers)) + } +} 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) } |