diff options
69 files changed, 1186 insertions, 283 deletions
diff --git a/.travis.yml b/.travis.yml index e90fc35267..6a7ac45e3d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ # based on http://www.paperplanes.de/2013/8/13/deploying-your-jekyll-blog-to-s3-with-travis-ci.html language: ruby rvm: - - 1.9.3 + - 2.2 script: bundle exec jekyll build -s spec/ -d build/spec install: bundle install @@ -1,7 +1,7 @@ # To build the spec on Travis CI source "https://rubygems.org" -gem "jekyll", "2.0.0.alpha.2" +gem "jekyll", "2.5.3" gem "rouge" # gem 's3_website' # gem 'redcarpet' diff --git a/src/compiler/scala/tools/nsc/Reporting.scala b/src/compiler/scala/tools/nsc/Reporting.scala index 72a4b69536..4e7a527a5a 100644 --- a/src/compiler/scala/tools/nsc/Reporting.scala +++ b/src/compiler/scala/tools/nsc/Reporting.scala @@ -46,7 +46,7 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w private val _deprecationWarnings = new ConditionalWarning("deprecation", settings.deprecation)() private val _uncheckedWarnings = new ConditionalWarning("unchecked", settings.unchecked)() private val _featureWarnings = new ConditionalWarning("feature", settings.feature)() - private val _inlinerWarnings = new ConditionalWarning("inliner", settings.YinlinerWarnings)(if (settings.isBCodeAskedFor) settings.YoptWarnings.name else settings.YinlinerWarnings.name) + private val _inlinerWarnings = new ConditionalWarning("inliner", settings.YinlinerWarnings)(if (settings.isBCodeActive) settings.YoptWarnings.name else settings.YinlinerWarnings.name) private val _allConditionalWarnings = List(_deprecationWarnings, _uncheckedWarnings, _featureWarnings, _inlinerWarnings) // TODO: remove in favor of the overload that takes a Symbol, give that argument a default (NoSymbol) diff --git a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala index 96939e616c..52b8a51a79 100755 --- a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala @@ -6,6 +6,7 @@ package scala.tools.nsc package ast.parser +import scala.annotation.tailrec import scala.collection.mutable import mutable.{ Buffer, ArrayBuffer, ListBuffer } import scala.util.control.ControlThrowable @@ -172,20 +173,19 @@ trait MarkupParsers { } def appendText(pos: Position, ts: Buffer[Tree], txt: String): Unit = { - def append(t: String) = ts append handle.text(pos, t) - - if (preserveWS) append(txt) - else { + def append(text: String): Unit = { + val tree = handle.text(pos, text) + ts append tree + } + val clean = if (preserveWS) txt else { val sb = new StringBuilder() - txt foreach { c => if (!isSpace(c)) sb append c else if (sb.isEmpty || !isSpace(sb.last)) sb append ' ' } - - val trimmed = sb.toString.trim - if (!trimmed.isEmpty) append(trimmed) + sb.toString.trim } + if (!clean.isEmpty) append(clean) } /** adds entity/character to ts as side-effect @@ -216,44 +216,75 @@ trait MarkupParsers { if (xCheckEmbeddedBlock) ts append xEmbeddedExpr else appendText(p, ts, xText) - /** Returns true if it encounters an end tag (without consuming it), - * appends trees to ts as side-effect. + /** At an open angle-bracket, detects an end tag + * or consumes CDATA, comment, PI or element. + * Trees are appended to `ts` as a side-effect. + * @return true if an end tag (without consuming it) */ - private def content_LT(ts: ArrayBuffer[Tree]): Boolean = { - if (ch == '/') - return true // end tag - - val toAppend = ch match { - case '!' => nextch() ; if (ch =='[') xCharData else xComment // CDATA or Comment - case '?' => nextch() ; xProcInstr // PI - case _ => element // child node + private def content_LT(ts: ArrayBuffer[Tree]): Boolean = + (ch == '/') || { + val toAppend = ch match { + case '!' => nextch() ; if (ch =='[') xCharData else xComment // CDATA or Comment + case '?' => nextch() ; xProcInstr // PI + case _ => element // child node + } + ts append toAppend + false } - ts append toAppend - false - } - def content: Buffer[Tree] = { val ts = new ArrayBuffer[Tree] - while (true) { - if (xEmbeddedBlock) + val coalescing = settings.XxmlSettings.isCoalescing + @tailrec def loopContent(): Unit = + if (xEmbeddedBlock) { ts append xEmbeddedExpr - else { + loopContent() + } else { tmppos = o2p(curOffset) ch match { - // end tag, cdata, comment, pi or child node - case '<' => nextch() ; if (content_LT(ts)) return ts - // either the character '{' or an embedded scala block } - case '{' => content_BRACE(tmppos, ts) // } - // EntityRef or CharRef - case '&' => content_AMP(ts) - case SU => return ts - // text content - here xEmbeddedBlock might be true - case _ => appendText(tmppos, ts, xText) + case '<' => // end tag, cdata, comment, pi or child node + nextch() + if (!content_LT(ts)) loopContent() + case '{' => // } literal brace or embedded Scala block + content_BRACE(tmppos, ts) + loopContent() + case '&' => // EntityRef or CharRef + content_AMP(ts) + loopContent() + case SU => () + case _ => // text content - here xEmbeddedBlock might be true + appendText(tmppos, ts, xText) + loopContent() } } + // merge text sections and strip attachments + def coalesce(): ArrayBuffer[Tree] = { + def copy() = { + val buf = new ArrayBuffer[Tree] + var acc = new StringBuilder + var pos: Position = NoPosition + def emit() = if (acc.nonEmpty) { + appendText(pos, buf, acc.toString) + acc.clear() + } + for (t <- ts) + t.attachments.get[handle.TextAttache] match { + case Some(ta) => + if (acc.isEmpty) pos = ta.pos + acc append ta.text + case _ => + emit() + buf += t + } + emit() + buf + } + val res = if (ts.count(_.hasAttachment[handle.TextAttache]) > 1) copy() else ts + for (t <- res) t.removeAttachment[handle.TextAttache] + res } - unreachable + loopContent() + if (coalescing) coalesce() else ts } /** '<' element ::= xmlTag1 '>' { xmlExpr | '{' simpleExpr '}' } ETag @@ -289,20 +320,16 @@ trait MarkupParsers { private def xText: String = { assert(!xEmbeddedBlock, "internal error: encountered embedded block") val buf = new StringBuilder - def done = buf.toString - - while (ch != SU) { - if (ch == '}') { - if (charComingAfter(nextch()) == '}') nextch() - else errorBraces() - } - - buf append ch - nextch() - if (xCheckEmbeddedBlock || ch == '<' || ch == '&') - return done - } - done + if (ch != SU) + do { + if (ch == '}') { + if (charComingAfter(nextch()) == '}') nextch() + else errorBraces() + } + buf append ch + nextch() + } while (!(ch == SU || xCheckEmbeddedBlock || ch == '<' || ch == '&')) + buf.toString } /** Some try/catch/finally logic used by xLiteral and xLiteralPattern. */ @@ -344,12 +371,12 @@ trait MarkupParsers { tmppos = o2p(curOffset) // Iuli: added this line, as it seems content_LT uses tmppos when creating trees content_LT(ts) - // parse more XML ? + // parse more XML? if (charComingAfter(xSpaceOpt()) == '<') { do { xSpaceOpt() nextch() - ts append element + content_LT(ts) } while (charComingAfter(xSpaceOpt()) == '<') handle.makeXMLseq(r2p(start, start, curOffset), ts) } diff --git a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala index d2a999cdec..99399e363f 100755 --- a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala @@ -36,6 +36,7 @@ abstract class SymbolicXMLBuilder(p: Parsers#Parser, preserveWS: Boolean) { val _MetaData: NameType = "MetaData" val _NamespaceBinding: NameType = "NamespaceBinding" val _NodeBuffer: NameType = "NodeBuffer" + val _PCData: NameType = "PCData" val _PrefixedAttribute: NameType = "PrefixedAttribute" val _ProcInstr: NameType = "ProcInstr" val _Text: NameType = "Text" @@ -46,6 +47,7 @@ abstract class SymbolicXMLBuilder(p: Parsers#Parser, preserveWS: Boolean) { private object xmlterms extends TermNames { val _Null: NameType = "Null" val __Elem: NameType = "Elem" + val _PCData: NameType = "PCData" val __Text: NameType = "Text" val _buf: NameType = "$buf" val _md: NameType = "$md" @@ -55,10 +57,15 @@ abstract class SymbolicXMLBuilder(p: Parsers#Parser, preserveWS: Boolean) { val _xml: NameType = "xml" } - import xmltypes.{_Comment, _Elem, _EntityRef, _Group, _MetaData, _NamespaceBinding, _NodeBuffer, - _PrefixedAttribute, _ProcInstr, _Text, _Unparsed, _UnprefixedAttribute} + import xmltypes.{ + _Comment, _Elem, _EntityRef, _Group, _MetaData, _NamespaceBinding, _NodeBuffer, + _PCData, _PrefixedAttribute, _ProcInstr, _Text, _Unparsed, _UnprefixedAttribute + } + + import xmlterms.{ _Null, __Elem, __Text, _buf, _md, _plus, _scope, _tmpscope, _xml } - import xmlterms.{_Null, __Elem, __Text, _buf, _md, _plus, _scope, _tmpscope, _xml} + /** Attachment for trees deriving from text nodes (Text, CData, entities). Used for coalescing. */ + case class TextAttache(pos: Position, text: String) // convenience methods private def LL[A](x: A*): List[List[A]] = List(List(x:_*)) @@ -108,16 +115,22 @@ abstract class SymbolicXMLBuilder(p: Parsers#Parser, preserveWS: Boolean) { final def entityRef(pos: Position, n: String) = atPos(pos)( New(_scala_xml_EntityRef, LL(const(n))) ) + private def coalescing = settings.XxmlSettings.isCoalescing + // create scala.xml.Text here <: scala.xml.Node final def text(pos: Position, txt: String): Tree = atPos(pos) { - if (isPattern) makeTextPat(const(txt)) - else makeText1(const(txt)) + val t = if (isPattern) makeTextPat(const(txt)) else makeText1(const(txt)) + if (coalescing) t updateAttachment TextAttache(pos, txt) else t } def makeTextPat(txt: Tree) = Apply(_scala_xml__Text, List(txt)) def makeText1(txt: Tree) = New(_scala_xml_Text, LL(txt)) def comment(pos: Position, text: String) = atPos(pos)( Comment(const(text)) ) - def charData(pos: Position, txt: String) = atPos(pos)( makeText1(const(txt)) ) + def charData(pos: Position, txt: String) = atPos(pos) { + val t = if (isPattern) Apply(_scala_xml(xmlterms._PCData), List(const(txt))) + else New(_scala_xml(_PCData), LL(const(txt))) + if (coalescing) t updateAttachment TextAttache(pos, txt) else t + } def procInstr(pos: Position, target: String, txt: String) = atPos(pos)( ProcInstr(const(target), const(txt)) ) diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala index 185fd93501..137954b52d 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala @@ -883,7 +883,12 @@ abstract class GenICode extends SubComponent { case None => val saved = settings.uniqid settings.uniqid.value = true - try abort(s"symbol $sym does not exist in ${ctx.method}, which contains locals ${ctx.method.locals.mkString(",")}") + try { + val methodCode = unit.body.collect { case dd: DefDef + if dd.symbol == ctx.method.symbol => showCode(dd); + }.headOption.getOrElse("<unknown>") + abort(s"symbol $sym does not exist in ${ctx.method}, which contains locals ${ctx.method.locals.mkString(",")}. \nMethod code: $methodCode") + } finally settings.uniqid.value = saved } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index d8a17e975e..d690542f0e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -11,11 +11,12 @@ import scala.collection.concurrent.TrieMap import scala.reflect.internal.util.Position import scala.tools.asm import asm.Opcodes -import scala.tools.asm.tree.{MethodInsnNode, InnerClassNode, ClassNode} +import scala.tools.asm.tree.{MethodNode, MethodInsnNode, InnerClassNode, ClassNode} import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo} import scala.tools.nsc.backend.jvm.BackendReporting._ import scala.tools.nsc.backend.jvm.opt._ import scala.collection.convert.decorateAsScala._ +import scala.tools.nsc.settings.ScalaSettings /** * The BTypes component defines The BType class hierarchy. A BType stores all type information @@ -39,6 +40,8 @@ abstract class BTypes { */ val byteCodeRepository: ByteCodeRepository + val localOpt: LocalOpt[this.type] + val inliner: Inliner[this.type] val callGraph: CallGraph[this.type] @@ -48,14 +51,9 @@ abstract class BTypes { // Allows to define per-run caches here and in the CallGraph component, which don't have a global def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T - // When building the call graph, we need to know if global inlining is allowed (the component doesn't have a global) - def inlineGlobalEnabled: Boolean - - // When the inliner is not enabled, there's no point in adding InlineInfos to all ClassBTypes - def inlinerEnabled: Boolean + // Allows access to the compiler settings for backend components that don't have a global in scope + def compilerSettings: ScalaSettings - // Settings that define what kind of optimizer warnings are emitted. - def warnSettings: WarnSettings /** * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its @@ -83,6 +81,18 @@ abstract class BTypes { val javaDefinedClasses: collection.mutable.Set[InternalName] = recordPerRunCache(collection.mutable.Set.empty) /** + * Cache, contains methods whose unreachable instructions are eliminated. + * + * The ASM Analyzer class does not compute any frame information for unreachable instructions. + * Transformations that use an analyzer (including inlining) therefore require unreachable code + * to be eliminated. + * + * This cache allows running dead code elimination whenever an analyzer is used. If the method + * is already optimized, DCE can return early. + */ + val unreachableCodeEliminated: collection.mutable.Set[MethodNode] = recordPerRunCache(collection.mutable.Set.empty) + + /** * Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType * is constructed by parsing the corresponding classfile. * @@ -229,7 +239,7 @@ abstract class BTypes { // The InlineInfo is built from the classfile (not from the symbol) for all classes that are NOT // being compiled. For those classes, the info is only needed if the inliner is enabled, othewise // we can save the memory. - if (!inlinerEnabled) BTypes.EmptyInlineInfo + if (!compilerSettings.YoptInlinerEnabled) BTypes.EmptyInlineInfo else fromClassfileAttribute getOrElse fromClassfileWithoutAttribute } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index eeb6ed24a2..1b9fd5e298 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -7,9 +7,10 @@ package scala.tools.nsc package backend.jvm import scala.tools.asm -import scala.tools.nsc.backend.jvm.opt.{CallGraph, Inliner, ByteCodeRepository} +import scala.tools.nsc.backend.jvm.opt.{LocalOpt, CallGraph, Inliner, ByteCodeRepository} import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo, InternalName} import BackendReporting._ +import scala.tools.nsc.settings.ScalaSettings /** * This class mainly contains the method classBTypeFromSymbol, which extracts the necessary @@ -37,6 +38,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val byteCodeRepository = new ByteCodeRepository(global.classPath, javaDefinedClasses, recordPerRunCache(collection.concurrent.TrieMap.empty)) + val localOpt: LocalOpt[this.type] = new LocalOpt(this) + val inliner: Inliner[this.type] = new Inliner(this) val callGraph: CallGraph[this.type] = new CallGraph(this) @@ -49,19 +52,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T = perRunCaches.recordCache(cache) - def inlineGlobalEnabled: Boolean = settings.YoptInlineGlobal - - def inlinerEnabled: Boolean = settings.YoptInlinerEnabled - - def warnSettings: WarnSettings = { - val c = settings.YoptWarningsChoices - // cannot extract settings.YoptWarnings into a local val due to some dependent typing issue. - WarnSettings( - !settings.YoptWarnings.isSetByUser || settings.YoptWarnings.contains(c.atInlineFailedSummary.name) || settings.YoptWarnings.contains(c.atInlineFailed.name), - settings.YoptWarnings.contains(c.noInlineMixed.name), - settings.YoptWarnings.contains(c.noInlineMissingBytecode.name), - settings.YoptWarnings.contains(c.noInlineMissingScalaInlineInfoAttr.name)) - } + def compilerSettings: ScalaSettings = settings // helpers that need access to global. // TODO @lry create a separate component, they don't belong to BTypesFromSymbols @@ -418,8 +409,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { // phase travel required, see implementation of `compiles`. for nested classes, it checks if the // enclosingTopLevelClass is being compiled. after flatten, all classes are considered top-level, // so `compiles` would return `false`. - if (exitingPickler(currentRun.compiles(classSym))) buildFromSymbol // InlineInfo required for classes being compiled, we have to create the classfile attribute - else if (!inlinerEnabled) BTypes.EmptyInlineInfo // For other classes, we need the InlineInfo only inf the inliner is enabled. + if (exitingPickler(currentRun.compiles(classSym))) buildFromSymbol // InlineInfo required for classes being compiled, we have to create the classfile attribute + else if (!compilerSettings.YoptInlinerEnabled) BTypes.EmptyInlineInfo // For other classes, we need the InlineInfo only inf the inliner is enabled. else { // For classes not being compiled, the InlineInfo is read from the classfile attribute. This // fixes an issue with mixed-in methods: the mixin phase enters mixin methods only to class diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala index a06fb4bab8..d641f708d2 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala @@ -4,6 +4,8 @@ package backend.jvm import scala.tools.asm.tree.{AbstractInsnNode, MethodNode} import scala.tools.nsc.backend.jvm.BTypes.InternalName import scala.reflect.internal.util.Position +import scala.tools.nsc.settings.ScalaSettings +import scala.util.control.ControlThrowable /** * Interface for emitting inline warnings. The interface is required because the implementation @@ -73,24 +75,22 @@ object BackendReporting { } } - case class Invalid[A](e: A) extends Exception + case class Invalid[A](e: A) extends ControlThrowable /** * See documentation of orThrow above. */ def tryEither[A, B](op: => Either[A, B]): Either[A, B] = try { op } catch { case Invalid(e) => Left(e.asInstanceOf[A]) } - final case class WarnSettings(atInlineFailed: Boolean, noInlineMixed: Boolean, noInlineMissingBytecode: Boolean, noInlineMissingScalaInlineInfoAttr: Boolean) - sealed trait OptimizerWarning { - def emitWarning(settings: WarnSettings): Boolean + def emitWarning(settings: ScalaSettings): Boolean } // Method filter in RightBiasedEither requires an implicit empty value. Taking the value here // in scope allows for-comprehensions that desugar into filter calls (for example when using a // tuple de-constructor). implicit object emptyOptimizerWarning extends OptimizerWarning { - def emitWarning(settings: WarnSettings): Boolean = false + def emitWarning(settings: ScalaSettings): Boolean = false } sealed trait MissingBytecodeWarning extends OptimizerWarning { @@ -112,17 +112,17 @@ object BackendReporting { missingClass.map(c => s" Reason:\n$c").getOrElse("") } - def emitWarning(settings: WarnSettings): Boolean = this match { + def emitWarning(settings: ScalaSettings): Boolean = this match { case ClassNotFound(_, javaDefined) => - if (javaDefined) settings.noInlineMixed - else settings.noInlineMissingBytecode + if (javaDefined) settings.YoptWarningNoInlineMixed + else settings.YoptWarningNoInlineMissingBytecode case m @ MethodNotFound(_, _, _, missing) => if (m.isArrayMethod) false - else settings.noInlineMissingBytecode || missing.exists(_.emitWarning(settings)) + else settings.YoptWarningNoInlineMissingBytecode || missing.exists(_.emitWarning(settings)) case FieldNotFound(_, _, _, missing) => - settings.noInlineMissingBytecode || missing.exists(_.emitWarning(settings)) + settings.YoptWarningNoInlineMissingBytecode || missing.exists(_.emitWarning(settings)) } } @@ -141,9 +141,9 @@ object BackendReporting { s"Failed to get the type of class symbol $classFullName due to SI-9111." } - def emitWarning(settings: WarnSettings): Boolean = this match { + def emitWarning(settings: ScalaSettings): Boolean = this match { case NoClassBTypeInfoMissingBytecode(cause) => cause.emitWarning(settings) - case NoClassBTypeInfoClassSymbolInfoFailedSI9111(_) => settings.noInlineMissingBytecode + case NoClassBTypeInfoClassSymbolInfoFailedSI9111(_) => settings.YoptWarningNoInlineMissingBytecode } } @@ -175,11 +175,11 @@ object BackendReporting { cause.toString } - def emitWarning(settings: WarnSettings): Boolean = this match { + def emitWarning(settings: ScalaSettings): Boolean = this match { case MethodInlineInfoIncomplete(_, _, _, cause) => cause.emitWarning(settings) case MethodInlineInfoMissing(_, _, _, Some(cause)) => cause.emitWarning(settings) - case MethodInlineInfoMissing(_, _, _, None) => settings.noInlineMissingBytecode + case MethodInlineInfoMissing(_, _, _, None) => settings.YoptWarningNoInlineMissingBytecode case MethodInlineInfoError(_, _, _, cause) => cause.emitWarning(settings) @@ -214,11 +214,21 @@ object BackendReporting { 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. + """.stripMargin } - def emitWarning(settings: WarnSettings): Boolean = this match { - case _: IllegalAccessInstruction | _: MethodWithHandlerCalledOnNonEmptyStack | _: SynchronizedMethod => - settings.atInlineFailed + def emitWarning(settings: ScalaSettings): Boolean = this match { + case _: IllegalAccessInstruction | _: MethodWithHandlerCalledOnNonEmptyStack | _: SynchronizedMethod | _: StrictfpMismatch | _: ResultingMethodTooLarge => + settings.YoptWarningEmitAtInlineFailed case IllegalAccessCheckFailed(_, _, _, _, _, cause) => cause.emitWarning(settings) @@ -231,6 +241,10 @@ object BackendReporting { case class MethodWithHandlerCalledOnNonEmptyStack(calleeDeclarationClass: InternalName, name: String, descriptor: String, callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning case class SynchronizedMethod(calleeDeclarationClass: InternalName, name: String, descriptor: String) extends CannotInlineWarning + case class StrictfpMismatch(calleeDeclarationClass: InternalName, name: String, descriptor: String, + callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning + case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: String, descriptor: String, + callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning /** * Used in the InlineInfo of a ClassBType, when some issue occurred obtaining the inline information. @@ -250,11 +264,11 @@ object BackendReporting { s"Cannot read ScalaInlineInfo version $version in classfile $internalName. Use a more recent compiler." } - def emitWarning(settings: WarnSettings): Boolean = this match { - case NoInlineInfoAttribute(_) => settings.noInlineMissingScalaInlineInfoAttr + def emitWarning(settings: ScalaSettings): Boolean = this match { + case NoInlineInfoAttribute(_) => settings.YoptWarningNoInlineMissingScalaInlineInfoAttr case ClassNotFoundWhenBuildingInlineInfoFromSymbol(cause) => cause.emitWarning(settings) - case ClassSymbolInfoFailureSI9111(_) => settings.noInlineMissingBytecode - case UnknownScalaInlineInfoVersion(_, _) => settings.noInlineMissingScalaInlineInfoAttr + case ClassSymbolInfoFailureSI9111(_) => settings.YoptWarningNoInlineMissingBytecode + case UnknownScalaInlineInfoVersion(_, _) => settings.YoptWarningNoInlineMissingScalaInlineInfoAttr } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index be1595dc29..c6ee36d7b2 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -14,7 +14,6 @@ import scala.reflect.internal.util.Statistics import scala.tools.asm import scala.tools.asm.tree.ClassNode -import scala.tools.nsc.backend.jvm.opt.LocalOpt /* * Prepare in-memory representations of classfiles using the ASM Tree API, and serialize them to disk. @@ -215,17 +214,12 @@ abstract class GenBCode extends BCodeSyncAndTry { * - converting the plain ClassNode to byte array and placing it on queue-3 */ class Worker2 { - lazy val localOpt = new LocalOpt(settings) - def runGlobalOptimizations(): Unit = { import scala.collection.convert.decorateAsScala._ q2.asScala foreach { case Item2(_, _, plain, _, _) => // skip mirror / bean: wd don't inline into tem, and they are not used in the plain class - if (plain != null) { - localOpt.minimalRemoveUnreachableCode(plain) - callGraph.addClass(plain) - } + if (plain != null) callGraph.addClass(plain) } bTypes.inliner.runInliner() } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala index 14e8cccc60..201ab15177 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -10,6 +10,7 @@ package opt import scala.annotation.{tailrec, switch} import scala.collection.mutable import scala.reflect.internal.util.Collections._ +import scala.tools.asm.commons.CodeSizeEvaluator import scala.tools.asm.tree.analysis._ import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes} import scala.tools.asm.tree._ @@ -21,6 +22,12 @@ import scala.tools.nsc.backend.jvm.BTypes._ object BytecodeUtils { + // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.9.1 + final val maxJVMMethodSize = 65535 + + // 5% margin, more than enough for the instructions added by the inliner (store / load args, null check for instance methods) + final val maxMethodSizeAfterInline = maxJVMMethodSize - (maxJVMMethodSize / 20) + object Goto { def unapply(instruction: AbstractInsnNode): Option[JumpInsnNode] = { if (instruction.getOpcode == Opcodes.GOTO) Some(instruction.asInstanceOf[JumpInsnNode]) @@ -83,10 +90,14 @@ object BytecodeUtils { def isSynchronizedMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_SYNCHRONIZED) != 0 + def isNativeMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_NATIVE) != 0 + def isFinalClass(classNode: ClassNode): Boolean = (classNode.access & Opcodes.ACC_FINAL) != 0 def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) != 0 + def isStrictfpMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STRICT) != 0 + def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = { var result = instruction do { result = result.getNext } @@ -215,7 +226,7 @@ object BytecodeUtils { * to create a separate visitor for computing those values, duplicating the functionality from the * MethodWriter. */ - def computeMaxLocalsMaxStack(method: MethodNode) { + def computeMaxLocalsMaxStack(method: MethodNode): Unit = { val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS) val excs = method.exceptions.asScala.toArray val mw = cw.visitMethod(method.access, method.name, method.desc, method.signature, excs).asInstanceOf[MethodWriter] @@ -224,6 +235,21 @@ object BytecodeUtils { method.maxStack = mw.getMaxStack } + def codeSizeOKForInlining(caller: MethodNode, callee: MethodNode): Boolean = { + // Looking at the implementation of CodeSizeEvaluator, all instructions except tableswitch and + // lookupswitch are <= 8 bytes. These should be rare enough for 8 to be an OK rough upper bound. + def roughUpperBound(methodNode: MethodNode): Int = methodNode.instructions.size * 8 + + def maxSize(methodNode: MethodNode): Int = { + val eval = new CodeSizeEvaluator(null) + methodNode.accept(eval) + eval.getMaxSize + } + + (roughUpperBound(caller) + roughUpperBound(callee) > maxMethodSizeAfterInline) && + (maxSize(caller) + maxSize(callee) > maxMethodSizeAfterInline) + } + def removeLineNumberNodes(classNode: ClassNode): Unit = { for (m <- classNode.methods.asScala) removeLineNumberNodes(m.instructions) } 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 47d32c94cb..028f0f8fa6 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -43,14 +43,14 @@ class CallGraph[BT <: BTypes](val btypes: BT) { // callee, we only check there for the methodInlineInfo, we should find it there. calleeDeclarationClassBType.info.orThrow.inlineInfo.methodInfos.get(methodSignature) match { case Some(methodInlineInfo) => - val canInlineFromSource = inlineGlobalEnabled || calleeSource == CompilationUnit + val canInlineFromSource = compilerSettings.YoptInlineGlobal || calleeSource == CompilationUnit val isAbstract = BytecodeUtils.isAbstractMethod(calleeMethodNode) // (1) A non-final method can be safe to inline if the receiver type is a final subclass. Example: // class A { @inline def f = 1 }; object B extends A; B.f // can be inlined // - // TODO: type analysis can render more calls statically resolved. Example˜∫ + // TODO: type analysis can render more calls statically resolved. Example: // new A.f // can be inlined, the receiver type is known to be exactly A. val isStaticallyResolved: Boolean = { methodInlineInfo.effectivelyFinal || @@ -68,8 +68,13 @@ class CallGraph[BT <: BTypes](val btypes: BT) { // (2) Final trait methods can be rewritten from the interface to the static implementation // method to enable inlining. CallsiteInfo( - safeToInline = canInlineFromSource && isStaticallyResolved && !isAbstract, // (1) - safeToRewrite = canInlineFromSource && isRewritableTraitCall, // (2) + safeToInline = + canInlineFromSource && + isStaticallyResolved && // (1) + !isAbstract && + !BytecodeUtils.isConstructor(calleeMethodNode) && + !BytecodeUtils.isNativeMethod(calleeMethodNode), + safeToRewrite = canInlineFromSource && isRewritableTraitCall, // (2) annotatedInline = methodInlineInfo.annotatedInline, annotatedNoInline = methodInlineInfo.annotatedNoInline, warning = warning) @@ -92,6 +97,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { // TODO: for now we run a basic analyzer to get the stack height at the call site. // once we run a more elaborate analyzer (types, nullness), we can get the stack height out of there. + localOpt.minimalRemoveUnreachableCode(methodNode, definingClass.internalName) val analyzer = new AsmAnalyzer(methodNode, definingClass.internalName) methodNode.instructions.iterator.asScala.collect({ 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 e14e57d3ab..ac5c9ce2e6 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -16,7 +16,7 @@ import scala.collection.convert.decorateAsJava._ import AsmUtils._ import BytecodeUtils._ import collection.mutable -import scala.tools.asm.tree.analysis.{SourceInterpreter, Analyzer} +import scala.tools.asm.tree.analysis.SourceInterpreter import BackendReporting._ import scala.tools.nsc.backend.jvm.BTypes.InternalName @@ -24,21 +24,39 @@ class Inliner[BT <: BTypes](val btypes: BT) { import btypes._ import callGraph._ + def eliminateUnreachableCodeAndUpdateCallGraph(methodNode: MethodNode, definingClass: InternalName): Unit = { + localOpt.minimalRemoveUnreachableCode(methodNode, definingClass) foreach { + case invocation: MethodInsnNode => callGraph.callsites.remove(invocation) + case _ => + } + } + def runInliner(): Unit = { rewriteFinalTraitMethodInvocations() for (request <- collectAndOrderInlineRequests) { val Right(callee) = request.callee // collectAndOrderInlineRequests returns callsites with a known callee - val r = inline(request.callsiteInstruction, request.callsiteStackHeight, request.callsiteMethod, request.callsiteClass, - callee.callee, callee.calleeDeclarationClass, - receiverKnownNotNull = false, keepLineNumbers = false) - - for (warning <- r) { - if ((callee.annotatedInline && btypes.warnSettings.atInlineFailed) || warning.emitWarning(warnSettings)) { - val annotWarn = if (callee.annotatedInline) " is annotated @inline but" else "" - val msg = s"${BackendReporting.methodSignature(callee.calleeDeclarationClass.internalName, callee.callee)}$annotWarn could not be inlined:\n$warning" - backendReporting.inlinerWarning(request.callsitePosition, msg) + // Inlining a method can create unreachable code. Example: + // def f = throw e + // def g = f; println() // println is unreachable after inlining f + // If we have an inline request for a call to g, and f has been already inlined into g, we + // need to run DCE before inlining g. + eliminateUnreachableCodeAndUpdateCallGraph(callee.callee, callee.calleeDeclarationClass.internalName) + + // DCE above removes unreachable callsites from the call graph. If the inlining request denotes + // such an eliminated callsite, do nothing. + if (callGraph.callsites contains request.callsiteInstruction) { + val r = inline(request.callsiteInstruction, request.callsiteStackHeight, request.callsiteMethod, request.callsiteClass, + callee.callee, callee.calleeDeclarationClass, + receiverKnownNotNull = false, keepLineNumbers = false) + + for (warning <- r) { + if ((callee.annotatedInline && btypes.compilerSettings.YoptWarningEmitAtInlineFailed) || warning.emitWarning(compilerSettings)) { + val annotWarn = if (callee.annotatedInline) " is annotated @inline but" else "" + val msg = s"${BackendReporting.methodSignature(callee.calleeDeclarationClass.internalName, callee.callee)}$annotWarn could not be inlined:\n$warning" + backendReporting.inlinerWarning(request.callsitePosition, msg) + } } } } @@ -75,7 +93,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { val res = doInlineCallsite(callsite) if (!res) { - if (annotatedInline && btypes.warnSettings.atInlineFailed) { + if (annotatedInline && btypes.compilerSettings.YoptWarningEmitAtInlineFailed) { // if the callsite is annotated @inline, we report an inline warning even if the underlying // reason is, for example, mixed compilation (which has a separate -Yopt-warning flag). def initMsg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)} is annotated @inline but cannot be inlined" @@ -86,7 +104,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { backendReporting.inlinerWarning(pos, s"$initMsg: the method is not final and may be overridden." + warnMsg) else backendReporting.inlinerWarning(pos, s"$initMsg." + warnMsg) - } else if (warning.isDefined && warning.get.emitWarning(warnSettings)) { + } else if (warning.isDefined && warning.get.emitWarning(compilerSettings)) { // when annotatedInline is false, and there is some warning, the callsite metadata is possibly incomplete. backendReporting.inlinerWarning(pos, s"there was a problem determining if method ${callee.name} can be inlined: \n"+ warning.get) } @@ -95,7 +113,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { res case Callsite(ins, _, _, Left(warning), _, _, pos) => - if (warning.emitWarning(warnSettings)) + if (warning.emitWarning(compilerSettings)) backendReporting.inlinerWarning(pos, s"failed to determine if ${ins.name} should be inlined:\n$warning") false }).toList @@ -106,7 +124,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { */ def doInlineCallsite(callsite: Callsite): Boolean = callsite match { case Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, annotatedInline, _, warning)), _, _, pos) => - annotatedInline && safeToInline + if (compilerSettings.YoptInlineHeuristics.value == "everything") safeToInline + else annotatedInline && safeToInline case _ => false } @@ -167,6 +186,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { // VerifyError. We run a `SourceInterpreter` to find all producer instructions of the // receiver value and add a cast to the self type after each. if (!selfTypeOk) { + // there's no need to run eliminateUnreachableCode here. building the call graph does that + // already, no code can become unreachable in the meantime. val analyzer = new AsmAnalyzer(callsite.callsiteMethod, callsite.callsiteClass.internalName, new SourceInterpreter) val receiverValue = analyzer.frameAt(callsite.callsiteInstruction).peekDown(traitMethodArgumentTypes.length) for (i <- receiverValue.insns.asScala) { @@ -311,6 +332,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { val localVarShift = callsiteMethod.maxLocals clonedInstructions.iterator.asScala foreach { case varInstruction: VarInsnNode => varInstruction.`var` += localVarShift + case iinc: IincInsnNode => iinc.`var` += localVarShift case _ => () } @@ -433,6 +455,9 @@ class Inliner[BT <: BTypes](val btypes: BT) { // Remove the elided invocation from the call graph callGraph.callsites.remove(callsiteInstruction) + // Inlining a method body can render some code unreachable, see example above (in runInliner). + unreachableCodeEliminated -= callsiteMethod + callsiteMethod.maxLocals += returnType.getSize + callee.maxLocals callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, callee.maxStack + callsiteStackHeight) @@ -472,10 +497,18 @@ class Inliner[BT <: BTypes](val btypes: BT) { callsiteStackHeight > expectedArgs } - if (isSynchronizedMethod(callee)) { + if (codeSizeOKForInlining(callsiteMethod, callee)) { + Some(ResultingMethodTooLarge( + calleeDeclarationClass.internalName, callee.name, callee.desc, + callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) + } else if (isSynchronizedMethod(callee)) { // Could be done by locking on the receiver, wrapping the inlined code in a try and unlocking // in finally. But it's probably not worth the effort, scala never emits synchronized methods. Some(SynchronizedMethod(calleeDeclarationClass.internalName, callee.name, callee.desc)) + } else if (isStrictfpMethod(callsiteMethod) != isStrictfpMethod(callee)) { + Some(StrictfpMismatch( + calleeDeclarationClass.internalName, callee.name, callee.desc, + callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) } else if (!callee.tryCatchBlocks.isEmpty && stackHasNonParameters) { Some(MethodWithHandlerCalledOnNonEmptyStack( calleeDeclarationClass.internalName, callee.name, callee.desc, diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala index f6cfc5598b..5f51a94673 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala @@ -14,7 +14,6 @@ import scala.tools.asm.tree._ import scala.collection.convert.decorateAsScala._ import scala.tools.nsc.backend.jvm.BTypes.InternalName import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ -import scala.tools.nsc.settings.ScalaSettings /** * Optimizations within a single method. @@ -47,18 +46,9 @@ import scala.tools.nsc.settings.ScalaSettings * stale labels * - eliminate labels that are not referenced, merge sequences of label definitions. */ -class LocalOpt(settings: ScalaSettings) { - /** - * Remove unreachable code from all methods of `classNode`. See of its overload. - * - * @param classNode The class to optimize - * @return `true` if unreachable code was removed from any method - */ - def minimalRemoveUnreachableCode(classNode: ClassNode): Boolean = { - classNode.methods.asScala.foldLeft(false) { - case (changed, method) => minimalRemoveUnreachableCode(method, classNode.name) || changed - } - } +class LocalOpt[BT <: BTypes](val btypes: BT) { + import LocalOptImpls._ + import btypes._ /** * Remove unreachable code from a method. @@ -66,25 +56,30 @@ class LocalOpt(settings: ScalaSettings) { * This implementation only removes instructions that are unreachable for an ASM analyzer / * interpreter. This ensures that future analyses will not produce `null` frames. The inliner * and call graph builder depend on this property. + * + * @return A set containing the eliminated instructions */ - def minimalRemoveUnreachableCode(method: MethodNode, ownerClassName: InternalName): Boolean = { - if (method.instructions.size == 0) return false // fast path for abstract methods + def minimalRemoveUnreachableCode(method: MethodNode, ownerClassName: InternalName): Set[AbstractInsnNode] = { + if (method.instructions.size == 0) return Set.empty // fast path for abstract methods + if (unreachableCodeEliminated(method)) return Set.empty // we know there is no unreachable code // For correctness, after removing unreachable code, we have to eliminate empty exception // handlers, see scaladoc of def methodOptimizations. Removing an live handler may render more // code unreachable and therefore requires running another round. - def removalRound(): Boolean = { - val (codeRemoved, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName) - if (codeRemoved) { + def removalRound(): Set[AbstractInsnNode] = { + val (removedInstructions, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName) + val removedRecursively = if (removedInstructions.nonEmpty) { val liveHandlerRemoved = removeEmptyExceptionHandlers(method).exists(h => liveLabels(h.start)) if (liveHandlerRemoved) removalRound() - } - codeRemoved + else Set.empty + } else Set.empty + removedInstructions ++ removedRecursively } - val codeRemoved = removalRound() - if (codeRemoved) removeUnusedLocalVariableNodes(method)() - codeRemoved + val removedInstructions = removalRound() + if (removedInstructions.nonEmpty) removeUnusedLocalVariableNodes(method)() + unreachableCodeEliminated += method + removedInstructions } /** @@ -95,7 +90,7 @@ class LocalOpt(settings: ScalaSettings) { * @return `true` if unreachable code was eliminated in some method, `false` otherwise. */ def methodOptimizations(clazz: ClassNode): Boolean = { - !settings.YoptNone && clazz.methods.asScala.foldLeft(false) { + !compilerSettings.YoptNone && clazz.methods.asScala.foldLeft(false) { case (changed, method) => methodOptimizations(method, clazz.name) || changed } } @@ -144,15 +139,15 @@ class LocalOpt(settings: ScalaSettings) { def removalRound(): Boolean = { // unreachable-code, empty-handlers and simplify-jumps run until reaching a fixpoint (see doc on class LocalOpt) - val (codeRemoved, handlersRemoved, liveHandlerRemoved) = if (settings.YoptUnreachableCode) { - val (codeRemoved, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName) + val (codeRemoved, handlersRemoved, liveHandlerRemoved) = if (compilerSettings.YoptUnreachableCode) { + val (removedInstructions, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName) val removedHandlers = removeEmptyExceptionHandlers(method) - (codeRemoved, removedHandlers.nonEmpty, removedHandlers.exists(h => liveLabels(h.start))) + (removedInstructions.nonEmpty, removedHandlers.nonEmpty, removedHandlers.exists(h => liveLabels(h.start))) } else { (false, false, false) } - val jumpsChanged = if (settings.YoptSimplifyJumps) simplifyJumps(method) else false + val jumpsChanged = if (compilerSettings.YoptSimplifyJumps) simplifyJumps(method) else false // Eliminating live handlers and simplifying jump instructions may render more code // unreachable, so we need to run another round. @@ -165,13 +160,13 @@ class LocalOpt(settings: ScalaSettings) { // (*) Removing stale local variable descriptors is required for correctness of unreachable-code val localsRemoved = - if (settings.YoptCompactLocals) compactLocalVariables(method) // also removes unused - else if (settings.YoptUnreachableCode) removeUnusedLocalVariableNodes(method)() // (*) + if (compilerSettings.YoptCompactLocals) compactLocalVariables(method) // also removes unused + else if (compilerSettings.YoptUnreachableCode) removeUnusedLocalVariableNodes(method)() // (*) else false - val lineNumbersRemoved = if (settings.YoptEmptyLineNumbers) removeEmptyLineNumbers(method) else false + val lineNumbersRemoved = if (compilerSettings.YoptEmptyLineNumbers) removeEmptyLineNumbers(method) else false - val labelsRemoved = if (settings.YoptEmptyLabels) removeEmptyLabelNodes(method) else false + val labelsRemoved = if (compilerSettings.YoptEmptyLabels) removeEmptyLabelNodes(method) else false // assert that local variable annotations are empty (we don't emit them) - otherwise we'd have // to eliminate those covering an empty range, similar to removeUnusedLocalVariableNodes. @@ -179,15 +174,22 @@ class LocalOpt(settings: ScalaSettings) { assert(nullOrEmpty(method.visibleLocalVariableAnnotations), method.visibleLocalVariableAnnotations) assert(nullOrEmpty(method.invisibleLocalVariableAnnotations), method.invisibleLocalVariableAnnotations) + unreachableCodeEliminated += method + codeHandlersOrJumpsChanged || localsRemoved || lineNumbersRemoved || labelsRemoved } +} + +object LocalOptImpls { /** * Removes unreachable basic blocks. * * TODO: rewrite, don't use computeMaxLocalsMaxStack (runs a ClassWriter) / Analyzer. Too slow. + * + * @return A set containing eliminated instructions, and a set containing all live label nodes. */ - def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: InternalName): (Boolean, Set[LabelNode]) = { + def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: InternalName): (Set[AbstractInsnNode], Set[LabelNode]) = { // The data flow analysis requires the maxLocals / maxStack fields of the method to be computed. computeMaxLocalsMaxStack(method) val a = new Analyzer(new BasicInterpreter) @@ -197,6 +199,7 @@ class LocalOpt(settings: ScalaSettings) { val initialSize = method.instructions.size var i = 0 var liveLabels = Set.empty[LabelNode] + var removedInstructions = Set.empty[AbstractInsnNode] val itr = method.instructions.iterator() while (itr.hasNext) { itr.next() match { @@ -209,11 +212,12 @@ class LocalOpt(settings: ScalaSettings) { // Instruction iterators allow removing during iteration. // Removing is O(1): instructions are doubly linked list elements. itr.remove() + removedInstructions += ins } } i += 1 } - (method.instructions.size != initialSize, liveLabels) + (removedInstructions, liveLabels) } /** diff --git a/src/compiler/scala/tools/nsc/plugins/Plugin.scala b/src/compiler/scala/tools/nsc/plugins/Plugin.scala index 5a7a0df595..1a5529140c 100644 --- a/src/compiler/scala/tools/nsc/plugins/Plugin.scala +++ b/src/compiler/scala/tools/nsc/plugins/Plugin.scala @@ -60,13 +60,15 @@ abstract class Plugin { * @return true to continue, or false to opt out */ def init(options: List[String], error: String => Unit): Boolean = { - if (!options.isEmpty) error(s"Error: $name takes no options") + // call to deprecated method required here, we must continue to support + // code that subclasses that override `processOptions`. + processOptions(options, error) true } @deprecated("use Plugin#init instead", since="2.11") def processOptions(options: List[String], error: String => Unit): Unit = { - init(options, error) + if (!options.isEmpty) error(s"Error: $name takes no options") } /** A description of this plugin's options, suitable as a response diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index d273995e6e..f217d21c35 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -139,6 +139,18 @@ trait ScalaSettings extends AbsScalaSettings val XnoPatmatAnalysis = BooleanSetting ("-Xno-patmat-analysis", "Don't perform exhaustivity/unreachability analysis. Also, ignore @switch annotation.") val XfullLubs = BooleanSetting ("-Xfull-lubs", "Retains pre 2.10 behavior of less aggressive truncation of least upper bounds.") + // XML parsing options + object XxmlSettings extends MultiChoiceEnumeration { + val coalescing = Choice("coalescing", "Convert PCData to Text and coalesce sibling nodes") + def isCoalescing = Xxml contains coalescing + } + val Xxml = MultiChoiceSetting( + name = "-Xxml", + helpArg = "property", + descr = "Configure XML parsing", + domain = XxmlSettings + ) + /** Compatibility stubs for options whose value name did * not previously match the option name. */ @@ -256,6 +268,13 @@ trait ScalaSettings extends AbsScalaSettings def YoptInlineGlobal = Yopt.contains(YoptChoices.inlineGlobal) def YoptInlinerEnabled = YoptInlineProject || YoptInlineGlobal + val YoptInlineHeuristics = ChoiceSetting( + name = "-Yopt-inline-heuristics", + helpArg = "strategy", + descr = "Set the heuristics for inlining decisions.", + choices = List("at-inline-annotated", "everything"), + default = "at-inline-annotated") + object YoptWarningsChoices extends MultiChoiceEnumeration { val none = Choice("none" , "No optimizer warnings.") val atInlineFailedSummary = Choice("at-inline-failed-summary" , "One-line summary if there were @inline method calls that could not be inlined.") @@ -267,7 +286,7 @@ trait ScalaSettings extends AbsScalaSettings val YoptWarnings = MultiChoiceSetting( name = "-Yopt-warnings", - helpArg = "warnings", + helpArg = "warning", descr = "Enable optimizer warnings", domain = YoptWarningsChoices, default = Some(List(YoptWarningsChoices.atInlineFailed.name))) withPostSetHook (self => { @@ -275,6 +294,15 @@ trait ScalaSettings extends AbsScalaSettings else YinlinerWarnings.value = true }) + def YoptWarningEmitAtInlineFailed = + !YoptWarnings.isSetByUser || + YoptWarnings.contains(YoptWarningsChoices.atInlineFailedSummary) || + YoptWarnings.contains(YoptWarningsChoices.atInlineFailed) + + def YoptWarningNoInlineMixed = YoptWarnings.contains(YoptWarningsChoices.noInlineMixed) + def YoptWarningNoInlineMissingBytecode = YoptWarnings.contains(YoptWarningsChoices.noInlineMissingBytecode) + def YoptWarningNoInlineMissingScalaInlineInfoAttr = YoptWarnings.contains(YoptWarningsChoices.noInlineMissingScalaInlineInfoAttr) + private def removalIn212 = "This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug." object YstatisticsPhases extends MultiChoiceEnumeration { val parser, typer, patmat, erasure, cleanup, jvm = Value } @@ -345,12 +373,7 @@ trait ScalaSettings extends AbsScalaSettings /** Test whether this is scaladoc we're looking at */ def isScaladoc = false - /** - * Helper utilities for use by checkConflictingSettings() - */ - def isBCodeActive = !isICodeAskedFor - def isBCodeAskedFor = (Ybackend.value != "GenASM") - def isICodeAskedFor = ((Ybackend.value == "GenASM") || optimiseSettings.exists(_.value) || writeICode.isSetByUser) + def isBCodeActive = Ybackend.value == "GenBCode" object MacroExpand { val None = "none" diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index 1ffa064b78..994bcd8359 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -15,6 +15,7 @@ import scala.collection.mutable.{ ListBuffer, ArrayBuffer } import scala.annotation.switch import scala.reflect.internal.{ JavaAccFlags } import scala.reflect.internal.pickling.{PickleBuffer, ByteCodecs} +import scala.reflect.io.NoAbstractFile import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.util.ClassFileLookup @@ -1022,11 +1023,18 @@ abstract class ClassfileParser { val sflags = jflags.toScalaFlags val owner = ownerForFlags(jflags) val scope = getScope(jflags) - val innerClass = owner.newClass(name.toTypeName, NoPosition, sflags) setInfo completer - val innerModule = owner.newModule(name.toTermName, NoPosition, sflags) setInfo completer + def newStub(name: Name) = + owner.newStubSymbol(name, s"Class file for ${entry.externalName} not found").setFlag(JAVA) - innerModule.moduleClass setInfo loaders.moduleClassLoader - List(innerClass, innerModule.moduleClass) foreach (_.associatedFile = file) + val (innerClass, innerModule) = if (file == NoAbstractFile) { + (newStub(name.toTypeName), newStub(name.toTermName)) + } else { + val cls = owner.newClass(name.toTypeName, NoPosition, sflags) setInfo completer + val mod = owner.newModule(name.toTermName, NoPosition, sflags) setInfo completer + mod.moduleClass setInfo loaders.moduleClassLoader + List(cls, mod.moduleClass) foreach (_.associatedFile = file) + (cls, mod) + } scope enter innerClass scope enter innerModule @@ -1046,10 +1054,8 @@ abstract class ClassfileParser { for (entry <- innerClasses.entries) { // create a new class member for immediate inner classes if (entry.outerName == currentClass) { - val file = classFileLookup.findClassFile(entry.externalName.toString) getOrElse { - throw new AssertionError(s"Class file for ${entry.externalName} not found") - } - enterClassAndModule(entry, file) + val file = classFileLookup.findClassFile(entry.externalName.toString) + enterClassAndModule(entry, file.getOrElse(NoAbstractFile)) } } } diff --git a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala index 3591372bbe..79776485de 100644 --- a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala +++ b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala @@ -207,7 +207,7 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure => } def transformMixinInfo(tp: Type): Type = tp match { - case ClassInfoType(parents, decls, clazz) => + case ClassInfoType(parents, decls, clazz) if clazz.isPackageClass || !clazz.isJavaDefined => if (clazz.needsImplClass) implClass(clazz setFlag lateINTERFACE) // generate an impl class diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index facce9062b..9fdc3a9d72 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -188,14 +188,16 @@ abstract class Erasure extends AddInterfaces /* Drop redundant types (ones which are implemented by some other parent) from the immediate parents. * This is important on Android because there is otherwise an interface explosion. */ - def minimizeParents(parents: List[Type]): List[Type] = { - var rest = parents - var leaves = collection.mutable.ListBuffer.empty[Type] + def minimizeParents(parents: List[Type]): List[Type] = if (parents.isEmpty) parents else { + def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait + + var rest = parents.tail + var leaves = collection.mutable.ListBuffer.empty[Type] += parents.head while(rest.nonEmpty) { val candidate = rest.head val nonLeaf = leaves exists { t => t.typeSymbol isSubClass candidate.typeSymbol } if(!nonLeaf) { - leaves = leaves filterNot { t => candidate.typeSymbol isSubClass t.typeSymbol } + leaves = leaves filterNot { t => isInterfaceOrTrait(t.typeSymbol) && (candidate.typeSymbol isSubClass t.typeSymbol) } leaves += candidate } rest = rest.tail diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index 5e2fe21eec..d1be1558b9 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -376,7 +376,7 @@ abstract class LambdaLift extends InfoTransform { private def addFreeArgs(pos: Position, sym: Symbol, args: List[Tree]) = { free get sym match { - case Some(fvs) => args ++ (fvs.toList map (fv => atPos(pos)(proxyRef(fv)))) + case Some(fvs) => addFree(sym, free = fvs.toList map (fv => atPos(pos)(proxyRef(fv))), original = args) case _ => args } } @@ -388,9 +388,9 @@ abstract class LambdaLift extends InfoTransform { case DefDef(_, _, _, vparams :: _, _, _) => val addParams = cloneSymbols(ps).map(_.setFlag(PARAM)) sym.updateInfo( - lifted(MethodType(sym.info.params ::: addParams, sym.info.resultType))) + lifted(MethodType(addFree(sym, free = addParams, original = sym.info.params), sym.info.resultType))) - copyDefDef(tree)(vparamss = List(vparams ++ freeParams)) + copyDefDef(tree)(vparamss = List(addFree(sym, free = freeParams, original = vparams))) case ClassDef(_, _, _, _) => // SI-6231 // Disabled attempt to to add getters to freeParams @@ -571,4 +571,12 @@ abstract class LambdaLift extends InfoTransform { } } // class LambdaLifter + private def addFree[A](sym: Symbol, free: List[A], original: List[A]): List[A] = { + val prependFree = ( + !sym.isConstructor // this condition is redundant for now. It will be needed if we remove the second condition in 2.12.x + && (settings.Ydelambdafy.value == "method" && sym.isDelambdafyTarget) // SI-8359 Makes the lambda body a viable as the target MethodHandle for a call to LambdaMetafactory + ) + if (prependFree) free ::: original + else original ::: free + } } diff --git a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala index db81eecdf5..e0d96df062 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala @@ -322,7 +322,7 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT case Super(_, mix) => if (sym.isValue && !sym.isMethod || sym.hasAccessorFlag) { if (!settings.overrideVars) - reporter.error(tree.pos, "super may be not be used on " + sym.accessedOrSelf) + reporter.error(tree.pos, "super may not be used on " + sym.accessedOrSelf) } else if (isDisallowed(sym)) { reporter.error(tree.pos, "super not allowed here: use this." + name.decode + " instead") } diff --git a/src/library/scala/collection/TraversableLike.scala b/src/library/scala/collection/TraversableLike.scala index 32d31f0be8..96374ef653 100644 --- a/src/library/scala/collection/TraversableLike.scala +++ b/src/library/scala/collection/TraversableLike.scala @@ -54,7 +54,7 @@ import scala.language.higherKinds * `HashMap` of objects. The traversal order for hash maps will * depend on the hash codes of its elements, and these hash codes might * differ from one run to the next. By contrast, a `LinkedHashMap` - * is ordered because it's `foreach` method visits elements in the + * is ordered because its `foreach` method visits elements in the * order they were inserted into the `HashMap`. * * @author Martin Odersky diff --git a/src/library/scala/collection/TraversableOnce.scala b/src/library/scala/collection/TraversableOnce.scala index 2eab58009c..c5b0d0f085 100644 --- a/src/library/scala/collection/TraversableOnce.scala +++ b/src/library/scala/collection/TraversableOnce.scala @@ -128,8 +128,21 @@ trait TraversableOnce[+A] extends Any with GenTraversableOnce[A] { * @example `Seq("a", 1, 5L).collectFirst({ case x: Int => x*10 }) = Some(10)` */ def collectFirst[B](pf: PartialFunction[A, B]): Option[B] = { - // make sure to use an iterator or `seq` - self.toIterator.foreach(pf.runWith(b => return Some(b))) + // TODO 2.12 -- move out alternate implementations into child classes + val i: Iterator[A] = self match { + case it: Iterator[A] => it + case _: GenIterable[_] => self.toIterator // If it might be parallel, be sure to .seq or use iterator! + case _ => // Not parallel, not iterable--just traverse + self.foreach(pf.runWith(b => return Some(b))) + return None + } + // Presumably the fastest way to get in and out of a partial function is for a sentinel function to return itself + // (Tested to be lower-overhead than runWith. Would be better yet to not need to (formally) allocate it--change in 2.12.) + val sentinel: Function1[A, Any] = new scala.runtime.AbstractFunction1[A, Any]{ def apply(a: A) = this } + while (i.hasNext) { + val x = pf.applyOrElse(i.next, sentinel) + if (x.asInstanceOf[AnyRef] ne sentinel) return Some(x.asInstanceOf[B]) + } None } diff --git a/src/partest-extras/scala/tools/partest/ParserTest.scala b/src/partest-extras/scala/tools/partest/ParserTest.scala new file mode 100644 index 0000000000..e4c92e3dc3 --- /dev/null +++ b/src/partest-extras/scala/tools/partest/ParserTest.scala @@ -0,0 +1,21 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + */ + +package scala.tools.partest + +/** A class for testing parser output. + * Just supply the `code` and update the check file. + */ +abstract class ParserTest extends DirectTest { + + override def extraSettings: String = "-usejavacp -Ystop-after:parser -Xprint:parser" + + override def show(): Unit = { + // redirect err to out, for logging + val prevErr = System.err + System.setErr(System.out) + compile() + System.setErr(prevErr) + } +} diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index d85ec22a84..4a39712ad7 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -794,6 +794,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => final def isAnonymousFunction = isSynthetic && (name containsName tpnme.ANON_FUN_NAME) final def isDelambdafyFunction = isSynthetic && (name containsName tpnme.DELAMBDAFY_LAMBDA_CLASS_NAME) + final def isDelambdafyTarget = isSynthetic && isMethod && (name containsName tpnme.ANON_FUN_NAME) final def isDefinedInPackage = effectiveOwner.isPackageClass final def needsFlatClasses = phase.flatClasses && rawowner != NoSymbol && !rawowner.isPackageClass diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 6efac6d873..237efd004f 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -1285,16 +1285,12 @@ private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUnive jclazz getDeclaredConstructor (effectiveParamClasses: _*) } - private def jArrayClass(elemClazz: jClass[_]): jClass[_] = { - jArray.newInstance(elemClazz, 0).getClass - } - /** The Java class that corresponds to given Scala type. * Pre: Scala type is already transformed to Java level. */ def typeToJavaClass(tpe: Type): jClass[_] = tpe match { case ExistentialType(_, rtpe) => typeToJavaClass(rtpe) - case TypeRef(_, ArrayClass, List(elemtpe)) => jArrayClass(typeToJavaClass(elemtpe)) + case TypeRef(_, ArrayClass, List(elemtpe)) => ScalaRunTime.arrayClass(typeToJavaClass(elemtpe)) case TypeRef(_, sym: ClassSymbol, _) => classToJava(sym.asClass) case tpe @ TypeRef(_, sym: AliasTypeSymbol, _) => typeToJavaClass(tpe.dealias) case SingleType(_, sym: ModuleSymbol) => classToJava(sym.moduleClass.asClass) diff --git a/src/scaladoc/scala/tools/nsc/doc/base/CommentFactoryBase.scala b/src/scaladoc/scala/tools/nsc/doc/base/CommentFactoryBase.scala index d31b877262..fb4ed34571 100755 --- a/src/scaladoc/scala/tools/nsc/doc/base/CommentFactoryBase.scala +++ b/src/scaladoc/scala/tools/nsc/doc/base/CommentFactoryBase.scala @@ -281,13 +281,16 @@ trait CommentFactoryBase { this: MemberLookupBase => parse0(docBody, tags + (key -> value), Some(key), ls, inCodeBlock) case line :: ls if (lastTagKey.isDefined) => - val key = lastTagKey.get - val value = - ((tags get key): @unchecked) match { - case Some(b :: bs) => (b + endOfLine + line) :: bs - case None => oops("lastTagKey set when no tag exists for key") - } - parse0(docBody, tags + (key -> value), lastTagKey, ls, inCodeBlock) + val newtags = if (!line.isEmpty) { + val key = lastTagKey.get + val value = + ((tags get key): @unchecked) match { + case Some(b :: bs) => (b + endOfLine + line) :: bs + case None => oops("lastTagKey set when no tag exists for key") + } + tags + (key -> value) + } else tags + parse0(docBody, newtags, lastTagKey, ls, inCodeBlock) case line :: ls => if (docBody.length > 0) docBody append endOfLine @@ -315,18 +318,18 @@ trait CommentFactoryBase { this: MemberLookupBase => val bodyTags: mutable.Map[TagKey, List[Body]] = mutable.Map(tagsWithoutDiagram mapValues {tag => tag map (parseWikiAtSymbol(_, pos, site))} toSeq: _*) - def oneTag(key: SimpleTagKey): Option[Body] = + def oneTag(key: SimpleTagKey, filterEmpty: Boolean = true): Option[Body] = ((bodyTags remove key): @unchecked) match { - case Some(r :: rs) => + case Some(r :: rs) if !(filterEmpty && r.blocks.isEmpty) => if (!rs.isEmpty) reporter.warning(pos, "Only one '@" + key.name + "' tag is allowed") Some(r) - case None => None + case _ => None } def allTags(key: SimpleTagKey): List[Body] = - (bodyTags remove key) getOrElse Nil + (bodyTags remove key).getOrElse(Nil).filterNot(_.blocks.isEmpty) - def allSymsOneTag(key: TagKey): Map[String, Body] = { + def allSymsOneTag(key: TagKey, filterEmpty: Boolean = true): Map[String, Body] = { val keys: Seq[SymbolTagKey] = bodyTags.keys.toSeq flatMap { case stk: SymbolTagKey if (stk.name == key.name) => Some(stk) @@ -342,11 +345,11 @@ trait CommentFactoryBase { this: MemberLookupBase => reporter.warning(pos, "Only one '@" + key.name + "' tag for symbol " + key.symbol + " is allowed") (key.symbol, bs.head) } - Map.empty[String, Body] ++ pairs + Map.empty[String, Body] ++ (if (filterEmpty) pairs.filterNot(_._2.blocks.isEmpty) else pairs) } def linkedExceptions: Map[String, Body] = { - val m = allSymsOneTag(SimpleTagKey("throws")) + val m = allSymsOneTag(SimpleTagKey("throws"), filterEmpty = false) m.map { case (name,body) => val link = memberLookup(pos, name, site) @@ -372,7 +375,7 @@ trait CommentFactoryBase { this: MemberLookupBase => version0 = oneTag(SimpleTagKey("version")), since0 = oneTag(SimpleTagKey("since")), todo0 = allTags(SimpleTagKey("todo")), - deprecated0 = oneTag(SimpleTagKey("deprecated")), + deprecated0 = oneTag(SimpleTagKey("deprecated"), filterEmpty = false), note0 = allTags(SimpleTagKey("note")), example0 = allTags(SimpleTagKey("example")), constructor0 = oneTag(SimpleTagKey("constructor")), diff --git a/src/scaladoc/scala/tools/nsc/doc/html/HtmlPage.scala b/src/scaladoc/scala/tools/nsc/doc/html/HtmlPage.scala index ce75749859..86155845b0 100644 --- a/src/scaladoc/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/scaladoc/scala/tools/nsc/doc/html/HtmlPage.scala @@ -206,25 +206,42 @@ abstract class HtmlPage extends Page { thisPage => case tpl :: tpls => templateToHtml(tpl) ++ sep ++ templatesToHtml(tpls, sep) } - /** Returns the _big image name corresponding to the DocTemplate Entity (upper left icon) */ - def docEntityKindToBigImage(ety: DocTemplateEntity) = - if (ety.isTrait && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None) "trait_to_object_big.png" - else if (ety.isTrait) "trait_big.png" - else if (ety.isClass && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None) "class_to_object_big.png" - else if (ety.isClass) "class_big.png" - else if ((ety.isAbstractType || ety.isAliasType) && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None) "type_to_object_big.png" - else if ((ety.isAbstractType || ety.isAliasType)) "type_big.png" - else if (ety.isObject && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None && ety.companion.get.isClass) "object_to_class_big.png" - else if (ety.isObject && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None && ety.companion.get.isTrait) "object_to_trait_big.png" - else if (ety.isObject && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None && (ety.companion.get.isAbstractType || ety.companion.get.isAliasType)) "object_to_trait_big.png" - else if (ety.isObject) "object_big.png" - else if (ety.isPackage) "package_big.png" - else "class_big.png" // FIXME: an entity *should* fall into one of the above categories, but AnyRef is somehow not + object Image extends Enumeration { + val Trait, Class, Type, Object, Package = Value + } + + /** Returns the _big image name and the alt attribute + * corresponding to the DocTemplate Entity (upper left icon) */ + def docEntityKindToBigImage(ety: DocTemplateEntity) = { + def entityToImage(e: DocTemplateEntity) = + if (e.isTrait) Image.Trait + else if (e.isClass) Image.Class + else if (e.isAbstractType || e.isAliasType) Image.Type + else if (e.isObject) Image.Object + else if (e.isPackage) Image.Package + else { + // FIXME: an entity *should* fall into one of the above categories, + // but AnyRef is somehow not + Image.Class + } + + val image = entityToImage(ety) + val companionImage = ety.companion filter { + e => e.visibility.isPublic && ! e.inSource.isEmpty + } map { entityToImage } + + (image, companionImage) match { + case (from, Some(to)) => + ((from + "_to_" + to + "_big.png").toLowerCase, from + "/" + to) + case (from, None) => + ((from + "_big.png").toLowerCase, from.toString) + } + } def permalink(template: Entity, isSelf: Boolean = true): Elem = <span class="permalink"> <a href={ memberToUrl(template, isSelf) } title="Permalink" target="_top"> - <img src={ relativeLinkTo(List("permalink.png", "lib")) } /> + <img src={ relativeLinkTo(List("permalink.png", "lib")) } alt="Permalink" /> </a> </span> diff --git a/src/scaladoc/scala/tools/nsc/doc/html/page/Template.scala b/src/scaladoc/scala/tools/nsc/doc/html/page/Template.scala index e10c54a414..c384ed7034 100644 --- a/src/scaladoc/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/scaladoc/scala/tools/nsc/doc/html/page/Template.scala @@ -103,11 +103,13 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp <body class={ if (tpl.isType) "type" else "value" }> <div id="definition"> { + val (src, alt) = docEntityKindToBigImage(tpl) + tpl.companion match { case Some(companion) if (companion.visibility.isPublic && companion.inSource != None) => - <a href={relativeLinkTo(companion)} title={docEntityKindToCompanionTitle(tpl)}><img src={ relativeLinkTo(List(docEntityKindToBigImage(tpl), "lib")) }/></a> + <a href={relativeLinkTo(companion)} title={docEntityKindToCompanionTitle(tpl)}><img alt={alt} src={ relativeLinkTo(List(src, "lib")) }/></a> case _ => - <img src={ relativeLinkTo(List(docEntityKindToBigImage(tpl), "lib")) }/> + <img alt={alt} src={ relativeLinkTo(List(src, "lib")) }/> }} { owner } <h1>{ displayName }</h1>{ diff --git a/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/template.css b/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/template.css index e129e6cf6a..e84d7c1ca6 100644 --- a/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/template.css +++ b/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/template.css @@ -210,6 +210,7 @@ dl.attributes > dd { display: block; padding-left: 10em; margin-bottom: 5px; + min-height: 15px; } #template .values > h3 { @@ -669,6 +670,7 @@ div.fullcomment dl.paramcmts > dd { padding-left: 10px; margin-bottom: 5px; margin-left: 70px; + min-height: 15px; } /* Members filter tool */ diff --git a/test/files/neg/case-collision2.flags b/test/files/neg/case-collision2.flags index 5bfa9da5c5..bea46902c9 100644 --- a/test/files/neg/case-collision2.flags +++ b/test/files/neg/case-collision2.flags @@ -1 +1 @@ --Ynooptimize -Ybackend:GenBCode -Xfatal-warnings +-Ybackend:GenBCode -Xfatal-warnings diff --git a/test/files/neg/inlineMaxSize.check b/test/files/neg/inlineMaxSize.check new file mode 100644 index 0000000000..d218a8b6e2 --- /dev/null +++ b/test/files/neg/inlineMaxSize.check @@ -0,0 +1,9 @@ +inlineMaxSize.scala:7: warning: C::i()I is annotated @inline but could not be inlined: +The size of the callsite method C::j()I +would exceed the JVM method size limit after inlining C::i()I. + + @inline final def j = i + i + ^ +error: No warnings can be incurred under -Xfatal-warnings. +one warning found +one error found diff --git a/test/files/neg/inlineMaxSize.flags b/test/files/neg/inlineMaxSize.flags new file mode 100644 index 0000000000..9c6b811622 --- /dev/null +++ b/test/files/neg/inlineMaxSize.flags @@ -0,0 +1 @@ +-Ybackend:GenBCode -Ydelambdafy:method -Yopt:l:classpath -Yopt-warnings -Xfatal-warnings
\ No newline at end of file diff --git a/test/files/neg/inlineMaxSize.scala b/test/files/neg/inlineMaxSize.scala new file mode 100644 index 0000000000..16dc0d9538 --- /dev/null +++ b/test/files/neg/inlineMaxSize.scala @@ -0,0 +1,8 @@ +// not a JUnit test because of https://github.com/scala-opt/scala/issues/23 +class C { + @inline final def f = 0 + @inline final def g = f + f + f + f + f + f + f + f + f + f + @inline final def h = g + g + g + g + g + g + g + g + g + g + @inline final def i = h + h + h + h + h + h + h + h + h + h + @inline final def j = i + i +} diff --git a/test/files/neg/t0899.check b/test/files/neg/t0899.check index 8b71be8e0c..28cb06ae5a 100644 --- a/test/files/neg/t0899.check +++ b/test/files/neg/t0899.check @@ -1,10 +1,10 @@ -t0899.scala:9: error: super may be not be used on value o +t0899.scala:9: error: super may not be used on value o override val o = "Ha! " + super.o ^ -t0899.scala:11: error: super may be not be used on variable v +t0899.scala:11: error: super may not be used on variable v super.v = "aa" ^ -t0899.scala:12: error: super may be not be used on variable v +t0899.scala:12: error: super may not be used on variable v println(super.v) ^ three errors found diff --git a/test/files/neg/t562.check b/test/files/neg/t562.check index 8c3823642a..95be075af1 100644 --- a/test/files/neg/t562.check +++ b/test/files/neg/t562.check @@ -1,4 +1,4 @@ -t562.scala:10: error: super may be not be used on value y +t562.scala:10: error: super may not be used on value y override val y = super.y; ^ one error found diff --git a/test/files/pos/t3368.flags b/test/files/pos/t3368.flags new file mode 100644 index 0000000000..cb20509902 --- /dev/null +++ b/test/files/pos/t3368.flags @@ -0,0 +1 @@ +-Ystop-after:parser diff --git a/test/files/pos/t3368.scala b/test/files/pos/t3368.scala new file mode 100644 index 0000000000..c8e861a899 --- /dev/null +++ b/test/files/pos/t3368.scala @@ -0,0 +1,5 @@ + +trait X { + // error: in XML literal: name expected, but char '!' cannot start a name + def x = <![CDATA[hi & bye]]> <![CDATA[red & black]]> +} diff --git a/test/files/pos/t8359-closelim-crash.flags b/test/files/pos/t8359-closelim-crash.flags new file mode 100644 index 0000000000..49d036a887 --- /dev/null +++ b/test/files/pos/t8359-closelim-crash.flags @@ -0,0 +1 @@ +-optimize diff --git a/test/files/pos/t8359-closelim-crash.scala b/test/files/pos/t8359-closelim-crash.scala new file mode 100644 index 0000000000..1413694d10 --- /dev/null +++ b/test/files/pos/t8359-closelim-crash.scala @@ -0,0 +1,23 @@ +package test + +// This is a minimization of code that crashed the compiler during bootstrapping +// in the first iteration of https://github.com/scala/scala/pull/4373, the PR +// that adjusted the order of free and declared params in LambdaLift. + +// Was: +// java.lang.AssertionError: assertion failed: +// Record Record(<$anon: Function1>,Map(value a$1 -> Deref(LocalVar(value b)))) does not contain a field value b$1 +// at scala.tools.nsc.Global.assert(Global.scala:262) +// at scala.tools.nsc.backend.icode.analysis.CopyPropagation$copyLattice$State.getFieldNonRecordValue(CopyPropagation.scala:113) +// at scala.tools.nsc.backend.icode.analysis.CopyPropagation$copyLattice$State.getFieldNonRecordValue(CopyPropagation.scala:122) +// at scala.tools.nsc.backend.opt.ClosureElimination$ClosureElim$$anonfun$analyzeMethod$1$$anonfun$apply$2.replaceFieldAccess$1(ClosureElimination.scala:124) +class Typer { + def bar(a: Boolean, b: Boolean): Unit = { + @inline + def baz(): Unit = { + ((_: Any) => (Typer.this, a, b)).apply("") + } + ((_: Any) => baz()).apply("") + } +} + diff --git a/test/files/pos/t9239/Declaration.scala b/test/files/pos/t9239/Declaration.scala new file mode 100644 index 0000000000..452dcc1e77 --- /dev/null +++ b/test/files/pos/t9239/Declaration.scala @@ -0,0 +1,3 @@ +class Foo[A] +trait Bar[A] extends Foo[A] +class Baz[A] extends Bar[A] diff --git a/test/files/pos/t9239/Usage.java b/test/files/pos/t9239/Usage.java new file mode 100644 index 0000000000..d1e3fb0c3e --- /dev/null +++ b/test/files/pos/t9239/Usage.java @@ -0,0 +1,15 @@ +/** + * Used to fail with: + * + * Usage.java:5: error: incompatible types: Baz<String> cannot be converted to Foo<String> + * foo(f); + * ^ + */ +public class Usage { + public Usage() { + Baz<String> f = null; + foo(f); + } + + public void foo(Foo<String> f) { }; +} diff --git a/test/files/run/t3368.check b/test/files/run/t3368.check new file mode 100644 index 0000000000..1d9dd677f6 --- /dev/null +++ b/test/files/run/t3368.check @@ -0,0 +1,46 @@ +[[syntax trees at end of parser]] // newSource1.scala +package <empty> { + abstract trait X extends scala.AnyRef { + def $init$() = { + () + }; + def x = { + val $buf = new _root_.scala.xml.NodeBuffer(); + $buf.$amp$plus(new _root_.scala.xml.PCData("hi & bye")); + $buf.$amp$plus(new _root_.scala.xml.PCData("red & black")); + $buf + } + }; + abstract trait Y extends scala.AnyRef { + def $init$() = { + () + }; + def y = { + { + new _root_.scala.xml.Elem(null, "a", _root_.scala.xml.Null, $scope, false, ({ + val $buf = new _root_.scala.xml.NodeBuffer(); + $buf.$amp$plus({ + { + new _root_.scala.xml.Elem(null, "b", _root_.scala.xml.Null, $scope, true) + } + }); + $buf.$amp$plus(new _root_.scala.xml.Text("starthi & bye")); + $buf.$amp$plus({ + { + new _root_.scala.xml.Elem(null, "c", _root_.scala.xml.Null, $scope, true) + } + }); + $buf.$amp$plus(new _root_.scala.xml.Text("world")); + $buf.$amp$plus({ + { + new _root_.scala.xml.Elem(null, "d", _root_.scala.xml.Null, $scope, true) + } + }); + $buf.$amp$plus(new _root_.scala.xml.Text("stuffred & black")); + $buf + }: _*)) + } + } + } +} + diff --git a/test/files/run/t3368.scala b/test/files/run/t3368.scala new file mode 100644 index 0000000000..15acba5099 --- /dev/null +++ b/test/files/run/t3368.scala @@ -0,0 +1,18 @@ + +import scala.tools.partest.ParserTest + + +object Test extends ParserTest { + + override def code = """ + trait X { + // error: in XML literal: name expected, but char '!' cannot start a name + def x = <![CDATA[hi & bye]]> <![CDATA[red & black]]> + } + trait Y { + def y = <a><b/>start<![CDATA[hi & bye]]><c/>world<d/>stuff<![CDATA[red & black]]></a> + } + """ + + override def extraSettings = s"${super.extraSettings} -Xxml:coalescing" +} diff --git a/test/files/run/t5699.scala b/test/files/run/t5699.scala index ec3b1d26b4..409bcd250c 100755 --- a/test/files/run/t5699.scala +++ b/test/files/run/t5699.scala @@ -1,21 +1,13 @@ -import scala.tools.partest.DirectTest +import scala.tools.partest.ParserTest import scala.reflect.internal.util.BatchSourceFile -object Test extends DirectTest { +object Test extends ParserTest { // Java code override def code = """ |public @interface MyAnnotation { String value(); } """.stripMargin - override def extraSettings: String = "-usejavacp -Ystop-after:typer -Xprint:parser" - - override def show(): Unit = { - // redirect err to out, for logging - val prevErr = System.err - System.setErr(System.out) - compile() - System.setErr(prevErr) - } + override def extraSettings: String = "-usejavacp -Ystop-after:namer -Xprint:parser" override def newSources(sourceCodes: String*) = { assert(sourceCodes.size == 1) diff --git a/test/files/run/t7407.flags b/test/files/run/t7407.flags index be4ef0798a..ffc65f4b81 100644 --- a/test/files/run/t7407.flags +++ b/test/files/run/t7407.flags @@ -1 +1 @@ --Ynooptimise -Yopt:l:none -Ybackend:GenBCode +-Yopt:l:none -Ybackend:GenBCode diff --git a/test/files/run/t7407b.flags b/test/files/run/t7407b.flags index c8547a27dc..c30091d3de 100644 --- a/test/files/run/t7407b.flags +++ b/test/files/run/t7407b.flags @@ -1 +1 @@ --Ynooptimise -Ybackend:GenBCode +-Ybackend:GenBCode diff --git a/test/files/run/t7741a/GroovyInterface$1Dump.java b/test/files/run/t7741a/GroovyInterface$1Dump.java new file mode 100644 index 0000000000..0c0eab3f1b --- /dev/null +++ b/test/files/run/t7741a/GroovyInterface$1Dump.java @@ -0,0 +1,222 @@ +import java.util.*; +import scala.tools.asm.*; + +// generated with +// git clone alewando/scala_groovy_interop +// SCALA_HOME=... GROOVY_HOME=... ant +// cd /code/scala2 +// java -classpath build/asm/classes:/Users/jason/code/scala_groovy_interop/classes:/code/scala2/build/pack/lib/scala-library.jar:/usr/local/Cellar/groovy/2.4.1/libexec/embeddable/groovy-all-2.4.1.jar scala.tools.asm.util.ASMifier 'GroovyInterface$1' +// java -classpath build/asm/classes:/Users/jason/code/scala_groovy_interop/classes:/code/scala2/build/pack/lib/scala-library.jar:/usr/local/Cellar/groovy/2.4.1/libexec/embeddable/groovy-all-2.4.1.jar scala.tools.asm.util.ASMifier 'GroovyInterface$1' +public class GroovyInterface$1Dump implements Opcodes { + + public static byte[] dump () throws Exception { + + ClassWriter cw = new ClassWriter(0); + FieldVisitor fv; + MethodVisitor mv; + AnnotationVisitor av0; + + cw.visit(V1_5, ACC_SUPER + ACC_SYNTHETIC, "GroovyInterface$1", null, "java/lang/Object", new String[] {}); + + cw.visitInnerClass("GroovyInterface$1", "GroovyInterface", "1", ACC_SYNTHETIC); + + { + fv = cw.visitField(ACC_STATIC + ACC_SYNTHETIC, "$class$GroovyInterface", "Ljava/lang/Class;", null, null); + fv.visitEnd(); + } + { + fv = cw.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$staticClassInfo", "Lorg/codehaus/groovy/reflection/ClassInfo;", null, null); + fv.visitEnd(); + } + { + fv = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_TRANSIENT + ACC_SYNTHETIC, "__$stMC", "Z", null, null); + fv.visitEnd(); + } + { + fv = cw.visitField(ACC_PRIVATE + ACC_TRANSIENT + ACC_SYNTHETIC, "metaClass", "Lgroovy/lang/MetaClass;", null, null); + fv.visitEnd(); + } + { + fv = cw.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$callSiteArray", "Ljava/lang/ref/SoftReference;", null, null); + fv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); + mv.visitMethodInsn(INVOKESTATIC, "GroovyInterface$1", "$getCallSiteArray", "()[Lorg/codehaus/groovy/runtime/callsite/CallSite;", false); + mv.visitVarInsn(ASTORE, 1); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, "GroovyInterface$1", "$getStaticMetaClass", "()Lgroovy/lang/MetaClass;", false); + mv.visitVarInsn(ASTORE, 2); + mv.visitVarInsn(ALOAD, 2); + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(SWAP); + mv.visitFieldInsn(PUTFIELD, "GroovyInterface$1", "metaClass", "Lgroovy/lang/MetaClass;"); + mv.visitVarInsn(ALOAD, 2); + mv.visitInsn(POP); + mv.visitInsn(RETURN); + mv.visitMaxs(2, 3); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PROTECTED + ACC_SYNTHETIC, "$getStaticMetaClass", "()Lgroovy/lang/MetaClass;", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false); + mv.visitLdcInsn(Type.getType("LGroovyInterface$1;")); + Label l0 = new Label(); + mv.visitJumpInsn(IF_ACMPEQ, l0); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/ScriptBytecodeAdapter", "initMetaClass", "(Ljava/lang/Object;)Lgroovy/lang/MetaClass;", false); + mv.visitInsn(ARETURN); + mv.visitLabel(l0); + mv.visitFieldInsn(GETSTATIC, "GroovyInterface$1", "$staticClassInfo", "Lorg/codehaus/groovy/reflection/ClassInfo;"); + mv.visitVarInsn(ASTORE, 1); + mv.visitVarInsn(ALOAD, 1); + Label l1 = new Label(); + mv.visitJumpInsn(IFNONNULL, l1); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false); + mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/reflection/ClassInfo", "getClassInfo", "(Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;", false); + mv.visitInsn(DUP); + mv.visitVarInsn(ASTORE, 1); + mv.visitFieldInsn(PUTSTATIC, "GroovyInterface$1", "$staticClassInfo", "Lorg/codehaus/groovy/reflection/ClassInfo;"); + mv.visitLabel(l1); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, "org/codehaus/groovy/reflection/ClassInfo", "getMetaClass", "()Lgroovy/lang/MetaClass;", false); + mv.visitInsn(ARETURN); + mv.visitMaxs(2, 2); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PUBLIC + ACC_SYNTHETIC, "getMetaClass", "()Lgroovy/lang/MetaClass;", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, "GroovyInterface$1", "metaClass", "Lgroovy/lang/MetaClass;"); + mv.visitInsn(DUP); + Label l0 = new Label(); + mv.visitJumpInsn(IFNULL, l0); + mv.visitInsn(ARETURN); + mv.visitLabel(l0); + mv.visitInsn(POP); + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKEVIRTUAL, "GroovyInterface$1", "$getStaticMetaClass", "()Lgroovy/lang/MetaClass;", false); + mv.visitFieldInsn(PUTFIELD, "GroovyInterface$1", "metaClass", "Lgroovy/lang/MetaClass;"); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, "GroovyInterface$1", "metaClass", "Lgroovy/lang/MetaClass;"); + mv.visitInsn(ARETURN); + mv.visitMaxs(2, 1); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PUBLIC + ACC_SYNTHETIC, "setMetaClass", "(Lgroovy/lang/MetaClass;)V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitFieldInsn(PUTFIELD, "GroovyInterface$1", "metaClass", "Lgroovy/lang/MetaClass;"); + mv.visitInsn(RETURN); + mv.visitMaxs(2, 2); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PUBLIC + ACC_SYNTHETIC, "invokeMethod", "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, "GroovyInterface$1", "getMetaClass", "()Lgroovy/lang/MetaClass;", false); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEINTERFACE, "groovy/lang/MetaClass", "invokeMethod", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", true); + mv.visitInsn(ARETURN); + mv.visitMaxs(4, 3); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PUBLIC + ACC_SYNTHETIC, "getProperty", "(Ljava/lang/String;)Ljava/lang/Object;", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, "GroovyInterface$1", "getMetaClass", "()Lgroovy/lang/MetaClass;", false); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEINTERFACE, "groovy/lang/MetaClass", "getProperty", "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;", true); + mv.visitInsn(ARETURN); + mv.visitMaxs(3, 2); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PUBLIC + ACC_SYNTHETIC, "setProperty", "(Ljava/lang/String;Ljava/lang/Object;)V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, "GroovyInterface$1", "getMetaClass", "()Lgroovy/lang/MetaClass;", false); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEINTERFACE, "groovy/lang/MetaClass", "setProperty", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)V", true); + mv.visitInsn(RETURN); + mv.visitMaxs(4, 3); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null); + mv.visitCode(); + mv.visitLdcInsn(Type.getType("LGroovyInterface;")); + mv.visitVarInsn(ASTORE, 0); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(PUTSTATIC, "GroovyInterface$1", "$class$GroovyInterface", "Ljava/lang/Class;"); + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(POP); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$createCallSiteArray", "()Lorg/codehaus/groovy/runtime/callsite/CallSiteArray;", null, null); + mv.visitCode(); + mv.visitLdcInsn(new Integer(0)); + mv.visitTypeInsn(ANEWARRAY, "java/lang/String"); + mv.visitVarInsn(ASTORE, 0); + mv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/callsite/CallSiteArray"); + mv.visitInsn(DUP); + mv.visitLdcInsn(Type.getType("LGroovyInterface$1;")); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "org/codehaus/groovy/runtime/callsite/CallSiteArray", "<init>", "(Ljava/lang/Class;[Ljava/lang/String;)V", false); + mv.visitInsn(ARETURN); + mv.visitMaxs(4, 1); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC + ACC_SYNTHETIC, "$getCallSiteArray", "()[Lorg/codehaus/groovy/runtime/callsite/CallSite;", null, null); + mv.visitCode(); + mv.visitFieldInsn(GETSTATIC, "GroovyInterface$1", "$callSiteArray", "Ljava/lang/ref/SoftReference;"); + Label l0 = new Label(); + mv.visitJumpInsn(IFNULL, l0); + mv.visitFieldInsn(GETSTATIC, "GroovyInterface$1", "$callSiteArray", "Ljava/lang/ref/SoftReference;"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ref/SoftReference", "get", "()Ljava/lang/Object;", false); + mv.visitTypeInsn(CHECKCAST, "org/codehaus/groovy/runtime/callsite/CallSiteArray"); + mv.visitInsn(DUP); + mv.visitVarInsn(ASTORE, 0); + Label l1 = new Label(); + mv.visitJumpInsn(IFNONNULL, l1); + mv.visitLabel(l0); + mv.visitMethodInsn(INVOKESTATIC, "GroovyInterface$1", "$createCallSiteArray", "()Lorg/codehaus/groovy/runtime/callsite/CallSiteArray;", false); + mv.visitVarInsn(ASTORE, 0); + mv.visitTypeInsn(NEW, "java/lang/ref/SoftReference"); + mv.visitInsn(DUP); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/ref/SoftReference", "<init>", "(Ljava/lang/Object;)V", false); + mv.visitFieldInsn(PUTSTATIC, "GroovyInterface$1", "$callSiteArray", "Ljava/lang/ref/SoftReference;"); + mv.visitLabel(l1); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, "org/codehaus/groovy/runtime/callsite/CallSiteArray", "array", "[Lorg/codehaus/groovy/runtime/callsite/CallSite;"); + mv.visitInsn(ARETURN); + mv.visitMaxs(3, 1); + mv.visitEnd(); + } + cw.visitEnd(); + + return cw.toByteArray(); + } +} diff --git a/test/files/run/t7741a/GroovyInterfaceDump.java b/test/files/run/t7741a/GroovyInterfaceDump.java new file mode 100644 index 0000000000..87c09e272f --- /dev/null +++ b/test/files/run/t7741a/GroovyInterfaceDump.java @@ -0,0 +1,51 @@ +import java.util.*; +import scala.tools.asm.*; + +// generated with +// git clone alewando/scala_groovy_interop +// SCALA_HOME=... GROOVY_HOME=... ant +// cd /code/scala2 +// java -classpath build/asm/classes:/Users/jason/code/scala_groovy_interop/classes:/code/scala2/build/pack/lib/scala-library.jar:/usr/local/Cellar/groovy/2.4.1/libexec/embeddable/groovy-all-2.4.1.jar scala.tools.asm.util.ASMifier 'GroovyInterface$1' +// java -classpath build/asm/classes:/Users/jason/code/scala_groovy_interop/classes:/code/scala2/build/pack/lib/scala-library.jar:/usr/local/Cellar/groovy/2.4.1/libexec/embeddable/groovy-all-2.4.1.jar scala.tools.asm.util.ASMifier 'GroovyInterface$1' +public class GroovyInterfaceDump implements Opcodes { + + public static byte[] dump () throws Exception { + + ClassWriter cw = new ClassWriter(0); + FieldVisitor fv; + MethodVisitor mv; + AnnotationVisitor av0; + + cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "GroovyInterface", null, "java/lang/Object", null); + + cw.visitInnerClass("GroovyInterface$1", "GroovyInterface", "1", ACC_SYNTHETIC); + + cw.visitInnerClass("GroovyInterface$__clinit__closure1", null, null, 0); + + { + fv = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "closure", "Ljava/lang/Object;", null, null); + fv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null); + mv.visitCode(); + mv.visitTypeInsn(NEW, "GroovyInterface$__clinit__closure1"); + mv.visitInsn(DUP); + mv.visitFieldInsn(GETSTATIC, "GroovyInterface$1", "$class$GroovyInterface", "Ljava/lang/Class;"); + mv.visitFieldInsn(GETSTATIC, "GroovyInterface$1", "$class$GroovyInterface", "Ljava/lang/Class;"); + mv.visitMethodInsn(INVOKESPECIAL, "GroovyInterface$__clinit__closure1", "<init>", "(Ljava/lang/Object;Ljava/lang/Object;)V", false); + mv.visitVarInsn(ASTORE, 0); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(PUTSTATIC, "GroovyInterface", "closure", "Ljava/lang/Object;"); + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(POP); + mv.visitInsn(RETURN); + mv.visitMaxs(4, 1); + mv.visitEnd(); + } + cw.visitEnd(); + + return cw.toByteArray(); + } +} + diff --git a/test/files/run/t7741a/Test.scala b/test/files/run/t7741a/Test.scala new file mode 100644 index 0000000000..a75cb6c9eb --- /dev/null +++ b/test/files/run/t7741a/Test.scala @@ -0,0 +1,47 @@ +import java.io.{ByteArrayInputStream, FileOutputStream, BufferedOutputStream} +import java.util + +import java.io.File + +import scala.tools.partest.DirectTest + +object Test extends DirectTest { + + def code = "" + + override def show(): Unit = { + + val class1: Array[Byte] = GroovyInterfaceDump.dump() + val class2: Array[Byte] = GroovyInterface$1Dump.dump() + def writeFile(contents: Array[Byte], f: java.io.File): Unit = { + val out = new BufferedOutputStream(new FileOutputStream(f)) + try { + out.write(contents) + } finally out.close() + } + + val outdir = testOutput.jfile + + // interface GroovyInterface { + // + // // This is the line that causes scalac to choke. + // // It results in a GroovyInterface$1 class, which is a non-static inner class but it's constructor does not + // // include the implicit parameter that is the immediate enclosing instance. + // // See http://jira.codehaus.org/browse/GROOVY-7312 + // // + // // Scalac error: + // // [scalac] error: error while loading 1, class file '..../scala_groovy_interop/classes/com/example/groovy/GroovyInterface$1.class' is broken + // // [scalac] (class java.util.NoSuchElementException/head of empty list) + // final static def closure = { x -> "banana" } + // + // } + writeFile(GroovyInterfaceDump.dump(), new File(outdir, "GroovyInterface.class")) + writeFile(GroovyInterface$1Dump.dump(), new File(outdir, "GroovyInterface$1.class")) + compileCode("object Test { def foo(g: GroovyInterface) = g.toString }") + } + + def compileCode(code: String) = { + val classpath = List(sys.props("partest.lib"), testOutput.path) mkString sys.props("path.separator") + compileString(newCompiler("-cp", classpath, "-d", testOutput.path))(code) + } +} diff --git a/test/files/run/t7741b.check b/test/files/run/t7741b.check new file mode 100644 index 0000000000..a19e54aa3a --- /dev/null +++ b/test/files/run/t7741b.check @@ -0,0 +1,3 @@ +1. Don't refer to Inner +2. Refering to Inner +pos: NoPosition Class file for HasInner$Inner not found ERROR diff --git a/test/files/run/t7741b/HasInner.java b/test/files/run/t7741b/HasInner.java new file mode 100644 index 0000000000..a1d0d0d81a --- /dev/null +++ b/test/files/run/t7741b/HasInner.java @@ -0,0 +1,3 @@ +class HasInner { + class Inner {} +} diff --git a/test/files/run/t7741b/Test.scala b/test/files/run/t7741b/Test.scala new file mode 100644 index 0000000000..569ae6b679 --- /dev/null +++ b/test/files/run/t7741b/Test.scala @@ -0,0 +1,29 @@ +import java.io.File + +import scala.tools.partest.StoreReporterDirectTest + +object Test extends StoreReporterDirectTest { + + def code = "" + + override def show(): Unit = { + deleteClass("HasInner$Inner") + println("1. Don't refer to Inner") + compileCode("class Test { def test(x: HasInner) = x }") + assert(filteredInfos.isEmpty, filteredInfos) + println("2. Refering to Inner") + compileCode("class Test { def test(x: HasInner#Inner) = x }") + println(filteredInfos.mkString("\n")) + } + + def deleteClass(name: String) { + val classFile = new File(testOutput.path, name + ".class") + assert(classFile.exists) + assert(classFile.delete()) + } + + def compileCode(code: String) = { + val classpath = List(sys.props("partest.lib"), testOutput.path) mkString sys.props("path.separator") + compileString(newCompiler("-cp", classpath, "-d", testOutput.path))(code) + } +} diff --git a/test/files/run/t8845.flags b/test/files/run/t8845.flags index aada25f80d..c30091d3de 100644 --- a/test/files/run/t8845.flags +++ b/test/files/run/t8845.flags @@ -1 +1 @@ --Ybackend:GenBCode -Ynooptimize +-Ybackend:GenBCode diff --git a/test/files/run/t8925.flags b/test/files/run/t8925.flags index be4ef0798a..ffc65f4b81 100644 --- a/test/files/run/t8925.flags +++ b/test/files/run/t8925.flags @@ -1 +1 @@ --Ynooptimise -Yopt:l:none -Ybackend:GenBCode +-Yopt:l:none -Ybackend:GenBCode diff --git a/test/files/run/t9252.check b/test/files/run/t9252.check new file mode 100644 index 0000000000..b00d748f7f --- /dev/null +++ b/test/files/run/t9252.check @@ -0,0 +1 @@ +class [Lscala.runtime.BoxedUnit; diff --git a/test/files/run/t9252.scala b/test/files/run/t9252.scala new file mode 100644 index 0000000000..da698948e1 --- /dev/null +++ b/test/files/run/t9252.scala @@ -0,0 +1,5 @@ +import scala.reflect.runtime.universe._ + +object Test extends App { + println(rootMirror.runtimeClass(typeOf[Array[Unit]])) +}
\ No newline at end of file diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala index 5d5215d887..d0ffd06b01 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala @@ -8,7 +8,6 @@ import scala.reflect.io.VirtualDirectory import scala.tools.asm.Opcodes import scala.tools.asm.tree.{ClassNode, MethodNode} import scala.tools.cmd.CommandLineParser -import scala.tools.nsc.backend.jvm.opt.LocalOpt import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.reporters.StoreReporter import scala.tools.nsc.settings.MutableSettings @@ -157,12 +156,6 @@ object CodeGenTools { assertTrue(h.start == insVec(startIndex) && h.end == insVec(endIndex) && h.handler == insVec(handlerIndex)) } - val localOpt = { - val settings = new MutableSettings(msg => throw new IllegalArgumentException(msg)) - settings.processArguments(List("-Yopt:l:method"), processAll = true) - new LocalOpt(settings) - } - import scala.language.implicitConversions implicit def aliveInstruction(ins: Instruction): (Instruction, Boolean) = (ins, true) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala index 7b0504fec0..cb01f3d164 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala @@ -40,7 +40,7 @@ class EmptyExceptionHandlersTest extends ClearAfterClass { Op(RETURN) ) assertTrue(convertMethod(asmMethod).handlers.length == 1) - localOpt.removeEmptyExceptionHandlers(asmMethod) + LocalOptImpls.removeEmptyExceptionHandlers(asmMethod) assertTrue(convertMethod(asmMethod).handlers.isEmpty) } @@ -61,7 +61,7 @@ class EmptyExceptionHandlersTest extends ClearAfterClass { Op(RETURN) ) assertTrue(convertMethod(asmMethod).handlers.length == 1) - localOpt.removeEmptyExceptionHandlers(asmMethod) + LocalOptImpls.removeEmptyExceptionHandlers(asmMethod) assertTrue(convertMethod(asmMethod).handlers.isEmpty) } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala index 8c0168826e..7283e20745 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala @@ -42,14 +42,14 @@ class EmptyLabelsAndLineNumbersTest { ) val method = genMethod()(ops.map(_._1): _*) - assertTrue(localOpt.removeEmptyLineNumbers(method)) + assertTrue(LocalOptImpls.removeEmptyLineNumbers(method)) assertSameCode(instructionsFromMethod(method), ops.filter(_._2).map(_._1)) } @Test def badlyLocatedLineNumbers(): Unit = { def t(ops: Instruction*) = - assertThrows[AssertionError](localOpt.removeEmptyLineNumbers(genMethod()(ops: _*))) + assertThrows[AssertionError](LocalOptImpls.removeEmptyLineNumbers(genMethod()(ops: _*))) // line numbers have to be right after their referenced label node t(LineNumber(0, Label(1)), Label(1)) @@ -88,7 +88,7 @@ class EmptyLabelsAndLineNumbersTest { ) val method = genMethod(handlers = handler)(ops(2, 3, 8, 8, 9, 11).map(_._1): _*) - assertTrue(localOpt.removeEmptyLabelNodes(method)) + assertTrue(LocalOptImpls.removeEmptyLabelNodes(method)) val m = convertMethod(method) assertSameCode(m.instructions, ops(1, 1, 7, 7, 7, 10).filter(_._2).map(_._1)) assertTrue(m.handlers match { 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 fedc074a15..029caa995c 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala @@ -143,4 +143,52 @@ class InlineWarningTest extends ClearAfterClass { compileClasses(newCompiler(extraArgs = InlineWarningTest.argsNoWarn + " -Yopt-warnings:no-inline-mixed"))(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; warns.exists(i.msg contains _)}) assert(c == 2, c) } + + @Test + def cannotInlinePrivateCallIntoDifferentClass(): Unit = { + val code = + """class M { + | @inline final def f = { + | @noinline def nested = 0 + | nested + | } + | + | def t = f // ok + |} + | + |class N { + | def t(a: M) = a.f // not possible + |} + """.stripMargin + + val warn = + """M::f()I is annotated @inline but could not be inlined: + |The callee M::f()I contains the instruction INVOKESPECIAL M.nested$1 ()I + |that would cause an IllegalAccessError when inlined into class N""".stripMargin + + var c = 0 + compile(code, allowMessage = i => { c += 1; i.msg contains warn }) + assert(c == 1, c) + } + + @Test + def cannotMixStrictfp(): Unit = { + val code = + """import annotation.strictfp + |class C { + | @strictfp @inline final def f = 0 + | @strictfp def t1 = f + | def t2 = f + |} + """.stripMargin + + val warn = + """C::f()I is annotated @inline but could not be inlined: + |The callsite method C::t2()I + |does not have the same strictfp mode as the callee C::f()I.""".stripMargin + + var c = 0 + compile(code, allowMessage = i => { c += 1; i.msg contains warn }) + assert(c == 1, c) + } } 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 39fb28570e..17724aecb1 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -152,7 +152,7 @@ class InlinerTest extends ClearAfterClass { assertSameCode(convertMethod(g).instructions.dropNonOp.take(4), expectedInlined) - localOpt.methodOptimizations(g, "C") + compiler.genBCode.bTypes.localOpt.methodOptimizations(g, "C") assertSameCode(convertMethod(g).instructions.dropNonOp, expectedInlined ++ List(VarOp(ASTORE, 2), VarOp(ALOAD, 2), Op(ATHROW))) } @@ -950,4 +950,29 @@ class InlinerTest extends ClearAfterClass { assertInvoke(getSingleMethod(t, "t3"), "B", "<init>") assertInvoke(getSingleMethod(t, "t4"), "B", "<init>") } + + @Test + def dontInlineNative(): Unit = { + val code = + """class C { + | def t = System.arraycopy(null, 0, null, 0, 0) + |} + """.stripMargin + val List(c) = compileClasses(newCompiler(extraArgs = InlinerTest.args + " -Yopt-inline-heuristics:everything"))(code) + assertInvoke(getSingleMethod(c, "t"), "java/lang/System", "arraycopy") + } + + @Test + def inlineMayRenderCodeDead(): Unit = { + val code = + """class C { + | @inline final def f: String = throw new Error("") + | @inline final def g: String = "a" + f + "b" // after inlining f, need to run DCE, because the rest of g becomes dead. + | def t = g // the inliner requires no dead code when inlining g (uses an Analyzer). + |} + """.stripMargin + + val List(c) = compile(code) + assertInvoke(getSingleMethod(c, "t"), "java/lang/Error", "<init>") + } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala index 360fa1d23d..a685ae7dd5 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala @@ -26,7 +26,7 @@ class SimplifyJumpsTest { Op(RETURN) ) val method = genMethod()(ops: _*) - assertTrue(localOpt.simplifyJumps(method)) + assertTrue(LocalOptImpls.simplifyJumps(method)) assertSameCode(instructionsFromMethod(method), Op(RETURN) :: ops.tail) } @@ -45,7 +45,7 @@ class SimplifyJumpsTest { Jump(GOTO, Label(2)) :: // replaced by ATHROW rest: _* ) - assertTrue(localOpt.simplifyJumps(method)) + assertTrue(LocalOptImpls.simplifyJumps(method)) assertSameCode(instructionsFromMethod(method), Op(ACONST_NULL) :: Op(ATHROW) :: rest) } @@ -66,11 +66,11 @@ class SimplifyJumpsTest { Op(RETURN) ) val method = genMethod(handlers = handler)(initialInstrs: _*) - assertFalse(localOpt.simplifyJumps(method)) + assertFalse(LocalOptImpls.simplifyJumps(method)) assertSameCode(instructionsFromMethod(method), initialInstrs) val optMethod = genMethod()(initialInstrs: _*) // no handler - assertTrue(localOpt.simplifyJumps(optMethod)) + assertTrue(LocalOptImpls.simplifyJumps(optMethod)) assertSameCode(instructionsFromMethod(optMethod).take(3), List(Label(1), Op(ACONST_NULL), Op(ATHROW))) } @@ -91,7 +91,7 @@ class SimplifyJumpsTest { Op(IRETURN) ) val method = genMethod()(begin ::: rest: _*) - assertTrue(localOpt.simplifyJumps(method)) + assertTrue(LocalOptImpls.simplifyJumps(method)) assertSameCode( instructionsFromMethod(method), List(VarOp(ILOAD, 1), Jump(IFLT, Label(3))) ::: rest.tail ) @@ -99,7 +99,7 @@ class SimplifyJumpsTest { // no label allowed between begin and rest. if there's another label, then there could be a // branch that label. eliminating the GOTO would change the behavior. val nonOptMethod = genMethod()(begin ::: Label(22) :: rest: _*) - assertFalse(localOpt.simplifyJumps(nonOptMethod)) + assertFalse(LocalOptImpls.simplifyJumps(nonOptMethod)) } @Test @@ -116,7 +116,7 @@ class SimplifyJumpsTest { // ensures that the goto is safely removed. ASM supports removing while iterating, but not the // next element of the current. Here, the current is the IFGE, the next is the GOTO. val method = genMethod()(code(Jump(IFGE, Label(2)), Jump(GOTO, Label(3))): _*) - assertTrue(localOpt.simplifyJumps(method)) + assertTrue(LocalOptImpls.simplifyJumps(method)) assertSameCode(instructionsFromMethod(method), code(Jump(IFLT, Label(3)))) } @@ -131,7 +131,7 @@ class SimplifyJumpsTest { Op(IRETURN) ) val method = genMethod()(ops: _*) - assertTrue(localOpt.simplifyJumps(method)) + assertTrue(LocalOptImpls.simplifyJumps(method)) assertSameCode(instructionsFromMethod(method), ops.tail) } @@ -157,7 +157,7 @@ class SimplifyJumpsTest { Op(IRETURN) ) val method = genMethod()(ops(1, 2, 3): _*) - assertTrue(localOpt.simplifyJumps(method)) + assertTrue(LocalOptImpls.simplifyJumps(method)) assertSameCode(instructionsFromMethod(method), ops(3, 3, 3)) } @@ -181,7 +181,7 @@ class SimplifyJumpsTest { ) val method = genMethod()(ops(2): _*) - assertTrue(localOpt.simplifyJumps(method)) + assertTrue(LocalOptImpls.simplifyJumps(method)) assertSameCode(instructionsFromMethod(method), ops(3)) } @@ -202,7 +202,7 @@ class SimplifyJumpsTest { ) val method = genMethod()(ops(Jump(IFGE, Label(1))): _*) - assertTrue(localOpt.simplifyJumps(method)) + assertTrue(LocalOptImpls.simplifyJumps(method)) assertSameCode(instructionsFromMethod(method), ops(Op(POP))) } @@ -215,7 +215,7 @@ class SimplifyJumpsTest { Jump(GOTO, Label(1)) ) val method = genMethod()(ops(List(Jump(IF_ICMPGE, Label(1)))): _*) - assertTrue(localOpt.simplifyJumps(method)) + assertTrue(LocalOptImpls.simplifyJumps(method)) assertSameCode(instructionsFromMethod(method), ops(List(Op(POP), Op(POP)))) } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala index da9853148b..902af7b7fa 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala @@ -44,7 +44,7 @@ class UnreachableCodeTest extends ClearAfterClass { def assertEliminateDead(code: (Instruction, Boolean)*): Unit = { val method = genMethod()(code.map(_._1): _*) - localOpt.removeUnreachableCodeImpl(method, "C") + LocalOptImpls.removeUnreachableCodeImpl(method, "C") val nonEliminated = instructionsFromMethod(method) val expectedLive = code.filter(_._2).map(_._1).toList assertSameCode(nonEliminated, expectedLive) diff --git a/test/pending/run/delambdafy-lambdametafactory.scala b/test/pending/run/delambdafy-lambdametafactory.scala new file mode 100644 index 0000000000..daea8a39fe --- /dev/null +++ b/test/pending/run/delambdafy-lambdametafactory.scala @@ -0,0 +1,50 @@ +// +// Tests that the static accessor method for lambda bodies +// (generated under -Ydelambdafy:method) are compatible with +// Java 8's LambdaMetafactory. +// +import java.lang.invoke._ + +class C { + def test1: Unit = { + (x: String) => x.reverse + } + def test2: Unit = { + val capture1 = "capture1" + (x: String) => capture1 + " " + x.reverse + } + def test3: Unit = { + (x: String) => C.this + " " + x.reverse + } +} +trait T { + def test4: Unit = { + (x: String) => x.reverse + } +} + +// A functional interface. Function1 contains abstract methods that are filled in by mixin +trait Function1ish[A, B] { + def apply(a: A): B +} + +object Test { + def lambdaFactory[A, B](hostClass: Class[_], instantiatedParam: Class[A], instantiatedRet: Class[B], accessorName: String, + capturedParams: Array[(Class[_], AnyRef)] = Array()) = { + val caller = MethodHandles.lookup + val methodType = MethodType.methodType(classOf[AnyRef], Array[Class[_]](classOf[AnyRef])) + val instantiatedMethodType = MethodType.methodType(instantiatedRet, Array[Class[_]](instantiatedParam)) + val (capturedParamTypes, captured) = capturedParams.unzip + val targetMethodType = MethodType.methodType(instantiatedRet, capturedParamTypes :+ instantiatedParam) + val invokedType = MethodType.methodType(classOf[Function1ish[_, _]], capturedParamTypes) + val target = caller.findStatic(hostClass, accessorName, targetMethodType) + val site = LambdaMetafactory.metafactory(caller, "apply", invokedType, methodType, target, instantiatedMethodType) + site.getTarget.invokeWithArguments(captured: _*).asInstanceOf[Function1ish[A, B]] + } + def main(args: Array[String]) { + println(lambdaFactory(classOf[C], classOf[String], classOf[String], "accessor$1").apply("abc")) + println(lambdaFactory(classOf[C], classOf[String], classOf[String], "accessor$2", Array(classOf[String] -> "capture1")).apply("abc")) + println(lambdaFactory(classOf[C], classOf[String], classOf[String], "accessor$3", Array(classOf[C] -> new C)).apply("abc")) + println(lambdaFactory(Class.forName("T$class"), classOf[String], classOf[String], "accessor$4", Array(classOf[T] -> new T{})).apply("abc")) + } +} diff --git a/test/scaladoc/run/t5795.check b/test/scaladoc/run/t5795.check new file mode 100644 index 0000000000..d08ab619ed --- /dev/null +++ b/test/scaladoc/run/t5795.check @@ -0,0 +1,4 @@ +newSource:16: warning: Could not find any member to link for "Exception". + /** + ^ +Done. diff --git a/test/scaladoc/run/t5795.scala b/test/scaladoc/run/t5795.scala new file mode 100644 index 0000000000..767e4f1a72 --- /dev/null +++ b/test/scaladoc/run/t5795.scala @@ -0,0 +1,63 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def code = """ +/** + * Only the 'deprecated' tag should stay. + * + * @author + * @since + * @todo + * @note + * @see + * @version + * @deprecated + * @example + * @constructor + */ +object Test { + /** + * Only the 'throws' tag should stay. + * @param foo + * @param bar + * @param baz + * @return + * @throws Exception + * @tparam T + */ + def foo[T](foo: Any, bar: Any, baz: Any): Int = 1 +} + """ + + def scaladocSettings = "" + + def test(b: Boolean, text: => String): Unit = if (!b) println(text) + + def testModel(root: Package) = { + import access._ + val obj = root._object("Test") + val c = obj.comment.get + + test(c.authors.isEmpty, s"expected no authors, found: ${c.authors}") + test(!c.since.isDefined, s"expected no since tag, found: ${c.since}") + test(c.todo.isEmpty, s"expected no todos, found: ${c.todo}") + test(c.note.isEmpty, s"expected no note, found: ${c.note}") + test(c.see.isEmpty, s"expected no see, found: ${c.see}") + test(!c.version.isDefined, s"expected no version tag, found: ${c.version}") + // deprecated stays + test(c.deprecated.isDefined, s"expected deprecated tag, found none") + test(c.example.isEmpty, s"expected no example, found: ${c.example}") + test(!c.constructor.isDefined, s"expected no constructor tag, found: ${c.constructor}") + + val method = obj._method("foo") + val mc = method.comment.get + + test(mc.valueParams.isEmpty, s"expected empty value params, found: ${mc.valueParams}") + test(mc.typeParams.isEmpty, s"expected empty type params, found: ${mc.typeParams}") + test(!mc.result.isDefined, s"expected no result tag, found: ${mc.result}") + // throws stay + test(!mc.throws.isEmpty, s"expected an exception tag, found: ${mc.throws}") + } +} diff --git a/test/scaladoc/scalacheck/CommentFactoryTest.scala b/test/scaladoc/scalacheck/CommentFactoryTest.scala index ff64a25602..d30b78087c 100644 --- a/test/scaladoc/scalacheck/CommentFactoryTest.scala +++ b/test/scaladoc/scalacheck/CommentFactoryTest.scala @@ -24,8 +24,11 @@ class Factory(val g: Global, val s: doc.Settings) } } + def getComment(s: String): Comment = + parse(s, "", scala.tools.nsc.util.NoPosition, null) + def parseComment(s: String): Option[Inline] = - strip(parse(s, "", scala.tools.nsc.util.NoPosition, null)) + strip(getComment(s)) def createBody(s: String) = parse(s, "", scala.tools.nsc.util.NoPosition, null).body @@ -166,4 +169,19 @@ object Test extends Properties("CommentFactory") { } } + property("Empty parameter text should be empty") = { + // used to fail with + // body == Body(List(Paragraph(Chain(List(Summary(Text('\n'))))))) + factory.getComment( + """ +/** + * @deprecated + */ + """).deprecated match { + case Some(Body(l)) if l.isEmpty => true + case other => + println(other) + false + } + } } diff --git a/test/scaladoc/scalacheck/HtmlFactoryTest.scala b/test/scaladoc/scalacheck/HtmlFactoryTest.scala index 51633be440..6a6b1f8901 100644 --- a/test/scaladoc/scalacheck/HtmlFactoryTest.scala +++ b/test/scaladoc/scalacheck/HtmlFactoryTest.scala @@ -685,7 +685,7 @@ object Test extends Properties("HtmlFactory") { case node: scala.xml.Node => { val s = node.toString s.contains("<h6>Author:</h6>") && - s.contains("<p>The Only Author\n</p>") + s.contains("<p>The Only Author</p>") } case _ => false } @@ -699,7 +699,7 @@ object Test extends Properties("HtmlFactory") { val s = node.toString s.contains("<h6>Authors:</h6>") && s.contains("<p>The First Author</p>") && - s.contains("<p>The Second Author\n</p>") + s.contains("<p>The Second Author</p>") } case _ => false } |