From 736293ab0977a79175ed025149263456526dc561 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 4 Dec 2014 12:24:15 -0800 Subject: SI-9027 Backport xml parser fix Fingers crossed, I have no local java 6 here to test. No test because no q"" on 2.10. --- src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala index 553a2088a6..a27bf6bea2 100755 --- a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala @@ -349,13 +349,12 @@ trait MarkupParsers { content_LT(ts) // parse more XML ? - if (charComingAfter(xSpaceOpt) == '<') { - xSpaceOpt - while (ch == '<') { - nextch + if (charComingAfter(xSpaceOpt()) == '<') { + do { + xSpaceOpt() + nextch() ts append element - xSpaceOpt - } + } while (charComingAfter(xSpaceOpt()) == '<') handle.makeXMLseq(r2p(start, start, curOffset), ts) } else { -- cgit v1.2.3 From 6ba7068b9f0389812bb03eae88c31035ad73c1aa Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 14 Nov 2014 11:16:06 -0800 Subject: SI-8976 MutableList.tail.iterator.size is length The previous behavior was to iterate over the mutated list of arbitrary length. The previous iteration of the iterator would also iterate the terminal element of the list without halting. This is fixed by capping the length of iterator. That is OK because mutating the list by adding to it during iteration is not recommended. For good measure, the exhausted iterator does not hold a reference to any remaining tail. A test is added that will no doubt be superseded by the QCC tests. (Quasi-Comprehensive Collections.) The test just checks that the extra tail is not strongly reachable from the iterator. If the garbage collector happens to kick in and determine that the object is weakly reachable, then the check terminates early. --- .../scala/collection/mutable/MutableList.scala | 17 +++++-- .../scala/collection/mutable/MutableListTest.scala | 37 ++++++++++++++ .../scala/tools/testing/AssertThrowsTest.scala | 2 +- test/junit/scala/tools/testing/AssertUtil.scala | 59 ++++++++++++++++++++-- .../junit/scala/tools/testing/AssertUtilTest.scala | 21 ++++++++ 5 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 test/junit/scala/collection/mutable/MutableListTest.scala create mode 100644 test/junit/scala/tools/testing/AssertUtilTest.scala (limited to 'src') diff --git a/src/library/scala/collection/mutable/MutableList.scala b/src/library/scala/collection/mutable/MutableList.scala index b852a4747b..646023f469 100644 --- a/src/library/scala/collection/mutable/MutableList.scala +++ b/src/library/scala/collection/mutable/MutableList.scala @@ -13,7 +13,6 @@ package mutable import generic._ import immutable.{List, Nil} -// !!! todo: convert to LinkedListBuffer? /** * This class is used internally to represent mutable lists. It is the * basis for the implementation of the class `Queue`. @@ -113,9 +112,21 @@ extends AbstractSeq[A] } } - /** Returns an iterator over all elements of this list. + /** Returns an iterator over up to `length` elements of this list. */ - override def iterator: Iterator[A] = first0.iterator + override def iterator: Iterator[A] = if (isEmpty) Iterator.empty else + new AbstractIterator[A] { + var elems = first0 + var count = len + def hasNext = count > 0 && elems.nonEmpty + def next() = { + if (!hasNext) throw new NoSuchElementException + count = count - 1 + val e = elems.elem + elems = if (count == 0) null else elems.next + e + } + } override def last = { if (isEmpty) throw new NoSuchElementException("MutableList.empty.last") diff --git a/test/junit/scala/collection/mutable/MutableListTest.scala b/test/junit/scala/collection/mutable/MutableListTest.scala new file mode 100644 index 0000000000..ac6d30def0 --- /dev/null +++ b/test/junit/scala/collection/mutable/MutableListTest.scala @@ -0,0 +1,37 @@ +package scala.collection.mutable + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import org.junit.Assert._ + +import scala.tools.testing.AssertUtil._ + +@RunWith(classOf[JUnit4]) +class MutableListTest { + + // Tests SI-8976 + @Test def tailIteratorMustTerminateAtLength(): Unit = { + val is = MutableList(1,2,3) + val tl = is.tail + assertEquals(tl.length, tl.iterator.length) + is += 5 + assertEquals(tl.length, tl.iterator.length) + assertSameElements(tl, tl.iterator) + } + @Test def iteratorMustFailEventually(): Unit = assertThrows[NoSuchElementException] { + MutableList[Unit]().iterator.next() + } + // was: Root empty iterator held reference + @Test def iteratorMustNotHoldOntoLast(): Unit = { + val is = MutableList(Some(1), Some(2)) + val it = is.iterator + val x = Some(3) + is += x + assertNotReachable(x, it) { + it.next() + it.next() + } + assertTrue(it.isEmpty) + } +} diff --git a/test/junit/scala/tools/testing/AssertThrowsTest.scala b/test/junit/scala/tools/testing/AssertThrowsTest.scala index d91e450bac..76758f51d2 100644 --- a/test/junit/scala/tools/testing/AssertThrowsTest.scala +++ b/test/junit/scala/tools/testing/AssertThrowsTest.scala @@ -38,6 +38,6 @@ class AssertThrowsTest { } catch { case e: AssertionError => return } - assert(false, "assertThrows should error if the tested expression does not throw anything") + fail("assertThrows should error if the tested expression does not throw anything") } } diff --git a/test/junit/scala/tools/testing/AssertUtil.scala b/test/junit/scala/tools/testing/AssertUtil.scala index 83a637783f..d29f9a473f 100644 --- a/test/junit/scala/tools/testing/AssertUtil.scala +++ b/test/junit/scala/tools/testing/AssertUtil.scala @@ -2,18 +2,42 @@ package scala.tools package testing import org.junit.Assert -import Assert.fail +import Assert._ import scala.runtime.ScalaRunTime.stringOf import scala.collection.{ GenIterable, IterableLike } +import scala.collection.JavaConverters._ +import scala.collection.mutable +import java.lang.ref._ +import java.lang.reflect._ +import java.util.IdentityHashMap /** This module contains additional higher-level assert statements * that are ultimately based on junit.Assert primitives. */ object AssertUtil { - /** - * Check if throwable T (or a subclass) was thrown during evaluation of f, and that its message - * satisfies the `checkMessage` predicate. - * If any other exception will be re-thrown. + private final val timeout = 60 * 1000L // wait a minute + + private implicit class `ref helper`[A](val r: Reference[A]) extends AnyVal { + def isEmpty: Boolean = r.get == null + def nonEmpty: Boolean = !isEmpty + } + private implicit class `class helper`(val clazz: Class[_]) extends AnyVal { + def allFields: List[Field] = { + def loop(k: Class[_]): List[Field] = + if (k == null) Nil + else k.getDeclaredFields.toList ::: loop(k.getSuperclass) + loop(clazz) + } + } + private implicit class `field helper`(val f: Field) extends AnyVal { + def follow(o: AnyRef): AnyRef = { + f setAccessible true + f get o + } + } + + /** Check if throwable T (or a subclass) was thrown during evaluation of f, and that its message + * satisfies the `checkMessage` predicate. If any other exception will be re-thrown. */ def assertThrows[T <: Throwable](f: => Any, checkMessage: String => Boolean = s => true) @@ -41,4 +65,29 @@ object AssertUtil { */ def assertSameElements[A, B >: A](expected: IterableLike[A, _], actual: Iterator[B]): Unit = assertSameElements(expected, actual.toList, "") + + /** Value is not strongly reachable from roots after body is evaluated. + */ + def assertNotReachable[A <: AnyRef](a: => A, roots: AnyRef*)(body: => Unit): Unit = { + val wkref = new WeakReference(a) + def refs(root: AnyRef): mutable.Set[AnyRef] = { + val seen = new IdentityHashMap[AnyRef, Unit] + def loop(o: AnyRef): Unit = + if (wkref.nonEmpty && o != null && !seen.containsKey(o)) { + seen.put(o, ()) + for { + f <- o.getClass.allFields + if !Modifier.isStatic(f.getModifiers) + if !f.getType.isPrimitive + if !classOf[Reference[_]].isAssignableFrom(f.getType) + } loop(f follow o) + } + loop(root) + seen.keySet.asScala + } + body + for (r <- roots if wkref.nonEmpty) { + assertFalse(s"Root $r held reference", refs(r) contains wkref.get) + } + } } diff --git a/test/junit/scala/tools/testing/AssertUtilTest.scala b/test/junit/scala/tools/testing/AssertUtilTest.scala new file mode 100644 index 0000000000..03d8815ab2 --- /dev/null +++ b/test/junit/scala/tools/testing/AssertUtilTest.scala @@ -0,0 +1,21 @@ +package scala.tools +package testing + +import org.junit.Assert._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import AssertUtil._ + +import java.lang.ref._ + +@RunWith(classOf[JUnit4]) +class AssertUtilTest { + + @Test def reachableIgnoresReferences(): Unit = { + class Holder[A](val ref: SoftReference[A]) + val o = new Object + val r = new SoftReference(o) + assertNotReachable(o, new Holder(r)) { } + } +} -- cgit v1.2.3 From a4a892fb0196f2f66d86f9cfa508deabe7d2aaae Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Thu, 11 Dec 2014 12:57:32 +0100 Subject: SI-8841 report named arg / assignment ambiguity also in silent mode. For local definitions (eg. in a block that is an argument of a method call), the type completer may have a silent context. A CyclicReference is then not thrown but transformed into a NormalTypeError. When deciding if 'x = e' is an assignment or a named arg, we need to report cyclic references, but not other type errors. In the above case, the cyclic reference was not reported. Also makes sure that warnings are printed after typing argument expressions. --- .../scala/tools/nsc/typechecker/ContextErrors.scala | 17 +++++++++++++---- .../scala/tools/nsc/typechecker/NamesDefaults.scala | 18 ++++++++++++++++-- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 19 ++++++++++++++----- test/files/neg/names-defaults-neg.check | 12 ++++++------ test/files/neg/t5044.check | 4 ++-- test/files/neg/t5091.check | 4 ++-- test/files/neg/t8463.check | 19 ++++++++++++++++++- test/files/neg/t8841.check | 9 +++++++++ test/files/neg/t8841.scala | 15 +++++++++++++++ 9 files changed, 95 insertions(+), 22 deletions(-) create mode 100644 test/files/neg/t8841.check create mode 100644 test/files/neg/t8841.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 866ca37303..6868f06606 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -45,6 +45,14 @@ trait ContextErrors { case class NormalTypeError(underlyingTree: Tree, errMsg: String) extends TreeTypeError + /** + * Marks a TypeError that was constructed from a CyclicReference (under silent). + * This is used for named arguments, where we need to know if an assignment expression + * failed with a cyclic reference or some other type error. + */ + class NormalTypeErrorFromCyclicReference(underlyingTree: Tree, errMsg: String) + extends NormalTypeError(underlyingTree, errMsg) + case class AccessTypeError(underlyingTree: Tree, errMsg: String) extends TreeTypeError @@ -1087,8 +1095,9 @@ trait ContextErrors { // hence we (together with reportTypeError in TypeDiagnostics) make sure that this CyclicReference // evades all the handlers on its way and successfully reaches `isCyclicOrErroneous` in Implicits throw ex - case CyclicReference(sym, info: TypeCompleter) => - issueNormalTypeError(tree, typer.cyclicReferenceMessage(sym, info.tree) getOrElse ex.getMessage()) + case c @ CyclicReference(sym, info: TypeCompleter) => + val error = new NormalTypeErrorFromCyclicReference(tree, typer.cyclicReferenceMessage(sym, info.tree) getOrElse ex.getMessage) + issueTypeError(error) case _ => contextNamerErrorGen.issue(TypeErrorWithUnderlyingTree(tree, ex)) } @@ -1275,8 +1284,8 @@ trait ContextErrors { } def WarnAfterNonSilentRecursiveInference(param: Symbol, arg: Tree)(implicit context: Context) = { - val note = "type-checking the invocation of "+ param.owner +" checks if the named argument expression '"+ param.name + " = ...' is a valid assignment\n"+ - "in the current scope. The resulting type inference error (see above) can be fixed by providing an explicit type in the local definition for "+ param.name +"." + val note = "failed to determine if '"+ param.name + " = ...' is a named argument or an assignment expression.\n"+ + "an explicit type is required for the definition mentioned in the error message above." context.warning(arg.pos, note) } diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala index b6387fd56b..4f943fcd28 100644 --- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala +++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala @@ -521,8 +521,22 @@ trait NamesDefaults { self: Analyzer => WarnAfterNonSilentRecursiveInference(param, arg)(context) res } match { - case SilentResultValue(t) => !t.isErroneous // #4041 - case _ => false + case SilentResultValue(t) => + !t.isErroneous // #4041 + case SilentTypeError(e: NormalTypeErrorFromCyclicReference) => + // If we end up here, the CyclicReference was reported in a silent context. This can + // happen for local definitions, when the completer for a definition is created during + // type checking in silent mode. ContextErrors.TypeSigError catches that cyclic reference + // and transforms it into a NormalTypeErrorFromCyclicReference. + // The cycle needs to be reported, because the program cannot be typed: we don't know + // if we have an assignment or a named arg. + context.issue(e) + // 'err = true' is required because we're in a silent context + WarnAfterNonSilentRecursiveInference(param, arg)(context) + false + case _ => + // We got a type error, so it cannot be an assignment (it doesn't type check as one). + false } catch { // `silent` only catches and returns TypeErrors which are not diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index aaa75b5ee1..22cf8c6808 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -76,7 +76,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case s : SilentTypeError => f(s.reportableErrors) } } - class SilentTypeError private(val errors: List[AbsTypeError]) extends SilentResult[Nothing] { + class SilentTypeError private(val errors: List[AbsTypeError], val warnings: List[(Position, String)]) extends SilentResult[Nothing] { override def isEmpty = true def err: AbsTypeError = errors.head def reportableErrors = errors match { @@ -87,10 +87,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } object SilentTypeError { - def apply(errors: AbsTypeError*): SilentTypeError = new SilentTypeError(errors.toList) + def apply(errors: AbsTypeError*): SilentTypeError = apply(errors.toList, Nil) + def apply(errors: List[AbsTypeError], warnings: List[(Position, String)]): SilentTypeError = new SilentTypeError(errors, warnings) + // todo: this extracts only one error, should be a separate extractor. def unapply(error: SilentTypeError): Option[AbsTypeError] = error.errors.headOption } + // todo: should include reporter warnings in SilentResultValue. + // e.g. tryTypedApply could print warnings on arguments when the typing succeeds. case class SilentResultValue[+T](value: T) extends SilentResult[T] { override def isEmpty = false } def newTyper(context: Context): Typer = new NormalTyper(context) @@ -661,7 +665,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper @inline def wrapResult(reporter: ContextReporter, result: T) = if (reporter.hasErrors) { stopStats() - SilentTypeError(reporter.errors: _*) + SilentTypeError(reporter.errors.toList, reporter.warnings.toList) } else SilentResultValue(result) try { @@ -4401,7 +4405,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def tryTypedApply(fun: Tree, args: List[Tree]): Tree = { val start = if (Statistics.canEnable) Statistics.startTimer(failedApplyNanos) else null - def onError(typeErrors: Seq[AbsTypeError]): Tree = { + def onError(typeErrors: Seq[AbsTypeError], warnings: Seq[(Position, String)]): Tree = { if (Statistics.canEnable) Statistics.stopTimer(failedApplyNanos, start) // If the problem is with raw types, copnvert to existentials and try again. @@ -4449,10 +4453,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } typeErrors foreach context.issue + warnings foreach { case (p, m) => context.warning(p, m) } setError(treeCopy.Apply(tree, fun, args)) } - silent(_.doTypedApply(tree, fun, args, mode, pt)) orElse onError + silent(_.doTypedApply(tree, fun, args, mode, pt)) match { + case SilentResultValue(value) => value + case e: SilentTypeError => onError(e.errors, e.warnings) + } } def normalTypedApply(tree: Tree, fun: Tree, args: List[Tree]) = { @@ -4503,6 +4511,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case err: SilentTypeError => onError({ err.reportableErrors foreach context.issue + err.warnings foreach { case (p, m) => context.warning(p, m) } args foreach (arg => typed(arg, mode, ErrorType)) setError(tree) }) diff --git a/test/files/neg/names-defaults-neg.check b/test/files/neg/names-defaults-neg.check index 20ddd55f1f..2db24b6f32 100644 --- a/test/files/neg/names-defaults-neg.check +++ b/test/files/neg/names-defaults-neg.check @@ -151,15 +151,15 @@ names-defaults-neg.scala:144: error: variable definition needs type because 'x' names-defaults-neg.scala:147: error: variable definition needs type because 'x' is used as a named argument in its body. object t6 { var x = t.f(x = 1) } ^ -names-defaults-neg.scala:147: warning: type-checking the invocation of method f checks if the named argument expression 'x = ...' is a valid assignment -in the current scope. The resulting type inference error (see above) can be fixed by providing an explicit type in the local definition for x. +names-defaults-neg.scala:147: warning: failed to determine if 'x = ...' is a named argument or an assignment expression. +an explicit type is required for the definition mentioned in the error message above. object t6 { var x = t.f(x = 1) } ^ names-defaults-neg.scala:150: error: variable definition needs type because 'x' is used as a named argument in its body. class t9 { var x = t.f(x = 1) } ^ -names-defaults-neg.scala:150: warning: type-checking the invocation of method f checks if the named argument expression 'x = ...' is a valid assignment -in the current scope. The resulting type inference error (see above) can be fixed by providing an explicit type in the local definition for x. +names-defaults-neg.scala:150: warning: failed to determine if 'x = ...' is a named argument or an assignment expression. +an explicit type is required for the definition mentioned in the error message above. class t9 { var x = t.f(x = 1) } ^ names-defaults-neg.scala:164: error: variable definition needs type because 'x' is used as a named argument in its body. @@ -174,8 +174,8 @@ names-defaults-neg.scala:170: error: reference to x is ambiguous; it is both a m names-defaults-neg.scala:177: error: variable definition needs type because 'x' is used as a named argument in its body. class u15 { var x = u.f(x = 1) } ^ -names-defaults-neg.scala:177: warning: type-checking the invocation of method f checks if the named argument expression 'x = ...' is a valid assignment -in the current scope. The resulting type inference error (see above) can be fixed by providing an explicit type in the local definition for x. +names-defaults-neg.scala:177: warning: failed to determine if 'x = ...' is a named argument or an assignment expression. +an explicit type is required for the definition mentioned in the error message above. class u15 { var x = u.f(x = 1) } ^ names-defaults-neg.scala:180: error: reference to x is ambiguous; it is both a method parameter and a variable in scope. diff --git a/test/files/neg/t5044.check b/test/files/neg/t5044.check index 197da2a4e8..dc3708123f 100644 --- a/test/files/neg/t5044.check +++ b/test/files/neg/t5044.check @@ -1,8 +1,8 @@ t5044.scala:7: error: recursive value a needs type val id = m(a) ^ -t5044.scala:6: warning: type-checking the invocation of method foo checks if the named argument expression 'id = ...' is a valid assignment -in the current scope. The resulting type inference error (see above) can be fixed by providing an explicit type in the local definition for id. +t5044.scala:6: warning: failed to determine if 'id = ...' is a named argument or an assignment expression. +an explicit type is required for the definition mentioned in the error message above. val a = foo(id = 1) ^ one warning found diff --git a/test/files/neg/t5091.check b/test/files/neg/t5091.check index abd24e3145..156f695f41 100644 --- a/test/files/neg/t5091.check +++ b/test/files/neg/t5091.check @@ -1,8 +1,8 @@ t5091.scala:8: error: recursive value xxx needs type val param = bar(xxx) ^ -t5091.scala:7: warning: type-checking the invocation of method foo checks if the named argument expression 'param = ...' is a valid assignment -in the current scope. The resulting type inference error (see above) can be fixed by providing an explicit type in the local definition for param. +t5091.scala:7: warning: failed to determine if 'param = ...' is a named argument or an assignment expression. +an explicit type is required for the definition mentioned in the error message above. val xxx = foo(param = null) ^ one warning found diff --git a/test/files/neg/t8463.check b/test/files/neg/t8463.check index 1a3eea2870..9aaacf8391 100644 --- a/test/files/neg/t8463.check +++ b/test/files/neg/t8463.check @@ -7,4 +7,21 @@ Note that implicit conversions are not applicable because they are ambiguous: are possible conversion functions from Long to ?T[Long] insertCell(Foo(5)) ^ -one error found +t8463.scala:5: error: no type parameters for method apply: (activity: T[Long])Test.Foo[T] in object Foo exist so that it can be applied to arguments (Long) + --- because --- +argument expression's type is not compatible with formal parameter type; + found : Long + required: ?T[Long] + insertCell(Foo(5)) + ^ +t8463.scala:5: error: type mismatch; + found : Long(5L) + required: T[Long] + insertCell(Foo(5)) + ^ +t8463.scala:5: error: type mismatch; + found : Test.Foo[T] + required: Test.Foo[Test.Cell] + insertCell(Foo(5)) + ^ +four errors found diff --git a/test/files/neg/t8841.check b/test/files/neg/t8841.check new file mode 100644 index 0000000000..ad525dc3f8 --- /dev/null +++ b/test/files/neg/t8841.check @@ -0,0 +1,9 @@ +t8841.scala:13: error: recursive value c needs type + val ambiguousName = c.ambiguousName + ^ +t8841.scala:12: warning: failed to determine if 'ambiguousName = ...' is a named argument or an assignment expression. +an explicit type is required for the definition mentioned in the error message above. + val c = new Cell(ambiguousName = Some("bla")) + ^ +one warning found +one error found diff --git a/test/files/neg/t8841.scala b/test/files/neg/t8841.scala new file mode 100644 index 0000000000..80430d997e --- /dev/null +++ b/test/files/neg/t8841.scala @@ -0,0 +1,15 @@ +class Cell(val ambiguousName: Option[String]) + +class Test { + def wrap(f: Any): Nothing = ??? + + wrap { + // the namer for these two ValDefs is created when typing the argument expression + // of wrap. This happens to be in a silent context (tryTypedApply). Therefore, the + // cyclic reference will not be thrown, but transformed into a NormalTypeError by + // `silent`. This requires different handling in NamesDefaults. + + val c = new Cell(ambiguousName = Some("bla")) + val ambiguousName = c.ambiguousName + } +} -- cgit v1.2.3 From 08f27bbd41d91122d96ad58a70a42e55483c8b2b Mon Sep 17 00:00:00 2001 From: Denton Cockburn Date: Tue, 6 Jan 2015 17:58:47 -0500 Subject: SI-7770 mutable.BitSet.toImmutable isn't immutable Mark method as deprecated due to it not providing the expected result, while fixing it will break existing code. --- src/library/scala/collection/mutable/BitSet.scala | 3 +++ test/files/jvm/serialization-new.check | 2 +- test/files/jvm/serialization.check | 2 +- test/files/run/bitsets.check | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/library/scala/collection/mutable/BitSet.scala b/src/library/scala/collection/mutable/BitSet.scala index faa4155317..78150b5e88 100644 --- a/src/library/scala/collection/mutable/BitSet.scala +++ b/src/library/scala/collection/mutable/BitSet.scala @@ -160,6 +160,9 @@ class BitSet(protected final var elems: Array[Long]) extends AbstractSet[Int] * * @return an immutable set containing all the elements of this set. */ + @deprecated("If this BitSet contains a value that is 128 or greater, the result of this method is an 'immutable' " + + "BitSet that shares state with this mutable BitSet. Thus, if the mutable BitSet is modified, it will violate the " + + "immutability of the result.", "2.11.6") def toImmutable = immutable.BitSet.fromBitMaskNoCopy(elems) override def clone(): BitSet = { diff --git a/test/files/jvm/serialization-new.check b/test/files/jvm/serialization-new.check index 1555135926..cb26446f40 100644 --- a/test/files/jvm/serialization-new.check +++ b/test/files/jvm/serialization-new.check @@ -1,4 +1,4 @@ -warning: there were two deprecation warnings; re-run with -deprecation for details +warning: there were three deprecation warnings; re-run with -deprecation for details a1 = Array[1,2,3] _a1 = Array[1,2,3] arrayEquals(a1, _a1): true diff --git a/test/files/jvm/serialization.check b/test/files/jvm/serialization.check index 1555135926..cb26446f40 100644 --- a/test/files/jvm/serialization.check +++ b/test/files/jvm/serialization.check @@ -1,4 +1,4 @@ -warning: there were two deprecation warnings; re-run with -deprecation for details +warning: there were three deprecation warnings; re-run with -deprecation for details a1 = Array[1,2,3] _a1 = Array[1,2,3] arrayEquals(a1, _a1): true diff --git a/test/files/run/bitsets.check b/test/files/run/bitsets.check index 41c2ccdcb8..c24fd6238f 100644 --- a/test/files/run/bitsets.check +++ b/test/files/run/bitsets.check @@ -1,3 +1,4 @@ +warning: there were three deprecation warnings; re-run with -deprecation for details ms0 = BitSet(2) ms1 = BitSet(2) ms2 = BitSet(2) -- cgit v1.2.3 From d1d3225e28ed1847470d976ffeefbce40e90d9ac Mon Sep 17 00:00:00 2001 From: Denton Cockburn Date: Mon, 15 Dec 2014 21:58:46 -0500 Subject: SI-8988 Escaping character in StringLike.split(c) prevents usage of optimized String.split code path Escaping a char when calling split is slow. We end up compiling a Pattern to simply match a character literal. Instead, we just use an loop with indexOf to construct our resulting Array. Current speed up over old behaviour is about 12-1 --- .../scala/collection/immutable/StringLike.scala | 31 ++++++++++++++++-- .../collection/immutable/StringLikeTest.scala | 37 ++++++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 test/junit/scala/collection/immutable/StringLikeTest.scala (limited to 'src') diff --git a/src/library/scala/collection/immutable/StringLike.scala b/src/library/scala/collection/immutable/StringLike.scala index f0daaf25a5..1ead894faf 100644 --- a/src/library/scala/collection/immutable/StringLike.scala +++ b/src/library/scala/collection/immutable/StringLike.scala @@ -10,7 +10,7 @@ package scala package collection package immutable -import mutable.Builder +import mutable.{ ArrayBuilder, Builder } import scala.util.matching.Regex import scala.math.ScalaNumber import scala.reflect.ClassTag @@ -203,8 +203,33 @@ self => private def escape(ch: Char): String = "\\Q" + ch + "\\E" - @throws(classOf[java.util.regex.PatternSyntaxException]) - def split(separator: Char): Array[String] = toString.split(escape(separator)) + def split(separator: Char): Array[String] = { + val thisString = toString + var pos = thisString.indexOf(separator) + + if (pos != -1) { + val res = new ArrayBuilder.ofRef[String] + + var prev = 0 + do { + res += thisString.substring(prev, pos) + prev = pos + 1 + pos = thisString.indexOf(separator, prev) + } while (pos != -1) + + if (prev != thisString.size) + res += thisString.substring(prev, thisString.size) + + val initialResult = res.result() + pos = initialResult.length + while (pos > 0 && initialResult(pos - 1).isEmpty) pos = pos - 1 + if (pos != initialResult.length) { + val trimmed = new Array[String](pos) + Array.copy(initialResult, 0, trimmed, 0, pos) + trimmed + } else initialResult + } else Array[String](thisString) + } @throws(classOf[java.util.regex.PatternSyntaxException]) def split(separators: Array[Char]): Array[String] = { diff --git a/test/junit/scala/collection/immutable/StringLikeTest.scala b/test/junit/scala/collection/immutable/StringLikeTest.scala new file mode 100644 index 0000000000..3722bdfe4d --- /dev/null +++ b/test/junit/scala/collection/immutable/StringLikeTest.scala @@ -0,0 +1,37 @@ +package scala.collection.immutable + +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +import scala.tools.testing.AssertUtil +import scala.util.Random + +/* Test for SI-8988 */ +@RunWith(classOf[JUnit4]) +class StringLikeTest { + @Test + def testStringSplitWithChar: Unit = { + val chars = (0 to 255).map(_.toChar) + def randString = Random.nextString(30) + + for (c <- chars) { + val s = randString + val jString = new java.lang.String(s) + + // make sure we can match a literal character done by Java's split + val jSplit = jString.split("\\Q" + c.toString + "\\E") + val sSplit = s.split(c) + AssertUtil.assertSameElements(jSplit, sSplit, s"Not same result as Java split for char $c in string $s") + } + } + + @Test + def testSplitEdgeCases: Unit = { + AssertUtil.assertSameElements("abcd".split('d'), Array("abc")) // not Array("abc", "") + AssertUtil.assertSameElements("abccc".split('c'), Array("ab")) // not Array("ab", "", "", "") + AssertUtil.assertSameElements("xxx".split('x'), Array[String]()) // not Array("", "", "", "") + AssertUtil.assertSameElements("".split('x'), Array("")) // not Array() + AssertUtil.assertSameElements("--ch--omp--".split("-"), Array("", "", "ch", "", "omp")) // All the cases! + } +} -- cgit v1.2.3 From 207b67a10048b6eb3385187db288fbf72648907c Mon Sep 17 00:00:00 2001 From: dickwall Date: Fri, 9 Jan 2015 11:18:06 -0800 Subject: Added package scaladoc for the concurrent package. --- src/library/scala/concurrent/package.scala | 74 ++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) (limited to 'src') diff --git a/src/library/scala/concurrent/package.scala b/src/library/scala/concurrent/package.scala index 4843d28679..d159dda414 100644 --- a/src/library/scala/concurrent/package.scala +++ b/src/library/scala/concurrent/package.scala @@ -12,6 +12,75 @@ import scala.concurrent.duration.Duration import scala.annotation.implicitNotFound /** This package object contains primitives for concurrent and parallel programming. + * + * == Guide == + * + * A more detailed guide to Futures and Promises, including discussion and examples + * can be found at + * [[http://docs.scala-lang.org/overviews/core/futures.html]]. + * + * == Common Imports == + * + * When working with Futures, you will often find that importing the whole concurrent + * package is convenient, furthermore you are likely to need an implicit ExecutionContext + * in scope for many operations involving Futures and Promises: + * + * {{{ + * import scala.concurrent._ + * import ExecutionContext.Implicits.global + * }}} + * + * == Specifying Durations == + * + * Operations often require a duration to be specified. A duration DSL is available + * to make defining these easier: + * + * {{{ + * import scala.concurrent.duration._ + * val d: Duration = 10.seconds + * }}} + * + * == Using Futures For Non-blocking Computation == + * + * Basic use of futures is easy with the factory method on Future, which executes a + * provided function asynchronously, handing you back a future result of that function + * without blocking the current thread. In order to create the Future you will need + * either an implicit or explicit ExecutionContext to be provided: + * + * {{{ + * import scala.concurrent._ + * import ExecutionContext.Implicits.global // implicit execution context + * + * val firstZebra: Future[Int] = Future { + * val source = scala.io.Source.fromFile("/etc/dictionaries-common/words") + * source.toSeq.indexOfSlice("zebra") + * } + * }}} + * + * == Avoid Blocking == + * + * Although blocking is possible in order to await results (with a mandatory timeout duration): + * + * {{{ + * import scala.concurrent.duration._ + * Await.result(firstZebra, 10.seconds) + * }}} + * + * and although this is sometimes necessary to do, in particular for testing purposes, blocking + * in general is discouraged when working with Futures and concurrency in order to avoid + * potential deadlocks and improve performance. Instead, use callbacks or combinators to + * remain in the future domain: + * + * {{{ + * val animalRange: Future[Int] = for { + * aardvark <- firstAardvark + * zebra <- firstZebra + * } yield zebra - aardvark + * + * animalRange.onSuccess { + * case x if x > 500000 => println("It's a long way from Aardvark to Zebra") + * } + * }}} */ package object concurrent { type ExecutionException = java.util.concurrent.ExecutionException @@ -70,6 +139,11 @@ package concurrent { /** * `Await` is what is used to ensure proper handling of blocking for `Awaitable` instances. + * + * While occasionally useful, e.g. for testing, it is recommended that you avoid Await + * when possible in favor of callbacks and combinators like onComplete and use in + * for comprehensions. Await will block the thread on which it runs, and could cause + * performance and deadlock issues. */ object Await { /** -- cgit v1.2.3 From 09ce5c32f1db71ccaa5d4b22076d9ec606ad5ec9 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 15 Jan 2015 21:48:47 +1000 Subject: SI-6502 More robust REPL :require - handle missing files gracefully (rather than NPE) - read the class name with ASM, rather than with a dummy classloader. The dummy classloader is prone to throwing `LinkageError`s, as reported in the comments of SI-6502. Manual test of the original report: ``` % qscala Welcome to Scala version 2.11.5-20150115-183424-155dbf3fdf (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_25). Type in expressions to have them evaluated. Type :help for more information. scala> :require does/not/exist Cannot read: does/not/exist scala> classOf[org.junit.Test] :8: error: object junit is not a member of package org classOf[org.junit.Test] ^ scala> :require /Users/jason/.m2/repository/junit/junit/4.11/junit-4.11.jar Added '/Users/jason/.m2/repository/junit/junit/4.11/junit-4.11.jar' to classpath. scala> classOf[org.junit.Test] res1: Class[org.junit.Test] = interface org.junit.Test ``` I have commited an automated test that is a minimization of this one. --- src/repl/scala/tools/nsc/interpreter/ILoop.scala | 24 ++++++++++++---------- test/files/run/t6502.scala | 26 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala index 4fd5768b79..4d71e0e09e 100644 --- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala @@ -12,6 +12,7 @@ import scala.annotation.tailrec import Predef.{ println => _, _ } import interpreter.session._ import StdReplTags._ +import scala.tools.asm.ClassReader import scala.util.Properties.{ jdkHome, javaVersion, versionString, javaVmName } import scala.tools.nsc.util.{ ClassPath, Exceptional, stringFromWriter, stringFromStream } import scala.reflect.classTag @@ -633,28 +634,29 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) * the interpreter and replays all interpreter expressions. */ def require(arg: String): Unit = { - class InfoClassLoader extends java.lang.ClassLoader { - def classOf(arr: Array[Byte]): Class[_] = - super.defineClass(null, arr, 0, arr.length) - } - val f = File(arg).normalize - if (f.isDirectory) { - echo("Adding directories to the classpath is not supported. Add a jar instead.") + val jarFile = AbstractFile.getDirectory(new java.io.File(arg)) + if (jarFile == null) { + echo(s"Cannot load '$arg'") return } - val jarFile = AbstractFile.getDirectory(new java.io.File(arg)) - def flatten(f: AbstractFile): Iterator[AbstractFile] = if (f.isClassContainer) f.iterator.flatMap(flatten) else Iterator(f) val entries = flatten(jarFile) - val cloader = new InfoClassLoader - def classNameOf(classFile: AbstractFile): String = cloader.classOf(classFile.toByteArray).getName + def classNameOf(classFile: AbstractFile): String = { + val input = classFile.input + try { + val reader = new ClassReader(input) + reader.getClassName.replace('/', '.') + } finally { + input.close() + } + } def alreadyDefined(clsName: String) = intp.classLoader.tryToLoadClass(clsName).isDefined val exists = entries.filter(_.hasExtension("class")).map(classNameOf).exists(alreadyDefined) diff --git a/test/files/run/t6502.scala b/test/files/run/t6502.scala index 4ce034a482..b0d93ac3fd 100644 --- a/test/files/run/t6502.scala +++ b/test/files/run/t6502.scala @@ -46,6 +46,12 @@ object Test extends StoreReporterDirectTest { } }""" + def app6 = """ + package test6 + class A extends Test { println("created test6.A") } + class Z extends Test { println("created test6.Z") } + trait Test""" + def test1(): Unit = { val jar = "test1.jar" compileCode(app1, jar) @@ -105,11 +111,31 @@ object Test extends StoreReporterDirectTest { println(s"test4 res2: $res2") } + def test5(): Unit = { + val codeToRun = ":require /does/not/exist.jar" + val output = ILoop.run(codeToRun, settings) + assert(!output.contains("NullPointerException"), output) + assert(output.contains("Cannot load '/does/not/exist.jar'"), output) + } + + def test6(): Unit = { + // Avoid java.lang.NoClassDefFoundError triggered by the old appoach of using a Java + // classloader to parse .class files in order to read their names. + val jar = "test6.jar" + compileCode(app6, jar) + val codeToRun = toCodeInSeparateLines(s":require ${testOutput.path}/$jar", "import test6._; new A; new Z") + val output = ILoop.run(codeToRun, settings) + assert(output.contains("created test6.A"), output) + assert(output.contains("created test6.Z"), output) + } + def show(): Unit = { test1() test2() test3() test4() + test5() + test6() } def toCodeInSeparateLines(lines: String*): String = lines.map(_ + "\n").mkString -- cgit v1.2.3 From a3c2b55b16ad404b51f4eea0628dd00badeb0fbe Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 16 Jan 2015 11:38:44 +1000 Subject: REPL: Tread EOF a "no" in the "yes"/"no" prompt. Before, we got in an endless loop if using ^D to try to end the session. When piping commands into the REPL, this was rather annoying! ``` scala-hash v2.11.5 Welcome to Scala version 2.11.5-20150101-184742-3fafbc204f (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_25). Type in expressions to have them evaluated. Type :help for more information. scala> :require xxx java.lang.NullPointerException at scala.tools.nsc.interpreter.ILoop.scala$tools$nsc$interpreter$ILoop$$flatten$1(ILoop.scala:651) at scala.tools.nsc.interpreter.ILoop.require(ILoop.scala:654) That entry seems to have slain the compiler. Shall I replay your session? I can re-run each line except the last one. [y/n]^D You must enter y or n. That entry seems to have slain the compiler. Shall I replay your session? I can re-run each line except the last one. [y/n]^D ... ``` --- src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala b/src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala index 28ddf2939c..ed69d449cb 100644 --- a/src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala +++ b/src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala @@ -23,6 +23,7 @@ trait InteractiveReader { def readYesOrNo(prompt: String, alt: => Boolean): Boolean = readOneKey(prompt) match { case 'y' => true case 'n' => false + case -1 => false // EOF case _ => alt } -- cgit v1.2.3 From 286dafbd45caa2b85f8113845105aaaec98be71a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 16 Jan 2015 13:25:31 +1000 Subject: SI-9050 Fix crasher with value classes, recursion From the "Substitution is hard to do" department. In 7babdab9a, TreeSymSubstitutor was modified to mutate the info of symbols defined in the tree, if that symbol's info referred to one of the `from` symbols in the substitution. It would have been more principled to create a cloned symbol with the updated info, and add that to the substitution. But I wasn't able implement that correctly (let alone efficiently.) The in-place mutation of the info of a symbol led to the crasher in this bug: a singleton type over that symbol ends up with a stale cached value of 'underlying'. In the enclosed test case, this leads to a type error in the `SubstituteRecursion` of the extension methods phase. This commit performs a cleanup job at the end of `substituteSymbols` by invalidating the cache of any `SingleType`-s in the tree that refer to one of the mutated symbols. --- src/reflect/scala/reflect/internal/Trees.scala | 20 +++++++++++++++++++- test/files/pos/t9050.scala | 13 +++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 test/files/pos/t9050.scala (limited to 'src') diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index 35de3adff6..ccf907e05d 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -1576,6 +1576,7 @@ trait Trees extends api.Trees { */ class TreeSymSubstituter(from: List[Symbol], to: List[Symbol]) extends Transformer { val symSubst = new SubstSymMap(from, to) + private var mutatedSymbols: List[Symbol] = Nil override def transform(tree: Tree): Tree = { def subst(from: List[Symbol], to: List[Symbol]) { if (!from.isEmpty) @@ -1594,6 +1595,7 @@ trait Trees extends api.Trees { |TreeSymSubstituter: updated info of symbol ${tree.symbol} | Old: ${showRaw(tree.symbol.info, printTypes = true, printIds = true)} | New: ${showRaw(newInfo, printTypes = true, printIds = true)}""") + mutatedSymbols ::= tree.symbol tree.symbol updateInfo newInfo } case _ => @@ -1613,7 +1615,23 @@ trait Trees extends api.Trees { } else super.transform(tree) } - def apply[T <: Tree](tree: T): T = transform(tree).asInstanceOf[T] + def apply[T <: Tree](tree: T): T = { + val tree1 = transform(tree) + invalidateSingleTypeCaches(tree1) + tree1.asInstanceOf[T] + } + private def invalidateSingleTypeCaches(tree: Tree): Unit = { + if (mutatedSymbols.nonEmpty) + for (t <- tree) + for (tp <- t.tpe) { + tp match { + case s: SingleType if mutatedSymbols contains s.sym => + s.underlyingPeriod = NoPeriod + s.underlyingCache = NoType + case _ => + } + } + } override def toString() = "TreeSymSubstituter/" + substituterString("Symbol", "Symbol", from, to) } diff --git a/test/files/pos/t9050.scala b/test/files/pos/t9050.scala new file mode 100644 index 0000000000..b1ab09f901 --- /dev/null +++ b/test/files/pos/t9050.scala @@ -0,0 +1,13 @@ +final class Mu[F](val value: Any) extends AnyVal { + def cata(f: F) { + // crash + ((y: Mu[F]) => y.cata(f)) + // crash + def foo(x : Mu[F]) = x.cata(f) + + // // okay + def x: Mu[F] = ??? + (() => x.cata(f)) + assert(true, cata(f)) + } +} -- cgit v1.2.3 From fb043761806f8ef2c7a662f15ba5ac5731426dbf Mon Sep 17 00:00:00 2001 From: Mark Zitnik Date: Sun, 18 Jan 2015 18:34:15 +0200 Subject: SI-9072 Vector ++ concatenation of parallel collection cause inconsistent results --- src/library/scala/collection/immutable/Vector.scala | 2 +- .../scala/collection/immutable/VectorTest.scala | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 test/junit/scala/collection/immutable/VectorTest.scala (limited to 'src') diff --git a/src/library/scala/collection/immutable/Vector.scala b/src/library/scala/collection/immutable/Vector.scala index c7da447f72..47a623a616 100644 --- a/src/library/scala/collection/immutable/Vector.scala +++ b/src/library/scala/collection/immutable/Vector.scala @@ -215,7 +215,7 @@ override def companion: GenericCompanion[Vector] = Vector import Vector.{Log2ConcatFaster, TinyAppendFaster} if (that.isEmpty) this.asInstanceOf[That] else { - val again = if (!that.isTraversableAgain) that.toVector else that + val again = if (!that.isTraversableAgain) that.toVector else that.seq again.size match { // Often it's better to append small numbers of elements (or prepend if RHS is a vector) case n if n <= TinyAppendFaster || n < (this.size >> Log2ConcatFaster) => diff --git a/test/junit/scala/collection/immutable/VectorTest.scala b/test/junit/scala/collection/immutable/VectorTest.scala new file mode 100644 index 0000000000..e7edba3e43 --- /dev/null +++ b/test/junit/scala/collection/immutable/VectorTest.scala @@ -0,0 +1,20 @@ +package scala.collection.immutable + +import org.junit.{Assert, Test} +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(classOf[JUnit4]) +class VectorTest { + /** + * Test Vector ++ with a small parallel collection concatenation (SI-9072). + * + */ + @Test + def testPlusPlus(): Unit = { + val smallVec = (0 to 1) + val smallParVec = smallVec.par + val testElementsSize = (0 to 1000).map( _ => Vector.empty ++ smallParVec ) + Assert.assertTrue(testElementsSize.forall( v => v.size == 2 )) + } +} -- cgit v1.2.3 From c30ed29f6cee9d4e0bd4a99c4be804ffcb20d281 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 19 Jan 2015 13:42:35 +1000 Subject: SI-9093 Fix value discarding / multiple param list crasher The type error stemming from missing argument list was being swallowed when the expected type was `Unit` and there were undetermined type parameters in the expression. This commit modifies `adapt` to avoid using `instantiateExpectingUnit` when the tree is typed with `MethodType`. --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 2 +- test/files/neg/t9093.check | 6 ++++++ test/files/neg/t9093.scala | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 test/files/neg/t9093.check create mode 100644 test/files/neg/t9093.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index e6fa9a0142..9773028b76 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1177,7 +1177,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } def instantiatePossiblyExpectingUnit(tree: Tree, mode: Mode, pt: Type): Tree = { - if (mode.typingExprNotFun && pt.typeSymbol == UnitClass) + if (mode.typingExprNotFun && pt.typeSymbol == UnitClass && !tree.tpe.isInstanceOf[MethodType]) instantiateExpectingUnit(tree, mode) else instantiate(tree, mode, pt) diff --git a/test/files/neg/t9093.check b/test/files/neg/t9093.check new file mode 100644 index 0000000000..085a433f0b --- /dev/null +++ b/test/files/neg/t9093.check @@ -0,0 +1,6 @@ +t9093.scala:3: error: polymorphic expression cannot be instantiated to expected type; + found : [C](f: C)Null + required: Unit + val x: Unit = apply2(0)/*(0)*/ + ^ +one error found diff --git a/test/files/neg/t9093.scala b/test/files/neg/t9093.scala new file mode 100644 index 0000000000..d9922ad70e --- /dev/null +++ b/test/files/neg/t9093.scala @@ -0,0 +1,5 @@ +object Main { + def apply2[C](fa: Any)(f: C) = null + val x: Unit = apply2(0)/*(0)*/ +} + -- cgit v1.2.3 From a8b6e361de37705036c4e8307ad75e279fafe9f5 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 19 Jan 2015 21:40:20 +0100 Subject: SI-9097 Remove spurious warning about conflicting filenames When using delambdafy:method, closure classes are generated late. The class is added to a map and integrated into the PackageDef in transformStats. When declaring a package object, there are potentially multiple PackageDefs for the same package. In this case, the closure class was added to all of them. As a result, GenASM / GenBCode would run multiple times on the closure class. In GenBCode this would trigger a warning about conflicting filenames. --- .../scala/tools/nsc/transform/Delambdafy.scala | 4 +++- test/files/pos/t9097.flags | 1 + test/files/pos/t9097.scala | 9 ++++++++ test/files/run/t9097.scala | 26 ++++++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 test/files/pos/t9097.flags create mode 100644 test/files/pos/t9097.scala create mode 100644 test/files/run/t9097.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index d2c511a2d1..1f832ba81e 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -113,7 +113,9 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre // after working on the entire compilation until we'll have a set of // new class definitions to add to the top level override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = { - super.transformStats(stats, exprOwner) ++ lambdaClassDefs(exprOwner) + // Need to remove from the lambdaClassDefs map: there may be multiple PackageDef for the same + // package when defining a package object. We only add the lambda class to one. See SI-9097. + super.transformStats(stats, exprOwner) ++ lambdaClassDefs.remove(exprOwner).getOrElse(Nil) } private def optionSymbol(sym: Symbol): Option[Symbol] = if (sym.exists) Some(sym) else None diff --git a/test/files/pos/t9097.flags b/test/files/pos/t9097.flags new file mode 100644 index 0000000000..0f8175b88b --- /dev/null +++ b/test/files/pos/t9097.flags @@ -0,0 +1 @@ +-Ydelambdafy:method -Ybackend:GenBCode -Xfatal-warnings \ No newline at end of file diff --git a/test/files/pos/t9097.scala b/test/files/pos/t9097.scala new file mode 100644 index 0000000000..5e0e921271 --- /dev/null +++ b/test/files/pos/t9097.scala @@ -0,0 +1,9 @@ +package o +package a { + class C { + def hihi = List(1,2).map(_ * 2) + } +} +package object a { + def f = 1 +} diff --git a/test/files/run/t9097.scala b/test/files/run/t9097.scala new file mode 100644 index 0000000000..0f148c3b9d --- /dev/null +++ b/test/files/run/t9097.scala @@ -0,0 +1,26 @@ +import scala.tools.partest._ +import java.io.{Console => _, _} + +object Test extends DirectTest { + + override def extraSettings: String = "-usejavacp -Ydelambdafy:method -Xprint:delambdafy -d " + testOutput.path + + override def code = """package o + |package a { + | class C { + | def hihi = List(1,2).map(_ * 2) + | } + |} + |package object a { + | def f = 1 + |} + |""".stripMargin.trim + + override def show(): Unit = { + val baos = new java.io.ByteArrayOutputStream() + Console.withOut(baos)(Console.withErr(baos)(compile())) + val out = baos.toString("UTF-8") + // was 2 before the fix, the two PackageDefs for a would both contain the ClassDef for the closure + assert(out.lines.count(_ contains "class hihi$1") == 1, out) + } +} -- cgit v1.2.3 From 43818d4e5d8369387e7b315eafde01aae73acaa6 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 18 Dec 2014 00:47:00 -0800 Subject: SI-7623 Trailing sequence wildcard warning An -Xlint:stars-align warning for the case of patterns with at least one "fixed" component and a varargs component. Warn if the fixed patterns don't exactly align with the fixed value components, such that a sequence wildcard aligns exactly with the varargs component (either a T* parameter in a case class or a Seq[T] in an extractor result). This addresses the case of the xml.Elem extractor, which does not correspond to the Elem class constructor. One can be fooled into supplying an extra field for extraction. Vanilla extractors of type `Option[Seq[_]]` are unaffected by this flag. It's OK to ask for `case X(a, b, c)` in the expectation that three results are forthcoming. There is no semantic confusion over where the varargs begin. --- .../scala/tools/nsc/settings/Warnings.scala | 13 +++++--- .../transform/patmat/ScalacPatternExpanders.scala | 4 ++- test/files/neg/t7623.check | 21 ++++++++++++ test/files/neg/t7623.flags | 1 + test/files/neg/t7623.scala | 38 ++++++++++++++++++++++ 5 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 test/files/neg/t7623.check create mode 100644 test/files/neg/t7623.flags create mode 100644 test/files/neg/t7623.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index d174dc86c7..41ce0837cb 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -28,10 +28,11 @@ trait Warnings { val warnUnusedImport = BooleanSetting("-Ywarn-unused-import", "Warn when imports are unused.") // Experimental lint warnings that are turned off, but which could be turned on programmatically. - // These warnings are said to blind those who dare enable them. - // They are not activated by -Xlint and can't be enabled on the command line. - val warnValueOverrides = { // Currently turned off as experimental. Created using constructor (new BS), so not available on the command line. - val flag = new BooleanSetting("value-overrides", "Generated value class method overrides an implementation") + // They are not activated by -Xlint and can't be enabled on the command line because they are not + // created using the standard factory methods. + + val warnValueOverrides = { + val flag = new BooleanSetting("value-overrides", "Generated value class method overrides an implementation.") flag.value = false flag } @@ -53,10 +54,11 @@ trait Warnings { val TypeParameterShadow = LintWarning("type-parameter-shadow", "A local type parameter shadows a type already in scope.") val PolyImplicitOverload = LintWarning("poly-implicit-overload", "Parameterized overloaded implicit methods are not visible as view bounds.") val OptionImplicit = LintWarning("option-implicit", "Option.apply used implicit view.") - val DelayedInitSelect = LintWarning("delayedinit-select", "Selecting member of DelayedInit") + val DelayedInitSelect = LintWarning("delayedinit-select", "Selecting member of DelayedInit.") val ByNameRightAssociative = LintWarning("by-name-right-associative", "By-name parameter of right associative operator.") val PackageObjectClasses = LintWarning("package-object-classes", "Class or object defined in package object.") val UnsoundMatch = LintWarning("unsound-match", "Pattern match may not be typesafe.") + val StarsAlign = LintWarning("stars-align", "Pattern sequence wildcard must align with sequence component.") def allLintWarnings = values.toSeq.asInstanceOf[Seq[LintWarning]] } @@ -77,6 +79,7 @@ trait Warnings { def warnByNameRightAssociative = lint contains ByNameRightAssociative def warnPackageObjectClasses = lint contains PackageObjectClasses def warnUnsoundMatch = lint contains UnsoundMatch + def warnStarsAlign = lint contains StarsAlign // Lint warnings that are currently -Y, but deprecated in that usage @deprecated("Use warnAdaptedArgs", since="2.11.2") diff --git a/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala b/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala index 8924394b72..2753baa51d 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala @@ -110,8 +110,10 @@ trait ScalacPatternExpanders { err("Star pattern must correspond with varargs or unapplySeq") else if (elementArity < 0) arityError("not enough") - else if (elementArity > 0 && !extractor.hasSeq) + else if (elementArity > 0 && !isSeq) arityError("too many") + else if (settings.warnStarsAlign && isSeq && productArity > 0 && (elementArity > 0 || !isStar)) + warn("A repeated case parameter or extracted sequence should be matched only by a sequence wildcard (_*).") aligned } diff --git a/test/files/neg/t7623.check b/test/files/neg/t7623.check new file mode 100644 index 0000000000..db368dd369 --- /dev/null +++ b/test/files/neg/t7623.check @@ -0,0 +1,21 @@ +t7623.scala:19: warning: A repeated case parameter or extracted sequence should be matched only by a sequence wildcard (_*). + def f = "" match { case X(s) => } + ^ +t7623.scala:21: warning: A repeated case parameter or extracted sequence should be matched only by a sequence wildcard (_*). + def g = "" match { case X(s, t) => } + ^ +t7623.scala:23: warning: A repeated case parameter or extracted sequence should be matched only by a sequence wildcard (_*). + def h = "" match { case X(s, t, u @ _*) => } + ^ +t7623.scala:9: warning: A repeated case parameter or extracted sequence should be matched only by a sequence wildcard (_*). + def f = C("") match { case C(s) => } + ^ +t7623.scala:11: warning: A repeated case parameter or extracted sequence should be matched only by a sequence wildcard (_*). + def g = C("") match { case C(s, t) => } + ^ +t7623.scala:13: warning: A repeated case parameter or extracted sequence should be matched only by a sequence wildcard (_*). + def h = C("") match { case C(s, t, u @ _*) => } + ^ +error: No warnings can be incurred under -Xfatal-warnings. +6 warnings found +one error found diff --git a/test/files/neg/t7623.flags b/test/files/neg/t7623.flags new file mode 100644 index 0000000000..74c9e38323 --- /dev/null +++ b/test/files/neg/t7623.flags @@ -0,0 +1 @@ +-Xlint:stars-align -Xfatal-warnings diff --git a/test/files/neg/t7623.scala b/test/files/neg/t7623.scala new file mode 100644 index 0000000000..5c40f37bc1 --- /dev/null +++ b/test/files/neg/t7623.scala @@ -0,0 +1,38 @@ + + +case class C(s: String, xs: Int*) + +object X { def unapplySeq(a: Any): Option[(String, Seq[Int])] = Some("", List(1,2,3)) } + +// for case classes with varargs, avoid misaligned patterns +trait Ctest { + def f = C("") match { case C(s) => } + + def g = C("") match { case C(s, t) => } + + def h = C("") match { case C(s, t, u @ _*) => } + + def ok = C("") match { case C(s, u @ _*) => } +} +// for extractors that unapplySeq: Option[(Something, Seq[_])], avoid misaligned patterns +trait Xtest { + def f = "" match { case X(s) => } + + def g = "" match { case X(s, t) => } + + def h = "" match { case X(s, t, u @ _*) => } + + def ok = "" match { case X(s, u @ _*) => } +} +// for extractors that unapplySeq: Option[Seq[_]], anything goes +trait Rtest { + val r = "(a+)".r + + def f = "" match { case r(s) => } + + def g = "" match { case r(s, t) => } + + def h = "" match { case r(s, t, u @ _*) => } + + def whatever = "" match { case r(u @ _*) => } +} -- cgit v1.2.3 From 3c97888ab896e7ffc63e44515902aba1c5022072 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 15 Jan 2015 11:16:05 +1000 Subject: SI-9086 Fix regression in implicit search Implicit search declines to force the info of candidate implicits that either a) are defined beyond the position of the implicit search site, or b) enclose the implicit search site. The second criterion used to prevent consideration of `O` in the super constructor call: implicit object O extends C( { implicitly[X] }) However, after https://github.com/scala/scala/pull/4043, the block containing the implicit search is typechecked in a context owned by a local dummy symbol rather than by `O`. (The dummy and `O` share an owner.) This led to `O` being considered as a candidate for this implicit search. This search is undertaken during completion of the info of `O`, which leads to it being excluded on account of the LOCKED flag. Unfortunately, this also excludes it from use in implicit search sites subsequent to `O`, as `ImplicitInfo` caches `isCyclicOrErroneous`. This commit adjusts the position of the local dummy to be identical to that of the object. This serves to exclude `O` as a candidate during the super call on account of criterion a). --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 6 +++++- test/files/pos/t9086.scala | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 test/files/pos/t9086.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index e6fa9a0142..d33f98109d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1536,7 +1536,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val cbody1 = treeCopy.Block(cbody, preSuperStats, superCall1) val clazz = context.owner assert(clazz != NoSymbol, templ) - val dummy = context.outer.owner.newLocalDummy(templ.pos) + // SI-9086 The position of this symbol is material: implicit search will avoid triggering + // cyclic errors in an implicit search in argument to the super constructor call on + // account of the "ignore symbols without complete info that succeed the implicit search" + // in this source file. See `ImplicitSearch#isValid` and `ImplicitInfo#isCyclicOrErroneous`. + val dummy = context.outer.owner.newLocalDummy(context.owner.pos) val cscope = context.outer.makeNewScope(ctor, dummy) if (dummy.isTopLevel) currentRun.symSource(dummy) = currentUnit.source.file val cbody2 = { // called both during completion AND typing. diff --git a/test/files/pos/t9086.scala b/test/files/pos/t9086.scala new file mode 100644 index 0000000000..fba34ee226 --- /dev/null +++ b/test/files/pos/t9086.scala @@ -0,0 +1,8 @@ +class X[A](a: A) +object Test { + implicit val ImplicitBoolean: Boolean = true + def local = { + implicit object X extends X({ implicitly[Boolean] ; "" }) + implicitly[X[String]] // failed in 2.11.5 + } +} -- cgit v1.2.3 From 9735108e9bb13b7b5a48bd2a051a5ec3e8a2f2ac Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 29 Jan 2015 16:01:32 +1000 Subject: SI-9123 More coherent trees with patmat, dependent types The pattern matcher needs to substitute references to bound variables with references to either a) synthetic temporary vals, or to b) selections. The latter occurs under -optimize to avoid to be frugal with local variable slots. For instance: ``` def test(s: Some[String]) = s match { case Some(elem) => elem.length } ``` Is translated to: ``` def test(s: Some[String]): Int = { case val x1: Some[String] = s; case4(){ if (x1.ne(null)) matchEnd3(x1.x.length()) else case5() }; case5(){ matchEnd3(throw new MatchError(x1)) }; matchEnd3(x: Int){ x } } ``` However, for a long time this translation failed to consider references to the binder in types. #4122 tried to address this by either using standard substitution facilities where available (references to temp vals), and by expanding the patmat's home grown substitution to handle the more complex case of referencing a selection. However, this left the tree in an incoherent state; while it patched up the `.tpe` field of `Tree`s, it failed to modify the info of `Symbol`-s. This led to a crash in the later uncurry phase under `-Ydelambdafy:method`. This commit modifies the info of such symbols to get rid of stray refeferences to the pattern binder symbols. --- .../scala/tools/nsc/transform/patmat/PatternMatching.scala | 5 +++++ test/files/pos/t9123.flags | 1 + test/files/pos/t9123.scala | 10 ++++++++++ 3 files changed, 16 insertions(+) create mode 100644 test/files/pos/t9123.flags create mode 100644 test/files/pos/t9123.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala index d35aad964d..b2f2516b5b 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala @@ -239,6 +239,11 @@ trait Interface extends ast.TreeDSL { case Ident(_) => subst(from, to) case _ => super.transform(tree) } + tree1 match { + case _: DefTree => + tree1.symbol.modifyInfo(_.substituteTypes(from, toTypes)) + case _ => + } tree1.modifyType(_.substituteTypes(from, toTypes)) } } diff --git a/test/files/pos/t9123.flags b/test/files/pos/t9123.flags new file mode 100644 index 0000000000..c16e2f71dc --- /dev/null +++ b/test/files/pos/t9123.flags @@ -0,0 +1 @@ +-optimize -Ydelambdafy:method diff --git a/test/files/pos/t9123.scala b/test/files/pos/t9123.scala new file mode 100644 index 0000000000..22d55b4351 --- /dev/null +++ b/test/files/pos/t9123.scala @@ -0,0 +1,10 @@ +trait Setting { + type T + def value: T +} + +object Test { + def test(x: Some[Setting]) = x match { + case Some(dep) => Some(dep.value) map (_ => true) + } +} -- cgit v1.2.3 From 06ba676c7dcba424246a276f63bcf23962a8e1f9 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Fri, 30 Jan 2015 14:09:29 +0100 Subject: Fix intellij 14 setup script to read scala version from version.properties --- src/intellij-14/scala.ipr.SAMPLE | 16 ++++++++-------- src/intellij-14/setup.sh | 3 +++ 2 files changed, 11 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/intellij-14/scala.ipr.SAMPLE b/src/intellij-14/scala.ipr.SAMPLE index 7c2022f3a9..1e3d07466d 100644 --- a/src/intellij-14/scala.ipr.SAMPLE +++ b/src/intellij-14/scala.ipr.SAMPLE @@ -233,14 +233,14 @@ - - - + + + - - + + @@ -248,9 +248,9 @@ - - - + + + diff --git a/src/intellij-14/setup.sh b/src/intellij-14/setup.sh index ec303778ed..cf08898f24 100755 --- a/src/intellij-14/setup.sh +++ b/src/intellij-14/setup.sh @@ -12,3 +12,6 @@ for f in "$SCRIPT_DIR"/*.SAMPLE; do g=${f%.SAMPLE} cp $f $g done + +SCALA_VERSION="`cat $SCRIPT_DIR/../../versions.properties | grep 'starr.version' | awk '{split($0,a,"="); print a[2]}'`" +sed "s/#scala-version#/$SCALA_VERSION/g" $SCRIPT_DIR/scala.ipr.SAMPLE > $SCRIPT_DIR/scala.ipr \ No newline at end of file -- cgit v1.2.3 From f2d0231898a4537218656867e9356e09d05d3a85 Mon Sep 17 00:00:00 2001 From: Denton Cockburn Date: Sat, 31 Jan 2015 11:22:57 -0500 Subject: SI-9095 Memory leak in LinkedHasMap and LinkedHashSet The clear() method in scala.collection.mutable.LinkedHashSet and scala.collection.mutable.LinkedHashMap does not set lastEntry to null. In result it holds a reference to an object that was removed from the collection. --- .../scala/collection/mutable/LinkedHashMap.scala | 1 + .../scala/collection/mutable/LinkedHashSet.scala | 1 + .../collection/mutable/LinkedHashMapTest.scala | 25 ++++++++++++++++++++++ .../collection/mutable/LinkedHashSetTest.scala | 25 ++++++++++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 test/junit/scala/collection/mutable/LinkedHashMapTest.scala create mode 100644 test/junit/scala/collection/mutable/LinkedHashSetTest.scala (limited to 'src') diff --git a/src/library/scala/collection/mutable/LinkedHashMap.scala b/src/library/scala/collection/mutable/LinkedHashMap.scala index b64504be3d..275f490675 100644 --- a/src/library/scala/collection/mutable/LinkedHashMap.scala +++ b/src/library/scala/collection/mutable/LinkedHashMap.scala @@ -160,6 +160,7 @@ class LinkedHashMap[A, B] extends AbstractMap[A, B] override def clear() { clearTable() firstEntry = null + lastEntry = null } private def writeObject(out: java.io.ObjectOutputStream) { diff --git a/src/library/scala/collection/mutable/LinkedHashSet.scala b/src/library/scala/collection/mutable/LinkedHashSet.scala index 1768c946ed..756a2f73c1 100644 --- a/src/library/scala/collection/mutable/LinkedHashSet.scala +++ b/src/library/scala/collection/mutable/LinkedHashSet.scala @@ -112,6 +112,7 @@ class LinkedHashSet[A] extends AbstractSet[A] override def clear() { clearTable() firstEntry = null + lastEntry = null } private def writeObject(out: java.io.ObjectOutputStream) { diff --git a/test/junit/scala/collection/mutable/LinkedHashMapTest.scala b/test/junit/scala/collection/mutable/LinkedHashMapTest.scala new file mode 100644 index 0000000000..37dcd028a5 --- /dev/null +++ b/test/junit/scala/collection/mutable/LinkedHashMapTest.scala @@ -0,0 +1,25 @@ +package scala.collection.mutable + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.{ Assert, Test } + +import scala.collection.mutable + +/* Test for SI-9095 */ +@RunWith(classOf[JUnit4]) +class LinkedHashMapTest { + class TestClass extends mutable.LinkedHashMap[String, Int] { + def lastItemRef = lastEntry + } + + @Test + def testClear: Unit = { + val lhm = new TestClass + Seq("a" -> 8, "b" -> 9).foreach(kv => lhm.put(kv._1, kv._2)) + + Assert.assertNotNull(lhm.lastItemRef) + lhm.clear() + Assert.assertNull(lhm.lastItemRef) + } +} diff --git a/test/junit/scala/collection/mutable/LinkedHashSetTest.scala b/test/junit/scala/collection/mutable/LinkedHashSetTest.scala new file mode 100644 index 0000000000..b419ad37ec --- /dev/null +++ b/test/junit/scala/collection/mutable/LinkedHashSetTest.scala @@ -0,0 +1,25 @@ +package scala.collection.mutable + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.{ Assert, Test } + +import scala.collection.mutable + +/* Test for SI-9095 */ +@RunWith(classOf[JUnit4]) +class LinkedHashSetTest { + class TestClass extends mutable.LinkedHashSet[String] { + def lastItemRef = lastEntry + } + + @Test + def testClear: Unit = { + val lhs = new TestClass + Seq("a", "b").foreach(k => lhs.add(k)) + + Assert.assertNotNull(lhs.lastItemRef) + lhs.clear() + Assert.assertNull(lhs.lastItemRef) + } +} -- cgit v1.2.3 From 1970a8315ed3b2ff8877dfef2afcee936d59871b Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 15 Dec 2014 16:21:26 +1000 Subject: SI-9041 Avoid unreported type error with overloading, implicits If `qual.foo(args)` fails to typecheck, we fall back to `ImplicitView(qual).foo(args)`. However, if the original type error stemmed from an overload ambiguity, the tree `Select(qual, 'foo')` holds onto an error symbol. The fall back attempt just returns an `Apply` tree containing the erroneous qualifier, as it does not want to issue cascading type errors. This commit replaces the error symbol with a `NoSymbol`, which triggers the second try typechecking to perform overload resolution again. A more principled fix might be to more pervasively duplicate trees before mutating their types and symbols, that this is beyond the scope of this bug fix. --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 10 ++++++++++ test/files/neg/t9041.check | 4 ++++ test/files/neg/t9041.scala | 17 +++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 test/files/neg/t9041.check create mode 100644 test/files/neg/t9041.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index e6fa9a0142..fb47f45bc7 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -825,6 +825,16 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } orElse { _ => val resetTree = resetAttrs(original) + resetTree match { + case treeInfo.Applied(fun, targs, args) => + if (fun.symbol != null && fun.symbol.isError) + // SI-9041 Without this, we leak error symbols past the typer! + // because the fallback typechecking notices the error-symbol, + // refuses to re-attempt typechecking, and presumes that someone + // else was responsible for issuing the related type error! + fun.setSymbol(NoSymbol) + case _ => + } debuglog(s"fallback on implicits: ${tree}/$resetTree") val tree1 = typed(resetTree, mode) // Q: `typed` already calls `pluginsTyped` and `adapt`. the only difference here is that diff --git a/test/files/neg/t9041.check b/test/files/neg/t9041.check new file mode 100644 index 0000000000..669e9434e0 --- /dev/null +++ b/test/files/neg/t9041.check @@ -0,0 +1,4 @@ +t9041.scala:11: error: could not find implicit value for parameter cellSetter: CellSetter[scala.math.BigDecimal] + def setCell(cell: Cell, data: math.BigDecimal) { cell.setCellValue(data) } + ^ +one error found diff --git a/test/files/neg/t9041.scala b/test/files/neg/t9041.scala new file mode 100644 index 0000000000..2bdef0d3ae --- /dev/null +++ b/test/files/neg/t9041.scala @@ -0,0 +1,17 @@ +// False negative test, requires overloading in Cell. + +trait Cell { def setCellValue(i: Int) = () ; def setCellValue(d: Double) = () } + +trait Nope { + def f = { + trait CellSetter[A] { + def setCell(cell: Cell, data: A): Unit + } + implicit val bigDecimalCellSetter = new CellSetter[math.BigDecimal]() { + def setCell(cell: Cell, data: math.BigDecimal) { cell.setCellValue(data) } + } + implicit class RichCell(cell: Cell) { + def setCellValue[A](data: A)(implicit cellSetter: CellSetter[A]) = cellSetter.setCell(cell, data) + } + } +} -- cgit v1.2.3 From 486f92c5ddd6f02cdcd6e32329ce92f90a9fa1c9 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 3 Feb 2015 15:15:51 +1000 Subject: Refactor transformStats impls for consistency Uses the same idiom in `Flatten` and `LambdaLift` as is established in `Extender` and (recently) `Delambdafy`. This avoids any possibility of adding a member to a package twice, as used to happen in SI-9097. --- src/compiler/scala/tools/nsc/transform/Flatten.scala | 2 +- src/compiler/scala/tools/nsc/transform/LambdaLift.scala | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/Flatten.scala b/src/compiler/scala/tools/nsc/transform/Flatten.scala index 6149e40fa7..fbb0307773 100644 --- a/src/compiler/scala/tools/nsc/transform/Flatten.scala +++ b/src/compiler/scala/tools/nsc/transform/Flatten.scala @@ -166,7 +166,7 @@ abstract class Flatten extends InfoTransform { override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = { val stats1 = super.transformStats(stats, exprOwner) if (currentOwner.isPackageClass) { - val lifted = liftedDefs(currentOwner).toList + val lifted = liftedDefs.remove(currentOwner).toList.flatten stats1 ::: lifted } else stats1 diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index fa0c1f797b..a703542587 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -539,12 +539,11 @@ abstract class LambdaLift extends InfoTransform { override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = { def addLifted(stat: Tree): Tree = stat match { case ClassDef(_, _, _, _) => - val lifted = liftedDefs get stat.symbol match { + val lifted = liftedDefs remove stat.symbol match { case Some(xs) => xs reverseMap addLifted case _ => log("unexpectedly no lifted defs for " + stat.symbol) ; Nil } - try deriveClassDef(stat)(impl => deriveTemplate(impl)(_ ::: lifted)) - finally liftedDefs -= stat.symbol + deriveClassDef(stat)(impl => deriveTemplate(impl)(_ ::: lifted)) case DefDef(_, _, _, _, _, Block(Nil, expr)) if !stat.symbol.isConstructor => deriveDefDef(stat)(_ => expr) -- cgit v1.2.3 From 1a56eb30da31dc4134c8ef78a45dde45df3c8b27 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 3 Feb 2015 18:46:33 +1000 Subject: SI-9133 Harden against infinite loop in NoSymbol.owner The available evidence gathered in an IDE hang suggests that while editing erronenous code, a call to `Erasure#javaSig` by the IDE's structure builder triggered the `ExplicitOuter` info transformer on a symbol with some sort of incoherent owner chain, which led to an infinite loop in `NoSymbol#outerClass`. This commit hardens that method to work in the same manner as a call to `NoSymbol.owner`: log the error under -Xdev or -Ydebug and return return `NoSymbol` to soldier on without crashing / hanging. I haven't formulated a theory about how we might have ended up with the corrupt owner chain. --- src/reflect/scala/reflect/internal/Symbols.scala | 6 +++++- test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index e91bfadc85..d5fc52abbf 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -2054,7 +2054,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => * where it is the outer class of the enclosing class. */ final def outerClass: Symbol = - if (owner.isClass) owner + if (this == NoSymbol) { + // ideally we shouldn't get here, but its better to harden against this than suffer the infinite loop in SI-9133 + devWarningDumpStack("NoSymbol.outerClass", 15) + NoSymbol + } else if (owner.isClass) owner else if (isClassLocalToConstructor) owner.enclClass.outerClass else owner.outerClass diff --git a/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala b/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala index 11e955a4bb..895ad9d683 100644 --- a/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala +++ b/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala @@ -44,4 +44,9 @@ class SymbolTableTest { assertFalse("Foo should be a superclass of Foo", fooSymbol.tpe <:< barSymbol.tpe) } + @Test + def noSymbolOuterClass_t9133: Unit = { + import symbolTable._ + assert(NoSymbol.outerClass == NoSymbol) + } } -- cgit v1.2.3 From bf20737faa2da5b45ad1ef5e6a43dff307c99788 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Sat, 20 Dec 2014 19:51:43 +0100 Subject: SI-8689 Avoid internal error in Promise after sequence of completions Calling `completeWith` when the `DefaultPromise` is already completed, leads to callbacks not being properly executed. This happened because `Future.InternalCallbackExecutor` extends `BatchingExecutor`[1] which assumes `unbatchedExecute` to be async, when in this case it is sync, and if there is an exception thrown by executing the batch, it creates a new batch with the remaining items from the current batch and submits that to `unbatchedExecute` and then rethrows, but if you have a sync `unbatchedExecute`, it will fail since it is not reentrant, as witnessed by the failed `require` as reported in this issue. This commit avoids problem by delegating `completeWith` to `tryComplete`, which has the effect of using `onComplete` + `tryComplete` i.s.o. `complete`, which means that when it fails (because of a benign race condition between completers) it won't throw an exception. It has been tested by the minimized reproducer. [1] Actually, in the 2.10.x branch where this patch is starting out, "The BatchingExecutor trait had to be inlined into InternalCallbackExecutor for binary compatibility.". This comment will be more literally correct in the context of 2.11.x and beyond --- src/library/scala/concurrent/Promise.scala | 7 +-- test/files/jvm/future-spec/PromiseTests.scala | 77 +++++++++++++++++++++++---- test/files/jvm/t8689.check | 1 + test/files/jvm/t8689.scala | 13 +++++ 4 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 test/files/jvm/t8689.check create mode 100644 test/files/jvm/t8689.scala (limited to 'src') diff --git a/src/library/scala/concurrent/Promise.scala b/src/library/scala/concurrent/Promise.scala index 8355a73a1f..02253d4bd9 100644 --- a/src/library/scala/concurrent/Promise.scala +++ b/src/library/scala/concurrent/Promise.scala @@ -66,11 +66,8 @@ trait Promise[T] { * * @return This promise */ - final def completeWith(other: Future[T]): this.type = { - other onComplete { this complete _ } - this - } - + final def completeWith(other: Future[T]): this.type = tryCompleteWith(other) + /** Attempts to complete this promise with the specified future, once that future is completed. * * @return This promise diff --git a/test/files/jvm/future-spec/PromiseTests.scala b/test/files/jvm/future-spec/PromiseTests.scala index 48f94666ba..3b20a96502 100644 --- a/test/files/jvm/future-spec/PromiseTests.scala +++ b/test/files/jvm/future-spec/PromiseTests.scala @@ -43,21 +43,80 @@ object PromiseTests extends MinimalScalaTest { Await.result(failure fallbackTo otherFailure, defaultTimeout) }.getMessage mustBe ("br0ken") } - + + "be completable with a completed Promise" in { + { + val p = Promise[String]() + p.tryCompleteWith(Promise[String]().success("foo").future) + Await.result(p.future, defaultTimeout) mustBe ("foo") + } + { + val p = Promise[String]() + p.completeWith(Promise[String]().success("foo").future) + Await.result(p.future, defaultTimeout) mustBe ("foo") + } + { + val p = Promise[String]() + p.tryCompleteWith(Promise[String]().failure(new RuntimeException("br0ken")).future) + intercept[RuntimeException] { + Await.result(p.future, defaultTimeout) + }.getMessage mustBe ("br0ken") + } + { + val p = Promise[String]() + p.tryCompleteWith(Promise[String]().failure(new RuntimeException("br0ken")).future) + intercept[RuntimeException] { + Await.result(p.future, defaultTimeout) + }.getMessage mustBe ("br0ken") + } + } } "A successful Promise" should { - val result = "test value" - val promise = Promise[String]().complete(Success(result)) - promise.isCompleted mustBe (true) - futureWithResult(_(promise.future, result)) + "be completed" in { + val result = "test value" + val promise = Promise[String]().complete(Success(result)) + promise.isCompleted mustBe (true) + futureWithResult(_(promise.future, result)) + } + + "not be completable with a completed Promise" in { + { + val p = Promise.successful("bar") + p.tryCompleteWith(Promise[String]().success("foo").future) + Await.result(p.future, defaultTimeout) mustBe ("bar") + } + { + val p = Promise.successful("bar") + p.completeWith(Promise[String]().success("foo").future) + Await.result(p.future, defaultTimeout) mustBe ("bar") + } + } } "A failed Promise" should { - val message = "Expected Exception" - val promise = Promise[String]().complete(Failure(new RuntimeException(message))) - promise.isCompleted mustBe (true) - futureWithException[RuntimeException](_(promise.future, message)) + "be completed" in { + val message = "Expected Exception" + val promise = Promise[String]().complete(Failure(new RuntimeException(message))) + promise.isCompleted mustBe (true) + futureWithException[RuntimeException](_(promise.future, message)) + } + "not be completable with a completed Promise" in { + { + val p = Promise[String]().failure(new RuntimeException("unbr0ken")) + p.tryCompleteWith(Promise[String].failure(new Exception("br0ken")).future) + intercept[RuntimeException] { + Await.result(p.future, defaultTimeout) + }.getMessage mustBe ("unbr0ken") + } + { + val p = Promise[String]().failure(new RuntimeException("unbr0ken")) + p.completeWith(Promise[String]().failure(new Exception("br0ken")).future) + intercept[RuntimeException] { + Await.result(p.future, defaultTimeout) + }.getMessage mustBe ("unbr0ken") + } + } } "An interrupted Promise" should { diff --git a/test/files/jvm/t8689.check b/test/files/jvm/t8689.check new file mode 100644 index 0000000000..2e9ba477f8 --- /dev/null +++ b/test/files/jvm/t8689.check @@ -0,0 +1 @@ +success diff --git a/test/files/jvm/t8689.scala b/test/files/jvm/t8689.scala new file mode 100644 index 0000000000..ef43a1df63 --- /dev/null +++ b/test/files/jvm/t8689.scala @@ -0,0 +1,13 @@ +object Test { + def main(args: Array[String]): Unit = { + import scala.concurrent._ + import ExecutionContext.Implicits.global + val source1 = Promise[Int]() + val source2 = Promise[Int]() + source2.completeWith(source1.future).future.onComplete { + case _ => print("success") + } + source2.tryFailure(new TimeoutException) + source1.success(123) + } +} \ No newline at end of file -- cgit v1.2.3 From 720ce5d7a111a8e5dcd380f0abe3a972a75392be Mon Sep 17 00:00:00 2001 From: BartekJanota Date: Tue, 3 Feb 2015 14:17:04 +0100 Subject: Scala doc update (sliding method). Added info about default step set to 1. Reading this doc (having no code) can be ambiguous. updated scala doc (overloaded sliding method) Step is a method parameter and must be always given explicit, so info about default value is wrong. --- src/library/scala/collection/IterableLike.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/library/scala/collection/IterableLike.scala b/src/library/scala/collection/IterableLike.scala index 91ab1f6ac2..7643b84a8b 100644 --- a/src/library/scala/collection/IterableLike.scala +++ b/src/library/scala/collection/IterableLike.scala @@ -179,6 +179,7 @@ self => /** Groups elements in fixed size blocks by passing a "sliding window" * over them (as opposed to partitioning them, as is done in grouped.) + * "Sliding window" step is 1 by default. * @see [[scala.collection.Iterator]], method `sliding` * * @param size the number of elements per group @@ -194,7 +195,7 @@ self => * * @param size the number of elements per group * @param step the distance between the first elements of successive - * groups (defaults to 1) + * groups * @return An iterator producing ${coll}s of size `size`, except the * last and the only element will be truncated if there are * fewer elements than size. -- cgit v1.2.3 From 6b26f3b6a187c4c8f606b8f7e4b0ae84dc9cdebe Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 5 Feb 2015 12:23:21 +1000 Subject: SI-9135 Fix NPE, a regression in the pattern matcher The community build discovered that #4252 introduced the possibility for a NullPointerException. The tree with a null type was a synthetic `Apply(<>)` created by the pattern matcher. This commit adds a null check. --- src/reflect/scala/reflect/internal/Trees.scala | 2 +- test/files/pos/t9135.scala | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 test/files/pos/t9135.scala (limited to 'src') diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index ccf907e05d..fd918b8595 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -1622,7 +1622,7 @@ trait Trees extends api.Trees { } private def invalidateSingleTypeCaches(tree: Tree): Unit = { if (mutatedSymbols.nonEmpty) - for (t <- tree) + for (t <- tree if t.tpe != null) for (tp <- t.tpe) { tp match { case s: SingleType if mutatedSymbols contains s.sym => diff --git a/test/files/pos/t9135.scala b/test/files/pos/t9135.scala new file mode 100644 index 0000000000..1e2c97baf9 --- /dev/null +++ b/test/files/pos/t9135.scala @@ -0,0 +1,16 @@ + +class Free[A] { + + + this match { + case a @ Gosub() => gosub(a.a)(x => gosub(???)(???)) + } + def gosub[A, B](a0: Free[A])(f0: A => Any): Free[B] = ??? +} + + + + case class Gosub[B]() extends Free[B] { + type C + def a: Free[C] = ??? + } -- cgit v1.2.3 From c88e2329bddadcd61b065e45a1aa1a0a0a729523 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 6 Feb 2015 12:27:53 -0800 Subject: IDE setup section --- CONTRIBUTING.md | 2 +- README.md | 25 ++++++++++++++++++++++++- src/eclipse/README.md | 23 ++--------------------- 3 files changed, 27 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0171d798af..e9505c26df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ # Welcome! Thank you for contributing to Scala! We follow the standard GitHub [fork & pull](https://help.github.com/articles/using-pull-requests/#fork--pull) approach to pull requests. Just fork the official repo, develop in a branch, and submit a PR! -You're always welcome to submit your PR straight away and start the discussion (without reading the rest of this wonderful doc, or the `READMEnot^H^H^H.md`). The goal of these notes is to make your experience contributing to Scala as smooth and pleasant as possible. +You're always welcome to submit your PR straight away and start the discussion (without reading the rest of this wonderful doc, or the `READMEnot^H^H^H.md`). The goal of these notes is to make your experience contributing to Scala as smooth and pleasant as possible. We're happy to guide you through the process once you've submitted your PR. ## The Scala Community Last year, you -- the Scala community -- matched the core team at EPFL in number of commits contributed to Scala 2.11, doubling the percentage of commits from outside EPFL/Typesafe since 2.10. Excellent work! (The split is roughly 25/25/50 for you/epfl/typesafe. By the way, the team at Typesafe is: @adriaanm, @gkossakowski, @lrytz and @retronym.) diff --git a/README.md b/README.md index c0a1b6d56e..cb9701b3f2 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,29 @@ To help you plan your contributions, we communicate our plans on a regular basis Once you've established some history submitting PRs, we will invite you to become a reviewer for others's code. The main goal of this whole process, in the end, is to ensure the health of the Scala project by improving the quality of the code base, the documentation, as well as this process itself. Thank you for doing your part! +## IDE Setup +### Eclipse +Download the [Scala IDE bundle](http://scala-ide.org/download/sdk.html). It comes preconfigured for optimal performance. + + - Run `ant init` to download some necessary jars. + - Import the project (in `src/eclipse`) via `File` → `Import Existing Projects into Workspace`. Check all projects and click ok. + +For important details on building, debugging and file encodings, please see [the excellent tutorial on scala-ide.org](http://scala-ide.org/docs/tutorials/scalac-trunk/index.html) and the included README.md in src/eclipse. + +### IntelliJ 14 +Use the latest IntelliJ IDEA release and install the Scala plugin from within the IDE. + +The following steps are required to use IntelliJ IDEA on Scala trunk + - Run `ant init`. This will download some JARs to `./build/deps`, which are included in IntelliJ's classpath. + - Run src/intellij-14/setup.sh + - Open ./src/intellij-14/scala.ipr in IntelliJ + - File, Project Settings, Project, SDK. Create an SDK entry named "1.6" containing the Java 1.6 SDK. + (You may use a later SDK for local development, but the CI will verify against Java 6.) + +Compilation within IDEA is performed in "-Dlocker.skip=1" mode: the sources are built +directly using the STARR compiler (which is downloaded from maven, according to `starr.version` in `versions.properties`). + + ## Building with Ant NOTE: we are working on migrating the build to sbt. @@ -122,7 +145,7 @@ compiles Scala in layers. Each layer is a complete compiled Scala compiler and l A superior layer is always compiled by the layer just below it. Here is a short description of the four layers that the build uses, from bottom to top: - - `starr`: the stable reference Scala release. We use an official version of Scala (specified by starr.version in versions.properties), downloaded from maven central. + - `starr`: the stable reference Scala release. We use an official version of Scala (specified by `starr.version` in `versions.properties`), downloaded from maven central. - `locker`: the local reference which is compiled by starr and is the work compiler in a typical development cycle. Add `locker.skip=true` to `build.properties` to skip this step and speed up development when you're not changing code generation. In any case, after it has been built once, it is “frozen” in this state. Updating it to fit the current source code must be explicitly requested (`ant locker.unlock`). - `quick`: the layer which is incrementally built when testing changes in the compiler or library. This is considered an actual new version when locker is up-to-date in relation to the source code. - `strap`: a test layer used to check stability of the build. diff --git a/src/eclipse/README.md b/src/eclipse/README.md index 5311651db5..03c7403b04 100644 --- a/src/eclipse/README.md +++ b/src/eclipse/README.md @@ -1,28 +1,9 @@ Eclipse project files ===================== -The following points describe how to get Scala to run in Eclipse: +The following points describe how to get Scala to run in Eclipse. Please also take a look at the [excellent tutorial on scala-ide.org](http://scala-ide.org/docs/tutorials/scalac-trunk/index.html). -0. To get Scala to work inside of Eclipse Kepler it is necessary to build the Scala IDE by your own -because for the moment there is no update site provided for the newest development version -of Scala. To do so enter the following commands one after the other: - - git clone https://github.com/scala-ide/scala-ide.git - cd scala-ide - ./build-all.sh clean install -Pscala-2.11.x -Peclipse-kepler -DskipTests - - After that you have an update site in `scala-ide/org.scala-ide.sdt.update-site/target/site`, which needs to be -installed in Eclipse. - -0. The second thing that needs to be done is building Scala in order to get all necessary -dependencies. To do that simply enter - - ant - - and wait until it is completed. To verify that everything has been built successfully, execute the REPL that can be found -at `scala/build/pack/bin/scala`. - -0. Import all projects inside of Eclipse by choosing `File/Import Existing Projects` +0. Import all projects into a [very recent version of Scala IDE for Eclipse](http://scala-ide.org/download/nightly.html) by choosing `File/Import Existing Projects` and navigate to `scala/src/eclipse`. Check all projects and click ok. 0. You need to define a `path variable` inside Eclipse. Define `SCALA_BASEDIR` in -- cgit v1.2.3 From 0bcc871e3f4c4331fb53ec0e7087669589a607d6 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 21 Jan 2015 19:57:45 +0100 Subject: SI-9105 Fix EnclosingMethod for classes defined in lambdas This change fixes both GenASM and GenBCode, except for the change to renaming in LamdaLift mentioned below. The reason for an inconsistent EnclosingMethod attribute was the symbol owner chain. Initially, closure class symbols don't exist, they are only created in UnCurry (delambdafy:inline). So walking the originalOwner of a definition does not yield closure classes. The commit also fixes uses of isAnonymousClass, isAnonymousFunction and isDelambdafyFunction in two ways: 1. by phase-travelling to an early phase. after flatten, the name includes the name of outer classes, so the properties may become accidentally true (they check for a substring in the name) 2. by ensuring that the (destructive) renames during LambdaLift don't make the above properties accidentally true. This was in fact the cause for SI-8900. --- .../scala/tools/nsc/CompilationUnits.scala | 6 +- .../tools/nsc/backend/jvm/BCodeAsmCommon.scala | 82 +++++++++++++++++++--- .../scala/tools/nsc/backend/jvm/BTypes.scala | 2 +- .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 4 +- .../scala/tools/nsc/backend/jvm/GenASM.scala | 11 +-- .../scala/tools/nsc/transform/LambdaLift.scala | 21 ++++-- src/reflect/scala/reflect/internal/StdNames.scala | 17 +++++ test/files/jvm/innerClassAttribute.check | 30 ++++---- test/files/jvm/innerClassAttribute/Classes_1.scala | 38 ++++++++++ test/files/jvm/innerClassAttribute/Test.scala | 78 ++++++++++++++++++-- test/files/jvm/t9105.check | 18 +++++ test/files/jvm/t9105.scala | 22 ++++++ 12 files changed, 286 insertions(+), 43 deletions(-) create mode 100644 test/files/jvm/t9105.check create mode 100644 test/files/jvm/t9105.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/CompilationUnits.scala b/src/compiler/scala/tools/nsc/CompilationUnits.scala index 1a6843a249..6be1fda1b5 100644 --- a/src/compiler/scala/tools/nsc/CompilationUnits.scala +++ b/src/compiler/scala/tools/nsc/CompilationUnits.scala @@ -25,9 +25,9 @@ trait CompilationUnits { global: Global => class CompilationUnit(val source: SourceFile) extends CompilationUnitContextApi { self => /** the fresh name creator */ - implicit val fresh: FreshNameCreator = new FreshNameCreator - def freshTermName(prefix: String = "x$") = global.freshTermName(prefix) - def freshTypeName(prefix: String) = global.freshTypeName(prefix) + implicit val fresh: FreshNameCreator = new FreshNameCreator + def freshTermName(prefix: String = nme.FRESH_TERM_NAME_PREFIX) = global.freshTermName(prefix) + def freshTypeName(prefix: String) = global.freshTypeName(prefix) /** the content of the compilation unit in tree form */ var body: Tree = EmptyTree diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala index a5f33aa786..90d2fcf4dc 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -21,6 +21,23 @@ final class BCodeAsmCommon[G <: Global](val global: G) { SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO } + /** + * Cache the value of delambdafy == "inline" for each run. We need to query this value many + * times, so caching makes sense. + */ + object delambdafyInline { + private var runId = -1 + private var value = false + + def apply(): Boolean = { + if (runId != global.currentRunId) { + runId = global.currentRunId + value = settings.Ydelambdafy.value == "inline" + } + value + } + } + /** * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a * member class. This method is used to decide if we should emit an EnclosingMethod attribute. @@ -29,10 +46,59 @@ final class BCodeAsmCommon[G <: Global](val global: G) { */ def isAnonymousOrLocalClass(classSym: Symbol): Boolean = { assert(classSym.isClass, s"not a class: $classSym") - // Here used to be an `assert(!classSym.isDelambdafyFunction)`: delambdafy lambda classes are - // always top-level. However, SI-8900 shows an example where the weak name-based implementation - // of isDelambdafyFunction failed (for a function declared in a package named "lambda"). - classSym.isAnonymousClass || !classSym.originalOwner.isClass + val r = exitingPickler(classSym.isAnonymousClass) || !classSym.originalOwner.isClass + if (r && settings.Ybackend.value == "GenBCode") { + // this assertion only holds in GenBCode. lambda lift renames symbols and may accidentally + // introduce `$lambda` into a class name, making `isDelambdafyFunction` true. under GenBCode + // we prevent this, see `nonAnon` in LambdaLift. + // phase travel necessary: after flatten, the name includes the name of outer classes. + // if some outer name contains $lambda, a non-lambda class is considered lambda. + assert(exitingPickler(!classSym.isDelambdafyFunction), classSym.name) + } + r + } + + /** + * The next enclosing definition in the source structure. Includes anonymous function classes + * under delambdafy:inline, even though they are only generated during UnCurry. + */ + def nextEnclosing(sym: Symbol): Symbol = { + val origOwner = sym.originalOwner + // phase travel necessary: after flatten, the name includes the name of outer classes. + // if some outer name contains $anon, a non-anon class is considered anon. + if (delambdafyInline() && sym.rawowner.isAnonymousFunction) { + // SI-9105: special handling for anonymous functions under delambdafy:inline. + // + // class C { def t = () => { def f { class Z } } } + // + // class C { def t = byNameMethod { def f { class Z } } } + // + // In both examples, the method f lambda-lifted into the anonfun class. + // + // In both examples, the enclosing method of Z is f, the enclosing class is the anonfun. + // So nextEnclosing needs to return the following chain: Z - f - anonFunClassSym - ... + // + // In the first example, the initial owner of f is a TermSymbol named "$anonfun" (note: not the anonFunClassSym!) + // In the second, the initial owner of f is t (no anon fun term symbol for by-name args!). + // + // In both cases, the rawowner of class Z is the anonFunClassSym. So the check in the `if` + // above makes sure we don't jump over the anonymous function in the by-name argument case. + // + // However, we cannot directly return the rawowner: if `sym` is Z, we need to include method f + // in the result. This is done by comparing the rawowners (read: lambdalift-targets) of `sym` + // and `sym.originalOwner`: if they are the same, then the originalOwner is "in between", and + // we need to return it. + // If the rawowners are different, the symbol was not in between. In the first example, the + // originalOwner of `f` is the anonfun-term-symbol, whose rawowner is C. So the nextEnclosing + // of `f` is its rawowner, the anonFunClassSym. + // + // In delambdafy:method we don't have that problem. The f method is lambda-lifted into C, + // not into the anonymous function class. The originalOwner chain is Z - f - C. + if (sym.originalOwner.rawowner == sym.rawowner) sym.originalOwner + else sym.rawowner + } else { + origOwner + } } /** @@ -63,9 +129,9 @@ final class BCodeAsmCommon[G <: Global](val global: G) { def enclosingMethod(sym: Symbol): Option[Symbol] = { if (sym.isClass || sym == NoSymbol) None else if (sym.isMethod) Some(sym) - else enclosingMethod(sym.originalOwner) + else enclosingMethod(nextEnclosing(sym)) } - enclosingMethod(classSym.originalOwner) + enclosingMethod(nextEnclosing(classSym)) } /** @@ -76,9 +142,9 @@ final class BCodeAsmCommon[G <: Global](val global: G) { assert(classSym.isClass, classSym) def enclosingClass(sym: Symbol): Symbol = { if (sym.isClass) sym - else enclosingClass(sym.originalOwner) + else enclosingClass(nextEnclosing(sym)) } - enclosingClass(classSym.originalOwner) + enclosingClass(nextEnclosing(classSym)) } final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index a9bce82acd..7814ed858b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -527,7 +527,7 @@ abstract class BTypes { * local and anonymous classes, no matter if there is an enclosing method or not. Accordingly, the * "class" field (see below) must be always defined, while the "method" field may be null. * - * NOTE: When a EnclosingMethod attribute is requried (local and anonymous classes), the "outer" + * NOTE: When an EnclosingMethod attribute is requried (local and anonymous classes), the "outer" * field in the InnerClass table must be null. * * Fields: diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 94f9b585d9..bc880e002e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -227,7 +227,9 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } val innerName: Option[String] = { - if (innerClassSym.isAnonymousClass || innerClassSym.isAnonymousFunction) None + // phase travel necessary: after flatten, the name includes the name of outer classes. + // if some outer name contains $anon, a non-anon class is considered anon. + if (exitingPickler(innerClassSym.isAnonymousClass || innerClassSym.isAnonymousFunction)) None else Some(innerClassSym.rawname + innerClassSym.moduleSuffix) // moduleSuffix for module classes } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index abe3bc512c..c36afd018b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -692,11 +692,12 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => } } - def innerName(innerSym: Symbol): String = - if (innerSym.isAnonymousClass || innerSym.isAnonymousFunction) - null - else - innerSym.rawname + innerSym.moduleSuffix + def innerName(innerSym: Symbol): String = { + // phase travel necessary: after flatten, the name includes the name of outer classes. + // if some outer name contains $anon, a non-anon class is considered anon. + if (exitingPickler(innerSym.isAnonymousClass || innerSym.isAnonymousFunction)) null + else innerSym.rawname + innerSym.moduleSuffix + } innerClassBuffer ++= { val members = exitingPickler(memberClassesOf(csym)) diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index fa0c1f797b..e9fd6e14a9 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -250,21 +250,30 @@ abstract class LambdaLift extends InfoTransform { debuglog("renaming in %s: %s => %s".format(sym.owner.fullLocationString, originalName, sym.name)) } + // make sure that the name doesn't make the symbol accidentally `isAnonymousClass` (et.al) by + // introducing `$anon` in its name. to be cautious, we don't make this change in the default + // backend under 2.11.x, so only in GenBCode. + def nonAnon(s: String) = if (settings.Ybackend.value == "GenBCode") nme.ensureNonAnon(s) else s + def newName(sym: Symbol): Name = { val originalName = sym.name def freshen(prefix: String): Name = if (originalName.isTypeName) unit.freshTypeName(prefix) else unit.freshTermName(prefix) + val join = nme.NAME_JOIN_STRING if (sym.isAnonymousFunction && sym.owner.isMethod) { - freshen(sym.name + nme.NAME_JOIN_STRING + sym.owner.name + nme.NAME_JOIN_STRING) + freshen(sym.name + join + nonAnon(sym.owner.name.toString) + join) } else { + val name = freshen(sym.name + join) // SI-5652 If the lifted symbol is accessed from an inner class, it will be made public. (where?) - // Generating a unique name, mangled with the enclosing class name, avoids a VerifyError - // in the case that a sub-class happens to lifts out a method with the *same* name. - val name = freshen("" + sym.name + nme.NAME_JOIN_STRING) - if (originalName.isTermName && !sym.enclClass.isImplClass && calledFromInner(sym)) nme.expandedName(name.toTermName, sym.enclClass) - else name + // Generating a unique name, mangled with the enclosing full class name (including + // package - subclass might have the same name), avoids a VerifyError in the case + // that a sub-class happens to lifts out a method with the *same* name. + if (originalName.isTermName && !sym.enclClass.isImplClass && calledFromInner(sym)) + newTermNameCached(nonAnon(sym.enclClass.fullName('$')) + nme.EXPAND_SEPARATOR_STRING + name) + else + name } } diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index c94ee996e4..f32b7326fe 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -112,6 +112,23 @@ trait StdNames { val ROOT: NameType = "" val SPECIALIZED_SUFFIX: NameType = "$sp" + val NESTED_IN: String = "$nestedIn" + val NESTED_IN_ANON_CLASS: String = NESTED_IN + ANON_CLASS_NAME.toString.replace("$", "") + val NESTED_IN_ANON_FUN: String = NESTED_IN + ANON_FUN_NAME.toString.replace("$", "") + val NESTED_IN_LAMBDA: String = NESTED_IN + DELAMBDAFY_LAMBDA_CLASS_NAME.toString.replace("$", "") + + /** + * Ensures that name mangling does not accidentally make a class respond `true` to any of + * isAnonymousClass, isAnonymousFunction, isDelambdafyFunction, e.g. by introducing "$anon". + */ + def ensureNonAnon(name: String) = { + name + .replace(nme.ANON_CLASS_NAME.toString, NESTED_IN_ANON_CLASS) + .replace(nme.ANON_FUN_NAME.toString, NESTED_IN_ANON_FUN) + .replace(nme.DELAMBDAFY_LAMBDA_CLASS_NAME.toString, NESTED_IN_LAMBDA) + } + + // value types (and AnyRef) are all used as terms as well // as (at least) arguments to the @specialize annotation. final val Boolean: NameType = "Boolean" diff --git a/test/files/jvm/innerClassAttribute.check b/test/files/jvm/innerClassAttribute.check index 20518aa49e..8395ff0b09 100644 --- a/test/files/jvm/innerClassAttribute.check +++ b/test/files/jvm/innerClassAttribute.check @@ -14,27 +14,27 @@ A19 / null / null A19 / null / null A19 / null / null -- A20 -- -A20$$anonfun$4 / null / null / 17 +A20$$anonfun$6 / null / null / 17 fun1: attribute for itself and the two child closures `() => ()` and `() => () => 1` -A20$$anonfun$4 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$1 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3 / null / null / 17 +A20$$anonfun$6 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$1 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3 / null / null / 17 fun2 () => (): itself and the outer closure -A20$$anonfun$4 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$1 / null / null / 17 +A20$$anonfun$6 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$1 / null / null / 17 fun3 () => () => (): itself, the outer closure and its child closure -A20$$anonfun$4 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3$$anonfun$apply$2 / null / null / 17 +A20$$anonfun$6 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3$$anonfun$apply$2 / null / null / 17 fun4: () => 1: itself and the two outer closures -A20$$anonfun$4 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3$$anonfun$apply$2 / null / null / 17 +A20$$anonfun$6 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3$$anonfun$apply$2 / null / null / 17 enclosing: nested closures have the apply method of the outer closure A20 / null / null -A20$$anonfun$4 / apply / ()Lscala/Function0; -A20$$anonfun$4 / apply / ()Lscala/Function0; -A20$$anonfun$4$$anonfun$apply$3 / apply / ()Lscala/Function0; +A20$$anonfun$6 / apply / ()Lscala/Function0; +A20$$anonfun$6 / apply / ()Lscala/Function0; +A20$$anonfun$6$$anonfun$apply$3 / apply / ()Lscala/Function0; #partest -Ydelambdafy:method -- A4 -- null / null / null diff --git a/test/files/jvm/innerClassAttribute/Classes_1.scala b/test/files/jvm/innerClassAttribute/Classes_1.scala index 9c3ea7f013..a2ade70c18 100644 --- a/test/files/jvm/innerClassAttribute/Classes_1.scala +++ b/test/files/jvm/innerClassAttribute/Classes_1.scala @@ -185,3 +185,41 @@ trait A24 extends A24Base { override object Conc extends A24Sym } } + +class SI_9105 { + // the EnclosingMethod attributes depend on the delambdafy strategy (inline vs method) + + // outerClass-inline enclMeth-inline outerClass-method enclMeth-method + val fun = () => { + class A // closure null (*) SI_9105 null + def m: Object = { class B; new B } // closure m$1 SI_9105 m$1 + val f: Object = { class C; new C } // closure null (*) SI_9105 null + } + def met = () => { + class D // closure null (*) SI_9105 met + def m: Object = { class E; new E } // closure m$1 SI_9105 m$1 + val f: Object = { class F; new F } // closure null (*) SI_9105 met + } + + // (*) the originalOwner chain of A (similar for D) is: SI_9105.fun.$anonfun-value.A + // we can get to the anonfun-class (created by uncurry), but not to the apply method. + // + // for C and F, the originalOwner chain is fun.$anonfun-value.f.C. at later phases, the rawowner of f is + // an apply$sp method of the closure class. we could use that as enclosing method, but it would be unsystematic + // (A / D don't have an encl meth either), and also strange to use the $sp, which is a compilation artifact. + // So using `null` looks more like the situation in the source code: C / F are nested classes of the anon-fun, and + // there's no method in between. + + def byName[T](op: => T) = 0 + + val bnV = byName { + class G // closure null (*) SI_9105 null + def m: Object = { class H; new H } // closure m$1 SI_9105 m$1 + val f: Object = { class I; new I } // closure null (*) SI_9105 null + } + def bnM = byName { + class J // closure null (*) SI_9105 bnM + def m: Object = { class K; new K } // closure m$1 SI_9105 m$1 + val f: Object = { class L; new L } // closure null (*) SI_9105 bnM + } +} diff --git a/test/files/jvm/innerClassAttribute/Test.scala b/test/files/jvm/innerClassAttribute/Test.scala index 3820048cb4..30dc412ff7 100644 --- a/test/files/jvm/innerClassAttribute/Test.scala +++ b/test/files/jvm/innerClassAttribute/Test.scala @@ -16,6 +16,8 @@ object Test extends BytecodeTest { loadClassNode(className).innerClasses.asScala.toList.sortBy(_.name) } + def ownInnerClassNode(n: String) = innerClassNodes(n).filter(_.name == n).head + final case class EnclosingMethod(name: String, descriptor: String, outerClass: String) def enclosingMethod(className: String) = { val n = loadClassNode(className) @@ -256,10 +258,10 @@ object Test extends BytecodeTest { printInnerClassNodes("A20") - val fun1 = lambdaClass("A20$$anonfun$4", "A20$lambda$1") - val fun2 = lambdaClass("A20$$anonfun$4$$anonfun$apply$1", "A20$lambda$$$nestedInAnonfun$5$1") - val fun3 = lambdaClass("A20$$anonfun$4$$anonfun$apply$3", "A20$lambda$$$nestedInAnonfun$5$2") - val fun4 = lambdaClass("A20$$anonfun$4$$anonfun$apply$3$$anonfun$apply$2", "A20$lambda$$$nestedInAnonfun$7$1") + val fun1 = lambdaClass("A20$$anonfun$6", "A20$lambda$1") + val fun2 = lambdaClass("A20$$anonfun$6$$anonfun$apply$1", "A20$lambda$$$nestedInAnonfun$5$1") + val fun3 = lambdaClass("A20$$anonfun$6$$anonfun$apply$3", "A20$lambda$$$nestedInAnonfun$5$2") + val fun4 = lambdaClass("A20$$anonfun$6$$anonfun$apply$3$$anonfun$apply$2", "A20$lambda$$$nestedInAnonfun$7$1") println("fun1: attribute for itself and the two child closures `() => ()` and `() => () => 1`") printInnerClassNodes(fun1) @@ -324,6 +326,73 @@ object Test extends BytecodeTest { assertMember(defsApiImpl, "A24Base", "DefinitionsApi$class", flags = Flags.ACC_PUBLIC | Flags.ACC_ABSTRACT) } + def testSI_9105() { + val isDelambdafyMethod = classpath.findClass("SI_9105$lambda$1").isDefined + if (isDelambdafyMethod) { + assertEnclosingMethod ("SI_9105$A$2" , "SI_9105", null , null) + assertEnclosingMethod ("SI_9105$B$5" , "SI_9105", "m$1", "()Ljava/lang/Object;") + assertEnclosingMethod ("SI_9105$C$1" , "SI_9105", null , null) + assertEnclosingMethod ("SI_9105$D$1" , "SI_9105", "met", "()Lscala/Function0;") + assertEnclosingMethod ("SI_9105$E$1" , "SI_9105", "m$3", "()Ljava/lang/Object;") + assertEnclosingMethod ("SI_9105$F$1" , "SI_9105", "met", "()Lscala/Function0;") + assertNoEnclosingMethod("SI_9105$lambda$$met$1") + assertNoEnclosingMethod("SI_9105$lambda$1") + assertNoEnclosingMethod("SI_9105") + + assertLocal(innerClassNodes("SI_9105$A$2").head, "SI_9105$A$2", "A$2") + assertLocal(innerClassNodes("SI_9105$B$5").head, "SI_9105$B$5", "B$5") + assertLocal(innerClassNodes("SI_9105$C$1").head, "SI_9105$C$1", "C$1") + assertLocal(innerClassNodes("SI_9105$D$1").head, "SI_9105$D$1", "D$1") + assertLocal(innerClassNodes("SI_9105$E$1").head, "SI_9105$E$1", "E$1") + assertLocal(innerClassNodes("SI_9105$F$1").head, "SI_9105$F$1", "F$1") + + // by-name + assertEnclosingMethod("SI_9105$G$1", "SI_9105", null , null) + assertEnclosingMethod("SI_9105$H$1", "SI_9105", "m$2", "()Ljava/lang/Object;") + assertEnclosingMethod("SI_9105$I$1", "SI_9105", null , null) + assertEnclosingMethod("SI_9105$J$1", "SI_9105", "bnM", "()I") + assertEnclosingMethod("SI_9105$K$2", "SI_9105", "m$4", "()Ljava/lang/Object;") + assertEnclosingMethod("SI_9105$L$1", "SI_9105", "bnM", "()I") + + assert(innerClassNodes("SI_9105$lambda$$met$1").isEmpty) + assert(innerClassNodes("SI_9105$lambda$1").isEmpty) + assert(innerClassNodes("SI_9105").length == 12) // the 12 local classes + } else { + // comment in innerClassAttribute/Classes_1.scala explains the difference between A / C and D / F. + assertEnclosingMethod ("SI_9105$$anonfun$4$A$2" , "SI_9105$$anonfun$4" , null , null) + assertEnclosingMethod ("SI_9105$$anonfun$4$B$5" , "SI_9105$$anonfun$4" , "m$1" , "()Ljava/lang/Object;") + assertEnclosingMethod ("SI_9105$$anonfun$4$C$1" , "SI_9105$$anonfun$4" , null , null) + assertEnclosingMethod ("SI_9105$$anonfun$met$1$D$1", "SI_9105$$anonfun$met$1", null , null) + assertEnclosingMethod ("SI_9105$$anonfun$met$1$E$1", "SI_9105$$anonfun$met$1", "m$3" , "()Ljava/lang/Object;") + assertEnclosingMethod ("SI_9105$$anonfun$met$1$F$1", "SI_9105$$anonfun$met$1", null , null) + assertEnclosingMethod ("SI_9105$$anonfun$4" , "SI_9105" , null , null) + assertEnclosingMethod ("SI_9105$$anonfun$met$1" , "SI_9105" , "met" , "()Lscala/Function0;") + assertNoEnclosingMethod("SI_9105") + + assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$A$2"), "SI_9105$$anonfun$4$A$2" , "A$2") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$B$5"), "SI_9105$$anonfun$4$B$5" , "B$5") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$C$1"), "SI_9105$$anonfun$4$C$1" , "C$1") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$met$1$D$1"), "SI_9105$$anonfun$met$1$D$1", "D$1") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$met$1$E$1"), "SI_9105$$anonfun$met$1$E$1", "E$1") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$met$1$F$1"), "SI_9105$$anonfun$met$1$F$1", "F$1") + + // by-name + assertEnclosingMethod("SI_9105$$anonfun$5$G$1", "SI_9105$$anonfun$5", null, null) + assertEnclosingMethod("SI_9105$$anonfun$5$H$1", "SI_9105$$anonfun$5", "m$2", "()Ljava/lang/Object;") + assertEnclosingMethod("SI_9105$$anonfun$5$I$1", "SI_9105$$anonfun$5", null, null) + assertEnclosingMethod("SI_9105$$anonfun$bnM$1$J$1", "SI_9105$$anonfun$bnM$1", null, null) + assertEnclosingMethod("SI_9105$$anonfun$bnM$1$K$2", "SI_9105$$anonfun$bnM$1", "m$4", "()Ljava/lang/Object;") + assertEnclosingMethod("SI_9105$$anonfun$bnM$1$L$1", "SI_9105$$anonfun$bnM$1", null, null) + + assertAnonymous(ownInnerClassNode("SI_9105$$anonfun$4"), "SI_9105$$anonfun$4") + assertAnonymous(ownInnerClassNode("SI_9105$$anonfun$met$1"), "SI_9105$$anonfun$met$1") + + assert(innerClassNodes("SI_9105$$anonfun$4").length == 4) // itself and three of the local classes + assert(innerClassNodes("SI_9105$$anonfun$met$1").length == 4) // itself and three of the local classes + assert(innerClassNodes("SI_9105").length == 4) // the four anon funs + } + } + def show(): Unit = { testA1() testA2() @@ -347,5 +416,6 @@ object Test extends BytecodeTest { testA22() testA23() testA24() + testSI_9105() } } diff --git a/test/files/jvm/t9105.check b/test/files/jvm/t9105.check new file mode 100644 index 0000000000..34750833f1 --- /dev/null +++ b/test/files/jvm/t9105.check @@ -0,0 +1,18 @@ +#partest !-Ydelambdafy:method +(class C$$anonfun$1$A$1,class C$$anonfun$1,null) +(class C$$anonfun$1$B$1,class C$$anonfun$1,private final java.lang.Object C$$anonfun$1.m$1()) +(class C$$anonfun$1$C$1,class C$$anonfun$1,null) +(class C$$anonfun$1$$anonfun$2$D$1,class C$$anonfun$1$$anonfun$2,null) +(class C$$anonfun$met$1$E$1,class C$$anonfun$met$1,null) +(class C$$anonfun$met$1$F$1,class C$$anonfun$met$1,private final java.lang.Object C$$anonfun$met$1.m$2()) +(class C$$anonfun$met$1$G$1,class C$$anonfun$met$1,null) +(class C$$anonfun$met$1$$anonfun$3$H$1,class C$$anonfun$met$1$$anonfun$3,null) +#partest -Ydelambdafy:method +(class C$A$1,class C,null) +(class C$B$1,class C,private final java.lang.Object C.m$1()) +(class C$C$1,class C,null) +(class C$D$1,class C,null) +(class C$E$1,class C,public scala.Function0 C.met()) +(class C$F$1,class C,private final java.lang.Object C.m$2()) +(class C$G$1,class C,public scala.Function0 C.met()) +(class C$H$1,class C,public scala.Function0 C.met()) diff --git a/test/files/jvm/t9105.scala b/test/files/jvm/t9105.scala new file mode 100644 index 0000000000..636ee8a768 --- /dev/null +++ b/test/files/jvm/t9105.scala @@ -0,0 +1,22 @@ +class C { + val fun = () => { + class A + def m: Object = { class B; new B } + val f: Object = { class C; new C } + val g = () => { class D; new D } + List[Object](new A, m, f, g()) + } + def met = () => { + class E + def m: Object = { class F; new F } + val f: Object = { class G; new G } + val g = () => { class H; new H } + List[Object](new E, m, f, g()) + } +} + +object Test extends App { + val x = new C().fun.apply() ::: new C().met.apply() + val results = x.map(_.getClass).map(cls => (cls, cls.getEnclosingClass, cls.getEnclosingMethod)) + println(results.mkString("\n")) +} -- cgit v1.2.3 From 1437aa88241d5e99900a86b25c6ce385c2708570 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 2 Feb 2015 21:04:49 +0100 Subject: SI-9124 fix EnclosingMethod of classes nested in implOnly trait defs Private trait methods are not added to the generated interface, they end up only in the implementation class. For classes nested in such methods, the EnclosingMethod attribute was incorrect. Since the EnclosingMethod attribute expresses a source-level property, but the actual enclosing method does not exist in the bytecode, we set the enclosing method to null. --- .../tools/nsc/backend/jvm/BCodeAsmCommon.scala | 6 ++++- test/files/jvm/innerClassAttribute/Classes_1.scala | 17 ++++++++++++ test/files/jvm/innerClassAttribute/Test.scala | 31 ++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala index 90d2fcf4dc..09d78fdf41 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -128,7 +128,11 @@ final class BCodeAsmCommon[G <: Global](val global: G) { assert(classSym.isClass, classSym) def enclosingMethod(sym: Symbol): Option[Symbol] = { if (sym.isClass || sym == NoSymbol) None - else if (sym.isMethod) Some(sym) + else if (sym.isMethod) { + // SI-9124, some trait methods don't exist in the generated interface. see comment in BTypes. + if (sym.owner.isTrait && sym.isImplOnly) None + else Some(sym) + } else enclosingMethod(nextEnclosing(sym)) } enclosingMethod(nextEnclosing(classSym)) diff --git a/test/files/jvm/innerClassAttribute/Classes_1.scala b/test/files/jvm/innerClassAttribute/Classes_1.scala index a2ade70c18..7b5f5d93b9 100644 --- a/test/files/jvm/innerClassAttribute/Classes_1.scala +++ b/test/files/jvm/innerClassAttribute/Classes_1.scala @@ -223,3 +223,20 @@ class SI_9105 { val f: Object = { class L; new L } // closure null (*) SI_9105 bnM } } + +trait SI_9124 { + trait A // member class, no enclosing method attribute + + new A { def f1 = 0 } // nested class, enclosing class SI_9124, no encl meth + + def f = new A { def f2 = 0 } // enclosing method is f in the interface SI_9124 + + private def g = new A { def f3 = 0 } // only encl class (SI_9124), encl meth is null because the interface SI_9124 doesn't have a method g + + object O { // member, no encl meth attribute + new A { def f4 = 0 } // enclosing class is O$, no enclosing method + } + + val f1 = { new A { def f5 = 0 }; 1 } // encl class SI_9124, no encl meth + private val f2 = { new A { def f6 = 0 }; 1 } // like above +} diff --git a/test/files/jvm/innerClassAttribute/Test.scala b/test/files/jvm/innerClassAttribute/Test.scala index 30dc412ff7..5dacd2d830 100644 --- a/test/files/jvm/innerClassAttribute/Test.scala +++ b/test/files/jvm/innerClassAttribute/Test.scala @@ -393,6 +393,36 @@ object Test extends BytecodeTest { } } + def testSI_9124() { + val classes: Map[String, String] = { + List("SI_9124$$anon$10", + "SI_9124$$anon$11", + "SI_9124$$anon$12", + "SI_9124$$anon$8", + "SI_9124$$anon$9", + "SI_9124$O$$anon$13").map({ name => + val node = loadClassNode(name) + val fMethod = node.methods.asScala.find(_.name.startsWith("f")).get.name + (fMethod, node.name) + }).toMap + } + + // println(classes) + + assertNoEnclosingMethod("SI_9124$A") + assertEnclosingMethod(classes("f1"), "SI_9124", null, null) + assertEnclosingMethod(classes("f2"), "SI_9124", "f", "()LSI_9124$A;") + assertEnclosingMethod(classes("f3"), "SI_9124", null, null) + assertEnclosingMethod(classes("f4"), "SI_9124$O$", null, null) + assertEnclosingMethod(classes("f5"), "SI_9124", null, null) + assertEnclosingMethod(classes("f6"), "SI_9124", null, null) + assertNoEnclosingMethod("SI_9124$O$") + + assertMember(ownInnerClassNode("SI_9124$A"), "SI_9124", "A", flags = publicAbstractInterface) + classes.values.foreach(n => assertAnonymous(ownInnerClassNode(n), n)) + assertMember(ownInnerClassNode("SI_9124$O$"), "SI_9124", "O$") + } + def show(): Unit = { testA1() testA2() @@ -417,5 +447,6 @@ object Test extends BytecodeTest { testA23() testA24() testSI_9105() + testSI_9124() } } -- cgit v1.2.3 From cd8f2f327106c7e2944afa7ac8b7675262626c1e Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 2 Feb 2015 15:37:24 +0100 Subject: Fix InnerClass/EnclosingMethod for trait impl and specialized classes Trait implementation classes and specialized classes are always considered top-level in terms of the InnerClass / EnclosingMethod attributes. These attributes describe source-level properties, and such classes are a compilation artifact. Note that the same is true for delambdafy:method closure classes (they are always top-level). --- .../tools/nsc/backend/jvm/BCodeAsmCommon.scala | 24 ++++-- .../scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 4 +- .../scala/tools/nsc/backend/jvm/BTypes.scala | 22 ++++++ .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 52 +++++++------ .../scala/tools/nsc/backend/jvm/GenASM.scala | 27 ++++--- test/files/jvm/innerClassAttribute/Classes_1.scala | 37 +++++++++ test/files/jvm/innerClassAttribute/Test.scala | 89 +++++++++++++++++++++- 7 files changed, 214 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala index 09d78fdf41..fb9804be7f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -21,6 +21,14 @@ final class BCodeAsmCommon[G <: Global](val global: G) { SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO } + /** + * True for classes generated by the Scala compiler that are considered top-level in terms of + * the InnerClass / EnclosingMethod classfile attributes. See comment in BTypes. + */ + def considerAsTopLevelImplementationArtifact(classSym: Symbol) = { + classSym.isImplClass || classSym.isSpecialized + } + /** * Cache the value of delambdafy == "inline" for each run. We need to query this value many * times, so caching makes sense. @@ -148,7 +156,10 @@ final class BCodeAsmCommon[G <: Global](val global: G) { if (sym.isClass) sym else enclosingClass(nextEnclosing(sym)) } - enclosingClass(nextEnclosing(classSym)) + val r = enclosingClass(nextEnclosing(classSym)) + // this should be an assertion, but we are more cautious for now as it was introduced before the 2.11.6 minor release + if (considerAsTopLevelImplementationArtifact(r)) devWarning(s"enclosing class of $classSym should not be an implementation artifact class: $r") + r } final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String) @@ -162,7 +173,8 @@ final class BCodeAsmCommon[G <: Global](val global: G) { * on the implementation of GenASM / GenBCode, so they need to be passed in. */ def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = { - if (isAnonymousOrLocalClass(classSym)) { + // trait impl classes are always top-level, see comment in BTypes + if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) { val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) debuglog(s"enclosing method for $classSym is $methodOpt (in ${methodOpt.map(_.enclClass)})") Some(EnclosingMethodEntry( @@ -191,11 +203,13 @@ final class BCodeAsmCommon[G <: Global](val global: G) { * The member classes of a class symbol. Note that the result of this method depends on the * current phase, for example, after lambdalift, all local classes become member of the enclosing * class. + * + * Impl classes are always considered top-level, see comment in BTypes. */ - def memberClassesOf(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({ - case sym if sym.isClass => + def memberClassesForInnerClassTable(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({ + case sym if sym.isClass && !considerAsTopLevelImplementationArtifact(sym) => sym - case sym if sym.isModule => + case sym if sym.isModule && !considerAsTopLevelImplementationArtifact(sym) => // impl classes get the lateMODULE flag in mixin val r = exitingPickler(sym.moduleClass) assert(r != NoSymbol, sym.fullLocationString) r diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 8d1c37532e..ccee230191 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -362,8 +362,8 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { else if (sym == definitions.NullClass) RT_NULL else { val r = classBTypeFromSymbol(sym) - if (r.isNestedClass) innerClassBufferASM += r - r + if (r.isNestedClass) innerClassBufferASM += r + r } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 7814ed858b..a194fe8fe4 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -634,6 +634,28 @@ abstract class BTypes { * } * } * + * + * Traits Members + * -------------- + * + * Some trait methods don't exist in the generated interface, but only in the implementation class + * (private methods in traits for example). Since EnclosingMethod expresses a source-level property, + * but the source-level enclosing method doesn't exist in the classfile, we the enclosing method + * is null (the enclosing class is still emitted). + * See BCodeAsmCommon.considerAsTopLevelImplementationArtifact + * + * + * Implementation Classes, Specialized Classes, Delambdafy:method closure classes + * ------------------------------------------------------------------------------ + * + * Trait implementation classes and specialized classes are always considered top-level. Again, + * the InnerClass / EnclosingMethod attributes describe a source-level properties. The impl + * classes are compilation artifacts. + * + * The same is true for delambdafy:method closure classes. These classes are generated at + * top-level in the delambdafy phase, no special support is required in the backend. + * + * * Mirror Classes * -------------- * diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index bc880e002e..52d5cafc9c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -129,35 +129,42 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { // member classes right after lambdalift, we obtain all nested classes, including local and // anonymous ones. val nestedClasses = { - val nested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(classSym)) + val nested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(classSym)) if (isTopLevelModuleClass(classSym)) { // For Java compatibility, member classes of top-level objects are treated as members of // the top-level companion class, see comment below. - val members = exitingPickler(memberClassesOf(classSym)) + val members = exitingPickler(memberClassesForInnerClassTable(classSym)) nested diff members } else { nested } } - // If this is a top-level class, the member classes of the companion object are added as - // members of the class. For example: - // class C { } - // object C { - // class D - // def f = { class E } - // } - // The class D is added as a member of class C. The reason is: for Java compatibility, the - // InnerClass attribute for D has "C" (NOT the module class "C$") as the outer class of D - // (done by buildNestedInfo). See comment in BTypes. - // For consistency, the InnerClass entry for D needs to be present in C - to Java it looks - // like D is a member of C, not C$. - val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases - val companionModuleMembers = { - // phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes, - // not local classes of the companion module (E in the exmaple) that were lifted by lambdalift. - if (isTopLevelModuleClass(linkedClass)) exitingPickler(memberClassesOf(linkedClass)) - else Nil + val companionModuleMembers = if (considerAsTopLevelImplementationArtifact(classSym)) Nil else { + // If this is a top-level non-impl (*) class, the member classes of the companion object are + // added as members of the class. For example: + // class C { } + // object C { + // class D + // def f = { class E } + // } + // The class D is added as a member of class C. The reason is: for Java compatibility, the + // InnerClass attribute for D has "C" (NOT the module class "C$") as the outer class of D + // (done by buildNestedInfo). See comment in BTypes. + // For consistency, the InnerClass entry for D needs to be present in C - to Java it looks + // like D is a member of C, not C$. + // + // (*) We exclude impl classes: if the classfile for the impl class exists on the classpath, + // a linkedClass symbol is found for which isTopLevelModule is true, so we end up searching + // members of that weird impl-class-module-class-symbol. that search probably cannot return + // any classes, but it's better to exclude it. + val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases + if (linkedClass != NoSymbol && isTopLevelModuleClass(linkedClass)) + // phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only sees member + // classes, not local classes of the companion module (E in the exmaple) that were lifted by lambdalift. + exitingPickler(memberClassesForInnerClassTable(linkedClass)) + else + Nil } nestedClasses ++ companionModuleMembers @@ -191,7 +198,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { assert(innerClassSym.isClass, s"Cannot build NestedInfo for non-class symbol $innerClassSym") val isTopLevel = innerClassSym.rawowner.isPackageClass - if (isTopLevel) None + // impl classes are considered top-level, see comment in BTypes + if (isTopLevel || considerAsTopLevelImplementationArtifact(innerClassSym)) None else { // See comment in BTypes, when is a class marked static in the InnerClass table. val isStaticNestedClass = isOriginallyStaticOwner(innerClassSym.originalOwner) @@ -248,7 +256,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { classBTypeFromInternalName.getOrElse(internalName, { val c = ClassBType(internalName) // class info consistent with BCodeHelpers.genMirrorClass - val nested = exitingPickler(memberClassesOf(moduleClassSym)) map classBTypeFromSymbol + val nested = exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol c.info = ClassInfo( superClass = Some(ObjectReference), interfaces = Nil, diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index c36afd018b..0543929aef 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -595,7 +595,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => val x = innerClassSymbolFor(s) if(x ne NoSymbol) { assert(x.isClass, "not an inner-class symbol") - val isInner = !x.rawowner.isPackageClass + // impl classes are considered top-level, see comment in BTypes + val isInner = !considerAsTopLevelImplementationArtifact(s) && !x.rawowner.isPackageClass if (isInner) { innerClassBuffer += x collectInnerClass(x.rawowner) @@ -700,23 +701,29 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => } innerClassBuffer ++= { - val members = exitingPickler(memberClassesOf(csym)) + val members = exitingPickler(memberClassesForInnerClassTable(csym)) // lambdalift makes all classes (also local, anonymous) members of their enclosing class - val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(csym)) + val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(csym)) // for the mirror class, we take the members of the companion module class (Java compat, // see doc in BTypes.scala). for module classes, we filter out those members. - if (isMirror) members + if (isMirror) members else if (isTopLevelModule(csym)) allNested diff members else allNested } - // If this is a top-level class, add members of the companion object. - val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases - if (isTopLevelModule(linkedClass)) { - // phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes, - // not local classes that were lifted by lambdalift. - innerClassBuffer ++= exitingPickler(memberClassesOf(linkedClass)) + if (!considerAsTopLevelImplementationArtifact(csym)) { + // If this is a top-level non-impl class, add members of the companion object. + // We exclude impl classes: if the classfile for the impl class exists on the classpath, a + // linkedClass symbol is found for which isTopLevelModule is true, so we end up searching + // members of that weird impl-class-module-class-symbol. that search probably cannot return + // any classes, but it's better to exclude it. + val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases + if (linkedClass != NoSymbol && isTopLevelModule(linkedClass)) { + // phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only + // sees member classes, not local classes that were lifted by lambdalift. + innerClassBuffer ++= exitingPickler(memberClassesForInnerClassTable(linkedClass)) + } } val allInners: List[Symbol] = innerClassBuffer.toList filterNot deadCode.elidedClosures diff --git a/test/files/jvm/innerClassAttribute/Classes_1.scala b/test/files/jvm/innerClassAttribute/Classes_1.scala index 7b5f5d93b9..e58fd6e26f 100644 --- a/test/files/jvm/innerClassAttribute/Classes_1.scala +++ b/test/files/jvm/innerClassAttribute/Classes_1.scala @@ -240,3 +240,40 @@ trait SI_9124 { val f1 = { new A { def f5 = 0 }; 1 } // encl class SI_9124, no encl meth private val f2 = { new A { def f6 = 0 }; 1 } // like above } + +trait ImplClassesAreTopLevel { + // all impl classes are top-level, so they don't appear in any InnerClass entry, and none of them have an EnclosingMethod attr + trait B1 { def f = 1 } + { trait B2 { def f = 1 }; new B2 {} } + val m = { + trait B3 { def f = 1 } + new B3 {} + } + def n = { + trait B4 { def f = 1 } + new B4 {} + } +} + +class SpecializedClassesAreTopLevel { + // all specialized classes are top-level + class A[@specialized(Int) T]; new A[Int] + + object T { + class B[@specialized(Int) T]; new B[Int] + } + + // these crash the compiler, SI-7625 + + // { class B[@specialized(Int) T]; new B[Int] } + + // val m: Object = { + // class C[@specialized(Int) T] + // new C[Int] + // } + + // def n: Object = { + // class D[@specialized(Int) T] + // new D[Int] + // } +} diff --git a/test/files/jvm/innerClassAttribute/Test.scala b/test/files/jvm/innerClassAttribute/Test.scala index 5dacd2d830..16e5d40052 100644 --- a/test/files/jvm/innerClassAttribute/Test.scala +++ b/test/files/jvm/innerClassAttribute/Test.scala @@ -18,6 +18,14 @@ object Test extends BytecodeTest { def ownInnerClassNode(n: String) = innerClassNodes(n).filter(_.name == n).head + def testInner(cls: String, fs: (InnerClassNode => Unit)*) = { + val ns = innerClassNodes(cls) + assert(ns.length == fs.length, ns) + (ns zip fs.toList) foreach { case (n, f) => f(n) } + } + + + final case class EnclosingMethod(name: String, descriptor: String, outerClass: String) def enclosingMethod(className: String) = { val n = loadClassNode(className) @@ -318,12 +326,11 @@ object Test extends BytecodeTest { } def testA24() { - val List(defsCls, abs, conc, defsApi, defsApiImpl) = innerClassNodes("A24$DefinitionsClass") + val List(defsCls, abs, conc, defsApi) = innerClassNodes("A24$DefinitionsClass") assertMember(defsCls, "A24", "DefinitionsClass") assertMember(abs, "A24$DefinitionsClass", "Abs$") assertMember(conc, "A24$DefinitionsClass", "Conc$") assertMember(defsApi, "A24Base", "DefinitionsApi", flags = publicAbstractInterface) - assertMember(defsApiImpl, "A24Base", "DefinitionsApi$class", flags = Flags.ACC_PUBLIC | Flags.ACC_ABSTRACT) } def testSI_9105() { @@ -423,6 +430,82 @@ object Test extends BytecodeTest { assertMember(ownInnerClassNode("SI_9124$O$"), "SI_9124", "O$") } + def testImplClassesTopLevel() { + val classes = List( + "ImplClassesAreTopLevel$$anon$14", + "ImplClassesAreTopLevel$$anon$15", + "ImplClassesAreTopLevel$$anon$16", + "ImplClassesAreTopLevel$B1$class", + "ImplClassesAreTopLevel$B1", + "ImplClassesAreTopLevel$B2$1$class", + "ImplClassesAreTopLevel$B2$1", + "ImplClassesAreTopLevel$B3$1$class", + "ImplClassesAreTopLevel$B3$1", + "ImplClassesAreTopLevel$B4$class", + "ImplClassesAreTopLevel$B4$1", + "ImplClassesAreTopLevel$class", + "ImplClassesAreTopLevel") + + classes.filter(_.endsWith("$class")).foreach(assertNoEnclosingMethod) + classes.flatMap(innerClassNodes).foreach(icn => assert(!icn.name.endsWith("$class"), icn)) + + assertNoEnclosingMethod("ImplClassesAreTopLevel$B1") // member, no encl meth attr + + // no encl meth, but encl class + List("ImplClassesAreTopLevel$B2$1", "ImplClassesAreTopLevel$B3$1", + "ImplClassesAreTopLevel$$anon$14", "ImplClassesAreTopLevel$$anon$15").foreach(assertEnclosingMethod(_, "ImplClassesAreTopLevel", null, null)) + + // encl meth n + List("ImplClassesAreTopLevel$B4$1", "ImplClassesAreTopLevel$$anon$16").foreach(assertEnclosingMethod(_, "ImplClassesAreTopLevel", "n", "()Ljava/lang/Object;")) + + val an14 = assertAnonymous(_: InnerClassNode, "ImplClassesAreTopLevel$$anon$14") + val an15 = assertAnonymous(_: InnerClassNode, "ImplClassesAreTopLevel$$anon$15") + val an16 = assertAnonymous(_: InnerClassNode, "ImplClassesAreTopLevel$$anon$16") + val b1 = assertMember(_: InnerClassNode, "ImplClassesAreTopLevel", "B1", flags = publicAbstractInterface) + val b2 = assertLocal(_ : InnerClassNode, "ImplClassesAreTopLevel$B2$1", "B2$1", flags = publicAbstractInterface) + val b3 = assertLocal(_ : InnerClassNode, "ImplClassesAreTopLevel$B3$1", "B3$1", flags = publicAbstractInterface) + val b4 = assertLocal(_ : InnerClassNode, "ImplClassesAreTopLevel$B4$1", "B4$1", flags = publicAbstractInterface) + + testInner("ImplClassesAreTopLevel$$anon$14", an14, b3) + testInner("ImplClassesAreTopLevel$$anon$15", an15, b2) + testInner("ImplClassesAreTopLevel$$anon$16", an16, b4) + + testInner("ImplClassesAreTopLevel$B1$class", b1) + testInner("ImplClassesAreTopLevel$B2$1$class", b2) + testInner("ImplClassesAreTopLevel$B3$1$class", b3) + testInner("ImplClassesAreTopLevel$B4$class", b4) + + testInner("ImplClassesAreTopLevel$B1", b1) + testInner("ImplClassesAreTopLevel$B2$1", b2) + testInner("ImplClassesAreTopLevel$B3$1", b3) + testInner("ImplClassesAreTopLevel$B4$1", b4) + + testInner("ImplClassesAreTopLevel$class", an14, an15, an16) + testInner("ImplClassesAreTopLevel", an14, an15, an16, b1, b2, b3, b4) + } + + def testSpecializedClassesTopLevel() { + val cls = List( + "SpecializedClassesAreTopLevel$A$mcI$sp", + "SpecializedClassesAreTopLevel$A", + "SpecializedClassesAreTopLevel$T$", + "SpecializedClassesAreTopLevel$T$B$mcI$sp", + "SpecializedClassesAreTopLevel$T$B", + "SpecializedClassesAreTopLevel") + + // all classes are members, no local (can't test local, they crash in specialize) + cls.foreach(assertNoEnclosingMethod) + cls.flatMap(innerClassNodes).foreach(icn => assert(!icn.name.endsWith("$sp"), icn)) + + val a = assertMember(_: InnerClassNode, "SpecializedClassesAreTopLevel", "A") + val t = assertMember(_: InnerClassNode, "SpecializedClassesAreTopLevel", "T$") + val b = assertMember(_: InnerClassNode, "SpecializedClassesAreTopLevel$T$", "B", Some("SpecializedClassesAreTopLevel$T$B")) + + List("SpecializedClassesAreTopLevel$A$mcI$sp", "SpecializedClassesAreTopLevel$A").foreach(testInner(_, a)) + testInner("SpecializedClassesAreTopLevel", a, t) + List("SpecializedClassesAreTopLevel$T$", "SpecializedClassesAreTopLevel$T$B$mcI$sp", "SpecializedClassesAreTopLevel$T$B").foreach(testInner(_, t, b)) + } + def show(): Unit = { testA1() testA2() @@ -448,5 +531,7 @@ object Test extends BytecodeTest { testA24() testSI_9105() testSI_9124() + testImplClassesTopLevel() + testSpecializedClassesTopLevel() } } -- cgit v1.2.3 From b2e22fa308b2f402d78dc3d3afc33256c1d3cbba Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Tue, 3 Feb 2015 11:29:08 +0100 Subject: Fix InnerClass / EnclosingMethod for closures nested in value classes Members of value classes are moved over to the companion object early. This change ensures that closure classes nested in value classes appear that way to Java reflection. This commit also changes the EnclosingMethod attribute for classes (and anonymous functions) nested in anonymous function bodies. Before, the enclosing method was in some cases the function's apply method. Not always though: () => { class C ... val a = { class D ...} } The class C used to be nested in the function's apply method, but not D, because the value definition for a was lifted out of the apply. After this commit, we uniformly set the enclosing method of classes nested in function bodies to `null`. This is consistent with the source-level view of the code. Note that under delambdafy:method, closures never appear as enclosing classes (this didn't change in this commit). --- .../tools/nsc/backend/jvm/BCodeAsmCommon.scala | 48 +++++++++++---- .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 37 ++++++++++-- .../scala/tools/nsc/backend/jvm/GenASM.scala | 30 ++++++++-- .../scala/tools/nsc/transform/UnCurry.scala | 4 ++ .../scala/tools/nsc/interactive/Global.scala | 2 +- src/reflect/scala/reflect/internal/Symbols.scala | 7 ++- test/files/jvm/innerClassAttribute.check | 10 ++-- test/files/jvm/innerClassAttribute/Classes_1.scala | 18 ++++++ test/files/jvm/innerClassAttribute/Test.scala | 69 +++++++++++++++++++--- test/files/jvm/javaReflection.check | 2 +- 10 files changed, 189 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala index fb9804be7f..27827015c3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -109,6 +109,15 @@ final class BCodeAsmCommon[G <: Global](val global: G) { } } + def nextEnclosingClass(sym: Symbol): Symbol = { + if (sym.isClass) sym + else nextEnclosingClass(nextEnclosing(sym)) + } + + def classOriginallyNestedInClass(nestedClass: Symbol, enclosingClass: Symbol) ={ + nextEnclosingClass(nextEnclosing(nestedClass)) == enclosingClass + } + /** * Returns the enclosing method for non-member classes. In the following example * @@ -134,12 +143,23 @@ final class BCodeAsmCommon[G <: Global](val global: G) { */ private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = { assert(classSym.isClass, classSym) + + def doesNotExist(method: Symbol) = { + // (1) SI-9124, some trait methods don't exist in the generated interface. see comment in BTypes. + // (2) Value classes. Member methods of value classes exist in the generated box class. However, + // nested methods lifted into a value class are moved to the companion object and don't exist + // in the value class itself. We can identify such nested methods: the initial enclosing class + // is a value class, but the current owner is some other class (the module class). + method.owner.isTrait && method.isImplOnly || { // (1) + val enclCls = nextEnclosingClass(method) + exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls // (2) + } + } + def enclosingMethod(sym: Symbol): Option[Symbol] = { if (sym.isClass || sym == NoSymbol) None else if (sym.isMethod) { - // SI-9124, some trait methods don't exist in the generated interface. see comment in BTypes. - if (sym.owner.isTrait && sym.isImplOnly) None - else Some(sym) + if (doesNotExist(sym)) None else Some(sym) } else enclosingMethod(nextEnclosing(sym)) } @@ -152,11 +172,7 @@ final class BCodeAsmCommon[G <: Global](val global: G) { */ private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = { assert(classSym.isClass, classSym) - def enclosingClass(sym: Symbol): Symbol = { - if (sym.isClass) sym - else enclosingClass(nextEnclosing(sym)) - } - val r = enclosingClass(nextEnclosing(classSym)) + val r = nextEnclosingClass(nextEnclosing(classSym)) // this should be an assertion, but we are more cautious for now as it was introduced before the 2.11.6 minor release if (considerAsTopLevelImplementationArtifact(r)) devWarning(s"enclosing class of $classSym should not be an implementation artifact class: $r") r @@ -175,10 +191,20 @@ final class BCodeAsmCommon[G <: Global](val global: G) { def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = { // trait impl classes are always top-level, see comment in BTypes if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) { - val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) - debuglog(s"enclosing method for $classSym is $methodOpt (in ${methodOpt.map(_.enclClass)})") + val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym) + val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) match { + case some @ Some(m) => + if (m.owner != enclosingClass) { + // This should never happen. In case it does, it prevents emitting an invalid + // EnclosingMethod attribute: if the attribute specifies an enclosing method, + // it needs to exist in the specified enclosing class. + devWarning(s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass") + None + } else some + case none => none + } Some(EnclosingMethodEntry( - classDesc(enclosingClassForEnclosingMethodAttribute(classSym)), + classDesc(enclosingClass), methodOpt.map(_.javaSimpleName.toString).orNull, methodOpt.map(methodDesc).orNull)) } else { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 52d5cafc9c..2af665d31c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -125,11 +125,21 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { * nested classes, but NOT nested in C, that are used within C. */ val nestedClassSymbols = { + val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases + // The lambdalift phase lifts all nested classes to the enclosing class, so if we collect // member classes right after lambdalift, we obtain all nested classes, including local and // anonymous ones. val nestedClasses = { - val nested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(classSym)) + val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(classSym)) + val nested = { + // Classes nested in value classes are nested in the companion at this point. For InnerClass / + // EnclosingMethod, we use the value class as the outer class. So we remove nested classes + // from the companion that were originally nested in the value class. + if (exitingPickler(linkedClass.isDerivedValueClass)) allNested.filterNot(classOriginallyNestedInClass(_, linkedClass)) + else allNested + } + if (isTopLevelModuleClass(classSym)) { // For Java compatibility, member classes of top-level objects are treated as members of // the top-level companion class, see comment below. @@ -158,13 +168,28 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { // a linkedClass symbol is found for which isTopLevelModule is true, so we end up searching // members of that weird impl-class-module-class-symbol. that search probably cannot return // any classes, but it's better to exclude it. - val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases - if (linkedClass != NoSymbol && isTopLevelModuleClass(linkedClass)) + val javaCompatMembers = { + if (linkedClass != NoSymbol && isTopLevelModuleClass(linkedClass)) // phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only sees member // classes, not local classes of the companion module (E in the exmaple) that were lifted by lambdalift. - exitingPickler(memberClassesForInnerClassTable(linkedClass)) - else - Nil + exitingPickler(memberClassesForInnerClassTable(linkedClass)) + else + Nil + } + + // Classes nested in value classes are nested in the companion at this point. For InnerClass / + // EnclosingMethod we use the value class as enclosing class. Here we search nested classes + // in the companion that were originally nested in the value class, and we add them as nested + // in the value class. + val valueClassCompanionMembers = { + if (linkedClass != NoSymbol && exitingPickler(classSym.isDerivedValueClass)) { + val moduleMemberClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(linkedClass)) + moduleMemberClasses.filter(classOriginallyNestedInClass(_, classSym)) + } else + Nil + } + + javaCompatMembers ++ valueClassCompanionMembers } nestedClasses ++ companionModuleMembers diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 0543929aef..707336e5de 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -700,30 +700,48 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => else innerSym.rawname + innerSym.moduleSuffix } + val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases + innerClassBuffer ++= { val members = exitingPickler(memberClassesForInnerClassTable(csym)) // lambdalift makes all classes (also local, anonymous) members of their enclosing class val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(csym)) + val nested = { + // Classes nested in value classes are nested in the companion at this point. For InnerClass / + // EnclosingMethod, we use the value class as the outer class. So we remove nested classes + // from the companion that were originally nested in the value class. + if (exitingPickler(linkedClass.isDerivedValueClass)) allNested.filterNot(classOriginallyNestedInClass(_, linkedClass)) + else allNested + } - // for the mirror class, we take the members of the companion module class (Java compat, - // see doc in BTypes.scala). for module classes, we filter out those members. + // for the mirror class, we take the members of the companion module class (Java compat, see doc in BTypes.scala). + // for module classes, we filter out those members. if (isMirror) members - else if (isTopLevelModule(csym)) allNested diff members - else allNested + else if (isTopLevelModule(csym)) nested diff members + else nested } if (!considerAsTopLevelImplementationArtifact(csym)) { - // If this is a top-level non-impl class, add members of the companion object. + // If this is a top-level non-impl class, add members of the companion object. These are the + // classes for which we change the InnerClass entry to allow using them from Java. // We exclude impl classes: if the classfile for the impl class exists on the classpath, a // linkedClass symbol is found for which isTopLevelModule is true, so we end up searching // members of that weird impl-class-module-class-symbol. that search probably cannot return // any classes, but it's better to exclude it. - val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases if (linkedClass != NoSymbol && isTopLevelModule(linkedClass)) { // phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only // sees member classes, not local classes that were lifted by lambdalift. innerClassBuffer ++= exitingPickler(memberClassesForInnerClassTable(linkedClass)) } + + // Classes nested in value classes are nested in the companion at this point. For InnerClass / + // EnclosingMethod we use the value class as enclosing class. Here we search nested classes + // in the companion that were originally nested in the value class, and we add them as nested + // in the value class. + if (linkedClass != NoSymbol && exitingPickler(csym.isDerivedValueClass)) { + val moduleMemberClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(linkedClass)) + innerClassBuffer ++= moduleMemberClasses.filter(classOriginallyNestedInClass(_, csym)) + } } val allInners: List[Symbol] = innerClassBuffer.toList filterNot deadCode.elidedClosures diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 3544dc9966..e1cf53059a 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -225,6 +225,10 @@ abstract class UnCurry extends InfoTransform if (inlineFunctionExpansion || !canUseDelamdafyMethod) { val parents = addSerializable(abstractFunctionForFunctionType(fun.tpe)) val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation + // The original owner is used in the backend for the EnclosingMethod attribute. If fun is + // nested in a value-class method, its owner was already changed to the extension method. + // Saving the original owner allows getting the source structure from the class symbol. + defineOriginalOwner(anonClass, fun.symbol.originalOwner) anonClass setInfo ClassInfoType(parents, newScope, anonClass) val applyMethodDef = mkMethod(anonClass, nme.apply) diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 9b34a39e02..a3d0346f81 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -152,7 +152,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") // don't keep the original owner in presentation compiler runs // (the map will grow indefinitely, and the only use case is the backend) - override protected def saveOriginalOwner(sym: Symbol) { } + override def defineOriginalOwner(sym: Symbol, owner: Symbol): Unit = { } override def forInteractive = true override protected def synchronizeNames = true diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index d5fc52abbf..d23a102b28 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -66,14 +66,19 @@ trait Symbols extends api.Symbols { self: SymbolTable => // when under some flag. Define per-phase invariants for owner/owned relationships, // e.g. after flatten all classes are owned by package classes, there are lots and // lots of these to be declared (or more realistically, discovered.) + // could be private since 2.11.6, but left protected to avoid potential breakages (eg ensime) protected def saveOriginalOwner(sym: Symbol): Unit = { // some synthetic symbols have NoSymbol as owner initially if (sym.owner != NoSymbol) { if (originalOwnerMap contains sym) () - else originalOwnerMap(sym) = sym.rawowner + else defineOriginalOwner(sym, sym.rawowner) } } + def defineOriginalOwner(sym: Symbol, owner: Symbol): Unit = { + originalOwnerMap(sym) = owner + } + def symbolOf[T: WeakTypeTag]: TypeSymbol = weakTypeOf[T].typeSymbolDirect.asType abstract class SymbolContextApiImpl extends SymbolApi { diff --git a/test/files/jvm/innerClassAttribute.check b/test/files/jvm/innerClassAttribute.check index 8395ff0b09..bb532e4f36 100644 --- a/test/files/jvm/innerClassAttribute.check +++ b/test/files/jvm/innerClassAttribute.check @@ -30,11 +30,11 @@ fun4: () => 1: itself and the two outer closures A20$$anonfun$6 / null / null / 17 A20$$anonfun$6$$anonfun$apply$3 / null / null / 17 A20$$anonfun$6$$anonfun$apply$3$$anonfun$apply$2 / null / null / 17 -enclosing: nested closures have the apply method of the outer closure +enclosing: nested closures have outer class defined, but no outer method A20 / null / null -A20$$anonfun$6 / apply / ()Lscala/Function0; -A20$$anonfun$6 / apply / ()Lscala/Function0; -A20$$anonfun$6$$anonfun$apply$3 / apply / ()Lscala/Function0; +A20$$anonfun$6 / null / null +A20$$anonfun$6 / null / null +A20$$anonfun$6$$anonfun$apply$3 / null / null #partest -Ydelambdafy:method -- A4 -- null / null / null @@ -47,7 +47,7 @@ fun1: attribute for itself and the two child closures `() => ()` and `() => () = fun2 () => (): itself and the outer closure fun3 () => () => (): itself, the outer closure and its child closure fun4: () => 1: itself and the two outer closures -enclosing: nested closures have the apply method of the outer closure +enclosing: nested closures have outer class defined, but no outer method null / null / null null / null / null null / null / null diff --git a/test/files/jvm/innerClassAttribute/Classes_1.scala b/test/files/jvm/innerClassAttribute/Classes_1.scala index e58fd6e26f..fb1f32aa3d 100644 --- a/test/files/jvm/innerClassAttribute/Classes_1.scala +++ b/test/files/jvm/innerClassAttribute/Classes_1.scala @@ -277,3 +277,21 @@ class SpecializedClassesAreTopLevel { // new D[Int] // } } + +object NestedInValueClass { + // note that we can only test anonymous functions, nested classes are not allowed inside value classes + class A(val arg: String) extends AnyVal { + // A has InnerClass entries for the two closures (and for A and A$). not for B / C + def f = { + def g = List().map(x => (() => x)) // outer class A, no outer method (g is moved to the companion, doesn't exist in A) + g.map(x => (() => x)) // outer class A, outer method f + } + // statements and field declarations are not allowed in value classes + } + + object A { + // A$ has InnerClass entries for B, C, A, A$. Also for the closures above, because they are referenced in A$'s bytecode. + class B // member class of A$ + def f = { class C; new C } // outer class A$, outer method f + } +} diff --git a/test/files/jvm/innerClassAttribute/Test.scala b/test/files/jvm/innerClassAttribute/Test.scala index 16e5d40052..bc9aa2376a 100644 --- a/test/files/jvm/innerClassAttribute/Test.scala +++ b/test/files/jvm/innerClassAttribute/Test.scala @@ -225,7 +225,7 @@ object Test extends BytecodeTest { assertAnonymous(anon1, "A18$$anon$5") assertAnonymous(anon2, "A18$$anon$6") - assertLocal(a, "A18$A$1", "A$1") + assertLocal(a, "A18$A$2", "A$2") assertLocal(b, "A18$B$4", "B$4") assertEnclosingMethod( @@ -236,7 +236,7 @@ object Test extends BytecodeTest { "A18", "g$1", "()V") assertEnclosingMethod( - "A18$A$1", + "A18$A$2", "A18", "g$1", "()V") assertEnclosingMethod( "A18$B$4", @@ -280,7 +280,7 @@ object Test extends BytecodeTest { println("fun4: () => 1: itself and the two outer closures") printInnerClassNodes(fun4) - println("enclosing: nested closures have the apply method of the outer closure") + println("enclosing: nested closures have outer class defined, but no outer method") printEnclosingMethod(fun1) printEnclosingMethod(fun2) printEnclosingMethod(fun3) @@ -336,7 +336,7 @@ object Test extends BytecodeTest { def testSI_9105() { val isDelambdafyMethod = classpath.findClass("SI_9105$lambda$1").isDefined if (isDelambdafyMethod) { - assertEnclosingMethod ("SI_9105$A$2" , "SI_9105", null , null) + assertEnclosingMethod ("SI_9105$A$3" , "SI_9105", null , null) assertEnclosingMethod ("SI_9105$B$5" , "SI_9105", "m$1", "()Ljava/lang/Object;") assertEnclosingMethod ("SI_9105$C$1" , "SI_9105", null , null) assertEnclosingMethod ("SI_9105$D$1" , "SI_9105", "met", "()Lscala/Function0;") @@ -346,7 +346,7 @@ object Test extends BytecodeTest { assertNoEnclosingMethod("SI_9105$lambda$1") assertNoEnclosingMethod("SI_9105") - assertLocal(innerClassNodes("SI_9105$A$2").head, "SI_9105$A$2", "A$2") + assertLocal(innerClassNodes("SI_9105$A$3").head, "SI_9105$A$3", "A$3") assertLocal(innerClassNodes("SI_9105$B$5").head, "SI_9105$B$5", "B$5") assertLocal(innerClassNodes("SI_9105$C$1").head, "SI_9105$C$1", "C$1") assertLocal(innerClassNodes("SI_9105$D$1").head, "SI_9105$D$1", "D$1") @@ -366,7 +366,7 @@ object Test extends BytecodeTest { assert(innerClassNodes("SI_9105").length == 12) // the 12 local classes } else { // comment in innerClassAttribute/Classes_1.scala explains the difference between A / C and D / F. - assertEnclosingMethod ("SI_9105$$anonfun$4$A$2" , "SI_9105$$anonfun$4" , null , null) + assertEnclosingMethod ("SI_9105$$anonfun$4$A$3" , "SI_9105$$anonfun$4" , null , null) assertEnclosingMethod ("SI_9105$$anonfun$4$B$5" , "SI_9105$$anonfun$4" , "m$1" , "()Ljava/lang/Object;") assertEnclosingMethod ("SI_9105$$anonfun$4$C$1" , "SI_9105$$anonfun$4" , null , null) assertEnclosingMethod ("SI_9105$$anonfun$met$1$D$1", "SI_9105$$anonfun$met$1", null , null) @@ -376,7 +376,7 @@ object Test extends BytecodeTest { assertEnclosingMethod ("SI_9105$$anonfun$met$1" , "SI_9105" , "met" , "()Lscala/Function0;") assertNoEnclosingMethod("SI_9105") - assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$A$2"), "SI_9105$$anonfun$4$A$2" , "A$2") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$A$3"), "SI_9105$$anonfun$4$A$3" , "A$3") assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$B$5"), "SI_9105$$anonfun$4$B$5" , "B$5") assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$C$1"), "SI_9105$$anonfun$4$C$1" , "C$1") assertLocal(ownInnerClassNode("SI_9105$$anonfun$met$1$D$1"), "SI_9105$$anonfun$met$1$D$1", "D$1") @@ -506,6 +506,60 @@ object Test extends BytecodeTest { List("SpecializedClassesAreTopLevel$T$", "SpecializedClassesAreTopLevel$T$B$mcI$sp", "SpecializedClassesAreTopLevel$T$B").foreach(testInner(_, t, b)) } + def testNestedInValueClass() { + List( + "NestedInValueClass", + "NestedInValueClass$", + "NestedInValueClass$A", + "NestedInValueClass$A$", + "NestedInValueClass$A$B").foreach(assertNoEnclosingMethod) + + assertEnclosingMethod("NestedInValueClass$A$C$2", "NestedInValueClass$A$", "f", "()Ljava/lang/Object;") + + type I = InnerClassNode + val a = assertMember(_: I, "NestedInValueClass", "A", flags = publicStatic | Flags.ACC_FINAL) + val am = assertMember(_: I, "NestedInValueClass", "A$", flags = publicStatic) + val b = assertMember(_: I, "NestedInValueClass$A$", "B", Some("NestedInValueClass$A$B"), flags = publicStatic) + val c = assertLocal(_: I, "NestedInValueClass$A$C$2", "C$2") + + testInner("NestedInValueClass$") + testInner("NestedInValueClass", a, am) + testInner("NestedInValueClass$A$B", am, b) + testInner("NestedInValueClass$A$C$2", am, c) + + val isDelambdafyMethod = classpath.findClass("NestedInValueClass$A$lambda$$f$extension$1").isDefined + if (isDelambdafyMethod) { + List( + "NestedInValueClass$A$lambda$$g$2$1", + "NestedInValueClass$A$lambda$$f$extension$1", + "NestedInValueClass$A$lambda$$$nestedInAnonfun$13$1", + "NestedInValueClass$A$lambda$$$nestedInAnonfun$15$1").foreach(assertNoEnclosingMethod) + testInner("NestedInValueClass$A", a, am) + testInner("NestedInValueClass$A$", a, am, b, c) + testInner("NestedInValueClass$A$lambda$$g$2$1", am) + testInner("NestedInValueClass$A$lambda$$f$extension$1", am) + testInner("NestedInValueClass$A$lambda$$$nestedInAnonfun$13$1", am) + testInner("NestedInValueClass$A$lambda$$$nestedInAnonfun$15$1", am) + } else { + assertEnclosingMethod("NestedInValueClass$A$$anonfun$g$2$1" , "NestedInValueClass$A" , null, null) + assertEnclosingMethod("NestedInValueClass$A$$anonfun$g$2$1$$anonfun$apply$4" , "NestedInValueClass$A$$anonfun$g$2$1" , null, null) + assertEnclosingMethod("NestedInValueClass$A$$anonfun$f$extension$1" , "NestedInValueClass$A" , "f", "()Lscala/collection/immutable/List;") + assertEnclosingMethod("NestedInValueClass$A$$anonfun$f$extension$1$$anonfun$apply$5", "NestedInValueClass$A$$anonfun$f$extension$1", null, null) + + val gfun = assertAnonymous(_: I, "NestedInValueClass$A$$anonfun$g$2$1") + val ffun = assertAnonymous(_: I, "NestedInValueClass$A$$anonfun$f$extension$1") + val gfunfun = assertAnonymous(_: I, "NestedInValueClass$A$$anonfun$g$2$1$$anonfun$apply$4") + val ffunfun = assertAnonymous(_: I, "NestedInValueClass$A$$anonfun$f$extension$1$$anonfun$apply$5") + + testInner("NestedInValueClass$A", a, am, ffun, gfun) + testInner("NestedInValueClass$A$", a, am, ffun, gfun, b, c) + testInner("NestedInValueClass$A$$anonfun$g$2$1", a, am, gfun, gfunfun) + testInner("NestedInValueClass$A$$anonfun$g$2$1$$anonfun$apply$4", am, gfun, gfunfun) + testInner("NestedInValueClass$A$$anonfun$f$extension$1", a, am, ffun, ffunfun) + testInner("NestedInValueClass$A$$anonfun$f$extension$1$$anonfun$apply$5", am, ffun, ffunfun) + } + } + def show(): Unit = { testA1() testA2() @@ -533,5 +587,6 @@ object Test extends BytecodeTest { testSI_9124() testImplClassesTopLevel() testSpecializedClassesTopLevel() + testNestedInValueClass() } } diff --git a/test/files/jvm/javaReflection.check b/test/files/jvm/javaReflection.check index d40599507d..8180ecff8a 100644 --- a/test/files/jvm/javaReflection.check +++ b/test/files/jvm/javaReflection.check @@ -5,7 +5,7 @@ A$$anonfun$$lessinit$greater$1 / null (canon) / $anonfun$$lessinit$greater$1 (si - properties : true (local) / false (member) A$$anonfun$$lessinit$greater$1$$anonfun$apply$1 / null (canon) / $anonfun$apply$1 (simple) - declared cls: List() -- enclosing : null (declaring cls) / class A$$anonfun$$lessinit$greater$1 (cls) / null (constr) / public final scala.Function0 A$$anonfun$$lessinit$greater$1.apply() (meth) +- enclosing : null (declaring cls) / class A$$anonfun$$lessinit$greater$1 (cls) / null (constr) / null (meth) - properties : true (local) / false (member) A$$anonfun$2 / null (canon) / $anonfun$2 (simple) - declared cls: List() -- cgit v1.2.3 From 357c15710d9db5d1a8a07cf91af7c24ae6ee884d Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Thu, 5 Feb 2015 14:05:25 +0100 Subject: IntelliJ project file fix --- src/intellij-14/scaladoc.iml.SAMPLE | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/intellij-14/scaladoc.iml.SAMPLE b/src/intellij-14/scaladoc.iml.SAMPLE index 1e7621ffed..5c7015aa61 100644 --- a/src/intellij-14/scaladoc.iml.SAMPLE +++ b/src/intellij-14/scaladoc.iml.SAMPLE @@ -13,5 +13,6 @@ + \ No newline at end of file -- cgit v1.2.3 From 2f5ff595fd141025de30dadfc97870ef01d44c9f Mon Sep 17 00:00:00 2001 From: Miles Sabin Date: Mon, 9 Feb 2015 21:25:26 +0000 Subject: Backported fix for SI-7753 to 2.10.x. --- src/reflect/scala/reflect/internal/Types.scala | 89 ++++++++++++++++---------- test/files/neg/t3873.check | 4 +- test/files/neg/t3873.scala | 2 +- test/files/pos/t7753.scala | 36 +++++++++++ test/files/pos/t8223.scala | 29 +++++++++ 5 files changed, 123 insertions(+), 37 deletions(-) create mode 100644 test/files/pos/t7753.scala create mode 100644 test/files/pos/t8223.scala (limited to 'src') diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 2f49995030..15f62bca30 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -4818,37 +4818,30 @@ trait Types extends api.Types { self: SymbolTable => } /** Note: This map is needed even for non-dependent method types, despite what the name might imply. - */ + */ class InstantiateDependentMap(params: List[Symbol], actuals0: List[Type]) extends TypeMap with KeepOnlyTypeConstraints { private val actuals = actuals0.toIndexedSeq private val existentials = new Array[Symbol](actuals.size) - def existentialsNeeded: List[Symbol] = existentials.filter(_ ne null).toList - - private object StableArg { - def unapply(param: Symbol) = Arg unapply param map actuals filter (tp => - tp.isStable && (tp.typeSymbol != NothingClass) - ) - } - private object Arg { - def unapply(param: Symbol) = Some(params indexOf param) filter (_ >= 0) - } - - def apply(tp: Type): Type = mapOver(tp) match { - // unsound to replace args by unstable actual #3873 - case SingleType(NoPrefix, StableArg(arg)) => arg - // (soundly) expand type alias selections on implicit arguments, - // see depmet_implicit_oopsla* test cases -- typically, `param.isImplicit` - case tp1 @ TypeRef(SingleType(NoPrefix, Arg(pid)), sym, targs) => - val arg = actuals(pid) - val res = typeRef(arg, sym, targs) - if (res.typeSymbolDirect.isAliasType) res.dealias else tp1 - // don't return the original `tp`, which may be different from `tp1`, - // due to dropping annotations - case tp1 => tp1 + def existentialsNeeded: List[Symbol] = existentials.iterator.filter(_ ne null).toList + + private object StableArgTp { + // type of actual arg corresponding to param -- if the type is stable + def unapply(param: Symbol): Option[Type] = (params indexOf param) match { + case -1 => None + case pid => + val tp = actuals(pid) + if (tp.isStable && (tp.typeSymbol != NothingClass)) Some(tp) + else None + } } - /* Return the type symbol for referencing a parameter inside the existential quantifier. - * (Only needed if the actual is unstable.) + /** Return the type symbol for referencing a parameter that's instantiated to an unstable actual argument. + * + * To soundly abstract over an unstable value (x: T) while retaining the most type information, + * use `x.type forSome { type x.type <: T with Singleton}` + * `typeOf[T].narrowExistentially(symbolOf[x])`. + * + * See also: captureThis in AsSeenFromMap. */ private def existentialFor(pid: Int) = { if (existentials(pid) eq null) { @@ -4856,11 +4849,43 @@ trait Types extends api.Types { self: SymbolTable => existentials(pid) = ( param.owner.newExistential(param.name.toTypeName append nme.SINGLETON_SUFFIX, param.pos, param.flags) setInfo singletonBounds(actuals(pid)) - ) + ) } existentials(pid) } + private object UnstableArgTp { + // existential quantifier and type of corresponding actual arg with unstable type + def unapply(param: Symbol): Option[(Symbol, Type)] = (params indexOf param) match { + case -1 => None + case pid => + val sym = existentialFor(pid) + Some((sym, sym.tpe)) // refers to an actual value, must be kind-* + } + } + + private object StabilizedArgTp { + def unapply(param: Symbol): Option[Type] = + param match { + case StableArgTp(tp) => Some(tp) // (1) + case UnstableArgTp(_, tp) => Some(tp) // (2) + case _ => None + } + } + + /** instantiate `param.type` to the (sound approximation of the) type `T` + * of the actual argument `arg` that was passed in for `param` + * + * (1) If `T` is stable, we can just use that. + * + * (2) SI-3873: it'd be unsound to instantiate `param.type` to an unstable `T`, + * so we approximate to `X forSome {type X <: T with Singleton}` -- we can't soundly say more. + */ + def apply(tp: Type): Type = tp match { + case SingleType(NoPrefix, StabilizedArgTp(tp)) => tp + case _ => mapOver(tp) + } + //AM propagate more info to annotations -- this seems a bit ad-hoc... (based on code by spoon) override def mapOver(arg: Tree, giveup: ()=>Nothing): Tree = { // TODO: this should be simplified; in the stable case, one can @@ -4883,13 +4908,9 @@ trait Types extends api.Types { self: SymbolTable => // Both examples are from run/constrained-types.scala. object treeTrans extends Transformer { override def transform(tree: Tree): Tree = tree.symbol match { - case StableArg(actual) => - gen.mkAttributedQualifier(actual, tree.symbol) - case Arg(pid) => - val sym = existentialFor(pid) - Ident(sym) copyAttrs tree setType typeRef(NoPrefix, sym, Nil) - case _ => - super.transform(tree) + case StableArgTp(tp) => gen.mkAttributedQualifier(tp, tree.symbol) + case UnstableArgTp(quant, tp) => Ident(quant) copyAttrs tree setType tp + case _ => super.transform(tree) } } treeTrans transform arg diff --git a/test/files/neg/t3873.check b/test/files/neg/t3873.check index 54d6abdf63..f9f413aeaf 100644 --- a/test/files/neg/t3873.check +++ b/test/files/neg/t3873.check @@ -1,6 +1,6 @@ t3873.scala:11: error: type mismatch; found : Test.a.B - required: a.B - wrongf(new A)(a.b) // should not compile -- TODO: improve error message? the "a" is ambiguous + required: a.B where val a: A + wrongf(new A)(a.b) // should not compile ^ one error found diff --git a/test/files/neg/t3873.scala b/test/files/neg/t3873.scala index e7815f0937..b27b4e9c9d 100644 --- a/test/files/neg/t3873.scala +++ b/test/files/neg/t3873.scala @@ -8,5 +8,5 @@ object Test { val a = new A wrongf(a)(a.b) - wrongf(new A)(a.b) // should not compile -- TODO: improve error message? the "a" is ambiguous + wrongf(new A)(a.b) // should not compile } \ No newline at end of file diff --git a/test/files/pos/t7753.scala b/test/files/pos/t7753.scala new file mode 100644 index 0000000000..93ad23f114 --- /dev/null +++ b/test/files/pos/t7753.scala @@ -0,0 +1,36 @@ +import scala.language.{ higherKinds, implicitConversions } + +trait Foo { type Out } + +trait SI { + val instance: Foo + type Out +} + +object Test { + def test { + def indirect(si: SI)(v: si.instance.Out) = v + + val foo: Foo { type Out = Int } = ??? + def conv(i: Foo): SI { type Out = i.Out; val instance: i.type } = ??? + + val converted = conv(foo) + + val v1: Int = indirect(converted)(23) // Okay (after refining the return type `instance` in the return type of `conv`) + /* + indirect(converted){(v: converted.instance.Out)converted.instance.Out}( + 23{Int(23)} + ){converted.instance.Out}; + */ + + val v2: Int = indirect(conv(foo))(23) // Used to fail as follows: + /* + indirect( + conv(foo){si.SI{type Out = foo.Out; val instance: si.Test..type}} + ){(v: si.instance.Out)si.instance.Out}( + 23{} + ){}; + */ + + } +} diff --git a/test/files/pos/t8223.scala b/test/files/pos/t8223.scala new file mode 100644 index 0000000000..52d6b0098e --- /dev/null +++ b/test/files/pos/t8223.scala @@ -0,0 +1,29 @@ +package p { + class ViewEnv[AIn] { + type A = AIn + class SubView { def has(x: A): Boolean = ??? } + def get: SubView = new SubView + } + + trait HasA { type A } + trait Indexable[R] extends HasA + class ArrayTC[AIn] extends Indexable[Array[AIn]] { type A = AIn } +} + +package object p { + implicit def arrayTypeClass[A] : ArrayTC[A] = new ArrayTC[A] + object intArrayTC extends ArrayTC[Int] + + type EnvAlias[W <: HasA] = ViewEnv[W#A] + type SubAlias[W <: HasA] = ViewEnv[W#A]#SubView + + def f0[R](xs: R)(implicit tc: Indexable[R]): ViewEnv[tc.A]#SubView = new ViewEnv[tc.A]() get + def f1[R](xs: R)(implicit tc: Indexable[R]): EnvAlias[tc.type]#SubView = new ViewEnv[tc.A]() get + def f2[R](xs: R)(implicit tc: Indexable[R]): SubAlias[tc.type] = new ViewEnv[tc.A]() get + + def g0 = f0(Array(1)) has 2 // ok + def g1 = f1(Array(1)) has 2 // ok + def g2 = f2(Array(1)) has 2 // "found: Int(2), required: tc.A" + def g3 = f2(Array(1))(new ArrayTC[Int]) has 2 // "found: Int(2), required: tc.A" + def g4 = f2(Array(1))(intArrayTC) has 2 // ok +} -- cgit v1.2.3 From 51800ce0e83daeadf68a90bef4d64734e4721f3a Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Sun, 21 Dec 2014 23:32:14 -0800 Subject: SI-8818 FreshName extractor forgives suffix The test is corrected (inverted) and the extractor is made more succinct. Succinctness isn't enforced by the test, but I checked it manually. --- .../scala/reflect/internal/FreshNames.scala | 24 +++++++++++++--------- .../tools/nsc/symtab/FreshNameExtractorTest.scala | 12 +++++------ test/junit/scala/tools/testing/AssertUtil.scala | 21 +++++++++---------- 3 files changed, 30 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/reflect/scala/reflect/internal/FreshNames.scala b/src/reflect/scala/reflect/internal/FreshNames.scala index 7e9a568266..17883d12ad 100644 --- a/src/reflect/scala/reflect/internal/FreshNames.scala +++ b/src/reflect/scala/reflect/internal/FreshNames.scala @@ -7,6 +7,7 @@ package reflect package internal import scala.reflect.internal.util.FreshNameCreator +import scala.util.matching.Regex trait FreshNames { self: Names with StdNames => // SI-6879 Keeps track of counters that are supposed to be globally unique @@ -23,17 +24,20 @@ trait FreshNames { self: Names with StdNames => // Extractor that matches names which were generated by some // FreshNameCreator with known prefix. Extracts user-specified // prefix that was used as a parameter to newName by stripping - // global creator prefix and unique number in the end of the name. + // global creator prefix and unique numerical suffix. + // The creator prefix and numerical suffix may both be empty. class FreshNameExtractor(creatorPrefix: String = "") { - // quote prefix so that it can be used with replaceFirst - // which expects regExp rather than simple string - val quotedCreatorPrefix = java.util.regex.Pattern.quote(creatorPrefix) - - def unapply(name: Name): Option[String] = { - val sname = name.toString - // name should start with creatorPrefix and end with number - if (!sname.startsWith(creatorPrefix) || !sname.matches("^.*\\d*$")) None - else Some(NameTransformer.decode(sname.replaceFirst(quotedCreatorPrefix, "").replaceAll("\\d*$", ""))) + + // name should start with creatorPrefix and end with number + val freshlyNamed = { + val pre = if (!creatorPrefix.isEmpty) Regex quote creatorPrefix else "" + s"""$pre(.*?)\\d*""".r } + + def unapply(name: Name): Option[String] = + name.toString match { + case freshlyNamed(prefix) => Some(prefix) + case _ => None + } } } diff --git a/test/junit/scala/tools/nsc/symtab/FreshNameExtractorTest.scala b/test/junit/scala/tools/nsc/symtab/FreshNameExtractorTest.scala index effbfb2f7c..7796345351 100644 --- a/test/junit/scala/tools/nsc/symtab/FreshNameExtractorTest.scala +++ b/test/junit/scala/tools/nsc/symtab/FreshNameExtractorTest.scala @@ -32,16 +32,16 @@ class FreshNameExtractorTest { val Creator = new FreshNameCreator(prefixes.head) val Extractor = new FreshNameExtractor(prefixes.tail.head) assertThrows[MatchError] { - val Extractor(_) = TermName(Creator.newName("foo")) + TermName(Creator.newName("foo")) match { case Extractor(_) => } } } - @Test @org.junit.Ignore // SI-8818 - def extractionsFailsIfNameDoesntEndWithNumber = { - val Creator = new FreshNameCreator(prefixes.head) + @Test + def `no numeric suffix? no problem!` = { + val Creator = new FreshNameCreator(prefixes.head) val Extractor = new FreshNameExtractor(prefixes.head) - assertThrows[MatchError] { - val Extractor(_) = TermName(Creator.newName("foo") + "bar") + TermName(Creator.newName("foo") + "bar") match { + case Extractor(_) => } } } diff --git a/test/junit/scala/tools/testing/AssertUtil.scala b/test/junit/scala/tools/testing/AssertUtil.scala index d29f9a473f..d798f2e53e 100644 --- a/test/junit/scala/tools/testing/AssertUtil.scala +++ b/test/junit/scala/tools/testing/AssertUtil.scala @@ -36,21 +36,20 @@ object AssertUtil { } } - /** Check if throwable T (or a subclass) was thrown during evaluation of f, and that its message - * satisfies the `checkMessage` predicate. If any other exception will be re-thrown. + /** Check that throwable T (or a subclass) was thrown during evaluation of `body`, + * and that its message satisfies the `checkMessage` predicate. + * Any other exception is propagated. */ - def assertThrows[T <: Throwable](f: => Any, + def assertThrows[T <: Throwable](body: => Any, checkMessage: String => Boolean = s => true) (implicit manifest: Manifest[T]): Unit = { - try f - catch { - case e: Throwable if checkMessage(e.getMessage) => - val clazz = manifest.runtimeClass - if (!clazz.isAssignableFrom(e.getClass)) - throw e - else return + try { + body + fail("Expression did not throw!") + } catch { + case e: Throwable if (manifest.runtimeClass isAssignableFrom e.getClass) && + checkMessage(e.getMessage) => } - fail("Expression did not throw!") } /** JUnit-style assertion for `IterableLike.sameElements`. -- cgit v1.2.3 From 5412766a834fa730865628a57c767f36eb1b4dc4 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Mon, 26 Jan 2015 22:35:44 -0800 Subject: SI-9116 Set.subsets has a param list Now both of the overloaded variants have a parameter list. This seems to make type inference happier. Or it makes someone happier. The user is unaware whether `subsets()` takes a default arg. But happily, empty application still kicks in. --- src/library/scala/collection/SetLike.scala | 2 +- test/files/pos/t9116.scala | 7 +++++++ test/files/run/settings-parse.scala | 5 ++--- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 test/files/pos/t9116.scala (limited to 'src') diff --git a/src/library/scala/collection/SetLike.scala b/src/library/scala/collection/SetLike.scala index 3e549f72cd..31465bb619 100644 --- a/src/library/scala/collection/SetLike.scala +++ b/src/library/scala/collection/SetLike.scala @@ -171,7 +171,7 @@ self => * * @return the iterator. */ - def subsets: Iterator[This] = new AbstractIterator[This] { + def subsets(): Iterator[This] = new AbstractIterator[This] { private val elms = self.toIndexedSeq private var len = 0 private var itr: Iterator[This] = Iterator.empty diff --git a/test/files/pos/t9116.scala b/test/files/pos/t9116.scala new file mode 100644 index 0000000000..16b04c2e6b --- /dev/null +++ b/test/files/pos/t9116.scala @@ -0,0 +1,7 @@ + +trait X { + List(1, 2, 3).toSet.subsets.map(_.toList) // ok now + + List(1, 2, 3).toSet.subsets().map(_.toList) // now also + List(1, 2, 3).toSet.subsets(2).map(_.toList) // still ok +} diff --git a/test/files/run/settings-parse.scala b/test/files/run/settings-parse.scala index 2754feb972..8d83caf68f 100644 --- a/test/files/run/settings-parse.scala +++ b/test/files/run/settings-parse.scala @@ -3,9 +3,8 @@ import scala.language.postfixOps import scala.tools.nsc._ object Test { - val tokens = List("", "-deprecation", "foo.scala") - val subsets = tokens.toSet.subsets.toList - val permutations0 = subsets.flatMap(_.toList.permutations).distinct + val tokens = "" :: "-deprecation" :: "foo.scala" :: Nil + val permutations0 = tokens.toSet.subsets.flatMap(_.toList.permutations).toList.distinct def runWithCp(cp: String) = { val permutations = permutations0 flatMap ("-cp CPTOKEN" :: _ permutations) -- cgit v1.2.3 From d95269ef2c7f6a8cdbbf853edfddf625adde008c Mon Sep 17 00:00:00 2001 From: Michael Pigg Date: Fri, 6 Feb 2015 11:38:07 -0500 Subject: SI-7660: Document behaviour of Set#++ for duplicate elements Updated documentation of + and ++ in SetLike to explicitly state that duplicate elements are not added to the set. --- src/library/scala/collection/SetLike.scala | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/library/scala/collection/SetLike.scala b/src/library/scala/collection/SetLike.scala index 3e549f72cd..c98ab6ecb1 100644 --- a/src/library/scala/collection/SetLike.scala +++ b/src/library/scala/collection/SetLike.scala @@ -107,22 +107,36 @@ self => */ def + (elem: A): This - /** Creates a new $coll with additional elements. + /** Creates a new $coll with additional elements, omitting duplicates. * - * This method takes two or more elements to be added. Another overloaded - * variant of this method handles the case where a single element is added. + * This method takes two or more elements to be added. Elements that already exist in the $coll will + * not be added. Another overloaded variant of this method handles the case where a single element is added. + * + * Example: + * {{{ + * scala> val a = Set(1, 3) + 2 + 3 + * a: scala.collection.immutable.Set[Int] = Set(1, 3, 2) + * }}} * * @param elem1 the first element to add. * @param elem2 the second element to add. * @param elems the remaining elements to add. - * @return a new $coll with the given elements added. + * @return a new $coll with the given elements added, omitting duplicates. */ def + (elem1: A, elem2: A, elems: A*): This = this + elem1 + elem2 ++ elems - /** Creates a new $coll by adding all elements contained in another collection to this $coll. + /** Creates a new $coll by adding all elements contained in another collection to this $coll, omitting duplicates. + * + * This method takes a collection of elements and adds all elements, omitting duplicates, into $coll. + * + * Example: + * {{{ + * scala> val a = Set(1, 2) ++ Set(2, "a") + * a: scala.collection.immutable.Set[Any] = Set(1, 2, a) + * }}} * - * @param elems the collection containing the added elements. - * @return a new $coll with the given elements added. + * @param elems the collection containing the elements to add. + * @return a new $coll with the given elements added, omitting duplicates. */ def ++ (elems: GenTraversableOnce[A]): This = (repr /: elems.seq)(_ + _) -- cgit v1.2.3 From c851a8d4bc432731242075c01f6f8ca500458e67 Mon Sep 17 00:00:00 2001 From: JustinPihony Date: Thu, 12 Feb 2015 21:22:18 -0500 Subject: SI-9148: Appends companion type to link tooltips --- src/scaladoc/scala/tools/nsc/doc/html/HtmlPage.scala | 14 +++++++++++++- src/scaladoc/scala/tools/nsc/doc/html/page/Template.scala | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/scaladoc/scala/tools/nsc/doc/html/HtmlPage.scala b/src/scaladoc/scala/tools/nsc/doc/html/HtmlPage.scala index 3738e79ffe..ce75749859 100644 --- a/src/scaladoc/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/scaladoc/scala/tools/nsc/doc/html/HtmlPage.scala @@ -227,6 +227,18 @@ abstract class HtmlPage extends Page { thisPage => + + def docEntityKindToCompanionTitle(ety: DocTemplateEntity, baseString: String = "See companion") = + ety.companion match{ + case Some(companion) => + s"$baseString${ + if(companion.isObject) " object" + else if(companion.isTrait) " trait" + else if(companion.isClass) " class" + else "" + }" + case None => baseString + } def companionAndPackage(tpl: DocTemplateEntity): Elem = { @@ -238,7 +250,7 @@ abstract class HtmlPage extends Page { thisPage => else s"class ${companionTpl.name}"
Related Docs: - {objClassTrait} + {objClassTrait} | {templateToHtml(tpl.inTemplate, s"package ${tpl.inTemplate.name}")}
case None => 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 eda52c5fbf..e10c54a414 100644 --- a/src/scaladoc/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/scaladoc/scala/tools/nsc/doc/html/page/Template.scala @@ -89,7 +89,7 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp val templateName = if (tpl.isRootPackage) "root package" else tpl.name val displayName = tpl.companion match { case Some(companion) if (companion.visibility.isPublic && companion.inSource != None) => - { templateName } + { templateName } case _ => templateName } @@ -105,7 +105,7 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp { tpl.companion match { case Some(companion) if (companion.visibility.isPublic && companion.inSource != None) => - + case _ => }} -- cgit v1.2.3 From f7bc59bc2f6815cf6ca9cc17173f4335d2a0fd0b Mon Sep 17 00:00:00 2001 From: Sébastien Doeraene Date: Mon, 5 Jan 2015 13:11:13 +0100 Subject: Scala.js-friendly `ClassTag.unapply` Use `j.l.Class.isInstance` for Scala.js, so that `unapply` works correctly when referencing raw JavaScript classes. --- src/library/scala/reflect/ClassTag.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/library/scala/reflect/ClassTag.scala b/src/library/scala/reflect/ClassTag.scala index 2f4aa9cb84..e83f689ca7 100644 --- a/src/library/scala/reflect/ClassTag.scala +++ b/src/library/scala/reflect/ClassTag.scala @@ -94,9 +94,9 @@ trait ClassTag[T] extends ClassManifestDeprecatedApis[T] with Equals with Serial def unapply(x: Double) : Option[T] = unapplyImpl(x, classOf[Double]) def unapply(x: Boolean) : Option[T] = unapplyImpl(x, classOf[Boolean]) def unapply(x: Unit) : Option[T] = unapplyImpl(x, classOf[Unit]) - + private[this] def unapplyImpl(x: Any, alternative: jClass[_] = null): Option[T] = { - val conforms = runtimeClass.isAssignableFrom(x.getClass) || (alternative != null && runtimeClass.isAssignableFrom(alternative)) + val conforms = runtimeClass.isInstance(x) || (alternative != null && runtimeClass.isAssignableFrom(alternative)) if (conforms) Some(x.asInstanceOf[T]) else None } -- cgit v1.2.3 From 788f38871ca52bc22967dca22355f6a1affb4e06 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 13 Feb 2015 11:25:25 -0800 Subject: Simplify `ClassTag.unapply` Inline overloaded calls to `unapply`, so we can get rid of them in 2.13. Note that theres a lot of `box(unbox(x))` going on behind the scenes. Is this needed? --- src/library/scala/reflect/ClassTag.scala | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/library/scala/reflect/ClassTag.scala b/src/library/scala/reflect/ClassTag.scala index e83f689ca7..b9b4772870 100644 --- a/src/library/scala/reflect/ClassTag.scala +++ b/src/library/scala/reflect/ClassTag.scala @@ -71,20 +71,21 @@ trait ClassTag[T] extends ClassManifestDeprecatedApis[T] with Equals with Serial */ def unapply(x: Any): Option[T] = x match { case null => None - case b: Byte => unapply(b) - case s: Short => unapply(s) - case c: Char => unapply(c) - case i: Int => unapply(i) - case l: Long => unapply(l) - case f: Float => unapply(f) - case d: Double => unapply(d) - case b: Boolean => unapply(b) - case u: Unit => unapply(u) - case a: Any => unapplyImpl(a) + case x: Byte => unapplyImpl(x, classOf[Byte]) // erases to: if (x instanceof Byte) unapplyImpl(BoxesRunTime.boxToByte(BoxesRunTime.unboxToByte(x)), Byte.TYPE) + case x: Short => unapplyImpl(x, classOf[Short]) + case x: Char => unapplyImpl(x, classOf[Char]) + case x: Int => unapplyImpl(x, classOf[Int]) + case x: Long => unapplyImpl(x, classOf[Long]) + case x: Float => unapplyImpl(x, classOf[Float]) + case x: Double => unapplyImpl(x, classOf[Double]) + case x: Boolean => unapplyImpl(x, classOf[Boolean]) + case x: Unit => unapplyImpl(x, classOf[Unit]) + // TODO: move this next case up and remove the redundant check in unapplyImpl? + case _ if runtimeClass isInstance x => Some(x.asInstanceOf[T]) + case _ => None } - // TODO: Inline the bodies of these into the Any-accepting unapply overload above and delete them. - // This cannot be done until at least 2.12.0 for reasons of binary compatibility + // TODO: deprecate overloads in 2.12.0, remove in 2.13.0 def unapply(x: Byte) : Option[T] = unapplyImpl(x, classOf[Byte]) def unapply(x: Short) : Option[T] = unapplyImpl(x, classOf[Short]) def unapply(x: Char) : Option[T] = unapplyImpl(x, classOf[Char]) @@ -95,10 +96,9 @@ trait ClassTag[T] extends ClassManifestDeprecatedApis[T] with Equals with Serial def unapply(x: Boolean) : Option[T] = unapplyImpl(x, classOf[Boolean]) def unapply(x: Unit) : Option[T] = unapplyImpl(x, classOf[Unit]) - private[this] def unapplyImpl(x: Any, alternative: jClass[_] = null): Option[T] = { - val conforms = runtimeClass.isInstance(x) || (alternative != null && runtimeClass.isAssignableFrom(alternative)) - if (conforms) Some(x.asInstanceOf[T]) else None - } + private[this] def unapplyImpl(x: Any, primitiveCls: java.lang.Class[_]): Option[T] = + if (runtimeClass.isInstance(x) || runtimeClass.isAssignableFrom(primitiveCls)) Some(x.asInstanceOf[T]) + else None // case class accessories override def canEqual(x: Any) = x.isInstanceOf[ClassTag[_]] -- cgit v1.2.3 From 01fad26258d73c1901dedeaf50db02d1407acd65 Mon Sep 17 00:00:00 2001 From: Denton Cockburn Date: Fri, 2 Jan 2015 18:31:07 -0500 Subject: SI-9059 Random.alphanumeric is inefficient Generate alphanumeric values by taking random element of string containing all alphanumerics. Instead of generating nextPrintableChar then filtering for alphanumerics, we can just take from a string containing all alphanumerics. This provides a significant performance improvement. --- bincompat-forward.whitelist.conf | 5 +++++ src/library/scala/util/Random.scala | 10 ++++++++-- test/junit/scala/util/RandomTest.scala | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 test/junit/scala/util/RandomTest.scala (limited to 'src') diff --git a/bincompat-forward.whitelist.conf b/bincompat-forward.whitelist.conf index 13e4f4ba85..3808083dd3 100644 --- a/bincompat-forward.whitelist.conf +++ b/bincompat-forward.whitelist.conf @@ -314,6 +314,11 @@ filter { { matchName="scala.reflect.runtime.Settings#IntSetting.valueSetByUser" problemName=MissingMethodProblem + }, + // SI-9059 + { + matchName="scala.util.Random.scala$util$Random$$nextAlphaNum$1" + problemName=MissingMethodProblem } ] } diff --git a/src/library/scala/util/Random.scala b/src/library/scala/util/Random.scala index 8d68c5be38..2d38c9d4a0 100644 --- a/src/library/scala/util/Random.scala +++ b/src/library/scala/util/Random.scala @@ -121,15 +121,21 @@ class Random(val self: java.util.Random) extends AnyRef with Serializable { (bf(xs) ++= buf).result() } + @deprecated("Preserved for backwards binary compatibility. To remove in 2.12.x.", "2.11.6") + final def `scala$util$Random$$isAlphaNum$1`(c: Char) = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') + /** Returns a Stream of pseudorandomly chosen alphanumeric characters, * equally chosen from A-Z, a-z, and 0-9. * * @since 2.8 */ def alphanumeric: Stream[Char] = { - def isAlphaNum(c: Char) = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') + def nextAlphaNum: Char = { + val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + chars charAt (self nextInt chars.length) + } - Stream continually nextPrintableChar filter isAlphaNum + Stream continually nextAlphaNum } } diff --git a/test/junit/scala/util/RandomTest.scala b/test/junit/scala/util/RandomTest.scala new file mode 100644 index 0000000000..32959675ee --- /dev/null +++ b/test/junit/scala/util/RandomTest.scala @@ -0,0 +1,15 @@ +package scala.util + +import org.junit.{ Assert, Test } + +class RandomTest { + // Test for SI-9059 + @Test def testAlphanumeric: Unit = { + def isAlphaNum(c: Char) = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') + + val items = Random.alphanumeric.take(100000) + for (c <- items) { + Assert.assertTrue(s"$c should be alphanumeric", isAlphaNum(c)) + } + } +} -- cgit v1.2.3 From c9930bfaf28093fe419de3cf034b4bf7888b3818 Mon Sep 17 00:00:00 2001 From: Rex Kerr Date: Fri, 30 Jan 2015 18:27:52 -0800 Subject: SI-8917 collection.mutable.BitSet's &= operator doesn't clear end Made &= run to the end of its own bitset, ignoring the size of what it's &-ing with (which is exactly what you want for &=). --- src/library/scala/collection/mutable/BitSet.scala | 6 ++++-- test/junit/scala/collection/mutable/BitSetTest.scala | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/library/scala/collection/mutable/BitSet.scala b/src/library/scala/collection/mutable/BitSet.scala index 78150b5e88..e92d48cfeb 100644 --- a/src/library/scala/collection/mutable/BitSet.scala +++ b/src/library/scala/collection/mutable/BitSet.scala @@ -121,8 +121,10 @@ class BitSet(protected final var elems: Array[Long]) extends AbstractSet[Int] * @return the bitset itself. */ def &= (other: BitSet): this.type = { - ensureCapacity(other.nwords - 1) - for (i <- 0 until other.nwords) + // Different from other operations: no need to ensure capacity because + // anything beyond the capacity is 0. Since we use other.word which is 0 + // off the end, we also don't need to make sure we stay in bounds there. + for (i <- 0 until nwords) elems(i) = elems(i) & other.word(i) this } diff --git a/test/junit/scala/collection/mutable/BitSetTest.scala b/test/junit/scala/collection/mutable/BitSetTest.scala index 8d164b50d4..d56cc45601 100644 --- a/test/junit/scala/collection/mutable/BitSetTest.scala +++ b/test/junit/scala/collection/mutable/BitSetTest.scala @@ -19,4 +19,13 @@ class BitSetTest { bitSet &~= bitSet assert(bitSet.toBitMask.length == size, "Capacity of bitset changed after &~=") } + + @Test def test_SI8917() { + val bigBitSet = BitSet(1, 100, 10000) + val littleBitSet = BitSet(100) + bigBitSet &= littleBitSet + assert(!(bigBitSet contains 10000), "&= not applied to the full bitset") + littleBitSet &= bigBitSet + assert(littleBitSet.toBitMask.length < bigBitSet.toBitMask.length, "Needlessly extended the size of bitset on &=") + } } -- cgit v1.2.3 From 32c422a5188c6c2c4ff8d75d018e9bb0508c0776 Mon Sep 17 00:00:00 2001 From: Lyle Kopnicky Date: Sun, 15 Feb 2015 16:34:55 -0800 Subject: SI-4959 - UNIX bin scripts now work for paths with spaces The bin scripts fsc, scala, scalac, scaladoc, and scalap were not working when spaces were in the true path of the file (after symbolic links were resolved). This is now fixed. The problem affected OS X, Linux, and mingw on Windows. It did not affect cygwin, because the code was special-cased for cygwin to use cygpath to convert to short filenames, which eliminates spaces. It did not affect the Windows command prompt, because that uses a separate batch file. The problem was that there was a shell function, classpathArgs, used to generate the arguments for the classpath. Shell functions can only return status codes, and emit text which can be captured in a command substitution. Thus, they can contain strings, but not distinguish which spaces should be part of a word, and which ones should separate words. The solution was to switch to using a bash array, as java_args and scala_args were already doing. In this way, each element of the array can contain spaces, but the elements are kept distinct. There was an additional problem with mingw. There was some code that used 'cmd //c' to convert the path to Windows format (drive letters with colons, backslashes and semicolons instead the UNIX-style slashes with colon separators). It turns out that when there are spaces in the path, 'cmd //c' adds quotes around the result to protect it. This was superfluous and actually caused a problem with parsing the first and last paths in the classpath, leading to trouble in finding jars. --- .../scala/tools/ant/templates/tool-unix.tmpl | 39 ++++++++++------------ 1 file changed, 18 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl index 7acb3632d2..9862ea7987 100755 --- a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl +++ b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl @@ -109,9 +109,6 @@ if [[ -n "$cygwin" ]]; then JAVA_HOME="$(cygpath --$format "$JAVA_HOME")" fi TOOL_CLASSPATH="$(cygpath --path --$format "$TOOL_CLASSPATH")" -elif [[ -n "$mingw" ]]; then - SCALA_HOME="$(cmd //c echo "$SCALA_HOME")" - TOOL_CLASSPATH="$(cmd //c echo "$TOOL_CLASSPATH")" fi if [[ -n "$cygwin$mingw" ]]; then @@ -131,23 +128,6 @@ fi declare -a java_args declare -a scala_args -# default to the boot classpath for speed, except on cygwin/mingw because -# JLine on Windows requires a custom DLL to be loaded. -unset usebootcp -if [[ -z "$cygwin$mingw" ]]; then - usebootcp="true" -fi - -# If using the boot classpath, also pass an empty classpath -# to java to suppress "." from materializing. -classpathArgs () { - if [[ -n $usebootcp ]]; then - echo "-Xbootclasspath/a:$TOOL_CLASSPATH -classpath \"\"" - else - echo "-classpath $TOOL_CLASSPATH" - fi -} - # SI-8358, SI-8368 -- the default should really be false, # but I don't want to flip the default during 2.11's RC cycle OVERRIDE_USEJAVACP="-Dscala.usejavacp=true" @@ -200,6 +180,23 @@ if [[ -z "$JAVACMD" && -n "$JAVA_HOME" && -x "$JAVA_HOME/bin/java" ]]; then JAVACMD="$JAVA_HOME/bin/java" fi +declare -a classpath_args + +# default to the boot classpath for speed, except on cygwin/mingw because +# JLine on Windows requires a custom DLL to be loaded. +unset usebootcp +if [[ -z "$cygwin$mingw" ]]; then + usebootcp="true" +fi + +# If using the boot classpath, also pass an empty classpath +# to java to suppress "." from materializing. +if [[ -n $usebootcp ]]; then + classpath_args=("-Xbootclasspath/a:$TOOL_CLASSPATH" -classpath "\"\"") +else + classpath_args=(-classpath "$TOOL_CLASSPATH") +fi + # note that variables which may intentionally be empty must not # be quoted: otherwise an empty string will appear as a command line # argument, and java will think that is the program to run. @@ -207,7 +204,7 @@ execCommand \ "${JAVACMD:=java}" \ $JAVA_OPTS \ "${java_args[@@]}" \ - $(classpathArgs) \ + "${classpath_args[@@]}" \ -Dscala.home="$SCALA_HOME" \ $OVERRIDE_USEJAVACP \ "$EMACS_OPT" \ -- cgit v1.2.3 From ad7a2f2d497c59eb61284447b38ea2697f9ab738 Mon Sep 17 00:00:00 2001 From: Rex Kerr Date: Fri, 26 Dec 2014 17:54:50 -0800 Subject: Optimization of IterableLike.scala Changed takeRight to have two tighter loops instead of one with a conditional. See about 10% performance improvement. Other changes were (surprisingly, in some cases) not a win. Overall, IterableLike is pretty well optimized. --- src/library/scala/collection/IterableLike.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/library/scala/collection/IterableLike.scala b/src/library/scala/collection/IterableLike.scala index 7643b84a8b..ecf64624e8 100644 --- a/src/library/scala/collection/IterableLike.scala +++ b/src/library/scala/collection/IterableLike.scala @@ -218,12 +218,12 @@ self => val b = newBuilder b.sizeHintBounded(n, this) val lead = this.iterator drop n - var go = false - for (x <- this) { - if (lead.hasNext) lead.next() - else go = true - if (go) b += x + val it = this.iterator + while (lead.hasNext) { + lead.next() + it.next() } + while (it.hasNext) b += it.next() b.result() } @@ -283,7 +283,7 @@ self => var i = 0 for (x <- this) { b += ((x, i)) - i +=1 + i += 1 } b.result() } -- cgit v1.2.3 From 5a89475526fc91f677d7002b6063bef9e549f0cc Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 17 Feb 2015 15:18:20 +1000 Subject: Cache name for dummy argument used in implicit search This avoids a minor inefficiency of interning the name on each implicit candidate. Instead, we follow the usual practice and use a pre-baked name from `StdNames`. --- src/compiler/scala/tools/nsc/typechecker/Implicits.scala | 2 +- src/reflect/scala/reflect/internal/StdNames.scala | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 71558273a6..0861d58240 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -609,7 +609,7 @@ trait Implicits { val itree2 = if (!isView) fallback else pt match { case Function1(arg1, arg2) => typed1( - atPos(itree0.pos)(Apply(itree1, List(Ident("") setType approximate(arg1)))), + atPos(itree0.pos)(Apply(itree1, List(Ident(nme.argument) setType approximate(arg1)))), EXPRmode, approximate(arg2) ) match { diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index f32b7326fe..c0562b0679 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -1078,6 +1078,7 @@ trait StdNames { val reflPolyCacheName: NameType = "reflPoly$Cache" val reflParamsCacheName: NameType = "reflParams$Cache" val reflMethodName: NameType = "reflMethod$Method" + val argument: NameType = "" } -- cgit v1.2.3 From a8ec6c97ea7edbf25ffffe2e798b2deedcd5955f Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 17 Feb 2015 15:21:03 +1000 Subject: SI-9153 More complete and stable results for completions Three items of background are needed to understand this bug. 1. When typechecking an application like `qual.m({stats; expr})`, the argument is typechecked using the formal parameter type of `m` as the expected type. If this fails with a type error located within in `expr`, the typer instead re-typechecks under `ContextMode.ReTyping` without an expected type, and then searches for an implicit adaptation to enable `view(qual).m(args)`. Under this mode, `Typer#typed1` clears the type of incoming trees. 2. The presentation compiler performs targetted operations like type completions by: - typechecking the enclosing tree, registering all typechecker `Context`s created in the process (`registerContext`) - finding the smallest enclosing `Context` around the target position (`doLocateContext`) - Using this context to perform implicit search, which can contribute members to the completion. (`applicableViews` within `interactive.Global#typeMembers`) 3. When verifiying whether or not a candidate implicit is applicable as a view from `F => T`, implicit search typechecks a dummy call of the form `q"candiate(${Ident("").setType(typeOf[F])})". Now, picture yourself at the nexus of these three storms. In the enclosed test case, we search for completions at: x + 1. 1. Because the code is incomplete, the application of `Int#+` doesn't typecheck, and the typer also tries to adapt `x` to a method applicable to the re-typechecked argument. 2. This process registers a context with `retypechecking` set to true. (If multiple contexts at the same position are registered, the last one wins.) 3. Implicit search uses this context to typecheck `Predef.Ensuring(.setType(Int))`, but the argument is promptly stripped of its type and retypechecking fails as there is no definition named `` in scope. As such, we missed out on extension methods, like `ensuring` in the list of completions. This commit changes the presentation compiler to turn off retyping mode in the context before starting to work with it. (Are the other modes that might cause similar bugs?) Once I made that change, I noticed that the results the enclosed test was not stable. I tracked this down to the use of a `HashMap` to carry the applicable implicit views, together with the way that the presentation compiler removes duplicates. This commit switched to a `LinkedHashMap`. --- .../scala/tools/nsc/typechecker/Implicits.scala | 4 +- .../scala/tools/nsc/interactive/ContextTrees.scala | 6 +- .../scala/tools/nsc/interactive/Global.scala | 1 - .../scala/reflect/internal/util/Collections.scala | 3 + test/files/presentation/infix-completion.check | 194 +++++++++++++++++++ .../presentation/infix-completion/Runner.scala | 3 + .../infix-completion/src/Snippet.scala | 1 + test/files/presentation/infix-completion2.check | 212 +++++++++++++++++++++ .../presentation/infix-completion2/Runner.scala | 3 + .../infix-completion2/src/Snippet.scala | 1 + 10 files changed, 424 insertions(+), 4 deletions(-) create mode 100644 test/files/presentation/infix-completion.check create mode 100644 test/files/presentation/infix-completion/Runner.scala create mode 100644 test/files/presentation/infix-completion/src/Snippet.scala create mode 100644 test/files/presentation/infix-completion2.check create mode 100644 test/files/presentation/infix-completion2/Runner.scala create mode 100644 test/files/presentation/infix-completion2/src/Snippet.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 0861d58240..d3cd26f256 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -918,7 +918,7 @@ trait Implicits { /** Returns all eligible ImplicitInfos and their SearchResults in a map. */ - def findAll() = mapFrom(eligible)(typedImplicit(_, ptChecked = false, isLocalToCallsite)) + def findAll() = linkedMapFrom(eligible)(typedImplicit(_, ptChecked = false, isLocalToCallsite)) /** Returns the SearchResult of the best match. */ @@ -963,7 +963,7 @@ trait Implicits { * symbols of the same name in succeeding lists. * @return map from infos to search results */ - def applicableInfos(iss: Infoss, isLocalToCallsite: Boolean): Map[ImplicitInfo, SearchResult] = { + def applicableInfos(iss: Infoss, isLocalToCallsite: Boolean): mutable.LinkedHashMap[ImplicitInfo, SearchResult] = { val start = if (Statistics.canEnable) Statistics.startCounter(subtypeAppInfos) else null val computation = new ImplicitComputation(iss, isLocalToCallsite) { } val applicable = computation.findAll() diff --git a/src/interactive/scala/tools/nsc/interactive/ContextTrees.scala b/src/interactive/scala/tools/nsc/interactive/ContextTrees.scala index bf718c27cc..a4cb3efa4f 100644 --- a/src/interactive/scala/tools/nsc/interactive/ContextTrees.scala +++ b/src/interactive/scala/tools/nsc/interactive/ContextTrees.scala @@ -55,7 +55,11 @@ trait ContextTrees { self: Global => context } } - locateContextTree(contexts, pos) map locateFinestContextTree map (_.context) + def sanitizeContext(c: Context): Context = { + c.retyping = false + c + } + locateContextTree(contexts, pos) map locateFinestContextTree map (ct => sanitizeContext(ct.context)) } /** Returns the ContextTree containing `pos`, or the ContextTree positioned just before `pos`, diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index a3d0346f81..e42a0fda0e 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -1084,7 +1084,6 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") case t => t } val context = doLocateContext(pos) - val shouldTypeQualifier = tree0.tpe match { case null => true case mt: MethodType => mt.isImplicit diff --git a/src/reflect/scala/reflect/internal/util/Collections.scala b/src/reflect/scala/reflect/internal/util/Collections.scala index d128521be8..a743d8962a 100644 --- a/src/reflect/scala/reflect/internal/util/Collections.scala +++ b/src/reflect/scala/reflect/internal/util/Collections.scala @@ -181,6 +181,9 @@ trait Collections { final def mapFrom[A, A1 >: A, B](xs: List[A])(f: A => B): Map[A1, B] = { Map[A1, B](xs map (x => (x, f(x))): _*) } + final def linkedMapFrom[A, A1 >: A, B](xs: List[A])(f: A => B): mutable.LinkedHashMap[A1, B] = { + mutable.LinkedHashMap[A1, B](xs map (x => (x, f(x))): _*) + } final def mapWithIndex[A, B](xs: List[A])(f: (A, Int) => B): List[B] = { val lb = new ListBuffer[B] diff --git a/test/files/presentation/infix-completion.check b/test/files/presentation/infix-completion.check new file mode 100644 index 0000000000..84b5325448 --- /dev/null +++ b/test/files/presentation/infix-completion.check @@ -0,0 +1,194 @@ +reload: Snippet.scala + +askTypeCompletion at Snippet.scala(1,34) +================================================================================ +[response] askTypeCompletion at (1,34) +retrieved 193 members +[inaccessible] protected def integralNum: math.Numeric.DoubleAsIfIntegral.type +[inaccessible] protected def num: math.Numeric.DoubleIsFractional.type +[inaccessible] protected def ord: math.Ordering.Double.type +[inaccessible] protected def unifiedPrimitiveEquals(x: Any): Boolean +[inaccessible] protected def unifiedPrimitiveHashcode(): Int +[inaccessible] protected[package lang] def clone(): Object +[inaccessible] protected[package lang] def finalize(): Unit +def !=(x: Byte): Boolean +def !=(x: Char): Boolean +def !=(x: Double): Boolean +def !=(x: Float): Boolean +def !=(x: Int): Boolean +def !=(x: Long): Boolean +def !=(x: Short): Boolean +def %(x: Byte): Int +def %(x: Char): Int +def %(x: Double): Double +def %(x: Float): Float +def %(x: Int): Int +def %(x: Long): Long +def %(x: Short): Int +def &(x: Byte): Int +def &(x: Char): Int +def &(x: Int): Int +def &(x: Long): Long +def &(x: Short): Int +def *(x: Byte): Int +def *(x: Char): Int +def *(x: Double): Double +def *(x: Float): Float +def *(x: Int): Int +def *(x: Long): Long +def *(x: Short): Int +def +(x: Byte): Int +def +(x: Char): Int +def +(x: Double): Double +def +(x: Float): Float +def +(x: Int): Int +def +(x: Long): Long +def +(x: Short): Int +def +(x: String): String +def -(x: Byte): Int +def -(x: Char): Int +def -(x: Double): Double +def -(x: Float): Float +def -(x: Int): Int +def -(x: Long): Long +def -(x: Short): Int +def ->[B](y: B): (Int, B) +def /(x: Byte): Int +def /(x: Char): Int +def /(x: Double): Double +def /(x: Float): Float +def /(x: Int): Int +def /(x: Long): Long +def /(x: Short): Int +def <(x: Byte): Boolean +def <(x: Char): Boolean +def <(x: Double): Boolean +def <(x: Float): Boolean +def <(x: Int): Boolean +def <(x: Long): Boolean +def <(x: Short): Boolean +def <<(x: Int): Int +def <<(x: Long): Int +def <=(x: Byte): Boolean +def <=(x: Char): Boolean +def <=(x: Double): Boolean +def <=(x: Float): Boolean +def <=(x: Int): Boolean +def <=(x: Long): Boolean +def <=(x: Short): Boolean +def ==(x: Byte): Boolean +def ==(x: Char): Boolean +def ==(x: Double): Boolean +def ==(x: Float): Boolean +def ==(x: Int): Boolean +def ==(x: Long): Boolean +def ==(x: Short): Boolean +def >(x: Byte): Boolean +def >(x: Char): Boolean +def >(x: Double): Boolean +def >(x: Float): Boolean +def >(x: Int): Boolean +def >(x: Long): Boolean +def >(x: Short): Boolean +def >=(x: Byte): Boolean +def >=(x: Char): Boolean +def >=(x: Double): Boolean +def >=(x: Float): Boolean +def >=(x: Int): Boolean +def >=(x: Long): Boolean +def >=(x: Short): Boolean +def >>(x: Int): Int +def >>(x: Long): Int +def >>>(x: Int): Int +def >>>(x: Long): Int +def ^(x: Byte): Int +def ^(x: Char): Int +def ^(x: Int): Int +def ^(x: Long): Long +def ^(x: Short): Int +def byteValue(): Byte +def ceil: Double +def compare(y: Double): Int +def compare(y: Long): Int +def compareTo(that: Double): Int +def compareTo(that: Long): Int +def compareTo(x$1: Double): Int +def compareTo(x$1: Long): Int +def doubleValue(): Double +def ensuring(cond: Boolean): Int +def ensuring(cond: Boolean,msg: => Any): Int +def ensuring(cond: Int => Boolean): Int +def ensuring(cond: Int => Boolean,msg: => Any): Int +def equals(x$1: Any): Boolean +def floatValue(): Float +def floor: Double +def formatted(fmtstr: String): String +def hashCode(): Int +def intValue(): Int +def isInfinite(): Boolean +def isInfinity: Boolean +def isNaN(): Boolean +def isNegInfinity: Boolean +def isPosInfinity: Boolean +def isValidLong: Boolean +def longValue(): Long +def round: Long +def shortValue(): Short +def to(end: Double): Range.Partial[Double,scala.collection.immutable.NumericRange[Double]] +def to(end: Double,step: Double): scala.collection.immutable.NumericRange.Inclusive[Double] +def to(end: Long): scala.collection.immutable.NumericRange.Inclusive[Long] +def to(end: Long,step: Long): scala.collection.immutable.NumericRange.Inclusive[Long] +def toBinaryString: String +def toByte: Byte +def toChar: Char +def toDegrees: Double +def toDouble: Double +def toFloat: Float +def toHexString: String +def toInt: Int +def toLong: Long +def toOctalString: String +def toRadians: Double +def toShort: Short +def toString(): String +def unary_+: Int +def unary_-: Int +def unary_~: Int +def underlying(): AnyRef +def until(end: Double): Range.Partial[Double,scala.collection.immutable.NumericRange[Double]] +def until(end: Double,step: Double): scala.collection.immutable.NumericRange.Exclusive[Double] +def until(end: Long): scala.collection.immutable.NumericRange.Exclusive[Long] +def until(end: Long,step: Long): scala.collection.immutable.NumericRange.Exclusive[Long] +def |(x: Byte): Int +def |(x: Char): Int +def |(x: Int): Int +def |(x: Long): Long +def |(x: Short): Int +def →[B](y: B): (Int, B) +final def !=(x$1: Any): Boolean +final def ##(): Int +final def ==(x$1: Any): Boolean +final def asInstanceOf[T0]: T0 +final def eq(x$1: AnyRef): Boolean +final def isInstanceOf[T0]: Boolean +final def ne(x$1: AnyRef): Boolean +final def notify(): Unit +final def notifyAll(): Unit +final def synchronized[T0](x$1: T0): T0 +final def wait(): Unit +final def wait(x$1: Long): Unit +final def wait(x$1: Long,x$2: Int): Unit +override def abs: Double +override def isValidByte: Boolean +override def isValidChar: Boolean +override def isValidInt: Boolean +override def isValidShort: Boolean +override def isWhole(): Boolean +override def max(that: Double): Double +override def max(that: Long): Long +override def min(that: Double): Double +override def min(that: Long): Long +override def signum: Int +private[this] val self: Double +type ResultWithoutStep = Range.Partial[Double,scala.collection.immutable.NumericRange[Double]] +================================================================================ diff --git a/test/files/presentation/infix-completion/Runner.scala b/test/files/presentation/infix-completion/Runner.scala new file mode 100644 index 0000000000..1c03e3d5ba --- /dev/null +++ b/test/files/presentation/infix-completion/Runner.scala @@ -0,0 +1,3 @@ +import scala.tools.nsc.interactive.tests._ + +object Test extends InteractiveTest diff --git a/test/files/presentation/infix-completion/src/Snippet.scala b/test/files/presentation/infix-completion/src/Snippet.scala new file mode 100644 index 0000000000..7e03c486ba --- /dev/null +++ b/test/files/presentation/infix-completion/src/Snippet.scala @@ -0,0 +1 @@ +object Snippet{val x = 123; 1 + 1./*!*/} diff --git a/test/files/presentation/infix-completion2.check b/test/files/presentation/infix-completion2.check new file mode 100644 index 0000000000..d61ca5e571 --- /dev/null +++ b/test/files/presentation/infix-completion2.check @@ -0,0 +1,212 @@ +reload: Snippet.scala + +askTypeCompletion at Snippet.scala(1,34) +================================================================================ +[response] askTypeCompletion at (1,34) +retrieved 212 members +[inaccessible] protected def integralNum: math.Numeric.DoubleAsIfIntegral.type +[inaccessible] protected def num: math.Numeric.DoubleIsFractional.type +[inaccessible] protected def ord: math.Ordering.Double.type +[inaccessible] protected def unifiedPrimitiveEquals(x: Any): Boolean +[inaccessible] protected def unifiedPrimitiveHashcode(): Int +[inaccessible] protected[package lang] def clone(): Object +[inaccessible] protected[package lang] def finalize(): Unit +def !=(x: Byte): Boolean +def !=(x: Char): Boolean +def !=(x: Double): Boolean +def !=(x: Float): Boolean +def !=(x: Int): Boolean +def !=(x: Long): Boolean +def !=(x: Short): Boolean +def %(x: Byte): Int +def %(x: Char): Int +def %(x: Double): Double +def %(x: Float): Float +def %(x: Int): Int +def %(x: Long): Long +def %(x: Short): Int +def &(x: Byte): Int +def &(x: Char): Int +def &(x: Int): Int +def &(x: Long): Long +def &(x: Short): Int +def *(x: Byte): Int +def *(x: Char): Int +def *(x: Double): Double +def *(x: Float): Float +def *(x: Int): Int +def *(x: Long): Long +def *(x: Short): Int +def +(x: Byte): Int +def +(x: Char): Int +def +(x: Double): Double +def +(x: Float): Float +def +(x: Int): Int +def +(x: Long): Long +def +(x: Short): Int +def +(x: String): String +def -(x: Byte): Int +def -(x: Char): Int +def -(x: Double): Double +def -(x: Float): Float +def -(x: Int): Int +def -(x: Long): Long +def -(x: Short): Int +def ->[B](y: B): (Int, B) +def /(x: Byte): Int +def /(x: Char): Int +def /(x: Double): Double +def /(x: Float): Float +def /(x: Int): Int +def /(x: Long): Long +def /(x: Short): Int +def <(x: Byte): Boolean +def <(x: Char): Boolean +def <(x: Double): Boolean +def <(x: Float): Boolean +def <(x: Int): Boolean +def <(x: Long): Boolean +def <(x: Short): Boolean +def <<(x: Int): Int +def <<(x: Long): Int +def <=(x: Byte): Boolean +def <=(x: Char): Boolean +def <=(x: Double): Boolean +def <=(x: Float): Boolean +def <=(x: Int): Boolean +def <=(x: Long): Boolean +def <=(x: Short): Boolean +def ==(x: Byte): Boolean +def ==(x: Char): Boolean +def ==(x: Double): Boolean +def ==(x: Float): Boolean +def ==(x: Int): Boolean +def ==(x: Long): Boolean +def ==(x: Short): Boolean +def >(x: Byte): Boolean +def >(x: Char): Boolean +def >(x: Double): Boolean +def >(x: Float): Boolean +def >(x: Int): Boolean +def >(x: Long): Boolean +def >(x: Short): Boolean +def >=(x: Byte): Boolean +def >=(x: Char): Boolean +def >=(x: Double): Boolean +def >=(x: Float): Boolean +def >=(x: Int): Boolean +def >=(x: Long): Boolean +def >=(x: Short): Boolean +def >>(x: Int): Int +def >>(x: Long): Int +def >>>(x: Int): Int +def >>>(x: Long): Int +def ^(x: Byte): Int +def ^(x: Char): Int +def ^(x: Int): Int +def ^(x: Long): Long +def ^(x: Short): Int +def byteValue(): Byte +def ceil: Double +def compare(y: Double): Int +def compare(y: Float): Int +def compare(y: Int): Int +def compare(y: Long): Int +def compareTo(that: Double): Int +def compareTo(that: Float): Int +def compareTo(that: Int): Int +def compareTo(that: Long): Int +def compareTo(x$1: Double): Int +def compareTo(x$1: Float): Int +def compareTo(x$1: Integer): Int +def compareTo(x$1: Long): Int +def doubleValue(): Double +def ensuring(cond: Boolean): Int +def ensuring(cond: Boolean,msg: => Any): Int +def ensuring(cond: Int => Boolean): Int +def ensuring(cond: Int => Boolean,msg: => Any): Int +def equals(x$1: Any): Boolean +def floatValue(): Float +def floor: Double +def formatted(fmtstr: String): String +def hashCode(): Int +def intValue(): Int +def isInfinite(): Boolean +def isInfinity: Boolean +def isNaN(): Boolean +def isNegInfinity: Boolean +def isPosInfinity: Boolean +def isValidLong: Boolean +def longValue(): Long +def round: Long +def shortValue(): Short +def to(end: Double): Range.Partial[Double,scala.collection.immutable.NumericRange[Double]] +def to(end: Double,step: Double): scala.collection.immutable.NumericRange.Inclusive[Double] +def to(end: Float): Range.Partial[Float,scala.collection.immutable.NumericRange[Float]] +def to(end: Float,step: Float): scala.collection.immutable.NumericRange.Inclusive[Float] +def to(end: Int): scala.collection.immutable.Range.Inclusive +def to(end: Int,step: Int): scala.collection.immutable.Range.Inclusive +def to(end: Long): scala.collection.immutable.NumericRange.Inclusive[Long] +def to(end: Long,step: Long): scala.collection.immutable.NumericRange.Inclusive[Long] +def toBinaryString: String +def toByte: Byte +def toChar: Char +def toDegrees: Double +def toDouble: Double +def toFloat: Float +def toHexString: String +def toInt: Int +def toLong: Long +def toOctalString: String +def toRadians: Double +def toShort: Short +def toString(): String +def unary_+: Int +def unary_-: Int +def unary_~: Int +def underlying(): AnyRef +def until(end: Double): Range.Partial[Double,scala.collection.immutable.NumericRange[Double]] +def until(end: Double,step: Double): scala.collection.immutable.NumericRange.Exclusive[Double] +def until(end: Float): Range.Partial[Float,scala.collection.immutable.NumericRange[Float]] +def until(end: Float,step: Float): scala.collection.immutable.NumericRange.Exclusive[Float] +def until(end: Int): scala.collection.immutable.Range +def until(end: Int,step: Int): scala.collection.immutable.Range +def until(end: Long): scala.collection.immutable.NumericRange.Exclusive[Long] +def until(end: Long,step: Long): scala.collection.immutable.NumericRange.Exclusive[Long] +def |(x: Byte): Int +def |(x: Char): Int +def |(x: Int): Int +def |(x: Long): Long +def |(x: Short): Int +def →[B](y: B): (Int, B) +final def !=(x$1: Any): Boolean +final def ##(): Int +final def ==(x$1: Any): Boolean +final def asInstanceOf[T0]: T0 +final def eq(x$1: AnyRef): Boolean +final def isInstanceOf[T0]: Boolean +final def ne(x$1: AnyRef): Boolean +final def notify(): Unit +final def notifyAll(): Unit +final def synchronized[T0](x$1: T0): T0 +final def wait(): Unit +final def wait(x$1: Long): Unit +final def wait(x$1: Long,x$2: Int): Unit +override def abs: Double +override def isValidByte: Boolean +override def isValidChar: Boolean +override def isValidInt: Boolean +override def isValidShort: Boolean +override def isWhole(): Boolean +override def max(that: Double): Double +override def max(that: Float): Float +override def max(that: Int): Int +override def max(that: Long): Long +override def min(that: Double): Double +override def min(that: Float): Float +override def min(that: Int): Int +override def min(that: Long): Long +override def signum: Int +private[this] val self: Double +type ResultWithoutStep = Range.Partial[Double,scala.collection.immutable.NumericRange[Double]] +================================================================================ diff --git a/test/files/presentation/infix-completion2/Runner.scala b/test/files/presentation/infix-completion2/Runner.scala new file mode 100644 index 0000000000..1c03e3d5ba --- /dev/null +++ b/test/files/presentation/infix-completion2/Runner.scala @@ -0,0 +1,3 @@ +import scala.tools.nsc.interactive.tests._ + +object Test extends InteractiveTest diff --git a/test/files/presentation/infix-completion2/src/Snippet.scala b/test/files/presentation/infix-completion2/src/Snippet.scala new file mode 100644 index 0000000000..4eb8c24a2e --- /dev/null +++ b/test/files/presentation/infix-completion2/src/Snippet.scala @@ -0,0 +1 @@ +object Snippet{val x = 123; 1 + x./*!*/} -- cgit v1.2.3 From 9cd5c881fc13acbd9aca08fd6ae2830292e5f1b4 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 17 Feb 2015 16:02:49 +1000 Subject: Remove incorrect completions: implicits can't add type members The checkfile of the tests added in the last commit offered a type member from `RichInt` in the completions for the type `Int`. However, only term members can be extension methods; type members cannot. --- src/interactive/scala/tools/nsc/interactive/Global.scala | 2 +- test/files/presentation/infix-completion.check | 3 +-- test/files/presentation/infix-completion2.check | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index e42a0fda0e..2d09435f60 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -1138,7 +1138,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") for (view <- applicableViews) { val vtree = viewApply(view) val vpre = stabilizedType(vtree) - for (sym <- vtree.tpe.members) { + for (sym <- vtree.tpe.members if sym.isTerm) { addTypeMember(sym, vpre, inherited = false, view.tree.symbol) } } diff --git a/test/files/presentation/infix-completion.check b/test/files/presentation/infix-completion.check index 84b5325448..f62dc81d34 100644 --- a/test/files/presentation/infix-completion.check +++ b/test/files/presentation/infix-completion.check @@ -3,7 +3,7 @@ reload: Snippet.scala askTypeCompletion at Snippet.scala(1,34) ================================================================================ [response] askTypeCompletion at (1,34) -retrieved 193 members +retrieved 192 members [inaccessible] protected def integralNum: math.Numeric.DoubleAsIfIntegral.type [inaccessible] protected def num: math.Numeric.DoubleIsFractional.type [inaccessible] protected def ord: math.Ordering.Double.type @@ -190,5 +190,4 @@ override def min(that: Double): Double override def min(that: Long): Long override def signum: Int private[this] val self: Double -type ResultWithoutStep = Range.Partial[Double,scala.collection.immutable.NumericRange[Double]] ================================================================================ diff --git a/test/files/presentation/infix-completion2.check b/test/files/presentation/infix-completion2.check index d61ca5e571..5c69cd84cb 100644 --- a/test/files/presentation/infix-completion2.check +++ b/test/files/presentation/infix-completion2.check @@ -3,7 +3,7 @@ reload: Snippet.scala askTypeCompletion at Snippet.scala(1,34) ================================================================================ [response] askTypeCompletion at (1,34) -retrieved 212 members +retrieved 211 members [inaccessible] protected def integralNum: math.Numeric.DoubleAsIfIntegral.type [inaccessible] protected def num: math.Numeric.DoubleIsFractional.type [inaccessible] protected def ord: math.Ordering.Double.type @@ -208,5 +208,4 @@ override def min(that: Int): Int override def min(that: Long): Long override def signum: Int private[this] val self: Double -type ResultWithoutStep = Range.Partial[Double,scala.collection.immutable.NumericRange[Double]] ================================================================================ -- cgit v1.2.3 From b6cbee9d81320524aa2e8a3d80dbf2062dd43fd2 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 18 Feb 2015 14:02:08 +1000 Subject: SI-9157 Avoid exponential blowup with chained type projections Calling `findMember` in the enclosed test was calling into `NonClassTypeRef#relativeInfo` an exponentially-increasing number of times, with respect to the length of the chained type projections. The numbers of calls increased as: 26, 326, 3336, 33446, 334556. Can any pattern spotters in the crowd that can identify the sequence? (I can't.) Tracing the calls saw we were computing the same `memberType` repeatedly. This part of the method was not guarded by the cache. I have changed the method to use the standard idiom of using the current period for cache invalidation. The enclosed test now compiles promptly, rather than in geological time. --- src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala | 1 - src/reflect/scala/reflect/internal/Types.scala | 10 +++++----- test/files/pos/t9157.scala | 13 +++++++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 test/files/pos/t9157.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala index 743bbe53bd..02356580cc 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala @@ -266,7 +266,6 @@ abstract class TreeCheckers extends Analyzer { if (tree ne typed) treesDiffer(tree, typed) - tree } diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index ce36f7efa3..8f114caac0 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -1976,13 +1976,13 @@ trait Types * usage scenario. */ private var relativeInfoCache: Type = _ - private var memberInfoCache: Type = _ + private var relativeInfoPeriod: Period = NoPeriod - private[Types] def relativeInfo = { - val memberInfo = pre.memberInfo(sym) - if (relativeInfoCache == null || (memberInfo ne memberInfoCache)) { - memberInfoCache = memberInfo + private[Types] def relativeInfo = /*trace(s"relativeInfo(${safeToString}})")*/{ + if (relativeInfoPeriod != currentPeriod) { + val memberInfo = pre.memberInfo(sym) relativeInfoCache = transformInfo(memberInfo) + relativeInfoPeriod = currentPeriod } relativeInfoCache } diff --git a/test/files/pos/t9157.scala b/test/files/pos/t9157.scala new file mode 100644 index 0000000000..e178b5d84d --- /dev/null +++ b/test/files/pos/t9157.scala @@ -0,0 +1,13 @@ +trait Flow[-In, +Out] { + type Repr[+O] <: Flow[In, O] + def map: Repr[String] +} + +class Test { + // typechecking was exponentially slow wrt the number of projections here. + def slowFlow( + f: Flow[String,String]#Repr[String]#Repr[String]#Repr[String]#Repr[String]#Repr[String]#Repr[String]#Repr[String]#Repr[String]#Repr[String]#Repr[String]#Repr[String] + ) = { + f.map + } +} -- cgit v1.2.3 From bff86367607a713288a939639a654802349d1067 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 18 Feb 2015 12:46:21 -0800 Subject: Use if/then/else to avoid box(unbox(_)) --- src/library/scala/reflect/ClassTag.scala | 29 ++++++++++++++--------------- test/junit/scala/reflect/ClassTag.scala | 29 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 15 deletions(-) create mode 100644 test/junit/scala/reflect/ClassTag.scala (limited to 'src') diff --git a/src/library/scala/reflect/ClassTag.scala b/src/library/scala/reflect/ClassTag.scala index b9b4772870..9dd96183da 100644 --- a/src/library/scala/reflect/ClassTag.scala +++ b/src/library/scala/reflect/ClassTag.scala @@ -69,21 +69,20 @@ trait ClassTag[T] extends ClassManifestDeprecatedApis[T] with Equals with Serial * `SomeExtractor(...)` is turned into `ct(SomeExtractor(...))` if `T` in `SomeExtractor.unapply(x: T)` * is uncheckable, but we have an instance of `ClassTag[T]`. */ - def unapply(x: Any): Option[T] = x match { - case null => None - case x: Byte => unapplyImpl(x, classOf[Byte]) // erases to: if (x instanceof Byte) unapplyImpl(BoxesRunTime.boxToByte(BoxesRunTime.unboxToByte(x)), Byte.TYPE) - case x: Short => unapplyImpl(x, classOf[Short]) - case x: Char => unapplyImpl(x, classOf[Char]) - case x: Int => unapplyImpl(x, classOf[Int]) - case x: Long => unapplyImpl(x, classOf[Long]) - case x: Float => unapplyImpl(x, classOf[Float]) - case x: Double => unapplyImpl(x, classOf[Double]) - case x: Boolean => unapplyImpl(x, classOf[Boolean]) - case x: Unit => unapplyImpl(x, classOf[Unit]) - // TODO: move this next case up and remove the redundant check in unapplyImpl? - case _ if runtimeClass isInstance x => Some(x.asInstanceOf[T]) - case _ => None - } + def unapply(x: Any): Option[T] = + if (null != x && ( + (runtimeClass.isInstance(x)) + || (x.isInstanceOf[Byte] && runtimeClass.isAssignableFrom(classOf[Byte])) + || (x.isInstanceOf[Short] && runtimeClass.isAssignableFrom(classOf[Short])) + || (x.isInstanceOf[Char] && runtimeClass.isAssignableFrom(classOf[Char])) + || (x.isInstanceOf[Int] && runtimeClass.isAssignableFrom(classOf[Int])) + || (x.isInstanceOf[Long] && runtimeClass.isAssignableFrom(classOf[Long])) + || (x.isInstanceOf[Float] && runtimeClass.isAssignableFrom(classOf[Float])) + || (x.isInstanceOf[Double] && runtimeClass.isAssignableFrom(classOf[Double])) + || (x.isInstanceOf[Boolean] && runtimeClass.isAssignableFrom(classOf[Boolean])) + || (x.isInstanceOf[Unit] && runtimeClass.isAssignableFrom(classOf[Unit]))) + ) Some(x.asInstanceOf[T]) + else None // TODO: deprecate overloads in 2.12.0, remove in 2.13.0 def unapply(x: Byte) : Option[T] = unapplyImpl(x, classOf[Byte]) diff --git a/test/junit/scala/reflect/ClassTag.scala b/test/junit/scala/reflect/ClassTag.scala new file mode 100644 index 0000000000..90cc981fc1 --- /dev/null +++ b/test/junit/scala/reflect/ClassTag.scala @@ -0,0 +1,29 @@ +package scala.reflect + +import org.junit.Test +import org.junit.Assert._ +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +import scala.tools.testing.AssertUtil._ + +class Misc + +@RunWith(classOf[JUnit4]) +class ClassTagTest { + def checkNotString[A: ClassTag](a: Any) = a match { case x: String => false case x: A => true case _ => false } + def checkNotInt[A: ClassTag](a: Any) = a match { case x: Int => false case x: A => true case _ => false } + def checkNotLong[A: ClassTag](a: Any) = a match { case x: Long => false case x: A => true case _ => false } + + @Test def checkMisc = assertTrue(checkNotString[Misc](new Misc)) + @Test def checkString = assertTrue(checkNotInt[String] ("woele")) + @Test def checkByte = assertTrue(checkNotInt[Byte] (0.toByte)) + @Test def checkShort = assertTrue(checkNotInt[Short] (0.toShort)) + @Test def checkChar = assertTrue(checkNotInt[Char] (0.toChar)) + @Test def checkInt = assertTrue(checkNotLong[Int] (0.toInt)) + @Test def checkLong = assertTrue(checkNotInt[Long] (0.toLong)) + @Test def checkFloat = assertTrue(checkNotInt[Float] (0.toFloat)) + @Test def checkDouble = assertTrue(checkNotInt[Double] (0.toDouble)) + @Test def checkBoolean = assertTrue(checkNotInt[Boolean](false)) + @Test def checkUnit = assertTrue(checkNotInt[Unit] ({})) +} \ No newline at end of file -- cgit v1.2.3 From da2d4caae990bcb9fabb13e7a242b2b7babe0e0b Mon Sep 17 00:00:00 2001 From: Aleksandar Prokopec Date: Wed, 11 Feb 2015 20:40:35 +0100 Subject: Fix SI-7943 -- make `TrieMap.getOrElseUpdate` atomic. Override `getOrElseUpdate` method in `TrieMap`. The signature and contract of this method corresponds closely to the `computeIfAbsent` from `java.util.concurrent.ConcurrentMap`. Eventually, `computeIfAbsent` should be added to `scala.collection.concurrent.Map`. Add tests. Review by @Ichoran --- .../scala/collection/concurrent/TrieMap.scala | 38 ++++++++++++++++++++++ src/library/scala/collection/mutable/MapLike.scala | 4 +++ test/files/scalacheck/Ctrie.scala | 19 +++++++++++ 3 files changed, 61 insertions(+) (limited to 'src') diff --git a/src/library/scala/collection/concurrent/TrieMap.scala b/src/library/scala/collection/concurrent/TrieMap.scala index fccc1d81b9..bcfea7a463 100644 --- a/src/library/scala/collection/concurrent/TrieMap.scala +++ b/src/library/scala/collection/concurrent/TrieMap.scala @@ -873,6 +873,44 @@ extends scala.collection.concurrent.Map[K, V] insertifhc(k, hc, v, INode.KEY_ABSENT) } + // TODO once computeIfAbsent is added to concurrent.Map, + // move the comment there and tweak the 'at most once' part + /** If the specified key is not already in the map, computes its value using + * the given thunk `op` and enters it into the map. + * + * Since concurrent maps cannot contain `null` for keys or values, + * a `NullPointerException` is thrown if the thunk `op` + * returns `null`. + * + * If the specified mapping function throws an exception, + * that exception is rethrown. + * + * Note: This method will invoke op at most once. + * However, `op` may be invoked without the result being added to the map if + * a concurrent process is also trying to add a value corresponding to the + * same key `k`. + * + * @param k the key to modify + * @param op the expression that computes the value + * @return the newly added value + */ + override def getOrElseUpdate(k: K, op: =>V): V = { + val oldv = lookup(k) + if (oldv != null) oldv.asInstanceOf[V] + else { + val v = op + if (v == null) { + throw new NullPointerException("Concurrent TrieMap values cannot be null.") + } else { + val hc = computeHash(k) + insertifhc(k, hc, v, INode.KEY_ABSENT) match { + case Some(oldv) => oldv + case None => v + } + } + } + } + def remove(k: K, v: V): Boolean = { val hc = computeHash(k) removehc(k, v, hc).nonEmpty diff --git a/src/library/scala/collection/mutable/MapLike.scala b/src/library/scala/collection/mutable/MapLike.scala index af28df1b88..44af886cf5 100644 --- a/src/library/scala/collection/mutable/MapLike.scala +++ b/src/library/scala/collection/mutable/MapLike.scala @@ -178,6 +178,10 @@ trait MapLike[A, B, +This <: MapLike[A, B, This] with Map[A, B]] * * Otherwise, computes value from given expression `op`, stores with key * in map and returns that value. + * + * Concurrent map implementations may evaluate the expression `op` + * multiple times, or may evaluate `op` without inserting the result. + * * @param key the key to test * @param op the computation yielding the value to associate with `key`, if * `key` is previously unbound. diff --git a/test/files/scalacheck/Ctrie.scala b/test/files/scalacheck/Ctrie.scala index 714f1c3b09..eef9d06f37 100644 --- a/test/files/scalacheck/Ctrie.scala +++ b/test/files/scalacheck/Ctrie.scala @@ -186,6 +186,25 @@ object Test extends Properties("concurrent.TrieMap") { }) } + property("concurrent getOrElseUpdate") = forAll(threadCounts, sizes) { + (p, sz) => + val totalInserts = new java.util.concurrent.atomic.AtomicInteger + val ct = new TrieMap[Wrap, String] + + val results = inParallel(p) { + idx => + (0 until sz) foreach { + i => + val v = ct.getOrElseUpdate(Wrap(i), idx + ":" + i) + if (v == idx + ":" + i) totalInserts.incrementAndGet() + } + } + + (totalInserts.get == sz) && ((0 until sz) forall { + case i => ct(Wrap(i)).split(":")(1).toInt == i + }) + } + } -- cgit v1.2.3 From 681800976ffe727d6b37fe055fbd1997568648ef Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 19 Feb 2015 22:25:07 +1000 Subject: SI-9164 Fix thread safety of Scaladoc diagram generator In a bug reminiscent of SI-7603 / ab8a223, when running multiple instances of Scaladoc in one JVM/classloader, we are exposed to race conditions in unprotected mutable state in a top-level object. This commit moves that state into the `Universe` object, which corresponds to a single Scaladoc instance. It also removes a little premature abstraction from the code. Note: the statistics code is still not thread safe, but this is no worse than the compiler itself, and not a real problem, as they are only enabled begind a `-Y` option. --- src/scaladoc/scala/tools/nsc/doc/Universe.scala | 3 +++ .../scala/tools/nsc/doc/html/HtmlFactory.scala | 21 ++++++++--------- .../doc/html/page/diagram/DiagramGenerator.scala | 26 ---------------------- .../html/page/diagram/DotDiagramGenerator.scala | 4 ++-- .../scala/tools/nsc/doc/model/ModelFactory.scala | 4 ++++ 5 files changed, 18 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/scaladoc/scala/tools/nsc/doc/Universe.scala b/src/scaladoc/scala/tools/nsc/doc/Universe.scala index 11520c810e..edf5112d7b 100644 --- a/src/scaladoc/scala/tools/nsc/doc/Universe.scala +++ b/src/scaladoc/scala/tools/nsc/doc/Universe.scala @@ -5,6 +5,8 @@ package scala.tools.nsc.doc +import scala.tools.nsc.doc.html.page.diagram.DotRunner + /** * Class to hold common dependencies across Scaladoc classes. * @author Pedro Furlanetto @@ -13,4 +15,5 @@ package scala.tools.nsc.doc trait Universe { def settings: Settings def rootPackage: model.Package + def dotRunner: DotRunner } diff --git a/src/scaladoc/scala/tools/nsc/doc/html/HtmlFactory.scala b/src/scaladoc/scala/tools/nsc/doc/html/HtmlFactory.scala index a0dd154d2e..61ab18d42d 100644 --- a/src/scaladoc/scala/tools/nsc/doc/html/HtmlFactory.scala +++ b/src/scaladoc/scala/tools/nsc/doc/html/HtmlFactory.scala @@ -13,8 +13,6 @@ import io.{ Streamable, Directory } import scala.collection._ import page.diagram._ -import html.page.diagram.DiagramGenerator - /** A class that can generate Scaladoc sites to some fixed root folder. * @author David Bernard * @author Gilles Dubochet */ @@ -121,28 +119,27 @@ class HtmlFactory(val universe: doc.Universe, index: doc.Index) { finally out.close() } - DiagramGenerator.initialize(universe.settings) - libResources foreach (s => copyResource("lib/" + s)) new page.Index(universe, index) writeFor this new page.IndexScript(universe, index) writeFor this - - writeTemplates(_ writeFor this) - - for (letter <- index.firstLetterIndex) { - new html.page.ReferenceIndex(letter._1, index, universe) writeFor this + try { + writeTemplates(_ writeFor this) + for (letter <- index.firstLetterIndex) { + new html.page.ReferenceIndex(letter._1, index, universe) writeFor this + } + } finally { + DiagramStats.printStats(universe.settings) + universe.dotRunner.cleanup() } - - DiagramGenerator.cleanup() } def writeTemplates(writeForThis: HtmlPage => Unit) { val written = mutable.HashSet.empty[DocTemplateEntity] - val diagramGenerator: DiagramGenerator = new DotDiagramGenerator(universe.settings) def writeTemplate(tpl: DocTemplateEntity) { if (!(written contains tpl)) { + val diagramGenerator: DiagramGenerator = new DotDiagramGenerator(universe.settings, universe.dotRunner) writeForThis(new page.Template(universe, diagramGenerator, tpl)) written += tpl tpl.templates collect { case d: DocTemplateEntity => d } map writeTemplate diff --git a/src/scaladoc/scala/tools/nsc/doc/html/page/diagram/DiagramGenerator.scala b/src/scaladoc/scala/tools/nsc/doc/html/page/diagram/DiagramGenerator.scala index 61c1819d11..cf65de4151 100644 --- a/src/scaladoc/scala/tools/nsc/doc/html/page/diagram/DiagramGenerator.scala +++ b/src/scaladoc/scala/tools/nsc/doc/html/page/diagram/DiagramGenerator.scala @@ -25,29 +25,3 @@ trait DiagramGenerator { */ def generate(d: Diagram, t: DocTemplateEntity, p: HtmlPage):NodeSeq } - -object DiagramGenerator { - - // TODO: This is tailored towards the dot generator, since it's the only generator. In the future it should be more - // general. - - private[this] var dotRunner: DotRunner = null - private[this] var settings: doc.Settings = null - - def initialize(s: doc.Settings) = - settings = s - - def getDotRunner() = { - if (dotRunner == null) - dotRunner = new DotRunner(settings) - dotRunner - } - - def cleanup() = { - DiagramStats.printStats(settings) - if (dotRunner != null) { - dotRunner.cleanup() - dotRunner = null - } - } -} \ No newline at end of file diff --git a/src/scaladoc/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala b/src/scaladoc/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala index 4ff436bdc6..b541cf721b 100644 --- a/src/scaladoc/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala +++ b/src/scaladoc/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala @@ -15,7 +15,7 @@ import scala.collection.immutable._ import model._ import model.diagram._ -class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { +class DotDiagramGenerator(settings: doc.Settings, dotRunner: DotRunner) extends DiagramGenerator { // the page where the diagram will be embedded private var page: HtmlPage = null @@ -317,7 +317,7 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { * Calls dot with a given dot string and returns the SVG output. */ private def generateSVG(dotInput: String, template: DocTemplateEntity) = { - val dotOutput = DiagramGenerator.getDotRunner().feedToDot(dotInput, template) + val dotOutput = dotRunner.feedToDot(dotInput, template) var tSVG = -System.currentTimeMillis val result = if (dotOutput != null) { diff --git a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala index 7289edc137..03d71f15a3 100644 --- a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala @@ -9,8 +9,11 @@ import base.comment._ import diagram._ import scala.collection._ +import scala.tools.nsc.doc.html.HtmlPage +import scala.tools.nsc.doc.html.page.diagram.{DotRunner} import scala.util.matching.Regex import scala.reflect.macros.internal.macroImpl +import scala.xml.NodeSeq import symtab.Flags import io._ @@ -47,6 +50,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { thisFactory.universe = thisUniverse val settings = thisFactory.settings val rootPackage = modelCreation.createRootPackage + lazy val dotRunner = new DotRunner(settings) } _modelFinished = true // complete the links between model entities, everthing that couldn't have been done before -- cgit v1.2.3 From 1d4a1f721d360c73b9f2468368c7e9b2b4c17fea Mon Sep 17 00:00:00 2001 From: Rex Kerr Date: Wed, 18 Feb 2015 18:52:46 -0800 Subject: SI-9126 Missing .seqs causes problems with parallel GenXs Added `.seq` in two essential places so that a parallel collection passed as an argument won't mess up side-effecting sequential computations in `List.flatMap` and `GenericTraversableTemplate.transpose` (thanks to retronym for finding the danger spots). Tests that `.seq` is called by constructing a `GenSeq` whose `.seq` disagrees with anything non-`.seq` (idea & working implementation from retronym). Also updates the `.seq` test for `Vector#++` to use the new more efficient method. --- .../generic/GenericTraversableTemplate.scala | 2 +- src/library/scala/collection/immutable/List.scala | 2 +- .../scala/collection/ParallelConsistencyTest.scala | 44 ++++++++++++++++++++++ .../scala/collection/immutable/VectorTest.scala | 20 ---------- 4 files changed, 46 insertions(+), 22 deletions(-) create mode 100644 test/junit/scala/collection/ParallelConsistencyTest.scala delete mode 100644 test/junit/scala/collection/immutable/VectorTest.scala (limited to 'src') diff --git a/src/library/scala/collection/generic/GenericTraversableTemplate.scala b/src/library/scala/collection/generic/GenericTraversableTemplate.scala index 54455c531a..bdd91ba7a4 100644 --- a/src/library/scala/collection/generic/GenericTraversableTemplate.scala +++ b/src/library/scala/collection/generic/GenericTraversableTemplate.scala @@ -216,7 +216,7 @@ trait GenericTraversableTemplate[+A, +CC[X] <: GenTraversable[X]] extends HasNew val bs: IndexedSeq[Builder[B, CC[B]]] = IndexedSeq.fill(headSize)(genericBuilder[B]) for (xs <- sequential) { var i = 0 - for (x <- asTraversable(xs)) { + for (x <- asTraversable(xs).seq) { if (i >= headSize) fail bs(i) += x i += 1 diff --git a/src/library/scala/collection/immutable/List.scala b/src/library/scala/collection/immutable/List.scala index a46b4adabb..254f14f13c 100644 --- a/src/library/scala/collection/immutable/List.scala +++ b/src/library/scala/collection/immutable/List.scala @@ -324,7 +324,7 @@ sealed abstract class List[+A] extends AbstractSeq[A] var h: ::[B] = null var t: ::[B] = null while (rest ne Nil) { - f(rest.head).foreach{ b => + f(rest.head).seq.foreach{ b => if (!found) { h = new ::(b, Nil) t = h diff --git a/test/junit/scala/collection/ParallelConsistencyTest.scala b/test/junit/scala/collection/ParallelConsistencyTest.scala new file mode 100644 index 0000000000..da96362413 --- /dev/null +++ b/test/junit/scala/collection/ParallelConsistencyTest.scala @@ -0,0 +1,44 @@ +package scala.collection.immutable + +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(classOf[JUnit4]) +class ParallelConsistencyTest { + + private val theSeq = Seq(1,2,3) + + // This collection will throw an exception if you do anything but call .length or .seq + private val mustCallSeq: collection.GenSeq[Int] = new collection.parallel.ParSeq[Int] { + def length = 3 + + // This method is surely sequential & safe -- want all access to go through here + def seq = theSeq + + def notSeq = throw new Exception("Access to parallel collection not via .seq") + + // These methods could possibly be used dangerously explicitly or internally + // (apply could also be used safely; if it is, do test with mustCallSeq) + def apply(i: Int) = notSeq + def splitter = notSeq + } + + // Test Vector ++ with a small parallel collection concatenation (SI-9072). + @Test + def testPlusPlus(): Unit = { + assert((Vector.empty ++ mustCallSeq) == theSeq, "Vector ++ unsafe with parallel vectors") + } + + // SI-9126, 1 of 2 + @Test + def testTranspose(): Unit = { + assert(List(mustCallSeq).transpose.flatten == theSeq, "Transposing inner parallel collection unsafe") + } + + // SI-9126, 2 of 2 + @Test + def testList_flatMap(): Unit = { + assert(List(1).flatMap(_ => mustCallSeq) == theSeq, "List#flatMap on inner parallel collection unsafe") + } +} diff --git a/test/junit/scala/collection/immutable/VectorTest.scala b/test/junit/scala/collection/immutable/VectorTest.scala deleted file mode 100644 index e7edba3e43..0000000000 --- a/test/junit/scala/collection/immutable/VectorTest.scala +++ /dev/null @@ -1,20 +0,0 @@ -package scala.collection.immutable - -import org.junit.{Assert, Test} -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 - -@RunWith(classOf[JUnit4]) -class VectorTest { - /** - * Test Vector ++ with a small parallel collection concatenation (SI-9072). - * - */ - @Test - def testPlusPlus(): Unit = { - val smallVec = (0 to 1) - val smallParVec = smallVec.par - val testElementsSize = (0 to 1000).map( _ => Vector.empty ++ smallParVec ) - Assert.assertTrue(testElementsSize.forall( v => v.size == 2 )) - } -} -- cgit v1.2.3 From 049e66cee8c27e1847044df43bc33c7d8421097e Mon Sep 17 00:00:00 2001 From: Rex Kerr Date: Sat, 27 Dec 2014 16:15:44 -0800 Subject: Performance optimization - SeqLike Big improvements to updated and patch by not creating intermediate collections. Moderate improvement to sorted by not using extra ArraySeq wrapper. Small improvements to diff, intersect, and padTo by watching for duplicate or slow operations a bit more carefully. ``` Performance summary (all timings JDK 1.8.0_25 Linux): method reason =========== =============================================================== diff 1.1x speedup on big seqs - avoid double map lookup intersect 1.1-1.2x speedup - avoid double map lookup padTo avoid multiple calls to length (good if length is slow) patch 2.4-4.0x speedup - avoid intermediate collections sorted 1.2-1.5x speedup - use array directly updated 3.5-9.6x speedup - avoid intermediates (but children override) ``` --- src/library/scala/collection/SeqLike.scala | 75 ++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/library/scala/collection/SeqLike.scala b/src/library/scala/collection/SeqLike.scala index 329273df5b..66fce0f902 100644 --- a/src/library/scala/collection/SeqLike.scala +++ b/src/library/scala/collection/SeqLike.scala @@ -447,9 +447,11 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ def diff[B >: A](that: GenSeq[B]): Repr = { val occ = occCounts(that.seq) val b = newBuilder - for (x <- this) - if (occ(x) == 0) b += x - else occ(x) -= 1 + for (x <- this) { + val ox = occ(x) // Avoid multiple map lookups + if (ox == 0) b += x + else occ(x) = ox - 1 + } b.result() } @@ -476,11 +478,13 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ def intersect[B >: A](that: GenSeq[B]): Repr = { val occ = occCounts(that.seq) val b = newBuilder - for (x <- this) - if (occ(x) > 0) { + for (x <- this) { + val ox = occ(x) // Avoid multiple map lookups + if (ox > 0) { b += x - occ(x) -= 1 + occ(x) = ox - 1 } + } b.result() } @@ -509,22 +513,35 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ def patch[B >: A, That](from: Int, patch: GenSeq[B], replaced: Int)(implicit bf: CanBuildFrom[Repr, B, That]): That = { val b = bf(repr) - val (prefix, rest) = this.splitAt(from) - b ++= toCollection(prefix) + var i = 0 + val it = this.iterator + while (i < from && it.hasNext) { + b += it.next() + i += 1 + } b ++= patch.seq - b ++= toCollection(rest).view drop replaced + i = replaced + while (i > 0 && it.hasNext) { + it.next() + i -= 1 + } + while (it.hasNext) b += it.next() b.result() } def updated[B >: A, That](index: Int, elem: B)(implicit bf: CanBuildFrom[Repr, B, That]): That = { if (index < 0) throw new IndexOutOfBoundsException(index.toString) val b = bf(repr) - val (prefix, rest) = this.splitAt(index) - val restColl = toCollection(rest) - if (restColl.isEmpty) throw new IndexOutOfBoundsException(index.toString) - b ++= toCollection(prefix) + var i = 0 + val it = this.iterator + while (i < index && it.hasNext) { + b += it.next() + i += 1 + } + if (!it.hasNext) throw new IndexOutOfBoundsException(index.toString) b += elem - b ++= restColl.view.tail + it.next() + while (it.hasNext) b += it.next() b.result() } @@ -544,8 +561,9 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ def padTo[B >: A, That](len: Int, elem: B)(implicit bf: CanBuildFrom[Repr, B, That]): That = { val b = bf(repr) - b.sizeHint(length max len) - var diff = len - length + val L = length + b.sizeHint(math.max(L, len)) + var diff = len - L b ++= thisCollection while (diff > 0) { b += elem @@ -617,16 +635,23 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ */ def sorted[B >: A](implicit ord: Ordering[B]): Repr = { val len = this.length - val arr = new ArraySeq[A](len) - var i = 0 - for (x <- this) { - arr(i) = x - i += 1 - } - java.util.Arrays.sort(arr.array, ord.asInstanceOf[Ordering[Object]]) val b = newBuilder - b.sizeHint(len) - for (x <- arr) b += x + if (len == 1) b ++= this + else if (len > 1) { + b.sizeHint(len) + val arr = new Array[AnyRef](len) // Previously used ArraySeq for more compact but slower code + var i = 0 + for (x <- this) { + arr(i) = x.asInstanceOf[AnyRef] + i += 1 + } + java.util.Arrays.sort(arr, ord.asInstanceOf[Ordering[Object]]) + i = 0 + while (i < arr.length) { + b += arr(i).asInstanceOf[A] + i += 1 + } + } b.result() } -- cgit v1.2.3 From cafed1b91d7aa09de0eb674c4f14d86d758c383e Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 20 Feb 2015 11:21:46 -0800 Subject: Scaladoc js location synch more robust Tested on: - Mac: FF35/Safari 8/Chrome 41 - Win: IE11 --- src/scaladoc/scala/tools/nsc/doc/html/resource/lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/index.js b/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/index.js index 3f5cfb4b52..3d9cf8d465 100644 --- a/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/index.js +++ b/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/index.js @@ -94,7 +94,7 @@ function setFrameSrcFromUrlFragment() { if (memberSig) { locWithMemeberSig += "#" + memberSig; } - frames["template"].location.replace(locWithMemeberSig); + frames["template"].location.replace(location.protocol + locWithMemeberSig); } else { console.log("empty fragment detected"); frames["template"].location.replace("package.html"); -- cgit v1.2.3 From dcd4d6700660c480b29eb4c8aeba77403c112d5f Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Sat, 31 Jan 2015 15:55:35 -0800 Subject: SI-9127 Xlint doesn't think spaces are significant For purposes of warning about missing interpolators, such as `"$greeting"` when the intended code was `s"$greeting"`, spaces are no longer significant. The heuristic was previously intended to allow compileresque strings, where the dollar sign is a common prefix. Currently, the Xlint warning can be selectively disabled. --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 11 ++++------- test/files/neg/t7848-interp-warn.check | 5 ++++- test/files/neg/t7848-interp-warn.scala | 2 +- test/files/neg/t9127.check | 12 ++++++++++++ test/files/neg/t9127.flags | 1 + test/files/neg/t9127.scala | 7 +++++++ 6 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 test/files/neg/t9127.check create mode 100644 test/files/neg/t9127.flags create mode 100644 test/files/neg/t9127.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 3a85d16f55..8efef8b4aa 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -5200,13 +5200,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def suspiciousExpr = InterpolatorCodeRegex findFirstIn s def suspiciousIdents = InterpolatorIdentRegex findAllIn s map (s => suspiciousSym(s drop 1)) - // heuristics - no warning on e.g. a string with only "$asInstanceOf" - if (s contains ' ') ( - if (suspiciousExpr.nonEmpty) - warn("detected an interpolated expression") // "${...}" - else - suspiciousIdents find isPlausible foreach (sym => warn(s"detected interpolated identifier `$$${sym.name}`")) // "$id" - ) + if (suspiciousExpr.nonEmpty) + warn("detected an interpolated expression") // "${...}" + else + suspiciousIdents find isPlausible foreach (sym => warn(s"detected interpolated identifier `$$${sym.name}`")) // "$id" } lit match { case Literal(Constant(s: String)) if !isRecognizablyNotForInterpolation => maybeWarn(s) diff --git a/test/files/neg/t7848-interp-warn.check b/test/files/neg/t7848-interp-warn.check index 4cf9d55ffd..637fc8941a 100644 --- a/test/files/neg/t7848-interp-warn.check +++ b/test/files/neg/t7848-interp-warn.check @@ -4,9 +4,12 @@ t7848-interp-warn.scala:8: warning: possible missing interpolator: detected inte t7848-interp-warn.scala:12: warning: possible missing interpolator: detected an interpolated expression "A doubly important ${foo * 2} message!" ^ +t7848-interp-warn.scala:15: warning: possible missing interpolator: detected interpolated identifier `$bar` + def i = s"Try using '${ "$bar" }' instead." // was: no warn on space test + ^ t7848-interp-warn.scala:16: warning: possible missing interpolator: detected interpolated identifier `$bar` def j = s"Try using '${ "something like $bar" }' instead." // warn ^ error: No warnings can be incurred under -Xfatal-warnings. -three warnings found +four warnings found one error found diff --git a/test/files/neg/t7848-interp-warn.scala b/test/files/neg/t7848-interp-warn.scala index 3887aff8de..a76141041d 100644 --- a/test/files/neg/t7848-interp-warn.scala +++ b/test/files/neg/t7848-interp-warn.scala @@ -12,7 +12,7 @@ object Test { "A doubly important ${foo * 2} message!" } def h = s"Try using '$$bar' instead." // no warn - def i = s"Try using '${ "$bar" }' instead." // no warn on space test + def i = s"Try using '${ "$bar" }' instead." // was: no warn on space test def j = s"Try using '${ "something like $bar" }' instead." // warn def k = f"Try using '$bar' instead." // no warn on other std interps } diff --git a/test/files/neg/t9127.check b/test/files/neg/t9127.check new file mode 100644 index 0000000000..2ecf8af464 --- /dev/null +++ b/test/files/neg/t9127.check @@ -0,0 +1,12 @@ +t9127.scala:4: warning: possible missing interpolator: detected interpolated identifier `$s` + val t = "$s" + ^ +t9127.scala:5: warning: possible missing interpolator: detected an interpolated expression + val u = "a${s}b" + ^ +t9127.scala:6: warning: possible missing interpolator: detected interpolated identifier `$s` + val v = "a$s b" + ^ +error: No warnings can be incurred under -Xfatal-warnings. +three warnings found +one error found diff --git a/test/files/neg/t9127.flags b/test/files/neg/t9127.flags new file mode 100644 index 0000000000..b0d7bc25cb --- /dev/null +++ b/test/files/neg/t9127.flags @@ -0,0 +1 @@ +-Xlint:missing-interpolator -Xfatal-warnings diff --git a/test/files/neg/t9127.scala b/test/files/neg/t9127.scala new file mode 100644 index 0000000000..c0746144eb --- /dev/null +++ b/test/files/neg/t9127.scala @@ -0,0 +1,7 @@ + +trait X { + val s = "hello" + val t = "$s" + val u = "a${s}b" + val v = "a$s b" +} -- cgit v1.2.3 From 97f7f5868961264d60a65cfdc3f8e8cdd6502164 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sun, 22 Feb 2015 18:50:33 +0100 Subject: fixes pluginsEnterStats Initial implementation of pluginsEnterStats was incorrect, because I got the foldLeft wrong, making it perpetuate the initial value of stats. This worked fine if zero or one macro plugins were active at a time, but broke down if there were multiple of such plugins (concretely, I discovered this issue when trying to marry macro paradise with scalahost). --- .../tools/nsc/typechecker/AnalyzerPlugins.scala | 2 +- test/files/run/macroPlugins-enterStats.check | 30 +++++++++++++ test/files/run/macroPlugins-enterStats.scala | 50 ++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 test/files/run/macroPlugins-enterStats.check create mode 100644 test/files/run/macroPlugins-enterStats.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala b/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala index 5a70d4c524..2c27bdb03a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala +++ b/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala @@ -447,6 +447,6 @@ trait AnalyzerPlugins { self: Analyzer => // performance opt if (macroPlugins.isEmpty) stats else macroPlugins.foldLeft(stats)((current, plugin) => - if (!plugin.isActive()) current else plugin.pluginsEnterStats(typer, stats)) + if (!plugin.isActive()) current else plugin.pluginsEnterStats(typer, current)) } } diff --git a/test/files/run/macroPlugins-enterStats.check b/test/files/run/macroPlugins-enterStats.check new file mode 100644 index 0000000000..133b1ae1af --- /dev/null +++ b/test/files/run/macroPlugins-enterStats.check @@ -0,0 +1,30 @@ +[[syntax trees at end of typer]] // newSource1.scala +package { + class C extends scala.AnyRef { + def (): C = { + C.super.(); + () + }; + def x: Int = 2; + def xmacroPlugin1: Nothing = scala.this.Predef.???; + def xmacroPlugin2: Nothing = scala.this.Predef.???; + def xmacroPlugin2macroPlugin1: Nothing = scala.this.Predef.???; + def y: Int = 3; + def ymacroPlugin1: Nothing = scala.this.Predef.???; + def ymacroPlugin2: Nothing = scala.this.Predef.???; + def ymacroPlugin2macroPlugin1: Nothing = scala.this.Predef.??? + } +} + +macroPlugin2:enterStat(class C extends scala.AnyRef { def () = { super.(); () }; def x = 2; def y = 3 }) +macroPlugin1:enterStat(class C extends scala.AnyRef { def () = { super.(); () }; def x = 2; def y = 3 }) +macroPlugin2:enterStat(def () = { super.(); () }) +macroPlugin2:enterStat(def x = 2) +macroPlugin2:enterStat(def y = 3) +macroPlugin1:enterStat(def () = { super.(); () }) +macroPlugin1:enterStat(def x = 2) +macroPlugin1:enterStat(def xmacroPlugin2 = $qmark$qmark$qmark) +macroPlugin1:enterStat(def y = 3) +macroPlugin1:enterStat(def ymacroPlugin2 = $qmark$qmark$qmark) +macroPlugin2:enterStat(super.()) +macroPlugin1:enterStat(super.()) diff --git a/test/files/run/macroPlugins-enterStats.scala b/test/files/run/macroPlugins-enterStats.scala new file mode 100644 index 0000000000..917233e990 --- /dev/null +++ b/test/files/run/macroPlugins-enterStats.scala @@ -0,0 +1,50 @@ +import scala.tools.partest._ +import scala.tools.nsc._ + +object Test extends DirectTest { + override def extraSettings: String = "-usejavacp -Xprint:typer" + + def code = """ + class C { + def x = 2 + def y = 3 + } + """.trim + + def show() { + val global = newCompiler() + import global._ + import analyzer._ + + val output = collection.mutable.ListBuffer[String]() + def log(what: String) = output += what.replace(String.format("%n"), " ") + + def logEnterStat(pluginName: String, stat: Tree): Unit = log(s"$pluginName:enterStat($stat)") + def deriveStat(pluginName: String, typer: Typer, stat: Tree): List[Tree] = stat match { + case DefDef(mods, name, Nil, Nil, TypeTree(), body) => + val derived = DefDef(NoMods, TermName(name + pluginName), Nil, Nil, TypeTree(), Ident(TermName("$qmark$qmark$qmark"))) + newNamer(typer.context).enterSym(derived) + List(derived) + case _ => + Nil + } + + object macroPlugin1 extends MacroPlugin { + override def pluginsEnterStats(typer: Typer, stats: List[Tree]): List[Tree] = { + stats.foreach(stat => logEnterStat("macroPlugin1", stat)) + stats.flatMap(stat => stat +: deriveStat("macroPlugin1", typer, stat)) + } + } + object macroPlugin2 extends MacroPlugin { + override def pluginsEnterStats(typer: Typer, stats: List[Tree]): List[Tree] = { + stats.foreach(stat => logEnterStat("macroPlugin2", stat)) + stats.flatMap(stat => stat +: deriveStat("macroPlugin2", typer, stat)) + } + } + + addMacroPlugin(macroPlugin1) + addMacroPlugin(macroPlugin2) + compileString(global)(code) + println(output.mkString("\n")) + } +} -- cgit v1.2.3 From 8cc66b0a8fb6c8c46c306a672a66cf5af431f733 Mon Sep 17 00:00:00 2001 From: Rex Kerr Date: Sun, 22 Feb 2015 15:54:29 -0800 Subject: SI-9172 FlatMapped views throw exception on filter Errant `self.length` changed to `length`. No tests; found and tested by collections-laws. --- src/library/scala/collection/SeqViewLike.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/library/scala/collection/SeqViewLike.scala b/src/library/scala/collection/SeqViewLike.scala index 59e0e73e89..3473c8aff1 100644 --- a/src/library/scala/collection/SeqViewLike.scala +++ b/src/library/scala/collection/SeqViewLike.scala @@ -83,7 +83,7 @@ trait SeqViewLike[+A, } def length = index(self.length) def apply(idx: Int) = { - if (idx < 0 || idx >= self.length) throw new IndexOutOfBoundsException(idx.toString) + if (idx < 0 || idx >= length) throw new IndexOutOfBoundsException(idx.toString) val row = findRow(idx, 0, self.length - 1) mapping(self(row)).seq.toSeq(idx - index(row)) } -- cgit v1.2.3 From bb7f2b8d98243c2d67ff1835fcfb1df5dbea9bb6 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 24 Feb 2015 16:25:06 +0100 Subject: better errors for macro applications with wrong number of arguments The tryNamesDefaults branch of application typechecking contains the checkNotMacro check, which errors out when we try to do named/default arguments for macros (that's not supported yet, see SI-5920 for discussion). Unfortunately, the check activates too early, because it turns out that we can also enter tryNamesDefaults when the user just provides insufficient number of arguments to a method by mistake, without expecting any default arguments at all. This leads to really confusing errors, which can luckily be fixed in a very simple way by moving the checkNotMacro check down the happy path of named/default typechecking. --- .../scala/tools/nsc/typechecker/Typers.scala | 2 +- test/files/neg/macro-invalidusage-badargs.check | 3 +- test/files/neg/t7157.check | 36 ++++++++++++++-------- 3 files changed, 27 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 3a85d16f55..d4756f0af3 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3387,7 +3387,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // defaults are needed. they are added to the argument list in named style as // calls to the default getters. Example: // foo[Int](a)() ==> foo[Int](a)(b = foo$qual.foo$default$2[Int](a)) - checkNotMacro() // SI-8111 transformNamedApplication eagerly shuffles around the application to preserve // evaluation order. During this process, it calls `changeOwner` on symbols that @@ -3434,6 +3433,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper duplErrTree } else if (lencmp2 == 0) { // useful when a default doesn't match parameter type, e.g. def f[T](x:T="a"); f[Int]() + checkNotMacro() context.diagUsedDefaults = true doTypedApply(tree, if (blockIsEmpty) fun else fun1, allArgs, mode, pt) } else { diff --git a/test/files/neg/macro-invalidusage-badargs.check b/test/files/neg/macro-invalidusage-badargs.check index 4c1115418b..19ac6528d3 100644 --- a/test/files/neg/macro-invalidusage-badargs.check +++ b/test/files/neg/macro-invalidusage-badargs.check @@ -9,7 +9,8 @@ Macros_Test_2.scala:6: error: too few argument lists for macro invocation Macros_Test_2.scala:7: error: Int does not take parameters foo(4)(2) ^ -Macros_Test_2.scala:8: error: macro applications do not support named and/or default arguments +Macros_Test_2.scala:8: error: not enough arguments for macro method foo: (x: Int)Int. +Unspecified value parameter x. foo() ^ Macros_Test_2.scala:9: error: too many arguments for macro method foo: (x: Int)Int diff --git a/test/files/neg/t7157.check b/test/files/neg/t7157.check index c6a7af9a23..3988460d4b 100644 --- a/test/files/neg/t7157.check +++ b/test/files/neg/t7157.check @@ -7,7 +7,8 @@ Test_2.scala:6: error: too many arguments for macro method m1_0_0: ()Unit Test_2.scala:7: error: too many arguments for macro method m1_0_0: ()Unit m1_0_0(1, 2, 3) ^ -Test_2.scala:9: error: macro applications do not support named and/or default arguments +Test_2.scala:9: error: not enough arguments for macro method m1_1_1: (x: Int)Unit. +Unspecified value parameter x. m1_1_1() ^ Test_2.scala:11: error: too many arguments for macro method m1_1_1: (x: Int)Unit @@ -16,22 +17,27 @@ Test_2.scala:11: error: too many arguments for macro method m1_1_1: (x: Int)Unit Test_2.scala:12: error: too many arguments for macro method m1_1_1: (x: Int)Unit m1_1_1(1, 2, 3) ^ -Test_2.scala:14: error: macro applications do not support named and/or default arguments +Test_2.scala:14: error: not enough arguments for macro method m1_2_2: (x: Int, y: Int)Unit. +Unspecified value parameters x, y. m1_2_2() ^ -Test_2.scala:15: error: macro applications do not support named and/or default arguments +Test_2.scala:15: error: not enough arguments for macro method m1_2_2: (x: Int, y: Int)Unit. +Unspecified value parameter y. m1_2_2(1) ^ Test_2.scala:17: error: too many arguments for macro method m1_2_2: (x: Int, y: Int)Unit m1_2_2(1, 2, 3) ^ -Test_2.scala:24: error: macro applications do not support named and/or default arguments +Test_2.scala:24: error: not enough arguments for macro method m1_1_inf: (x: Int, y: Int*)Unit. +Unspecified value parameters x, y. m1_1_inf() ^ -Test_2.scala:29: error: macro applications do not support named and/or default arguments +Test_2.scala:29: error: not enough arguments for macro method m1_2_inf: (x: Int, y: Int, z: Int*)Unit. +Unspecified value parameters x, y, z. m1_2_inf() ^ -Test_2.scala:30: error: macro applications do not support named and/or default arguments +Test_2.scala:30: error: not enough arguments for macro method m1_2_inf: (x: Int, y: Int, z: Int*)Unit. +Unspecified value parameters y, z. m1_2_inf(1) ^ Test_2.scala:35: error: too many arguments for macro method m2_0_0: ()Unit @@ -43,7 +49,8 @@ Test_2.scala:36: error: too many arguments for macro method m2_0_0: ()Unit Test_2.scala:37: error: too many arguments for macro method m2_0_0: ()Unit m2_0_0()(1, 2, 3) ^ -Test_2.scala:39: error: macro applications do not support named and/or default arguments +Test_2.scala:39: error: not enough arguments for macro method m2_1_1: (x: Int)Unit. +Unspecified value parameter x. m2_1_1()() ^ Test_2.scala:41: error: too many arguments for macro method m2_1_1: (x: Int)Unit @@ -52,22 +59,27 @@ Test_2.scala:41: error: too many arguments for macro method m2_1_1: (x: Int)Unit Test_2.scala:42: error: too many arguments for macro method m2_1_1: (x: Int)Unit m2_1_1()(1, 2, 3) ^ -Test_2.scala:44: error: macro applications do not support named and/or default arguments +Test_2.scala:44: error: not enough arguments for macro method m2_2_2: (x: Int, y: Int)Unit. +Unspecified value parameters x, y. m2_2_2()() ^ -Test_2.scala:45: error: macro applications do not support named and/or default arguments +Test_2.scala:45: error: not enough arguments for macro method m2_2_2: (x: Int, y: Int)Unit. +Unspecified value parameter y. m2_2_2()(1) ^ Test_2.scala:47: error: too many arguments for macro method m2_2_2: (x: Int, y: Int)Unit m2_2_2()(1, 2, 3) ^ -Test_2.scala:54: error: macro applications do not support named and/or default arguments +Test_2.scala:54: error: not enough arguments for macro method m2_1_inf: (x: Int, y: Int*)Unit. +Unspecified value parameters x, y. m2_1_inf()() ^ -Test_2.scala:59: error: macro applications do not support named and/or default arguments +Test_2.scala:59: error: not enough arguments for macro method m2_2_inf: (x: Int, y: Int, z: Int*)Unit. +Unspecified value parameters x, y, z. m2_2_inf()() ^ -Test_2.scala:60: error: macro applications do not support named and/or default arguments +Test_2.scala:60: error: not enough arguments for macro method m2_2_inf: (x: Int, y: Int, z: Int*)Unit. +Unspecified value parameters y, z. m2_2_inf()(1) ^ 24 errors found -- cgit v1.2.3 From 06e7e342d1e27097df0b9d0b31a322fd1cf0a34e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 25 Feb 2015 16:16:47 +1000 Subject: SI-9182 Fix runtime reflection with package object, overloads Eponymous modules and methods should be allowed to live in the same package scope. This can happen when using a module and and implicit class, or when defining the overloads manually. This commit tones back an assertion that was added for sanity checking runtime reflection thread safety to only fire when we are sure that neither the existing and current symbol of the given name are methods. --- src/reflect/scala/reflect/runtime/SymbolLoaders.scala | 3 ++- test/files/run/t9182.check | 3 +++ test/files/run/t9182.scala | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 test/files/run/t9182.check create mode 100644 test/files/run/t9182.scala (limited to 'src') diff --git a/src/reflect/scala/reflect/runtime/SymbolLoaders.scala b/src/reflect/scala/reflect/runtime/SymbolLoaders.scala index 50ea8d9868..9ce6331e33 100644 --- a/src/reflect/scala/reflect/runtime/SymbolLoaders.scala +++ b/src/reflect/scala/reflect/runtime/SymbolLoaders.scala @@ -107,7 +107,8 @@ private[reflect] trait SymbolLoaders { self: SymbolTable => if (isCompilerUniverse) super.enter(sym) else { val existing = super.lookupEntry(sym.name) - assert(existing == null || existing.sym.isMethod, s"pkgClass = $pkgClass, sym = $sym, existing = $existing") + def eitherIsMethod(sym1: Symbol, sym2: Symbol) = sym1.isMethod || sym2.isMethod + assert(existing == null || eitherIsMethod(existing.sym, sym), s"pkgClass = $pkgClass, sym = $sym, existing = $existing") super.enter(sym) } } diff --git a/test/files/run/t9182.check b/test/files/run/t9182.check new file mode 100644 index 0000000000..80e8b6c558 --- /dev/null +++ b/test/files/run/t9182.check @@ -0,0 +1,3 @@ +constructor package +method A +object A diff --git a/test/files/run/t9182.scala b/test/files/run/t9182.scala new file mode 100644 index 0000000000..1768aa688e --- /dev/null +++ b/test/files/run/t9182.scala @@ -0,0 +1,12 @@ +// Main.scala +package object ops { + object A + def A(a: Any) = () +} + +object Test { + def main(args: Array[String]): Unit = { + val pack = scala.reflect.runtime.currentMirror.staticModule("ops.package") + println(pack.info.decls.toList.map(_.toString).sorted.mkString("\n")) + } +} -- cgit v1.2.3 From a180f5f24f2094112a01cdb02e0e8f218db68d70 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 25 Feb 2015 20:33:20 -0800 Subject: SI-9170 More flexible SessionTest SessionTest session text can include line continuations and pasted text. Pasted script (which looks like a double prompt) probably doesn't work. This commit includes @retronym's SI-9170 one-liner. --- src/compiler/scala/tools/nsc/Global.scala | 3 +- .../scala/tools/partest/ReplTest.scala | 37 ++++++++++++-- src/repl/scala/tools/nsc/interpreter/ILoop.scala | 23 +++++---- test/files/run/t9170.scala | 58 ++++++++++++++++++++++ 4 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 test/files/run/t9170.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 1c9dbad4dd..b233acf271 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -1551,7 +1551,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter) if (reporter.hasErrors) { for ((sym, file) <- symSource.iterator) { - sym.reset(new loaders.SourcefileLoader(file)) + if (file != null) + sym.reset(new loaders.SourcefileLoader(file)) if (sym.isTerm) sym.moduleClass reset loaders.moduleClassLoader } diff --git a/src/partest-extras/scala/tools/partest/ReplTest.scala b/src/partest-extras/scala/tools/partest/ReplTest.scala index a728e8bdef..5b65d6ab9b 100644 --- a/src/partest-extras/scala/tools/partest/ReplTest.scala +++ b/src/partest-extras/scala/tools/partest/ReplTest.scala @@ -8,6 +8,7 @@ package scala.tools.partest import scala.tools.nsc.Settings import scala.tools.nsc.interpreter.ILoop import java.lang.reflect.{ Method => JMethod, Field => JField } +import scala.util.matching.Regex.Match /** A class for testing repl code. * It filters the line of output that mentions a version number. @@ -22,6 +23,9 @@ abstract class ReplTest extends DirectTest { s.Xnojline.value = true transformSettings(s) } + /** True for SessionTest to preserve session text. */ + def inSession: Boolean = false + /** True to preserve welcome text. */ def welcoming: Boolean = false lazy val welcome = "(Welcome to Scala) version .*".r def normalize(s: String) = s match { @@ -36,7 +40,7 @@ abstract class ReplTest extends DirectTest { val s = settings log("eval(): settings = " + s) //ILoop.runForTranscript(code, s).lines drop 1 // not always first line - val lines = ILoop.runForTranscript(code, s).lines + val lines = ILoop.runForTranscript(code, s, inSession = inSession).lines if (welcoming) lines map normalize else lines filter unwelcoming } @@ -57,13 +61,30 @@ abstract class SessionTest extends ReplTest { /** Session transcript, as a triple-quoted, multiline, marginalized string. */ def session: String - /** Expected output, as an iterator. */ - def expected = session.stripMargin.lines + /** Expected output, as an iterator, optionally marginally stripped. */ + def expected = if (stripMargins) session.stripMargin.lines else session.lines + + /** Override with false if we should not strip margins because of leading continuation lines. */ + def stripMargins: Boolean = true + + /** Analogous to stripMargins, don't mangle continuation lines on echo. */ + override def inSession: Boolean = true /** Code is the command list culled from the session (or the expected session output). - * Would be nicer if code were lazy lines. + * Would be nicer if code were lazy lines so you could generate arbitrarily long text. + * Retain user input: prompt lines and continuations, without the prefix; or pasted text plus ctl-D. */ - override final def code = expected filter (_ startsWith prompt) map (_ drop prompt.length) mkString "\n" + import SessionTest._ + override final def code = input findAllMatchIn (expected mkString ("", "\n", "\n")) map { + case input(null, null, prompted) => + def continued(m: Match): Option[String] = m match { + case margin(text) => Some(text) + case _ => None + } + margin.replaceSomeIn(prompted, continued) + case input(cmd, pasted, null) => + cmd + pasted + "\u0004" + } mkString final def prompt = "scala> " @@ -75,3 +96,9 @@ abstract class SessionTest extends ReplTest { if (evaled != wanted) Console print nest.FileManager.compareContents(wanted, evaled, "expected", "actual") } } +object SessionTest { + // \R for line break is Java 8, \v for vertical space might suffice + val input = """(?m)^scala> (:pa.*\u000A)// Entering paste mode.*\u000A\u000A((?:.*\u000A)*)\u000A// Exiting paste mode.*\u000A|^scala> (.*\u000A(?:\s*\| .*\u000A)*)""".r + + val margin = """(?m)^\s*\| (.*)$""".r +} diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala index 4d71e0e09e..4221126caa 100644 --- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala @@ -937,25 +937,30 @@ object ILoop { // Designed primarily for use by test code: take a String with a // bunch of code, and prints out a transcript of what it would look // like if you'd just typed it into the repl. - def runForTranscript(code: String, settings: Settings): String = { + def runForTranscript(code: String, settings: Settings, inSession: Boolean = false): String = { import java.io.{ BufferedReader, StringReader, OutputStreamWriter } stringFromStream { ostream => Console.withOut(ostream) { val output = new JPrintWriter(new OutputStreamWriter(ostream), true) { - override def write(str: String) = { - // completely skip continuation lines - if (str forall (ch => ch.isWhitespace || ch == '|')) () + // skip margin prefix for continuation lines, unless preserving session text for test + override def write(str: String) = + if (!inSession && (str forall (ch => ch.isWhitespace || ch == '|'))) () // repl.paste.ContinueString else super.write(str) - } } val input = new BufferedReader(new StringReader(code.trim + "\n")) { override def readLine(): String = { - val s = super.readLine() - // helping out by printing the line being interpreted. - if (s != null) + mark(1) // default buffer is 8k + val c = read() + if (c == -1 || c == 4) { + null + } else { + reset() + val s = super.readLine() + // helping out by printing the line being interpreted. output.println(s) - s + s + } } } val repl = new ILoop(input, output) diff --git a/test/files/run/t9170.scala b/test/files/run/t9170.scala new file mode 100644 index 0000000000..25a0e84581 --- /dev/null +++ b/test/files/run/t9170.scala @@ -0,0 +1,58 @@ + +import scala.tools.partest.SessionTest + +object Test extends SessionTest { + + override def stripMargins = false + + def session = +"""Type in expressions to have them evaluated. +Type :help for more information. + +scala> object Y { def f[A](a: => A) = 1 ; def f[A](a: => Either[Exception, A]) = 2 } +:7: error: double definition: +def f[A](a: => A): Int at line 7 and +def f[A](a: => Either[Exception,A]): Int at line 7 +have same type after erasure: (a: Function0)Int + object Y { def f[A](a: => A) = 1 ; def f[A](a: => Either[Exception, A]) = 2 } + ^ + +scala> object Y { def f[A](a: => A) = 1 ; def f[A](a: => Either[Exception, A]) = 2 } +:7: error: double definition: +def f[A](a: => A): Int at line 7 and +def f[A](a: => Either[Exception,A]): Int at line 7 +have same type after erasure: (a: Function0)Int + object Y { def f[A](a: => A) = 1 ; def f[A](a: => Either[Exception, A]) = 2 } + ^ + +scala> object Y { + | def f[A](a: => A) = 1 + | def f[A](a: => Either[Exception, A]) = 2 + | } +:9: error: double definition: +def f[A](a: => A): Int at line 8 and +def f[A](a: => Either[Exception,A]): Int at line 9 +have same type after erasure: (a: Function0)Int + def f[A](a: => Either[Exception, A]) = 2 + ^ + +scala> :pa +// Entering paste mode (ctrl-D to finish) + +object Y { + def f[A](a: => A) = 1 + def f[A](a: => Either[Exception, A]) = 2 +} + +// Exiting paste mode, now interpreting. + +:9: error: double definition: +def f[A](a: => A): Int at line 8 and +def f[A](a: => Either[Exception,A]): Int at line 9 +have same type after erasure: (a: Function0)Int + def f[A](a: => Either[Exception, A]) = 2 + ^ + +scala> :quit""" +} + -- cgit v1.2.3 From d04cd7b39107fb7cb817a08265724c00043b1396 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 24 Sep 2014 10:31:33 -0700 Subject: SI-8861 Handle alias when probing for Any If args to a method are alias types, dealias to see if they contain Any before warning about inferring it. Similarly for return and expected types. --- src/compiler/scala/tools/nsc/typechecker/Infer.scala | 5 ++--- test/files/pos/t8861.flags | 1 + test/files/pos/t8861.scala | 11 +++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 test/files/pos/t8861.flags create mode 100644 test/files/pos/t8861.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index cf97474d9a..27e17fc65f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -553,9 +553,8 @@ trait Infer extends Checkable { // ...or lower bound of a type param, since they're asking for it. def canWarnAboutAny = { val loBounds = tparams map (_.info.bounds.lo) - val hasAny = pt :: restpe :: formals ::: argtpes ::: loBounds exists (t => - (t contains AnyClass) || (t contains AnyValClass) - ) + def containsAny(t: Type) = (t contains AnyClass) || (t contains AnyValClass) + val hasAny = pt :: restpe :: formals ::: argtpes ::: loBounds exists (_.dealiasWidenChain exists containsAny) !hasAny } def argumentPosition(idx: Int): Position = context.tree match { diff --git a/test/files/pos/t8861.flags b/test/files/pos/t8861.flags new file mode 100644 index 0000000000..99a6391058 --- /dev/null +++ b/test/files/pos/t8861.flags @@ -0,0 +1 @@ +-Xlint:infer-any -Xfatal-warnings diff --git a/test/files/pos/t8861.scala b/test/files/pos/t8861.scala new file mode 100644 index 0000000000..816d15700e --- /dev/null +++ b/test/files/pos/t8861.scala @@ -0,0 +1,11 @@ + +trait Test { + type R = PartialFunction[Any, Unit] + + val x: R = { case "" => } + val y: R = { case "" => } + + val z: R = x orElse y + val zz = x orElse y +} + -- cgit v1.2.3 From d7b99c3c33de8d759353c7ffd1d89e2cb38d793b Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 10 Mar 2015 23:54:39 -0700 Subject: SI-9102: Reflect method invoke with mixed args A missing default branch when a method had value class or by-name params caused other args to present as null under reflective invocation. --- .../scala/reflect/runtime/JavaMirrors.scala | 9 ++- test/files/run/t9102.scala | 73 ++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 test/files/run/t9102.scala (limited to 'src') diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 1c751fb93b..7d72833d64 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -428,9 +428,12 @@ private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUnive var i = 0 while (i < args1.length) { val arg = args(i) - if (i >= paramCount) args1(i) = arg // don't transform varargs - else if (isByName(i)) args1(i) = () => arg // don't transform by-name value class params - else if (isDerivedValueClass(i)) args1(i) = paramUnboxers(i).invoke(arg) + args1(i) = ( + if (i >= paramCount) arg // don't transform varargs + else if (isByName(i)) () => arg // don't transform by-name value class params + else if (isDerivedValueClass(i)) paramUnboxers(i).invoke(arg) + else arg + ) i += 1 } jinvoke(args1) diff --git a/test/files/run/t9102.scala b/test/files/run/t9102.scala new file mode 100644 index 0000000000..37ae53b2ab --- /dev/null +++ b/test/files/run/t9102.scala @@ -0,0 +1,73 @@ + +object Test extends App { + import reflect.runtime._, universe._ + + class C { def f(i: Int, j: => Int) = i + j } + + class V(val v: Int) extends AnyVal { def doubled = 2 * v } + class D { def f(i: Int, j: V) = i + j.doubled } + + locally { + val ms = typeOf[C].member(TermName("f")).asMethod + val im = currentMirror reflect (new C) + val mm = im reflectMethod ms + assert(mm(2,3) == 5) + } + locally { + val ms = typeOf[D].member(TermName("f")).asMethod + val im = currentMirror reflect (new D) + val mm = im reflectMethod ms + assert(mm(2, new V(3)) == 8) + } +} + +/* Session tests without special init code should reside in simple script files. + * Also, provide filters such as for `(bound to C@74f7d1d2)`. + +import scala.tools.partest.SessionTest + +object Test extends SessionTest { +//Welcome to Scala version 2.11.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_40). + def session = + s"""|Type in expressions to have them evaluated. + |Type :help for more information. + | + |scala> import reflect.runtime._, universe._ + |import reflect.runtime._ + |import universe._ + | + |scala> class C { def f(i: Int, j: => Int) = i + j } + |defined class C + | + |scala> typeOf[C].member(TermName("f")) + |res0: reflect.runtime.universe.Symbol = method f + | + |scala> .asMethod + |res1: reflect.runtime.universe.MethodSymbol = method f + | + |scala> currentMirror reflect (new C) + |res2: reflect.runtime.universe.InstanceMirror = instance mirror for C@74f7d1d2 + | + |scala> res2 reflectMethod res1 + |res3: reflect.runtime.universe.MethodMirror = method mirror for def f(i: scala.Int,j: => scala.Int): scala.Int (bound to C@74f7d1d2) + | + |scala> res3(2,3) + |res4: Any = 5 + | + |scala> :quit""" +} +*/ + +/* was: +scala> res3(2,3) +java.lang.IllegalArgumentException + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:497) + at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaMethodMirror.jinvokeraw(JavaMirrors.scala:335) + at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaMethodMirror.jinvoke(JavaMirrors.scala:339) + at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaTransformingMethodMirror.apply(JavaMirrors.scala:436) + ... 33 elided +*/ + -- cgit v1.2.3 From 0a8dfd927e6362656b541360cf6920c2c7c69b08 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 11 Mar 2015 10:04:27 -0700 Subject: SI-9102: Improve test Cover the second use case reported on the ML (ctors). Improve formatting per the review. And it really does look a lot better. --- src/reflect/scala/reflect/runtime/JavaMirrors.scala | 8 ++++---- test/files/run/t9102.scala | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 7d72833d64..3b497227e7 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -429,10 +429,10 @@ private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUnive while (i < args1.length) { val arg = args(i) args1(i) = ( - if (i >= paramCount) arg // don't transform varargs - else if (isByName(i)) () => arg // don't transform by-name value class params - else if (isDerivedValueClass(i)) paramUnboxers(i).invoke(arg) - else arg + if (i >= paramCount) arg // don't transform varargs + else if (isByName(i)) () => arg // don't transform by-name value class params + else if (isDerivedValueClass(i)) paramUnboxers(i).invoke(arg) // do get the underlying value + else arg // don't molest anything else ) i += 1 } diff --git a/test/files/run/t9102.scala b/test/files/run/t9102.scala index 37ae53b2ab..c46cf0e4b4 100644 --- a/test/files/run/t9102.scala +++ b/test/files/run/t9102.scala @@ -7,6 +7,8 @@ object Test extends App { class V(val v: Int) extends AnyVal { def doubled = 2 * v } class D { def f(i: Int, j: V) = i + j.doubled } + class E(i: Int, j: V) + locally { val ms = typeOf[C].member(TermName("f")).asMethod val im = currentMirror reflect (new C) @@ -19,6 +21,12 @@ object Test extends App { val mm = im reflectMethod ms assert(mm(2, new V(3)) == 8) } + locally { + val ms = typeOf[E].typeSymbol.asClass.primaryConstructor + val cm = currentMirror reflectClass typeOf[E].typeSymbol.asClass + val mm = cm reflectConstructor ms.asMethod + assert(mm(42, new V(7)).isInstanceOf[E]) + } } /* Session tests without special init code should reside in simple script files. -- cgit v1.2.3 From 42054a1bebcc2155f773787ffda781b497d4178b Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 23 Feb 2015 20:43:21 +0100 Subject: Emit the ScalaInlineInfo attribute under GenASM The goal of this commit is to allow the new inliner (in GenBCode, coming soon) to inline methods generated by the GenASM backend of 2.11.6. The ScalaInlineInfo attribute is added to every classfile generated by GenASM. It contains metadata about the class and its methods that will be used by the new inliner. Storing this metadata to the classfile prevents the need to look up a class symbol for a certain class file name, a process that is known to be brittle due to name mangling. Also, some symbols are not exactly the same when originating in a class being compiled or an unpickled one. For example, method symbols for mixed-in members are only added to classes being compiled. The classfile attribute is relatively small, because all strings it references (class internal names, method names, method descriptors) would exist anyway in the constant pool. It just adds a few references and bits for each method in the classfile. Jar sizes before: 480142 scala-actors.jar 15531408 scala-compiler.jar 5543249 scala-library.jar 4663078 scala-reflect.jar 785953 scalap.jar After: 490491 scala-actors.jar (102.1%) 15865500 scala-compiler.jar (102.1%) 5722504 scala-library.jar (103.2%) 4788370 scala-reflect.jar (102.7%) 805890 scalap.jar (102.5%) --- .../scala/tools/nsc/backend/jvm/AsmUtils.scala | 5 +- .../tools/nsc/backend/jvm/BCodeAsmCommon.scala | 97 +++++++++++++- .../scala/tools/nsc/backend/jvm/BTypes.scala | 48 +++++++ .../scala/tools/nsc/backend/jvm/GenASM.scala | 4 + .../nsc/backend/jvm/opt/InlineInfoAttribute.scala | 147 +++++++++++++++++++++ .../scala/tools/nsc/settings/ScalaSettings.scala | 2 + .../scala/tools/nsc/transform/AddInterfaces.scala | 2 +- test/files/run/t7974.check | 23 +--- test/files/run/t7974/Test.scala | 14 +- .../nsc/backend/jvm/opt/ScalaInlineInfoTest.scala | 85 ++++++++++++ 10 files changed, 396 insertions(+), 31 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala create mode 100644 test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala index 75aa0fc984..d3f09217cd 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala @@ -8,8 +8,9 @@ package scala.tools.nsc.backend.jvm import scala.tools.asm.tree.{InsnList, AbstractInsnNode, ClassNode, MethodNode} import java.io.{StringWriter, PrintWriter} import scala.tools.asm.util.{TraceClassVisitor, TraceMethodVisitor, Textifier} -import scala.tools.asm.ClassReader +import scala.tools.asm.{Attribute, ClassReader} import scala.collection.convert.decorateAsScala._ +import scala.tools.nsc.backend.jvm.opt.InlineInfoAttributePrototype object AsmUtils { @@ -49,7 +50,7 @@ object AsmUtils { def readClass(bytes: Array[Byte]): ClassNode = { val node = new ClassNode() - new ClassReader(bytes).accept(node, 0) + new ClassReader(bytes).accept(node, Array[Attribute](InlineInfoAttributePrototype), 0) node } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala index 27827015c3..2ebf338f5e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -3,9 +3,11 @@ * @author Martin Odersky */ -package scala.tools.nsc.backend.jvm +package scala.tools.nsc +package backend.jvm import scala.tools.nsc.Global +import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo, InternalName} /** * This trait contains code shared between GenBCode and GenASM that depends on types defined in @@ -300,4 +302,97 @@ final class BCodeAsmCommon[G <: Global](val global: G) { } interfaces.map(_.typeSymbol) } + + /** + * This is a hack to work around SI-9111. The completer of `methodSym` may report type errors. We + * cannot change the typer context of the completer at this point and make it silent: the context + * captured when creating the completer in the namer. However, we can temporarily replace + * global.reporter (it's a var) to store errors. + */ + def completeSilentlyAndCheckErroneous(sym: Symbol): Boolean = { + if (sym.hasCompleteInfo) false + else { + val originalReporter = global.reporter + val storeReporter = new reporters.StoreReporter() + global.reporter = storeReporter + try { + sym.info + } finally { + global.reporter = originalReporter + } + sym.isErroneous + } + } + + /** + * Build the [[InlineInfo]] for a class symbol. + */ + def buildInlineInfoFromClassSymbol(classSym: Symbol, classSymToInternalName: Symbol => InternalName, methodSymToDescriptor: Symbol => String): InlineInfo = { + val selfType = { + // The mixin phase uses typeOfThis for the self parameter in implementation class methods. + val selfSym = classSym.typeOfThis.typeSymbol + if (selfSym != classSym) Some(classSymToInternalName(selfSym)) else None + } + + val isEffectivelyFinal = classSym.isEffectivelyFinal + + var warning = Option.empty[String] + + // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some + // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]]. + val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({ + case methodSym => + if (completeSilentlyAndCheckErroneous(methodSym)) { + // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler. + if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes") + warning = Some(s"Failed to get the type of a method of class symbol ${classSym.fullName} due to SI-9111") + None + } else { + val name = methodSym.javaSimpleName.toString // same as in genDefDef + val signature = name + methodSymToDescriptor(methodSym) + + // Some detours are required here because of changing flags (lateDEFERRED, lateMODULE): + // 1. Why the phase travel? Concrete trait methods obtain the lateDEFERRED flag in Mixin. + // This makes isEffectivelyFinalOrNotOverridden false, which would prevent non-final + // but non-overridden methods of sealed traits from being inlined. + // 2. Why the special case for `classSym.isImplClass`? Impl class symbols obtain the + // lateMODULE flag during Mixin. During the phase travel to exitingPickler, the late + // flag is ignored. The members are therefore not isEffectivelyFinal (their owner + // is not a module). Since we know that all impl class members are static, we can + // just take the shortcut. + val effectivelyFinal = classSym.isImplClass || exitingPickler(methodSym.isEffectivelyFinalOrNotOverridden) + + // Identify trait interface methods that have a static implementation in the implementation + // class. Invocations of these methods can be re-wrired directly to the static implementation + // if they are final or the receiver is known. + // + // Using `erasure.needsImplMethod` is not enough: it keeps field accessors, module getters + // and super accessors. When AddInterfaces creates the impl class, these methods are + // initially added to it. + // + // The mixin phase later on filters out most of these members from the impl class (see + // Mixin.isImplementedStatically). However, accessors for concrete lazy vals remain in the + // impl class after mixin. So the filter in mixin is not exactly what we need here (we + // want to identify concrete trait methods, not any accessors). So we check some symbol + // properties manually. + val traitMethodWithStaticImplementation = { + import symtab.Flags._ + classSym.isTrait && !classSym.isImplClass && + erasure.needsImplMethod(methodSym) && + !methodSym.isModule && + !(methodSym hasFlag (ACCESSOR | SUPERACCESSOR)) + } + + val info = MethodInlineInfo( + effectivelyFinal = effectivelyFinal, + traitMethodWithStaticImplementation = traitMethodWithStaticImplementation, + annotatedInline = methodSym.hasAnnotation(ScalaInlineClass), + annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass) + ) + Some((signature, info)) + } + }).toMap + + InlineInfo(selfType, isEffectivelyFinal, methodInlineInfos, warning) + } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index a194fe8fe4..ec26ef9b48 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -936,4 +936,52 @@ object BTypes { * But that would create overhead in a Collection[InternalName]. */ type InternalName = String + + /** + * Metadata about a ClassBType, used by the inliner. + * + * More information may be added in the future to enable more elaborate inlinine heuristics. + * + * @param traitImplClassSelfType `Some(tp)` if this InlineInfo describes a trait, and the `self` + * parameter type of the methods in the implementation class is not + * the trait itself. Example: + * trait T { self: U => def f = 1 } + * Generates something like: + * class T$class { static def f(self: U) = 1 } + * + * In order to inline a trat method call, the INVOKEINTERFACE is + * rewritten to an INVOKESTATIC of the impl class, so we need the + * self type (U) to get the right signature. + * + * `None` if the self type is the interface type, or if this + * InlineInfo does not describe a trait. + * + * @param isEffectivelyFinal True if the class cannot have subclasses: final classes, module + * classes, trait impl classes. + * + * @param methodInfos The [[MethodInlineInfo]]s for the methods declared in this class. + * The map is indexed by the string s"$name$descriptor" (to + * disambiguate overloads). + */ + final case class InlineInfo(traitImplClassSelfType: Option[InternalName], + isEffectivelyFinal: Boolean, + methodInfos: Map[String, MethodInlineInfo], + warning: Option[String]) + + val EmptyInlineInfo = InlineInfo(None, false, Map.empty, None) + + /** + * Metadata about a method, used by the inliner. + * + * @param effectivelyFinal True if the method cannot be overridden (in Scala) + * @param traitMethodWithStaticImplementation True if the method is an interface method method of + * a trait method and has a static counterpart in the + * implementation class. + * @param annotatedInline True if the method is annotated `@inline` + * @param annotatedNoInline True if the method is annotated `@noinline` + */ + final case class MethodInlineInfo(effectivelyFinal: Boolean, + traitMethodWithStaticImplementation: Boolean, + annotatedInline: Boolean, + annotatedNoInline: Boolean) } \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 707336e5de..62268e1907 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -9,6 +9,7 @@ package backend.jvm import scala.collection.{ mutable, immutable } import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer } +import scala.tools.nsc.backend.jvm.opt.InlineInfoAttribute import scala.tools.nsc.symtab._ import scala.tools.asm import asm.Label @@ -1292,6 +1293,9 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => jclass.visitAttribute(if(ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) emitAnnotations(jclass, c.symbol.annotations ++ ssa) + if (!settings.YskipInlineInfoAttribute.value) + jclass.visitAttribute(InlineInfoAttribute(buildInlineInfoFromClassSymbol(c.symbol, javaName, javaType(_).getDescriptor))) + // typestate: entering mode with valid call sequences: // ( visitInnerClass | visitField | visitMethod )* visitEnd diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala new file mode 100644 index 0000000000..4812f2290f --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala @@ -0,0 +1,147 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm +package opt + +import scala.tools.asm._ +import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo} + +/** + * This attribute stores the InlineInfo for a ClassBType as an independent classfile attribute. + * The compiler does so for every class being compiled. + * + * The reason is that a precise InlineInfo can only be obtained if the symbol for a class is available. + * For example, we need to know if a method is final in Scala's terms, or if it has the @inline annotation. + * Looking up a class symbol for a given class filename is brittle (name-mangling). + * + * The attribute is also helpful for inlining mixin methods. The mixin phase only adds mixin method + * symbols to classes that are being compiled. For all other class symbols, there are no mixin members. + * However, the inliner requires an InlineInfo for inlining mixin members. That problem is solved by + * reading the InlineInfo from this attribute. + * + * In principle we could encode the InlineInfo into a Java annotation (instead of a classfile attribute). + * However, an attribute allows us to save many bits. In particular, note that the strings in an + * InlineInfo are serialized as references to constants in the constant pool, and those strings + * (traitImplClassSelfType, method names, method signatures) would exist in there anyway. So the + * ScalaInlineAttribute remains relatively compact. + */ +case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineInfoAttribute.attributeName) { + /** + * Not sure what this method is good for, it is not invoked anywhere in the ASM framework. However, + * the example in the ASM manual also overrides it to `false` for custom attributes, so it might be + * a good idea. + */ + override def isUnknown: Boolean = false + + /** + * Serialize the `inlineInfo` into a byte array. Strings are added to the constant pool and serialized + * as references. + */ + override def write(cw: ClassWriter, code: Array[Byte], len: Int, maxStack: Int, maxLocals: Int): ByteVector = { + val result = new ByteVector() + + result.putByte(InlineInfoAttribute.VERSION) + + var hasSelfIsFinal = 0 + if (inlineInfo.isEffectivelyFinal) hasSelfIsFinal |= 1 + if (inlineInfo.traitImplClassSelfType.isDefined) hasSelfIsFinal |= 2 + result.putByte(hasSelfIsFinal) + + for (selfInternalName <- inlineInfo.traitImplClassSelfType) { + result.putShort(cw.newUTF8(selfInternalName)) + } + + // The method count fits in a short (the methods_count in a classfile is also a short) + result.putShort(inlineInfo.methodInfos.size) + + // Sort the methodInfos for stability of classfiles + for ((nameAndType, info) <- inlineInfo.methodInfos.toList.sortBy(_._1)) { + val (name, desc) = nameAndType.span(_ != '(') + // Name and desc are added separately because a NameAndType entry also stores them separately. + // This makes sure that we use the existing constant pool entries for the method. + result.putShort(cw.newUTF8(name)) + result.putShort(cw.newUTF8(desc)) + + var inlineInfo = 0 + if (info.effectivelyFinal) inlineInfo |= 1 + if (info.traitMethodWithStaticImplementation) inlineInfo |= 2 + if (info.annotatedInline) inlineInfo |= 4 + if (info.annotatedNoInline) inlineInfo |= 8 + result.putByte(inlineInfo) + } + + result + } + + /** + * De-serialize the attribute into an InlineInfo. The attribute starts at cr.b(off), but we don't + * need to access that array directly, we can use the `read` methods provided by the ClassReader. + * + * `buf` is a pre-allocated character array that is guaranteed to be long enough to hold any + * string of the constant pool. So we can use it to invoke `cr.readUTF8`. + */ + override def read(cr: ClassReader, off: Int, len: Int, buf: Array[Char], codeOff: Int, labels: Array[Label]): InlineInfoAttribute = { + var next = off + + def nextByte() = { val r = cr.readByte(next) ; next += 1; r } + def nextUTF8() = { val r = cr.readUTF8(next, buf); next += 2; r } + def nextShort() = { val r = cr.readShort(next) ; next += 2; r } + + val version = nextByte() + if (version == 1) { + val hasSelfIsFinal = nextByte() + val isFinal = (hasSelfIsFinal & 1) != 0 + val hasSelf = (hasSelfIsFinal & 2) != 0 + + val self = if (hasSelf) { + val selfName = nextUTF8() + Some(selfName) + } else { + None + } + + val numEntries = nextShort() + val infos = (0 until numEntries).map(_ => { + val name = nextUTF8() + val desc = nextUTF8() + + val inlineInfo = nextByte() + val isFinal = (inlineInfo & 1) != 0 + val traitMethodWithStaticImplementation = (inlineInfo & 2) != 0 + val isInline = (inlineInfo & 4) != 0 + val isNoInline = (inlineInfo & 8) != 0 + (name + desc, MethodInlineInfo(isFinal, traitMethodWithStaticImplementation, isInline, isNoInline)) + }).toMap + + InlineInfoAttribute(InlineInfo(self, isFinal, infos, None)) + } else { + val msg = s"Cannot read ScalaInlineInfo version $version in classfile ${cr.getClassName}. Use a more recent compiler." + InlineInfoAttribute(BTypes.EmptyInlineInfo.copy(warning = Some(msg))) + } + } +} + +object InlineInfoAttribute { + /** + * [u1] version + * [u1] isEffectivelyFinal (<< 0), hasTraitImplClassSelfType (<< 1) + * [u2]? traitImplClassSelfType (reference) + * [u2] numMethodEntries + * [u2] name (reference) + * [u2] descriptor (reference) + * [u1] isFinal (<< 0), traitMethodWithStaticImplementation (<< 1), hasInlineAnnotation (<< 2), hasNoInlineAnnotation (<< 3) + */ + final val VERSION: Byte = 1 + + final val attributeName = "ScalaInlineInfo" +} + +/** + * In order to instruct the ASM framework to de-serialize the ScalaInlineInfo attribute, we need + * to pass a prototype instance when running the class reader. + */ +object InlineInfoAttributePrototype extends InlineInfoAttribute(InlineInfo(null, false, null, null)) diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index a5b722612d..d7f4cca615 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -213,6 +213,8 @@ trait ScalaSettings extends AbsScalaSettings // the current standard is "inline" but we are moving towards "method" val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "inline") + val YskipInlineInfoAttribute = BooleanSetting("-Yskip-inline-info-attribute", "Do not add the ScalaInlineInfo attribute to classfiles generated by -Ybackend:GenASM") + object YoptChoices extends MultiChoiceEnumeration { val unreachableCode = Choice("unreachable-code", "Eliminate unreachable code, exception handlers protecting no instructions, debug information of eliminated variables.") val simplifyJumps = Choice("simplify-jumps", "Simplify branching instructions, eliminate unnecessary ones.") diff --git a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala index f786ffb8f3..3591372bbe 100644 --- a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala +++ b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala @@ -55,7 +55,7 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure => ) /** Does symbol need an implementation method? */ - private def needsImplMethod(sym: Symbol) = ( + def needsImplMethod(sym: Symbol) = ( sym.isMethod && isInterfaceMember(sym) && (!sym.hasFlag(DEFERRED | SUPERACCESSOR) || (sym hasFlag lateDEFERRED)) diff --git a/test/files/run/t7974.check b/test/files/run/t7974.check index d8152d3286..4eae5eb152 100644 --- a/test/files/run/t7974.check +++ b/test/files/run/t7974.check @@ -1,19 +1,3 @@ -public class Symbols { - - - - - // access flags 0x12 - private final Lscala/Symbol; someSymbol3 - - // access flags 0xA - private static Lscala/Symbol; symbol$1 - - // access flags 0xA - private static Lscala/Symbol; symbol$2 - - // access flags 0xA - private static Lscala/Symbol; symbol$3 // access flags 0x9 public static ()V @@ -33,6 +17,7 @@ public class Symbols { MAXSTACK = 2 MAXLOCALS = 0 + // access flags 0x1 public someSymbol1()Lscala/Symbol; GETSTATIC Symbols.symbol$1 : Lscala/Symbol; @@ -40,6 +25,7 @@ public class Symbols { MAXSTACK = 1 MAXLOCALS = 1 + // access flags 0x1 public someSymbol2()Lscala/Symbol; GETSTATIC Symbols.symbol$2 : Lscala/Symbol; @@ -47,6 +33,7 @@ public class Symbols { MAXSTACK = 1 MAXLOCALS = 1 + // access flags 0x1 public sameSymbol1()Lscala/Symbol; GETSTATIC Symbols.symbol$1 : Lscala/Symbol; @@ -54,6 +41,7 @@ public class Symbols { MAXSTACK = 1 MAXLOCALS = 1 + // access flags 0x1 public someSymbol3()Lscala/Symbol; ALOAD 0 @@ -62,6 +50,7 @@ public class Symbols { MAXSTACK = 1 MAXLOCALS = 1 + // access flags 0x1 public ()V ALOAD 0 @@ -72,4 +61,4 @@ public class Symbols { RETURN MAXSTACK = 2 MAXLOCALS = 1 -} + diff --git a/test/files/run/t7974/Test.scala b/test/files/run/t7974/Test.scala index 29d2b9cb64..296ec32ee2 100644 --- a/test/files/run/t7974/Test.scala +++ b/test/files/run/t7974/Test.scala @@ -1,20 +1,14 @@ -import java.io.PrintWriter; +import java.io.PrintWriter import scala.tools.partest.BytecodeTest +import scala.tools.nsc.backend.jvm.AsmUtils import scala.tools.asm.util._ import scala.tools.nsc.util.stringFromWriter +import scala.collection.convert.decorateAsScala._ object Test extends BytecodeTest { def show { val classNode = loadClassNode("Symbols", skipDebugInfo = true) - val textifier = new Textifier - classNode.accept(new TraceClassVisitor(null, textifier, null)) - - val classString = stringFromWriter(w => textifier.print(w)) - val result = - classString.split('\n') - .dropWhile(elem => elem != "public class Symbols {") - .filterNot(elem => elem.startsWith(" @Lscala/reflect/ScalaSignature") || elem.startsWith(" ATTRIBUTE ScalaSig")) - result foreach println + classNode.methods.asScala.foreach(m => println(AsmUtils.textify(m))) } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala new file mode 100644 index 0000000000..f8e887426b --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala @@ -0,0 +1,85 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.tools.asm.Opcodes._ +import org.junit.Assert._ + +import CodeGenTools._ +import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo} +import scala.tools.partest.ASMConverters +import ASMConverters._ +import scala.collection.convert.decorateAsScala._ + +object ScalaInlineInfoTest { + var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") + def clear(): Unit = { compiler = null } +} + +@RunWith(classOf[JUnit4]) +class ScalaInlineInfoTest { + val compiler = newCompiler() + + @Test + def traitMembersInlineInfo(): Unit = { + val code = + """trait T { + | def f1 = 1 // concrete method + | private def f2 = 1 // implOnly method (does not end up in the interface) + | def f3 = { + | def nest = 0 // nested method (does not end up in the interface) + | nest + | } + | + | @inline + | def f4 = super.toString // super accessor + | + | object O // module accessor (method is generated) + | def f5 = { + | object L { val x = 0 } // nested module (just flattened out) + | L.x + | } + | + | @noinline + | def f6: Int // abstract method (not in impl class) + | + | // fields + | + | val x1 = 0 + | var y2 = 0 + | var x3: Int + | lazy val x4 = 0 + | + | final val x5 = 0 + |} + """.stripMargin + + val cs @ List(t, tl, to, tCls) = compileClasses(compiler)(code) + val List(info) = t.attrs.asScala.collect({ case a: InlineInfoAttribute => a.inlineInfo }).toList + val expect = InlineInfo( + None, // self type + false, // final class + Map( + ("O()LT$O$;", MethodInlineInfo(true, false,false,false)), + ("T$$super$toString()Ljava/lang/String;",MethodInlineInfo(false,false,false,false)), + ("T$_setter_$x1_$eq(I)V", MethodInlineInfo(false,false,false,false)), + ("f1()I", MethodInlineInfo(false,true, false,false)), + ("f3()I", MethodInlineInfo(false,true, false,false)), + ("f4()Ljava/lang/String;", MethodInlineInfo(false,true, true, false)), + ("f5()I", MethodInlineInfo(false,true, false,false)), + ("f6()I", MethodInlineInfo(false,false,false,true )), + ("x1()I", MethodInlineInfo(false,false,false,false)), + ("x3()I", MethodInlineInfo(false,false,false,false)), + ("x3_$eq(I)V", MethodInlineInfo(false,false,false,false)), + ("x4()I", MethodInlineInfo(false,false,false,false)), + ("x5()I", MethodInlineInfo(true, false,false,false)), + ("y2()I", MethodInlineInfo(false,false,false,false)), + ("y2_$eq(I)V", MethodInlineInfo(false,false,false,false))), + None // warning + ) + assert(info == expect, info) + } +} -- cgit v1.2.3 From ff67161f946c515a3b0a719ce80531fa14a06a8f Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Sat, 17 Jan 2015 15:13:01 +0100 Subject: Find instructions that would cause an IllegalAccessError when inlined Some instructions would cause an IllegalAccessError if they are inlined into a different class. Based on Miguel's implementation in 6efc0528c6. --- src/asm/scala/tools/asm/tree/MethodInsnNode.java | 1 + .../scala/tools/nsc/backend/jvm/BTypes.scala | 43 ++++- .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 4 +- .../nsc/backend/jvm/opt/ByteCodeRepository.scala | 16 +- .../scala/tools/nsc/backend/jvm/opt/Inliner.scala | 118 +++++++++++++ .../scala/tools/nsc/backend/jvm/CodeGenTools.scala | 12 +- .../backend/jvm/opt/InlinerIllegalAccessTest.scala | 190 +++++++++++++++++++++ 7 files changed, 375 insertions(+), 9 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala create mode 100644 test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala (limited to 'src') diff --git a/src/asm/scala/tools/asm/tree/MethodInsnNode.java b/src/asm/scala/tools/asm/tree/MethodInsnNode.java index 1ec46d473d..30c7854646 100644 --- a/src/asm/scala/tools/asm/tree/MethodInsnNode.java +++ b/src/asm/scala/tools/asm/tree/MethodInsnNode.java @@ -45,6 +45,7 @@ public class MethodInsnNode extends AbstractInsnNode { /** * The internal name of the method's owner class (see * {@link scala.tools.asm.Type#getInternalName() getInternalName}). + * For methods of arrays, e.g., clone(), the array type descriptor. */ public String owner; diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index ec26ef9b48..ff30631c10 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -6,10 +6,11 @@ package scala.tools.nsc package backend.jvm +import scala.annotation.switch import scala.tools.asm import asm.Opcodes import scala.tools.asm.tree.{InnerClassNode, ClassNode} -import opt.ByteCodeRepository +import opt.{ByteCodeRepository, Inliner} import scala.collection.convert.decorateAsScala._ /** @@ -34,6 +35,8 @@ abstract class BTypes { */ val byteCodeRepository: ByteCodeRepository + val inliner: Inliner[this.type] + // 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 @@ -50,6 +53,31 @@ abstract class BTypes { */ val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType]) + /** + * Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType + * is constructed by parsing the corresponding classfile. + * + * Some JVM operations use either a full descriptor or only an internal name. Example: + * ANEWARRAY java/lang/String // a new array of strings (internal name for the String class) + * ANEWARRAY [Ljava/lang/String; // a new array of array of string (full descriptor for the String class) + * + * This method supports both descriptors and internal names. + */ + def bTypeForDescriptorOrInternalNameFromClassfile(desc: String): BType = (desc(0): @switch) match { + case 'V' => UNIT + case 'Z' => BOOL + case 'C' => CHAR + case 'B' => BYTE + case 'S' => SHORT + case 'I' => INT + case 'F' => FLOAT + case 'J' => LONG + case 'D' => DOUBLE + case '[' => ArrayBType(bTypeForDescriptorOrInternalNameFromClassfile(desc.substring(1))) + case 'L' if desc.last == ';' => classBTypeFromParsedClassfile(desc.substring(1, desc.length - 1)) + case _ => classBTypeFromParsedClassfile(desc) + } + /** * Parse the classfile for `internalName` and construct the [[ClassBType]]. */ @@ -725,6 +753,19 @@ abstract class BTypes { case Some(sc) => sc :: sc.superClassesTransitive } + /** + * The prefix of the internal name until the last '/', or the empty string. + */ + def packageInternalName: String = { + val name = internalName + name.lastIndexOf('/') match { + case -1 => "" + case i => name.substring(0, i) + } + } + + def isPublic = (info.flags & asm.Opcodes.ACC_PUBLIC) != 0 + def isNestedClass = info.nestedInfo.isDefined def enclosingNestedClassesChain: List[ClassBType] = diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 2af665d31c..117b377622 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -7,9 +7,9 @@ package scala.tools.nsc package backend.jvm import scala.tools.asm -import opt.ByteCodeRepository import scala.tools.asm.tree.ClassNode import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository.Source +import scala.tools.nsc.backend.jvm.opt.{Inliner, ByteCodeRepository} import BTypes.InternalName /** @@ -38,6 +38,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val byteCodeRepository = new ByteCodeRepository(global.classPath, recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, (ClassNode, Source)])) + val inliner: Inliner[this.type] = new Inliner(this) + final def initializeCoreBTypes(): Unit = { coreBTypes.setBTypes(new CoreBTypes[this.type](this)) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala index 7b424d2107..9e56f25888 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala @@ -59,12 +59,16 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class * * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring class. */ - def methodNode(classInternalName: InternalName, name: String, descriptor: String): Option[(MethodNode, InternalName)] = { - val c = classNode(classInternalName) - c.methods.asScala.find(m => m.name == name && m.desc == descriptor).map((_, classInternalName)) orElse { - val parents = Option(c.superName) ++ c.interfaces.asScala - // `view` to stop at the first result - parents.view.flatMap(methodNode(_, name, descriptor)).headOption + def methodNode(ownerInternalNameOrArrayDescriptor: String, name: String, descriptor: String): Option[(MethodNode, InternalName)] = { + // In a MethodInsnNode, the `owner` field may be an array descriptor, for exmple when invoking `clone`. + if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[') None + else { + val c = classNode(ownerInternalNameOrArrayDescriptor) + c.methods.asScala.find(m => m.name == name && m.desc == descriptor).map((_, ownerInternalNameOrArrayDescriptor)) orElse { + val parents = Option(c.superName) ++ c.interfaces.asScala + // `view` to stop at the first result + parents.view.flatMap(methodNode(_, name, descriptor)).headOption + } } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala new file mode 100644 index 0000000000..6e5e03f730 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -0,0 +1,118 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm +package opt + +import scala.tools.asm +import asm.Opcodes +import asm.tree._ +import scala.collection.convert.decorateAsScala._ +import OptimizerReporting._ + +class Inliner[BT <: BTypes](val btypes: BT) { + import btypes._ + import btypes.byteCodeRepository + + def findIllegalAccess(instructions: InsnList, destinationClass: ClassBType): Option[AbstractInsnNode] = { + + /** + * Check if a type is accessible to some class, as defined in JVMS 5.4.4. + * (A1) C is public + * (A2) C and D are members of the same run-time package + */ + def classIsAccessible(accessed: BType, from: ClassBType = destinationClass): Boolean = (accessed: @unchecked) match { + // TODO: A2 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok? + case c: ClassBType => c.isPublic || c.packageInternalName == from.packageInternalName + case a: ArrayBType => classIsAccessible(a.elementType, from) + case _: PrimitiveBType => true + } + + /** + * Check if a member reference is accessible from the [[destinationClass]], as defined in the + * JVMS 5.4.4. Note that the class name in a field / method reference is not necessarily the + * class in which the member is declared: + * + * class A { def f = 0 }; class B extends A { f } + * + * The INVOKEVIRTUAL instruction uses a method reference "B.f ()I". Therefore this method has + * two parameters: + * + * @param memberDeclClass The class in which the member is declared (A) + * @param memberRefClass The class used in the member reference (B) + * + * JVMS 5.4.4 summary: A field or method R is accessible to a class D (destinationClass) iff + * (B1) R is public + * (B2) R is protected, declared in C (memberDeclClass) and D is a subclass of C. + * If R is not static, R must contain a symbolic reference to a class T (memberRefClass), + * such that T is either a subclass of D, a superclass of D, or D itself. + * (B3) R is either protected or has default access and declared by a class in the same + * run-time package as D. + * (B4) R is private and is declared in D. + */ + def memberIsAccessible(memberFlags: Int, memberDeclClass: ClassBType, memberRefClass: ClassBType): Boolean = { + // TODO: B3 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok? + def samePackageAsDestination = memberDeclClass.packageInternalName == destinationClass.packageInternalName + + val key = (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE) & memberFlags + key match { + case Opcodes.ACC_PUBLIC => // B1 + true + + case Opcodes.ACC_PROTECTED => // B2 + val condB2 = destinationClass.isSubtypeOf(memberDeclClass) && { + val isStatic = (Opcodes.ACC_STATIC & memberFlags) != 0 + isStatic || memberRefClass.isSubtypeOf(destinationClass) || destinationClass.isSubtypeOf(memberRefClass) + } + condB2 || samePackageAsDestination // B3 (protected) + + case 0 => // B3 (default access) + samePackageAsDestination + + case Opcodes.ACC_PRIVATE => // B4 + memberDeclClass == destinationClass + } + } + + def isLegal(instruction: AbstractInsnNode): Boolean = instruction match { + case ti: TypeInsnNode => + // NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. For these instructions, the reference + // "must be a symbolic reference to a class, array, or interface type" (JVMS 6), so + // it can be an internal name, or a full array descriptor. + classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ti.desc)) + + case ma: MultiANewArrayInsnNode => + // "a symbolic reference to a class, array, or interface type" + classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ma.desc)) + + case fi: FieldInsnNode => + val fieldRefClass = classBTypeFromParsedClassfile(fi.owner) + val (fieldNode, fieldDeclClass) = byteCodeRepository.fieldNode(fieldRefClass.internalName, fi.name, fi.desc).get + memberIsAccessible(fieldNode.access, classBTypeFromParsedClassfile(fieldDeclClass), fieldRefClass) + + case mi: MethodInsnNode => + if (mi.owner.charAt(0) == '[') true // array methods are accessible + else { + val methodRefClass = classBTypeFromParsedClassfile(mi.owner) + val (methodNode, methodDeclClass) = byteCodeRepository.methodNode(methodRefClass.internalName, mi.name, mi.desc).get + memberIsAccessible(methodNode.access, classBTypeFromParsedClassfile(methodDeclClass), methodRefClass) + } + + case ivd: InvokeDynamicInsnNode => + // TODO @lry check necessary conditions to inline an indy, instead of giving up + false + + case ci: LdcInsnNode => ci.cst match { + case t: asm.Type => classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName)) + case _ => true + } + + case _ => true + } + + instructions.iterator.asScala.find(!isLegal(_)) + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala index c1c5a71b83..e94f33db3d 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala @@ -2,12 +2,14 @@ package scala.tools.nsc.backend.jvm import org.junit.Assert._ +import scala.collection.mutable.ListBuffer import scala.reflect.internal.util.BatchSourceFile import scala.reflect.io.VirtualDirectory import scala.tools.asm.Opcodes import scala.tools.asm.tree.{AbstractInsnNode, LabelNode, 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.settings.{MutableSettings, ScalaSettings} import scala.tools.nsc.{Settings, Global} import scala.tools.partest.ASMConverters @@ -54,7 +56,15 @@ object CodeGenTools { val run = new compiler.Run() run.compileSources(List(new BatchSourceFile("unitTestSource.scala", code))) val outDir = compiler.settings.outputDirs.getSingleOutput.get - (for (f <- outDir.iterator if !f.isDirectory) yield (f.name, f.toByteArray)).toList + def files(dir: AbstractFile): List[(String, Array[Byte])] = { + val res = ListBuffer.empty[(String, Array[Byte])] + for (f <- dir.iterator) { + if (!f.isDirectory) res += ((f.name, f.toByteArray)) + else if (f.name != "." && f.name != "..") res ++= files(f) + } + res.toList + } + files(outDir) } def compileClasses(compiler: Global)(code: String): List[ClassNode] = { diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala new file mode 100644 index 0000000000..406b8da570 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala @@ -0,0 +1,190 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.tools.asm.Opcodes._ +import org.junit.Assert._ + +import scala.tools.asm.tree._ +import scala.tools.testing.AssertUtil._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import AsmUtils._ + +import scala.collection.convert.decorateAsScala._ + +@RunWith(classOf[JUnit4]) +class InlinerIllegalAccessTest { + val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") + import compiler.genBCode.bTypes._ + + def addToRepo(cls: List[ClassNode]): Unit = for (c <- cls) byteCodeRepository.classes(c.name) = (c, ByteCodeRepository.Classfile) + def assertEmpty(ins: Option[AbstractInsnNode]) = for (i <- ins) throw new AssertionError(textify(i)) + + @Test + def typeAccessible(): Unit = { + val code = + """package a { + | private class C { // the Scala compiler makes all classes public + | def f1 = new C // NEW a/C + | def f2 = new Array[C](0) // ANEWARRAY a/C + | def f3 = new Array[Array[C]](0) // ANEWARRAY [La/C; + | } + | class D + |} + |package b { + | class E + |} + """.stripMargin + + val allClasses = compileClasses(compiler)(code) + val List(cClass, dClass, eClass) = allClasses + assert(cClass.name == "a/C" && dClass.name == "a/D" && eClass.name == "b/E", s"${cClass.name}, ${dClass.name}, ${eClass.name}") + addToRepo(allClasses) // they are not on the compiler's classpath, so we add them manually to the code repo + + val methods = cClass.methods.asScala.filter(_.name(0) == 'f').toList + + def check(classNode: ClassNode, test: Option[AbstractInsnNode] => Unit) = { + for (m <- methods) + test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(classNode.name))) + } + + check(cClass, assertEmpty) + check(dClass, assertEmpty) + check(eClass, assertEmpty) // C is public, so accessible in E + + byteCodeRepository.classes.clear() + classBTypeFromInternalName.clear() + + cClass.access &= ~ACC_PUBLIC // ftw + addToRepo(allClasses) + + // private classes can be accessed from the same package + check(cClass, assertEmpty) + check(dClass, assertEmpty) // accessing a private class in the same package is OK + check(eClass, { + case Some(ti: TypeInsnNode) if Set("a/C", "[La/C;")(ti.desc) => () + // MatchError otherwise + }) + } + + @Test + def memberAccessible(): Unit = { + val code = + """package a { + | class C { + | /*public*/ def a = 0 + | /*default*/ def b = 0 + | protected def c = 0 + | private def d = 0 + | + | /*public static*/ def e = 0 + | /*default static*/ def f = 0 + | protected /*static*/ def g = 0 + | private /*static*/ def h = 0 + | + | def raC = a + | def rbC = b + | def rcC = c + | def rdC = d + | def reC = e + | def rfC = f + | def rgC = g + | def rhC = h + | } + | + | class D extends C { + | def rbD = b // 1: default access b, accessed in D, declared in C. can be inlined into any class in the same package as C. + | def rcD = c // 2: protected c, accessed in D. can be inlined into C, D or E, but not into F (F and D are unrelated). + | + | def rfD = f // 1 + | def rgD = g // 2 + | } + | class E extends D + | + | class F extends C + | + | class G + |} + | + |package b { + | class H extends a.C + | class I + |} + """.stripMargin + + val allClasses = compileClasses(compiler)(code) + val List(cCl, dCl, eCl, fCl, gCl, hCl, iCl) = allClasses + addToRepo(allClasses) + + // set flags that Scala scala doesn't (default access, static) - a hacky way to test all access modes. + val names = ('a' to 'h').map(_.toString).toSet + val List(a, b, c, d, e, f, g, h) = cCl.methods.asScala.toList.filter(m => names(m.name)) + + def checkAccess(a: MethodNode, expected: Int): Unit = { + assert((a.access & (ACC_STATIC | ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE)) == expected, s"${a.name}, ${a.access}") + } + + checkAccess(a, ACC_PUBLIC) + b.access &= ~ACC_PUBLIC; checkAccess(b, 0) // make it default access + c.access &= ~ACC_PUBLIC; c.access |= ACC_PROTECTED; checkAccess(c, ACC_PROTECTED) // make it protected - scalac actually never emits PROTECTED in bytecode, see javaFlags in BTypesFromSymbols + checkAccess(d, ACC_PRIVATE) + + e.access |= ACC_STATIC; checkAccess(e, ACC_STATIC | ACC_PUBLIC) + f.access &= ~ACC_PUBLIC; f.access |= ACC_STATIC; checkAccess(f, ACC_STATIC) + g.access &= ~ACC_PUBLIC; g.access |= (ACC_STATIC | ACC_PROTECTED); checkAccess(g, ACC_STATIC | ACC_PROTECTED) + h.access |= ACC_STATIC; checkAccess(h, ACC_STATIC | ACC_PRIVATE) + + val List(raC, rbC, rcC, rdC, reC, rfC, rgC, rhC) = cCl.methods.asScala.toList.filter(_.name(0) == 'r').sortBy(_.name) + + val List(rbD, rcD, rfD, rgD) = dCl.methods.asScala.toList.filter(_.name(0) == 'r').sortBy(_.name) + + def check(method: MethodNode, dest: ClassNode, test: Option[AbstractInsnNode] => Unit): Unit = { + test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(dest.name))) + } + + val cOrDOwner = (_: Option[AbstractInsnNode] @unchecked) match { + case Some(mi: MethodInsnNode) if Set("a/C", "a/D")(mi.owner) => () + // MatchError otherwise + } + + // PUBLIC + + // public methods allowed everywhere + for (m <- Set(raC, reC); c <- allClasses) check(m, c, assertEmpty) + + // DEFAULT ACCESS + + // default access OK in same package + for (m <- Set(rbC, rfC, rbD, rfD); c <- allClasses) { + if (c.name startsWith "a/") check(m, c, assertEmpty) + else check(m, c, cOrDOwner) + } + + // PROTECTED + + // protected accessed in same class, or protected static accessed in subclass(rgD). + // can be inlined to subclasses, and classes in the same package (gCl) + for (m <- Set(rcC, rgC, rgD); c <- Set(cCl, dCl, eCl, fCl, gCl, hCl)) check(m, c, assertEmpty) + + // protected in non-subclass and different package + for (m <- Set(rcC, rgC)) check(m, iCl, cOrDOwner) + + // non-static protected accessed in subclass (rcD). can be inlined to related class, or classes in the same package + for (c <- Set(cCl, dCl, eCl, fCl, gCl)) check(rcD, c, assertEmpty) + + // rcD cannot be inlined into non-related classes, if the declaration and destination are not in the same package + for (c <- Set(hCl, iCl)) check(rcD, c, cOrDOwner) + + // PRIVATE + + // privated method accesses can only be inlined in the same class + for (m <- Set(rdC, rhC)) check(m, cCl, assertEmpty) + for (m <- Set(rdC, rhC); c <- allClasses.tail) check(m, c, cOrDOwner) + } +} -- cgit v1.2.3 From b34a452c0683d260ffb1644575a0e970559cae87 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Sat, 17 Jan 2015 15:29:49 +0100 Subject: Tools to perform inlining. The method Inliner.inline clones the bytecode of a method and copies the new instructions to the callsite with the necessary modifications. See comments in the code. More tests are added in a later commit which integrates the inliner into the backend - tests are easier to write after that. --- .../tools/nsc/backend/jvm/BCodeBodyBuilder.scala | 3 +- .../scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 1 + .../tools/nsc/backend/jvm/BCodeIdiomatic.scala | 4 +- .../tools/nsc/backend/jvm/BCodeSkelBuilder.scala | 1 + .../scala/tools/nsc/backend/jvm/BTypes.scala | 5 +- .../scala/tools/nsc/backend/jvm/GenBCode.scala | 3 + .../nsc/backend/jvm/opt/ByteCodeRepository.scala | 11 +- .../tools/nsc/backend/jvm/opt/BytecodeUtils.scala | 125 ++++++++++++- .../scala/tools/nsc/backend/jvm/opt/Inliner.scala | 203 ++++++++++++++++++++- .../scala/tools/nsc/backend/jvm/opt/LocalOpt.scala | 27 +-- .../nsc/backend/jvm/opt/OptimizerReporting.scala | 10 +- .../tools/nsc/backend/jvm/opt/InlinerTest.scala | 193 ++++++++++++++++++++ 12 files changed, 534 insertions(+), 52 deletions(-) create mode 100644 test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 062daa4eac..1b3f124dd8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -12,6 +12,7 @@ package jvm import scala.annotation.switch import scala.tools.asm +import GenBCode._ /* * @@ -613,7 +614,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { */ for (i <- args.length until dims) elemKind = ArrayBType(elemKind) } - (argsSize : @switch) match { + argsSize match { case 1 => bc newarray elemKind case _ => val descr = ('[' * argsSize) + elemKind.descriptor // denotes the same as: arrayN(elemKind, argsSize).descriptor diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index ccee230191..e366bbabb8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -10,6 +10,7 @@ package backend.jvm import scala.tools.asm import scala.collection.mutable import scala.tools.nsc.io.AbstractFile +import GenBCode._ /* * Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index c3db28151b..c743ebd16f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -10,6 +10,7 @@ package backend.jvm import scala.tools.asm import scala.annotation.switch import scala.collection.mutable +import GenBCode._ /* * A high-level facade to the ASM API for bytecode generation. @@ -42,9 +43,6 @@ abstract class BCodeIdiomatic extends SubComponent { val StringBuilderClassName = "scala/collection/mutable/StringBuilder" - val CLASS_CONSTRUCTOR_NAME = "" - val INSTANCE_CONSTRUCTOR_NAME = "" - val EMPTY_STRING_ARRAY = Array.empty[String] val EMPTY_INT_ARRAY = Array.empty[Int] val EMPTY_LABEL_ARRAY = Array.empty[asm.Label] diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 142c901c21..48df4e1121 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -16,6 +16,7 @@ import scala.annotation.switch import scala.tools.asm import scala.tools.asm.util.{TraceMethodVisitor, ASMifier} import java.io.PrintWriter +import GenBCode._ /* * diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index ff30631c10..fe4c4794a9 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -11,6 +11,7 @@ import scala.tools.asm import asm.Opcodes import scala.tools.asm.tree.{InnerClassNode, ClassNode} import opt.{ByteCodeRepository, Inliner} +import OptimizerReporting._ import scala.collection.convert.decorateAsScala._ /** @@ -273,7 +274,7 @@ abstract class BTypes { ObjectReference case _: MethodBType => - throw new AssertionError(s"unexpected method type when computing maxType: $this") + assertionError(s"unexpected method type when computing maxType: $this") } /** @@ -364,7 +365,7 @@ abstract class BTypes { */ final def maxValueType(other: BType): BType = { - def uncomparable: Nothing = throw new AssertionError(s"Cannot compute maxValueType: $this, $other") + def uncomparable: Nothing = assertionError(s"Cannot compute maxValueType: $this, $other") if (!other.isPrimitive && !other.isNothingType) uncomparable diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index d5e95c47cf..9b3bd7648d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -410,4 +410,7 @@ object GenBCode { final val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC final val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL + + val CLASS_CONSTRUCTOR_NAME = "" + val INSTANCE_CONSTRUCTOR_NAME = "" } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala index 9e56f25888..b3ac06877b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala @@ -13,6 +13,7 @@ import scala.collection.convert.decorateAsScala._ import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.util.ClassFileLookup import OptimizerReporting._ +import BytecodeUtils._ import ByteCodeRepository._ import BTypes.InternalName @@ -93,16 +94,6 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class inlineFailure(s"Class file for class $fullName not found.") } } - - private def removeLineNumberNodes(classNode: ClassNode): Unit = { - for (method <- classNode.methods.asScala) { - val iter = method.instructions.iterator() - while (iter.hasNext) iter.next() match { - case _: LineNumberNode => iter.remove() - case _ => - } - } - } } object ByteCodeRepository { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala index 6b4047c0a7..4cff92d38b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -10,9 +10,14 @@ package opt import scala.annotation.{tailrec, switch} import scala.collection.mutable import scala.reflect.internal.util.Collections._ -import scala.tools.asm.Opcodes +import scala.tools.asm.tree.analysis._ +import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes} import scala.tools.asm.tree._ import scala.collection.convert.decorateAsScala._ +import GenBCode._ +import scala.collection.convert.decorateAsScala._ +import scala.collection.convert.decorateAsJava._ +import scala.tools.nsc.backend.jvm.BTypes._ object BytecodeUtils { @@ -68,6 +73,16 @@ object BytecodeUtils { def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0 + def isConstructor(methodNode: MethodNode): Boolean = { + methodNode.name == INSTANCE_CONSTRUCTOR_NAME || methodNode.name == CLASS_CONSTRUCTOR_NAME + } + + def isStaticMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STATIC) != 0 + + def isAbstractMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_ABSTRACT) != 0 + + def isSynchronizedMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_SYNCHRONIZED) != 0 + def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = { var result = instruction do { result = result.getNext } @@ -181,4 +196,112 @@ object BytecodeUtils { if (handler.end == from) handler.end = to } } + + /** + * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM + * framework only computes these values during bytecode generation. + * + * Since there's currently no better way, we run a bytecode generator on the method and extract + * the computed values. This required changes to the ASM codebase: + * - the [[MethodWriter]] class was made public + * - accessors for maxLocals / maxStack were added to the MethodWriter class + * + * We could probably make this faster (and allocate less memory) by hacking the ASM framework + * more: create a subclass of MethodWriter with a /dev/null byteVector. Another option would be + * to create a separate visitor for computing those values, duplicating the functionality from the + * MethodWriter. + */ + def computeMaxLocalsMaxStack(method: MethodNode) { + val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS) + val excs = method.exceptions.asScala.toArray + val mw = cw.visitMethod(method.access, method.name, method.desc, method.signature, excs).asInstanceOf[MethodWriter] + method.accept(mw) + method.maxLocals = mw.getMaxLocals + method.maxStack = mw.getMaxStack + } + + def removeLineNumberNodes(classNode: ClassNode): Unit = { + for (m <- classNode.methods.asScala) removeLineNumberNodes(m.instructions) + } + + def removeLineNumberNodes(instructions: InsnList): Unit = { + val iter = instructions.iterator() + while (iter.hasNext) iter.next() match { + case _: LineNumberNode => iter.remove() + case _ => + } + } + + def cloneLabels(methodNode: MethodNode): Map[LabelNode, LabelNode] = { + methodNode.instructions.iterator().asScala.collect({ + case labelNode: LabelNode => (labelNode, newLabelNode) + }).toMap + } + + /** + * Create a new [[LabelNode]] with a correctly associated [[Label]]. + */ + def newLabelNode: LabelNode = { + val label = new Label + val labelNode = new LabelNode(label) + label.info = labelNode + labelNode + } + + /** + * Clone the instructions in `methodNode` into a new [[InsnList]], mapping labels according to + * the `labelMap`. Returns the new instruction list and a map from old to new instructions. + */ + def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode]): (InsnList, Map[AbstractInsnNode, AbstractInsnNode]) = { + val javaLabelMap = labelMap.asJava + val result = new InsnList + var map = Map.empty[AbstractInsnNode, AbstractInsnNode] + for (ins <- methodNode.instructions.iterator.asScala) { + val cloned = ins.clone(javaLabelMap) + result add cloned + map += ((ins, cloned)) + } + (result, map) + } + + /** + * Clone the local variable descriptors of `methodNode` and map their `start` and `end` labels + * according to the `labelMap`. + */ + def cloneLocalVariableNodes(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], prefix: String): List[LocalVariableNode] = { + methodNode.localVariables.iterator().asScala.map(localVariable => new LocalVariableNode( + prefix + localVariable.name, + localVariable.desc, + localVariable.signature, + labelMap(localVariable.start), + labelMap(localVariable.end), + localVariable.index + )).toList + } + + /** + * Clone the local try/catch blocks of `methodNode` and map their `start` and `end` and `handler` + * labels according to the `labelMap`. + */ + def cloneTryCatchBlockNodes(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode]): List[TryCatchBlockNode] = { + methodNode.tryCatchBlocks.iterator().asScala.map(tryCatch => new TryCatchBlockNode( + labelMap(tryCatch.start), + labelMap(tryCatch.end), + labelMap(tryCatch.handler), + tryCatch.`type` + )).toList + } + + class BasicAnalyzer(methodNode: MethodNode, classInternalName: InternalName) { + val analyzer = new Analyzer[BasicValue](new BasicInterpreter) + analyzer.analyze(classInternalName, methodNode) + def frameAt(instruction: AbstractInsnNode): Frame[BasicValue] = analyzer.getFrames()(methodNode.instructions.indexOf(instruction)) + } + + implicit class `frame extensions`[V <: Value](val frame: Frame[V]) extends AnyVal { + def peekDown(n: Int): V = { + val topIndex = frame.getStackSize - 1 + frame.getStack(topIndex - n) + } + } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala index 6e5e03f730..f964b5b25d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -8,15 +8,206 @@ package backend.jvm package opt import scala.tools.asm -import asm.Opcodes +import asm.Opcodes._ import asm.tree._ import scala.collection.convert.decorateAsScala._ +import scala.collection.convert.decorateAsJava._ +import AsmUtils._ +import BytecodeUtils._ import OptimizerReporting._ +import scala.tools.asm.tree.analysis._ class Inliner[BT <: BTypes](val btypes: BT) { import btypes._ import btypes.byteCodeRepository + /** + * Copy and adapt the instructions of a method to a callsite. + * + * Preconditions: + * - The maxLocals and maxStack values of the callsite method are correctly computed + * - The callsite method contains no unreachable basic blocks, i.e., running an [[Analyzer]] + * does not produce any `null` frames + * + * @param callsiteInstruction The invocation instruction + * @param callsiteStackHeight The stack height at the callsite + * @param callsiteMethod The method in which the invocation occurs + * @param callsiteClass The class in which the callsite method is defined + * @param callee The invoked method + * @param calleeDeclarationClass The class in which the invoked method is defined + * @param receiverKnownNotNull `true` if the receiver is known to be non-null + * @param keepLineNumbers `true` if LineNumberNodes should be copied to the call site + * @return `Some(message)` if inlining cannot be performed, `None` otherwise + */ + def inline(callsiteInstruction: MethodInsnNode, callsiteStackHeight: Int, callsiteMethod: MethodNode, callsiteClass: ClassBType, + callee: MethodNode, calleeDeclarationClass: ClassBType, + receiverKnownNotNull: Boolean, keepLineNumbers: Boolean): Option[String] = { + canInline(callsiteInstruction, callsiteStackHeight, callsiteMethod, callsiteClass, callee, calleeDeclarationClass) orElse { + // New labels for the cloned instructions + val labelsMap = cloneLabels(callee) + val (clonedInstructions, instructionMap) = cloneInstructions(callee, labelsMap) + if (!keepLineNumbers) { + removeLineNumberNodes(clonedInstructions) + } + + // local vars in the callee are shifted by the number of locals at the callsite + val localVarShift = callsiteMethod.maxLocals + clonedInstructions.iterator.asScala foreach { + case varInstruction: VarInsnNode => varInstruction.`var` += localVarShift + case _ => () + } + + // add a STORE instruction for each expected argument, including for THIS instance if any + val argStores = new InsnList + var nextLocalIndex = callsiteMethod.maxLocals + if (!isStaticMethod(callee)) { + if (!receiverKnownNotNull) { + argStores.add(new InsnNode(DUP)) + val nonNullLabel = newLabelNode + argStores.add(new JumpInsnNode(IFNONNULL, nonNullLabel)) + argStores.add(new InsnNode(ACONST_NULL)) + argStores.add(new InsnNode(ATHROW)) + argStores.add(nonNullLabel) + } + argStores.add(new VarInsnNode(ASTORE, nextLocalIndex)) + nextLocalIndex += 1 + } + + // We just use an asm.Type here, no need to create the MethodBType. + val calleAsmType = asm.Type.getMethodType(callee.desc) + + for(argTp <- calleAsmType.getArgumentTypes) { + val opc = argTp.getOpcode(ISTORE) // returns the correct xSTORE instruction for argTp + argStores.insert(new VarInsnNode(opc, nextLocalIndex)) // "insert" is "prepend" - the last argument is on the top of the stack + nextLocalIndex += argTp.getSize + } + + clonedInstructions.insert(argStores) + + // label for the exit of the inlined functions. xRETURNs are rplaced by GOTOs to this label. + val postCallLabel = newLabelNode + clonedInstructions.add(postCallLabel) + + // replace xRETURNs: + // - store the return value (if any) + // - clear the stack of the inlined method (insert DROPs) + // - load the return value + // - GOTO postCallLabel + + val returnType = calleAsmType.getReturnType + val hasReturnValue = returnType.getSort != asm.Type.VOID + val returnValueIndex = callsiteMethod.maxLocals + callee.maxLocals + nextLocalIndex += returnType.getSize + + def returnValueStore(returnInstruction: AbstractInsnNode) = { + val opc = returnInstruction.getOpcode match { + case IRETURN => ISTORE + case LRETURN => LSTORE + case FRETURN => FSTORE + case DRETURN => DSTORE + case ARETURN => ASTORE + } + new VarInsnNode(opc, returnValueIndex) + } + + // We run an interpreter to know the stack height at each xRETURN instruction and the sizes + // of the values on the stack. + val analyzer = new BasicAnalyzer(callee, calleeDeclarationClass.internalName) + + for (originalReturn <- callee.instructions.iterator().asScala if isReturn(originalReturn)) { + val frame = analyzer.frameAt(originalReturn) + var stackHeight = frame.getStackSize + + val inlinedReturn = instructionMap(originalReturn) + val returnReplacement = new InsnList + + def drop(slot: Int) = returnReplacement add getPop(frame.peekDown(slot).getSize) + + // for non-void methods, store the stack top into the return local variable + if (hasReturnValue) { + returnReplacement add returnValueStore(originalReturn) + stackHeight -= 1 + } + + // drop the rest of the stack + for (i <- 0 until stackHeight) drop(i) + + returnReplacement add new JumpInsnNode(GOTO, postCallLabel) + clonedInstructions.insert(inlinedReturn, returnReplacement) + clonedInstructions.remove(inlinedReturn) + } + + // Load instruction for the return value + if (hasReturnValue) { + val retVarLoad = { + val opc = returnType.getOpcode(ILOAD) + new VarInsnNode(opc, returnValueIndex) + } + clonedInstructions.insert(postCallLabel, retVarLoad) + } + + callsiteMethod.instructions.insert(callsiteInstruction, clonedInstructions) + callsiteMethod.instructions.remove(callsiteInstruction) + + callsiteMethod.localVariables.addAll(cloneLocalVariableNodes(callee, labelsMap, callee.name + "_").asJava) + callsiteMethod.tryCatchBlocks.addAll(cloneTryCatchBlockNodes(callee, labelsMap).asJava) + + callsiteMethod.maxLocals += returnType.getSize + callee.maxLocals + callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, callee.maxStack + callsiteStackHeight) + + None + } + } + + /** + * Check whether an inling can be performed. Parmeters are described in method [[inline]]. + * @return `Some(message)` if inlining cannot be performed, `None` otherwise + */ + def canInline(callsiteInstruction: MethodInsnNode, callsiteStackHeight: Int, callsiteMethod: MethodNode, callsiteClass: ClassBType, + callee: MethodNode, calleeDeclarationClass: ClassBType): Option[String] = { + + def calleeDesc = s"${callee.name} of type ${callee.desc} in ${calleeDeclarationClass.internalName}" + def methodMismatch = s"Wrong method node for inlining ${textify(callsiteInstruction)}: $calleeDesc" + assert(callsiteInstruction.name == callee.name, methodMismatch) + assert(callsiteInstruction.desc == callee.desc, methodMismatch) + assert(!isConstructor(callee), s"Constructors cannot be inlined: $calleeDesc") + assert(!BytecodeUtils.isAbstractMethod(callee), s"Callee is abstract: $calleeDesc") + assert(callsiteMethod.instructions.contains(callsiteInstruction), s"Callsite ${textify(callsiteInstruction)} is not an instruction of $calleeDesc") + + // When an exception is thrown, the stack is cleared before jumping to the handler. When + // inlining a method that catches an exception, all values that were on the stack before the + // call (in addition to the arguments) would be cleared (SI-6157). So we don't inline methods + // with handlers in case there are values on the stack. + // Alternatively, we could save all stack values below the method arguments into locals, but + // that would be inefficient: we'd need to pop all parameters, save the values, and push the + // parameters back for the (inlined) invocation. Similarly for the result after the call. + def stackHasNonParameters: Boolean = { + val expectedArgs = asm.Type.getArgumentTypes(callsiteInstruction.desc).length + (callsiteInstruction.getOpcode match { + case INVOKEVIRTUAL | INVOKESPECIAL | INVOKEINTERFACE => 1 + case INVOKESTATIC => 0 + case INVOKEDYNAMIC => + assertionError(s"Unexpected opcode, cannot inline ${textify(callsiteInstruction)}") + }) + callsiteStackHeight > expectedArgs + } + + if (isSynchronizedMethod(callee)) { + // Could be done by locking on the receiver, wrapping the inlined code in a try and unlocking + // in finally. But it's probably not worth the effort, scala never emits synchronized methods. + Some(s"Method ${methodSignature(calleeDeclarationClass.internalName, callee)} is not inlined because it is synchronized") + } else if (!callee.tryCatchBlocks.isEmpty && stackHasNonParameters) { + Some( + s"""The operand stack at the callsite in ${methodSignature(callsiteClass.internalName, callsiteMethod)} contains more values than the + |arguments expected by the callee ${methodSignature(calleeDeclarationClass.internalName, callee)}. These values would be discarded + |when entering an exception handler declared in the inlined method.""".stripMargin + ) + } else findIllegalAccess(callee.instructions, callsiteClass) map { + case illegalAccessIns => + s"""The callee ${methodSignature(calleeDeclarationClass.internalName, callee)} contains the instruction ${AsmUtils.textify(illegalAccessIns)} + |that would cause an IllegalAccessError when inlined into class ${callsiteClass.internalName}""".stripMargin + } + } + def findIllegalAccess(instructions: InsnList, destinationClass: ClassBType): Option[AbstractInsnNode] = { /** @@ -57,14 +248,14 @@ class Inliner[BT <: BTypes](val btypes: BT) { // TODO: B3 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok? def samePackageAsDestination = memberDeclClass.packageInternalName == destinationClass.packageInternalName - val key = (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE) & memberFlags + val key = (ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE) & memberFlags key match { - case Opcodes.ACC_PUBLIC => // B1 + case ACC_PUBLIC => // B1 true - case Opcodes.ACC_PROTECTED => // B2 + case ACC_PROTECTED => // B2 val condB2 = destinationClass.isSubtypeOf(memberDeclClass) && { - val isStatic = (Opcodes.ACC_STATIC & memberFlags) != 0 + val isStatic = (ACC_STATIC & memberFlags) != 0 isStatic || memberRefClass.isSubtypeOf(destinationClass) || destinationClass.isSubtypeOf(memberRefClass) } condB2 || samePackageAsDestination // B3 (protected) @@ -72,7 +263,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { case 0 => // B3 (default access) samePackageAsDestination - case Opcodes.ACC_PRIVATE => // B4 + case ACC_PRIVATE => // B4 memberDeclClass == destinationClass } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala index 87ad715e4d..3a7250031a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala @@ -8,7 +8,7 @@ package backend.jvm package opt import scala.annotation.switch -import scala.tools.asm.{Opcodes, MethodWriter, ClassWriter} +import scala.tools.asm.Opcodes import scala.tools.asm.tree.analysis.{Analyzer, BasicValue, BasicInterpreter} import scala.tools.asm.tree._ import scala.collection.convert.decorateAsScala._ @@ -73,7 +73,7 @@ class LocalOpt(settings: ScalaSettings) { * * Returns `true` if the bytecode of `method` was changed. */ - private def methodOptimizations(method: MethodNode, ownerClassName: String): Boolean = { + def methodOptimizations(method: MethodNode, ownerClassName: String): Boolean = { if (method.instructions.size == 0) return false // fast path for abstract methods // unreachable-code also removes unused local variable nodes and empty exception handlers. @@ -318,29 +318,6 @@ class LocalOpt(settings: ScalaSettings) { } } - /** - * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM - * framework only computes these values during bytecode generation. - * - * Since there's currently no better way, we run a bytecode generator on the method and extract - * the computed values. This required changes to the ASM codebase: - * - the [[MethodWriter]] class was made public - * - accessors for maxLocals / maxStack were added to the MethodWriter class - * - * We could probably make this faster (and allocate less memory) by hacking the ASM framework - * more: create a subclass of MethodWriter with a /dev/null byteVector. Another option would be - * to create a separate visitor for computing those values, duplicating the functionality from the - * MethodWriter. - */ - private def computeMaxLocalsMaxStack(method: MethodNode) { - val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS) - val excs = method.exceptions.asScala.toArray - val mw = cw.visitMethod(method.access, method.name, method.desc, method.signature, excs).asInstanceOf[MethodWriter] - method.accept(mw) - method.maxLocals = mw.getMaxLocals - method.maxStack = mw.getMaxStack - } - /** * Removes LineNumberNodes that don't describe any executable instructions. * diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala index 7002e43d98..a918e13534 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala @@ -8,17 +8,19 @@ package backend.jvm import scala.tools.asm import asm.tree._ +import scala.tools.nsc.backend.jvm.BTypes.InternalName /** * Reporting utilities used in the optimizer. + * + * TODO: move out of opt package, rename: it's already used outside the optimizer. + * Centralize backend reporting here. */ object OptimizerReporting { - def methodSignature(className: String, methodName: String, methodDescriptor: String): String = { - className + "::" + methodName + methodDescriptor + def methodSignature(classInternalName: InternalName, method: MethodNode): String = { + classInternalName + "::" + method.name + method.desc } - def methodSignature(className: String, method: MethodNode): String = methodSignature(className, method.name, method.desc) - def inlineFailure(reason: String): Nothing = MissingRequirementError.signal(reason) def assertionError(message: String): Nothing = throw new AssertionError(message) } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala new file mode 100644 index 0000000000..1aa7bd1391 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -0,0 +1,193 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.tools.asm.Opcodes._ +import org.junit.Assert._ + +import scala.tools.asm.tree._ +import scala.tools.asm.tree.analysis._ +import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.BasicAnalyzer +import scala.tools.testing.AssertUtil._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import AsmUtils._ + +import scala.collection.convert.decorateAsScala._ + +@RunWith(classOf[JUnit4]) +class InlinerTest { + val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") + import compiler.genBCode.bTypes._ + def addToRepo(cls: List[ClassNode]): List[ClassNode] = { + for (c <- cls) byteCodeRepository.classes(c.name) = (c, ByteCodeRepository.Classfile) + cls + } + + // inline first invocation of f into g in class C + def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): (MethodNode, Option[String]) = { + val List(cls) = addToRepo(compileClasses(compiler)(code)) + mod(cls) + val clsBType = classBTypeFromParsedClassfile(cls.name) + + val List(f, g) = cls.methods.asScala.filter(m => Set("f", "g")(m.name)).toList.sortBy(_.name) + val fCall = g.instructions.iterator.asScala.collect({ case i: MethodInsnNode if i.name == "f" => i }).next() + + val analyzer = new BasicAnalyzer(g, clsBType.internalName) + + val r = inliner.inline( + fCall, + analyzer.frameAt(fCall).getStackSize, + g, + clsBType, + f, + clsBType, + receiverKnownNotNull = true, + keepLineNumbers = true) + (g, r) + } + + @Test + def simpleInlineOK(): Unit = { + val code = + """class C { + | def f = 1 + | def g = f + f + |} + """.stripMargin + + val (g, _) = inlineTest(code) + + val gConv = convertMethod(g) + assertSameCode(gConv.instructions.dropNonOp, + List( + VarOp(ALOAD, 0), VarOp(ASTORE, 1), // store this + Op(ICONST_1), VarOp(ISTORE, 2), Jump(GOTO, Label(10)), // store return value + Label(10), VarOp(ILOAD, 2), // load return value + VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "C", "f", "()I", false), Op(IADD), Op(IRETURN))) + + // line numbers are kept, so there's a line 2 (from the inlined f) + assert(gConv.instructions exists { + case LineNumber(2, _) => true + case _ => false + }, gConv.instructions.filter(_.isInstanceOf[LineNumber])) + + assert(gConv.localVars.map(_.name).sorted == List("f_this", "this"), gConv.localVars) + assert(g.maxStack == 2 && g.maxLocals == 3, s"${g.maxLocals} - ${g.maxStack}") + } + + @Test + def nothingTypedOK(): Unit = { + val code = + """class C { + | def f: Nothing = ??? + | def g: Int = { f; 1 } + |} + """.stripMargin + + // On the bytecode level, methods of type Nothing have return type Nothing$. + // This can be treated like any other result object. + + // See also discussion around ATHROW in BCodeBodyBuilder + + val (g, _) = inlineTest(code) + val expectedInlined = List( + VarOp(ALOAD, 0), VarOp(ASTORE, 1), // store this + Field(GETSTATIC, "scala/Predef$", "MODULE$", "Lscala/Predef$;"), Invoke(INVOKEVIRTUAL, "scala/Predef$", "$qmark$qmark$qmark", "()Lscala/runtime/Nothing$;", false)) // inlined call to ??? + + assertSameCode(convertMethod(g).instructions.dropNonOp.take(4), expectedInlined) + + localOpt.methodOptimizations(g, "C") + assertSameCode(convertMethod(g).instructions.dropNonOp, + expectedInlined ++ List(VarOp(ASTORE, 2), VarOp(ALOAD, 2), Op(ATHROW))) + } + + @Test + def synchronizedNoInline(): Unit = { + val code = + """class C { + | def f: Int = 0 + | def g: Int = f + |} + """.stripMargin + + val (_, can) = inlineTest(code, cls => { + val f = cls.methods.asScala.find(_.name == "f").get + f.access |= ACC_SYNCHRONIZED + }) + assert(can.get contains "synchronized", can) + } + + @Test + def tryCatchOK(): Unit = { + val code = + """class C { + | def f: Int = try { 1 } catch { case _: Exception => 2 } + | def g = f + 1 + |} + """.stripMargin + val (_, r) = inlineTest(code) + assert(r.isEmpty, r) + } + + @Test + def tryCatchNoInline(): Unit = { + // cannot inline f: there's a value on g's stack. if f throws and enters the handler, all values + // on the stack are removed, including the one of g's stack that we still need. + val code = + """class C { + | def f: Int = try { 1 } catch { case _: Exception => 2 } + | def g = println(f) + |} + """.stripMargin + val (_, r) = inlineTest(code) + assert(r.get contains "operand stack at the callsite", r) + } + + @Test + def illegalAccessNoInline(): Unit = { + val code = + """package a { + | class C { + | private def f: Int = 0 + | def g: Int = f + | } + |} + |package b { + | class D { + | def h(c: a.C): Int = c.g + 1 + | } + |} + """.stripMargin + + val List(c, d) = addToRepo(compileClasses(compiler)(code)) + + val cTp = classBTypeFromParsedClassfile(c.name) + val dTp = classBTypeFromParsedClassfile(d.name) + + val g = c.methods.asScala.find(_.name == "g").get + val h = d.methods.asScala.find(_.name == "h").get + val gCall = h.instructions.iterator.asScala.collect({ + case m: MethodInsnNode if m.name == "g" => m + }).next() + + val analyzer = new BasicAnalyzer(h, dTp.internalName) + + val r = inliner.inline( + gCall, + analyzer.frameAt(gCall).getStackSize, + h, + dTp, + g, + cTp, + receiverKnownNotNull = true, + keepLineNumbers = true) + + assert(r.get contains "would cause an IllegalAccessError", r) + } +} -- cgit v1.2.3 From 4ffe9345ca6e611ff5a83a3fdabeb7658a2fce50 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Sun, 18 Jan 2015 23:22:25 +0100 Subject: Reuse the same compiler instance for all tests in a JUnit class Note that JUnit creates a new instance of the test class for running each test method. So the compiler instance is added to the companion. However, the JVM would quickly run out of memory when running multiple tests, as the compilers cannot be GCd. So we make it a `var`, and set it to null when a class is done. For that we use JUnit's `@AfterClass` which is required to be on a static method. Therefore we add a Java class with such a static method that we can extend from Scala. --- build.xml | 9 ++++ .../scala/reflect/internal/SymbolTable.scala | 8 ++++ .../scala/collection/IterableViewLikeTest.scala | 1 + .../scala/tools/nsc/backend/jvm/BTypesTest.scala | 52 ++++++++++++---------- .../tools/nsc/backend/jvm/DirectCompileTest.scala | 12 ++++- .../backend/jvm/opt/BTypesFromClassfileTest.scala | 4 +- .../jvm/opt/EmptyExceptionHandlersTest.scala | 19 ++++++-- .../backend/jvm/opt/InlinerIllegalAccessTest.scala | 12 ++++- .../tools/nsc/backend/jvm/opt/InlinerTest.scala | 29 +++++++++--- .../nsc/backend/jvm/opt/MethodLevelOpts.scala | 12 ++++- .../nsc/backend/jvm/opt/UnreachableCodeTest.scala | 36 ++++++++++----- .../backend/jvm/opt/UnusedLocalVariablesTest.scala | 12 ++++- .../junit/scala/tools/testing/ClearAfterClass.java | 20 +++++++++ 13 files changed, 175 insertions(+), 51 deletions(-) create mode 100644 test/junit/scala/tools/testing/ClearAfterClass.java (limited to 'src') diff --git a/build.xml b/build.xml index 5f6b04b8e4..ee6a045bda 100755 --- a/build.xml +++ b/build.xml @@ -1448,6 +1448,15 @@ TODO: + Option(ref.get).foreach(_.clear)) diff --git a/test/junit/scala/collection/IterableViewLikeTest.scala b/test/junit/scala/collection/IterableViewLikeTest.scala index ab09c4930b..435a43c215 100644 --- a/test/junit/scala/collection/IterableViewLikeTest.scala +++ b/test/junit/scala/collection/IterableViewLikeTest.scala @@ -13,6 +13,7 @@ class IterableViewLikeTest { def hasCorrectDropAndTakeMethods() { val iter = Iterable(1, 2, 3) + import scala.language.postfixOps assertEquals(Iterable.empty[Int], iter.view take Int.MinValue force) assertEquals(Iterable.empty[Int], iter.view takeRight Int.MinValue force) assertEquals(iter, iter.view drop Int.MinValue force) diff --git a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala index 2347e8288e..6ada0e20fb 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala @@ -7,35 +7,41 @@ import org.junit.Test import scala.tools.asm.Opcodes import org.junit.Assert._ -@RunWith(classOf[JUnit4]) -class BTypesTest { - val settings = new Settings() - settings.processArgumentString("-usejavacp") - val g: Global = new Global(settings) - val run = new g.Run() // initializes some compiler internals - import g.{definitions => d, Symbol} +import scala.tools.nsc.backend.jvm.CodeGenTools._ +import scala.tools.testing.ClearAfterClass - def duringBackend[T](f: => T) = g.exitingDelambdafy(f) +object BTypesTest extends ClearAfterClass.Clearable { + var compiler = { + val comp = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") + new comp.Run() // initializes some of the compiler + comp.exitingDelambdafy(comp.scalaPrimitives.init()) // needed: it's only done when running the backend, and we don't actually run the compiler + comp.exitingDelambdafy(comp.genBCode.bTypes.initializeCoreBTypes()) + comp + } + def clear(): Unit = { compiler = null } +} - val btypes = new BTypesFromSymbols[g.type](g) - import btypes._ - duringBackend(btypes.initializeCoreBTypes()) +@RunWith(classOf[JUnit4]) +class BTypesTest extends ClearAfterClass { + ClearAfterClass.stateToClear = BTypesTest - def classBTypeFromSymbol(sym: Symbol) = duringBackend(btypes.classBTypeFromSymbol(sym)) + val compiler = BTypesTest.compiler + import compiler.genBCode.bTypes._ - val jlo = d.ObjectClass - val jls = d.StringClass + def classBTFS(sym: compiler.Symbol) = compiler.exitingDelambdafy(classBTypeFromSymbol(sym)) - val o = classBTypeFromSymbol(jlo) - val s = classBTypeFromSymbol(jls) - val oArr = ArrayBType(o) - val method = MethodBType(List(oArr, INT, DOUBLE, s), UNIT) + def jlo = compiler.definitions.ObjectClass + def jls = compiler.definitions.StringClass + def o = classBTFS(jlo) + def s = classBTFS(jls) + def oArr = ArrayBType(o) + def method = MethodBType(List(oArr, INT, DOUBLE, s), UNIT) @Test def classBTypesEquality() { - val s1 = classBTypeFromSymbol(jls) - val s2 = classBTypeFromSymbol(jls) - val o = classBTypeFromSymbol(jlo) + val s1 = classBTFS(jls) + val s2 = classBTFS(jls) + val o = classBTFS(jlo) assertEquals(s1, s2) assertEquals(s1.hashCode, s2.hashCode) assert(s1 != o) @@ -53,7 +59,7 @@ class BTypesTest { assert(FLOAT.typedOpcode(Opcodes.IALOAD) == Opcodes.FALOAD) assert(LONG.typedOpcode(Opcodes.IALOAD) == Opcodes.LALOAD) assert(DOUBLE.typedOpcode(Opcodes.IALOAD) == Opcodes.DALOAD) - assert(classBTypeFromSymbol(jls).typedOpcode(Opcodes.IALOAD) == Opcodes.AALOAD) + assert(classBTFS(jls).typedOpcode(Opcodes.IALOAD) == Opcodes.AALOAD) assert(UNIT.typedOpcode(Opcodes.IRETURN) == Opcodes.RETURN) assert(BOOL.typedOpcode(Opcodes.IRETURN) == Opcodes.IRETURN) @@ -64,7 +70,7 @@ class BTypesTest { assert(FLOAT.typedOpcode(Opcodes.IRETURN) == Opcodes.FRETURN) assert(LONG.typedOpcode(Opcodes.IRETURN) == Opcodes.LRETURN) assert(DOUBLE.typedOpcode(Opcodes.IRETURN) == Opcodes.DRETURN) - assert(classBTypeFromSymbol(jls).typedOpcode(Opcodes.IRETURN) == Opcodes.ARETURN) + assert(classBTFS(jls).typedOpcode(Opcodes.IRETURN) == Opcodes.ARETURN) } @Test diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala index 89900291ca..3b1b009037 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala @@ -7,10 +7,18 @@ import org.junit.Assert._ import CodeGenTools._ import scala.tools.asm.Opcodes._ import scala.tools.partest.ASMConverters._ +import scala.tools.testing.ClearAfterClass + +object DirectCompileTest extends ClearAfterClass.Clearable { + var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:method") + def clear(): Unit = { compiler = null } +} @RunWith(classOf[JUnit4]) -class DirectCompileTest { - val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:method") +class DirectCompileTest extends ClearAfterClass { + ClearAfterClass.stateToClear = DirectCompileTest + + val compiler = DirectCompileTest.compiler @Test def testCompile(): Unit = { diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala index 2975bd060d..4481fcd6be 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala @@ -19,7 +19,8 @@ import scala.collection.convert.decorateAsScala._ @RunWith(classOf[JUnit4]) class BTypesFromClassfileTest { - val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode") + // inliner enabled -> inlineInfos are collected (and compared) in ClassBTypes + val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:inline-global") import compiler._ import definitions._ @@ -29,6 +30,7 @@ class BTypesFromClassfileTest { def duringBackend[T](f: => T) = compiler.exitingDelambdafy(f) val run = new compiler.Run() // initializes some of the compiler + duringBackend(compiler.scalaPrimitives.init()) // needed: it's only done when running the backend, and we don't actually run the compiler duringBackend(bTypes.initializeCoreBTypes()) def clearCache() = bTypes.classBTypeFromInternalName.clear() 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 7d83c54b5b..7b0504fec0 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala @@ -11,9 +11,23 @@ import org.junit.Assert._ import CodeGenTools._ import scala.tools.partest.ASMConverters import ASMConverters._ +import scala.tools.testing.ClearAfterClass + +object EmptyExceptionHandlersTest extends ClearAfterClass.Clearable { + var noOptCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") + var dceCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:unreachable-code") + def clear(): Unit = { + noOptCompiler = null + dceCompiler = null + } +} @RunWith(classOf[JUnit4]) -class EmptyExceptionHandlersTest { +class EmptyExceptionHandlersTest extends ClearAfterClass { + ClearAfterClass.stateToClear = EmptyExceptionHandlersTest + + val noOptCompiler = EmptyExceptionHandlersTest.noOptCompiler + val dceCompiler = EmptyExceptionHandlersTest.dceCompiler val exceptionDescriptor = "java/lang/Exception" @@ -51,9 +65,6 @@ class EmptyExceptionHandlersTest { assertTrue(convertMethod(asmMethod).handlers.isEmpty) } - val noOptCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") - val dceCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:unreachable-code") - @Test def eliminateUnreachableHandler(): Unit = { val code = "def f: Unit = try { } catch { case _: Exception => println(0) }; println(1)" diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala index 406b8da570..36f297767e 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala @@ -17,10 +17,18 @@ import ASMConverters._ import AsmUtils._ import scala.collection.convert.decorateAsScala._ +import scala.tools.testing.ClearAfterClass + +object InlinerIllegalAccessTest extends ClearAfterClass.Clearable { + var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") + def clear(): Unit = { compiler = null } +} @RunWith(classOf[JUnit4]) -class InlinerIllegalAccessTest { - val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") +class InlinerIllegalAccessTest extends ClearAfterClass { + ClearAfterClass.stateToClear = InlinerIllegalAccessTest + + val compiler = InlinerIllegalAccessTest.compiler import compiler.genBCode.bTypes._ def addToRepo(cls: List[ClassNode]): Unit = for (c <- cls) byteCodeRepository.classes(c.name) = (c, ByteCodeRepository.Classfile) 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 1aa7bd1391..2ec6853f13 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -5,6 +5,7 @@ package opt import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.Test +import scala.collection.generic.Clearable import scala.tools.asm.Opcodes._ import org.junit.Assert._ @@ -19,19 +20,37 @@ import ASMConverters._ import AsmUtils._ import scala.collection.convert.decorateAsScala._ +import scala.tools.testing.ClearAfterClass + +object InlinerTest extends ClearAfterClass.Clearable { + var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:project") + + // allows inspecting the caches after a compilation run + def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes) + notPerRun foreach compiler.perRunCaches.unrecordCache + + def clear(): Unit = { compiler = null } +} @RunWith(classOf[JUnit4]) -class InlinerTest { - val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") +class InlinerTest extends ClearAfterClass { + ClearAfterClass.stateToClear = InlinerTest + + val compiler = InlinerTest.compiler import compiler.genBCode.bTypes._ - def addToRepo(cls: List[ClassNode]): List[ClassNode] = { + + def compile(code: String): List[ClassNode] = { + InlinerTest.notPerRun.foreach(_.clear()) + val cls = compileClasses(compiler)(code) + // the compiler doesn't add classes being compiled to the code repo yet, so we do it manually. + // this line is removed in the next commit. for (c <- cls) byteCodeRepository.classes(c.name) = (c, ByteCodeRepository.Classfile) cls } // inline first invocation of f into g in class C def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): (MethodNode, Option[String]) = { - val List(cls) = addToRepo(compileClasses(compiler)(code)) + val List(cls) = compile(code) mod(cls) val clsBType = classBTypeFromParsedClassfile(cls.name) @@ -165,7 +184,7 @@ class InlinerTest { |} """.stripMargin - val List(c, d) = addToRepo(compileClasses(compiler)(code)) + val List(c, d) = compile(code) val cTp = classBTypeFromParsedClassfile(c.name) val dTp = classBTypeFromParsedClassfile(d.name) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala index 5430e33d6c..2c71e9d533 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala @@ -13,10 +13,18 @@ import scala.tools.testing.AssertUtil._ import CodeGenTools._ import scala.tools.partest.ASMConverters import ASMConverters._ +import scala.tools.testing.ClearAfterClass + +object MethodLevelOpts extends ClearAfterClass.Clearable { + var methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method") + def clear(): Unit = { methodOptCompiler = null } +} @RunWith(classOf[JUnit4]) -class MethodLevelOpts { - val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method") +class MethodLevelOpts extends ClearAfterClass { + ClearAfterClass.stateToClear = MethodLevelOpts + + val methodOptCompiler = MethodLevelOpts.methodOptCompiler def wrapInDefault(code: Instruction*) = List(Label(0), LineNumber(1, Label(0))) ::: code.toList ::: List(Label(1)) 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 4a45dd9138..7c9636b8b7 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala @@ -13,9 +13,34 @@ import scala.tools.testing.AssertUtil._ import CodeGenTools._ import scala.tools.partest.ASMConverters import ASMConverters._ +import scala.tools.testing.ClearAfterClass + +object UnreachableCodeTest extends ClearAfterClass.Clearable { + // jvm-1.6 enables emitting stack map frames, which impacts the code generation wrt dead basic blocks, + // see comment in BCodeBodyBuilder + var methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method") + var dceCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code") + var noOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:none") + + // jvm-1.5 disables computing stack map frames, and it emits dead code as-is. + var noOptNoFramesCompiler = newCompiler(extraArgs = "-target:jvm-1.5 -Ybackend:GenBCode -Yopt:l:none") + + def clear(): Unit = { + methodOptCompiler = null + dceCompiler = null + noOptCompiler = null + noOptNoFramesCompiler = null + } +} @RunWith(classOf[JUnit4]) -class UnreachableCodeTest { +class UnreachableCodeTest extends ClearAfterClass { + ClearAfterClass.stateToClear = UnreachableCodeTest + + val methodOptCompiler = UnreachableCodeTest.methodOptCompiler + val dceCompiler = UnreachableCodeTest.dceCompiler + val noOptCompiler = UnreachableCodeTest.noOptCompiler + val noOptNoFramesCompiler = UnreachableCodeTest.noOptNoFramesCompiler def assertEliminateDead(code: (Instruction, Boolean)*): Unit = { val method = genMethod()(code.map(_._1): _*) @@ -25,15 +50,6 @@ class UnreachableCodeTest { assertSameCode(nonEliminated, expectedLive) } - // jvm-1.6 enables emitting stack map frames, which impacts the code generation wrt dead basic blocks, - // see comment in BCodeBodyBuilder - val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method") - val dceCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code") - val noOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:none") - - // jvm-1.5 disables computing stack map frames, and it emits dead code as-is. - val noOptNoFramesCompiler = newCompiler(extraArgs = "-target:jvm-1.5 -Ybackend:GenBCode -Yopt:l:none") - @Test def basicElimination(): Unit = { assertEliminateDead( diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala index 24a1f9d1c1..769736669b 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala @@ -12,10 +12,18 @@ import scala.collection.JavaConverters._ import CodeGenTools._ import scala.tools.partest.ASMConverters import ASMConverters._ +import scala.tools.testing.ClearAfterClass + +object UnusedLocalVariablesTest extends ClearAfterClass.Clearable { + var dceCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:unreachable-code") + def clear(): Unit = { dceCompiler = null } +} @RunWith(classOf[JUnit4]) -class UnusedLocalVariablesTest { - val dceCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:unreachable-code") +class UnusedLocalVariablesTest extends ClearAfterClass { + ClearAfterClass.stateToClear = UnusedLocalVariablesTest + + val dceCompiler = UnusedLocalVariablesTest.dceCompiler @Test def removeUnusedVar(): Unit = { diff --git a/test/junit/scala/tools/testing/ClearAfterClass.java b/test/junit/scala/tools/testing/ClearAfterClass.java new file mode 100644 index 0000000000..232d459c4e --- /dev/null +++ b/test/junit/scala/tools/testing/ClearAfterClass.java @@ -0,0 +1,20 @@ +package scala.tools.testing; + +import org.junit.AfterClass; + +/** + * Extend this class to use JUnit's @AfterClass. This annotation only works on static methods, + * which cannot be written in Scala. + * + * Example: {@link scala.tools.nsc.backend.jvm.opt.InlinerTest} + */ +public class ClearAfterClass { + public static interface Clearable { + void clear(); + } + + public static Clearable stateToClear; + + @AfterClass + public static void clearState() { stateToClear.clear(); } +} -- cgit v1.2.3 From 0d8b32469ec655f35a31843e1843b8a580e772d1 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Sat, 21 Feb 2015 16:04:21 +0100 Subject: Build a call graph for inlining decisions Inlining decisions will be taken by analyzing the ASM bytecode. This commit adds tools to build a call graph representation that can be used for these decisions. The call graph is currently built by considering method descriptors of callsite instructions. It will become more precise by using data flow analyses. --- .../scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 127 +++---------- .../tools/nsc/backend/jvm/BCodeSkelBuilder.scala | 8 +- .../scala/tools/nsc/backend/jvm/BTypes.scala | 34 +++- .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 203 +++++++++++++++++++-- .../tools/nsc/backend/jvm/opt/BytecodeUtils.scala | 2 +- .../tools/nsc/backend/jvm/opt/CallGraph.scala | 114 ++++++++++++ .../scala/tools/nsc/backend/jvm/opt/Inliner.scala | 25 ++- .../scala/tools/nsc/backend/jvm/opt/LocalOpt.scala | 4 +- .../scala/tools/nsc/settings/ScalaSettings.scala | 10 +- .../backend/jvm/opt/BTypesFromClassfileTest.scala | 9 + .../tools/nsc/backend/jvm/opt/CallGraphTest.scala | 146 +++++++++++++++ .../tools/nsc/backend/jvm/opt/InlinerTest.scala | 8 +- 12 files changed, 553 insertions(+), 137 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala create mode 100644 test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index e366bbabb8..246d565987 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -332,125 +332,42 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { getClassBTypeAndRegisterInnerClass(classSym).internalName } - private def assertClassNotArray(sym: Symbol): Unit = { - assert(sym.isClass, sym) - assert(sym != definitions.ArrayClass || isCompilingArray, sym) - } - - private def assertClassNotArrayNotPrimitive(sym: Symbol): Unit = { - assertClassNotArray(sym) - assert(!primitiveTypeMap.contains(sym) || isCompilingPrimitive, sym) - } - /** * The ClassBType for a class symbol. If the class is nested, the ClassBType is added to the * innerClassBufferASM. * - * The class symbol scala.Nothing is mapped to the class scala.runtime.Nothing$. Similarly, - * scala.Null is mapped to scala.runtime.Null$. This is because there exist no class files - * for the Nothing / Null. If used for example as a parameter type, we use the runtime classes - * in the classfile method signature. - * - * Note that the referenced class symbol may be an implementation class. For example when - * compiling a mixed-in method that forwards to the static method in the implementation class, - * the class descriptor of the receiver (the implementation class) is obtained by creating the - * ClassBType. + * TODO: clean up the way we track referenced inner classes. + * doing it during code generation is not correct when the optimizer changes the code. */ final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = { - assertClassNotArrayNotPrimitive(sym) - - if (sym == definitions.NothingClass) RT_NOTHING - else if (sym == definitions.NullClass) RT_NULL - else { - val r = classBTypeFromSymbol(sym) - if (r.isNestedClass) innerClassBufferASM += r - r - } + val r = classBTypeFromSymbol(sym) + if (r.isNestedClass) innerClassBufferASM += r + r } /** - * This method returns the BType for a type reference, for example a parameter type. - * - * If the result is a ClassBType for a nested class, it is added to the innerClassBufferASM. - * - * If `t` references a class, toTypeKind ensures that the class is not an implementation class. - * See also comment on getClassBTypeAndRegisterInnerClass, which is invoked for implementation - * classes. + * The BType for a type reference. If the result is a ClassBType for a nested class, it is added + * to the innerClassBufferASM. + * TODO: clean up the way we track referenced inner classes. */ - final def toTypeKind(t: Type): BType = { - import definitions.ArrayClass - - /** - * Primitive types are represented as TypeRefs to the class symbol of, for example, scala.Int. - * The `primitiveTypeMap` maps those class symbols to the corresponding PrimitiveBType. - */ - def primitiveOrClassToBType(sym: Symbol): BType = { - assertClassNotArray(sym) - assert(!sym.isImplClass, sym) - primitiveTypeMap.getOrElse(sym, getClassBTypeAndRegisterInnerClass(sym)) - } - - /** - * When compiling Array.scala, the type parameter T is not erased and shows up in method - * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference. - */ - def nonClassTypeRefToBType(sym: Symbol): ClassBType = { - assert(sym.isType && isCompilingArray, sym) - ObjectReference - } - - t.dealiasWiden match { - case TypeRef(_, ArrayClass, List(arg)) => ArrayBType(toTypeKind(arg)) // Array type such as Array[Int] (kept by erasure) - case TypeRef(_, sym, _) if !sym.isClass => nonClassTypeRefToBType(sym) // See comment on nonClassTypeRefToBType - case TypeRef(_, sym, _) => primitiveOrClassToBType(sym) // Common reference to a type such as scala.Int or java.lang.String - case ClassInfoType(_, _, sym) => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes toTypeKind(moduleClassSymbol.info) - - /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for - * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning. - * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala. - */ - case a @ AnnotatedType(_, t) => - debuglog(s"typeKind of annotated type $a") - toTypeKind(t) - - /* ExistentialType should (probably) be eliminated by erasure. We know they get here for - * classOf constants: - * class C[T] - * class T { final val k = classOf[C[_]] } - */ - case e @ ExistentialType(_, t) => - debuglog(s"typeKind of existential type $e") - toTypeKind(t) - - /* The cases below should probably never occur. They are kept for now to avoid introducing - * new compiler crashes, but we added a warning. The compiler / library bootstrap and the - * test suite don't produce any warning. - */ - - case tp => - currentUnit.warning(tp.typeSymbol.pos, - s"an unexpected type representation reached the compiler backend while compiling $currentUnit: $tp. " + - "If possible, please file a bug on issues.scala-lang.org.") - - tp match { - case ThisType(ArrayClass) => ObjectReference // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test - case ThisType(sym) => getClassBTypeAndRegisterInnerClass(sym) - case SingleType(_, sym) => primitiveOrClassToBType(sym) - case ConstantType(_) => toTypeKind(t.underlying) - case RefinedType(parents, _) => parents.map(toTypeKind(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b)) - } - } + final def toTypeKind(t: Type): BType = typeToBType(t) match { + case c: ClassBType if c.isNestedClass => + innerClassBufferASM += c + c + case r => r } - /* - * must-single-thread + /** + * Class components that are nested classes are added to the innerClassBufferASM. + * TODO: clean up the way we track referenced inner classes. */ final def asmMethodType(msym: Symbol): MethodBType = { - assert(msym.isMethod, s"not a method-symbol: $msym") - val resT: BType = - if (msym.isClassConstructor || msym.isConstructor) UNIT - else toTypeKind(msym.tpe.resultType) - MethodBType(msym.tpe.paramTypes map toTypeKind, resT) + val r = methodBTypeFromSymbol(msym) + (r.returnType :: r.argumentTypes) foreach { + case c: ClassBType if c.isNestedClass => innerClassBufferASM += c + case _ => + } + r } /** diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 48df4e1121..b4de5cf52f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -10,6 +10,7 @@ package backend package jvm import scala.collection.{ mutable, immutable } +import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository import scala.tools.nsc.symtab._ import scala.annotation.switch @@ -126,9 +127,12 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern)) AsmUtils.traceClass(cnode) - cnode.innerClasses - assert(cd.symbol == claszSymbol, "Someone messed up BCodePhase.claszSymbol during genPlainClass().") + if (settings.YoptInlinerEnabled) { + // The inliner needs to find all classes in the code repo, also those being compiled + byteCodeRepository.classes(cnode.name) = (cnode, ByteCodeRepository.CompilationUnit) + } + assert(cd.symbol == claszSymbol, "Someone messed up BCodePhase.claszSymbol during genPlainClass().") } // end of method genPlainClass() /* diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index fe4c4794a9..c93496fb49 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -10,7 +10,8 @@ import scala.annotation.switch import scala.tools.asm import asm.Opcodes import scala.tools.asm.tree.{InnerClassNode, ClassNode} -import opt.{ByteCodeRepository, Inliner} +import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo} +import scala.tools.nsc.backend.jvm.opt.{CallGraph, ByteCodeRepository, Inliner} import OptimizerReporting._ import scala.collection.convert.decorateAsScala._ @@ -38,9 +39,14 @@ abstract class BTypes { val inliner: Inliner[this.type] + val callGraph: CallGraph[this.type] + // 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 + /** * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its * construction. @@ -54,6 +60,23 @@ abstract class BTypes { */ val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType]) + /** + * Build the [[InlineInfo]] for the methods of a class, given its internal name. + * + * The InlineInfo is part of the ClassBType's [[ClassInfo]]. Note that there are two ways to build + * a ClassBType: from a class symbol (methods in [[BTypesFromSymbols]]) or from a [[ClassNode]]. + * The InlineInfo however contains information that can only be retrieved from the symbol of + * the class (e.g., is a method annotated @inline). + * + * This method (implemented in [[BTypesFromSymbols]]) looks up the class symbol in the symbol + * table, using the classfile name of the class. + * + * The method tries to undo some of the name mangling, but the lookup does not succeed for all + * classes. In case it fails, the resulting ClassBType will simply not have an InlineInfo, and + * we won't be able to inline its methods. + */ + def inlineInfosFromSymbolLookup(internalName: InternalName): Map[String, MethodInlineInfo] + /** * Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType * is constructed by parsing the corresponding classfile. @@ -145,7 +168,8 @@ abstract class BTypes { val staticFlag = (innerEntry.access & Opcodes.ACC_STATIC) != 0 NestedInfo(enclosingClass, Option(innerEntry.outerName), Option(innerEntry.innerName), staticFlag) } - classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo) + + classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfosFromSymbolLookup(classBType.internalName)) classBType } @@ -901,9 +925,13 @@ abstract class BTypes { * @param nestedClasses Classes nested in this class. Those need to be added to the * InnerClass table, see the InnerClass spec summary above. * @param nestedInfo If this describes a nested class, information for the InnerClass table. + * @param inlineInfos The [[InlineInfo]]s for the methods declared in this class. The map is + * indexed by the string s"$name$descriptor" (to disambiguate overloads). + * Entries may be missing, see comment on [[inlineInfosFromSymbolLookup]]. */ final case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int, - nestedClasses: List[ClassBType], nestedInfo: Option[NestedInfo]) + nestedClasses: List[ClassBType], nestedInfo: Option[NestedInfo], + inlineInfos: Map[String, MethodInlineInfo]) /** * Information required to add a class to an InnerClass table. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 117b377622..9ed7b3174b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -9,8 +9,8 @@ package backend.jvm import scala.tools.asm import scala.tools.asm.tree.ClassNode import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository.Source -import scala.tools.nsc.backend.jvm.opt.{Inliner, ByteCodeRepository} -import BTypes.InternalName +import scala.tools.nsc.backend.jvm.opt.{CallGraph, Inliner, ByteCodeRepository} +import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo, InternalName} /** * This class mainly contains the method classBTypeFromSymbol, which extracts the necessary @@ -40,12 +40,58 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val inliner: Inliner[this.type] = new Inliner(this) + val callGraph: CallGraph[this.type] = new CallGraph(this) + + /** + * See doc in [[BTypes.inlineInfosFromSymbolLookup]]. + * TODO: once the optimzier uses parallelism, lock before symbol table accesses + */ + def inlineInfosFromSymbolLookup(internalName: InternalName): Map[String, MethodInlineInfo] = { + val name = internalName.replace('/', '.') + + // TODO: de-mangle more class names + + def inEmptyPackage = name.indexOf('.') == -1 + def isModule = name.endsWith("$") + def isTopLevel = { + // TODO: this is conservative, there's also $'s introduced by name mangling, e.g., $colon$colon + // for this, use NameTransformer.decode + if (isModule) name.indexOf('$') == (name.length - 1) + else name.indexOf('$') == -1 + } + + val lookupName = { + if (isModule) newTermName(name.substring(0, name.length - 1)) + else newTypeName(name) + } + + // for now we only try classes that look like top-level + val classSym = if (!isTopLevel) NoSymbol else { + val member = { + if (inEmptyPackage) { + // rootMirror.getClassIfDefined fails for classes / modules in the empty package. + // maybe that should be fixed. + rootMirror.EmptyPackageClass.info.member(lookupName) + } else { + if (isModule) rootMirror.getModuleIfDefined(lookupName) + else rootMirror.getClassIfDefined(lookupName) + } + } + if (isModule) member.moduleClass else member + } + + if (classSym == NoSymbol) Map.empty + else buildInlineInfos(classSym) + } + final def initializeCoreBTypes(): Unit = { coreBTypes.setBTypes(new CoreBTypes[this.type](this)) } def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T = perRunCaches.recordCache(cache) + def inlineGlobalEnabled: Boolean = settings.YoptInlineGlobal + // helpers that need access to global. // TODO @lry create a separate component, they don't belong to BTypesFromSymbols @@ -78,22 +124,125 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { // end helpers /** - * The ClassBType for a class symbol `sym`. + * The ClassBType for a class symbol `classSym`. + * + * The class symbol scala.Nothing is mapped to the class scala.runtime.Nothing$. Similarly, + * scala.Null is mapped to scala.runtime.Null$. This is because there exist no class files + * for the Nothing / Null. If used for example as a parameter type, we use the runtime classes + * in the classfile method signature. + * + * Note that the referenced class symbol may be an implementation class. For example when + * compiling a mixed-in method that forwards to the static method in the implementation class, + * the class descriptor of the receiver (the implementation class) is obtained by creating the + * ClassBType. */ final def classBTypeFromSymbol(classSym: Symbol): ClassBType = { assert(classSym != NoSymbol, "Cannot create ClassBType from NoSymbol") assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym") - assert( - (!primitiveTypeMap.contains(classSym) || isCompilingPrimitive) && - (classSym != NothingClass && classSym != NullClass), - s"Cannot create ClassBType for special class symbol ${classSym.fullName}") + assertClassNotArrayNotPrimitive(classSym) + assert(!primitiveTypeMap.contains(classSym) || isCompilingPrimitive, s"Cannot create ClassBType for primitive class symbol $classSym") + if (classSym == NothingClass) RT_NOTHING + else if (classSym == NullClass) RT_NULL + else { + val internalName = classSym.javaBinaryName.toString + classBTypeFromInternalName.getOrElse(internalName, { + // The new ClassBType is added to the map in its constructor, before we set its info. This + // allows initializing cyclic dependencies, see the comment on variable ClassBType._info. + setClassInfo(classSym, ClassBType(internalName)) + }) + } + } - val internalName = classSym.javaBinaryName.toString - classBTypeFromInternalName.getOrElse(internalName, { - // The new ClassBType is added to the map in its constructor, before we set its info. This - // allows initializing cyclic dependencies, see the comment on variable ClassBType._info. - setClassInfo(classSym, ClassBType(internalName)) - }) + /** + * Builds a [[MethodBType]] for a method symbol. + */ + final def methodBTypeFromSymbol(methodSymbol: Symbol): MethodBType = { + assert(methodSymbol.isMethod, s"not a method-symbol: $methodSymbol") + val resultType: BType = + if (methodSymbol.isClassConstructor || methodSymbol.isConstructor) UNIT + else typeToBType(methodSymbol.tpe.resultType) + MethodBType(methodSymbol.tpe.paramTypes map typeToBType, resultType) + } + + /** + * This method returns the BType for a type reference, for example a parameter type. + * + * If `t` references a class, typeToBType ensures that the class is not an implementation class. + * See also comment on classBTypeFromSymbol, which is invoked for implementation classes. + */ + final def typeToBType(t: Type): BType = { + import definitions.ArrayClass + + /** + * Primitive types are represented as TypeRefs to the class symbol of, for example, scala.Int. + * The `primitiveTypeMap` maps those class symbols to the corresponding PrimitiveBType. + */ + def primitiveOrClassToBType(sym: Symbol): BType = { + assertClassNotArray(sym) + assert(!sym.isImplClass, sym) + primitiveTypeMap.getOrElse(sym, classBTypeFromSymbol(sym)) + } + + /** + * When compiling Array.scala, the type parameter T is not erased and shows up in method + * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference. + */ + def nonClassTypeRefToBType(sym: Symbol): ClassBType = { + assert(sym.isType && isCompilingArray, sym) + ObjectReference + } + + t.dealiasWiden match { + case TypeRef(_, ArrayClass, List(arg)) => ArrayBType(typeToBType(arg)) // Array type such as Array[Int] (kept by erasure) + case TypeRef(_, sym, _) if !sym.isClass => nonClassTypeRefToBType(sym) // See comment on nonClassTypeRefToBType + case TypeRef(_, sym, _) => primitiveOrClassToBType(sym) // Common reference to a type such as scala.Int or java.lang.String + case ClassInfoType(_, _, sym) => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes typeToBType(moduleClassSymbol.info) + + /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for + * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning. + * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala. + */ + case a @ AnnotatedType(_, t) => + debuglog(s"typeKind of annotated type $a") + typeToBType(t) + + /* ExistentialType should (probably) be eliminated by erasure. We know they get here for + * classOf constants: + * class C[T] + * class T { final val k = classOf[C[_]] } + */ + case e @ ExistentialType(_, t) => + debuglog(s"typeKind of existential type $e") + typeToBType(t) + + /* The cases below should probably never occur. They are kept for now to avoid introducing + * new compiler crashes, but we added a warning. The compiler / library bootstrap and the + * test suite don't produce any warning. + */ + + case tp => + currentUnit.warning(tp.typeSymbol.pos, + s"an unexpected type representation reached the compiler backend while compiling $currentUnit: $tp. " + + "If possible, please file a bug on issues.scala-lang.org.") + + tp match { + case ThisType(ArrayClass) => ObjectReference // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test + case ThisType(sym) => classBTypeFromSymbol(sym) + case SingleType(_, sym) => primitiveOrClassToBType(sym) + case ConstantType(_) => typeToBType(t.underlying) + case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b)) + } + } + } + + def assertClassNotArray(sym: Symbol): Unit = { + assert(sym.isClass, sym) + assert(sym != definitions.ArrayClass || isCompilingArray, sym) + } + + def assertClassNotArrayNotPrimitive(sym: Symbol): Unit = { + assertClassNotArray(sym) + assert(!primitiveTypeMap.contains(sym) || isCompilingPrimitive, sym) } private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = { @@ -217,7 +366,9 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val nestedInfo = buildNestedInfo(classSym) - classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo) + val inlineInfos = buildInlineInfos(classSym) + + classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfos) classBType } @@ -272,6 +423,27 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } } + private def buildInlineInfos(classSym: Symbol): Map[String, MethodInlineInfo] = { + if (!settings.YoptInlinerEnabled) Map.empty + else { + // Primitve methods cannot be inlined, so there's no point in building an InlineInfo. Also, some + // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]]. + classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).map({ + case methodSym => + val methodBType = methodBTypeFromSymbol(methodSym) + val name = methodSym.javaSimpleName.toString // same as in genDefDef + val signature = name + methodBType.descriptor + val info = MethodInlineInfo( + effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden, + traitMethodWithStaticImplementation = false, // temporary, fixed in future commit + annotatedInline = methodSym.hasAnnotation(ScalaInlineClass), + annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass) + ) + (signature, info) + }).toMap + } + } + /** * For top-level objects without a companion class, the compilere generates a mirror class with * static forwarders (Java compat). There's no symbol for the mirror class, but we still need a @@ -289,7 +461,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { interfaces = Nil, flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL, nestedClasses = nested, - nestedInfo = None + nestedInfo = None, + Map.empty // no InlineInfo needed, scala never invokes methods on the mirror class ) c }) 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 4cff92d38b..74f46d04f9 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -293,7 +293,7 @@ object BytecodeUtils { } class BasicAnalyzer(methodNode: MethodNode, classInternalName: InternalName) { - val analyzer = new Analyzer[BasicValue](new BasicInterpreter) + val analyzer = new Analyzer(new BasicInterpreter) analyzer.analyze(classInternalName, methodNode) def frameAt(instruction: AbstractInsnNode): Frame[BasicValue] = analyzer.getFrames()(methodNode.instructions.indexOf(instruction)) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala new file mode 100644 index 0000000000..bfaa67004c --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -0,0 +1,114 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm +package opt + +import scala.tools.asm.tree._ +import scala.collection.convert.decorateAsScala._ +import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.BasicAnalyzer + +class CallGraph[BT <: BTypes](val btypes: BT) { + import btypes._ + + val callsites: collection.concurrent.Map[MethodInsnNode, Callsite] = recordPerRunCache(collection.concurrent.TrieMap.empty[MethodInsnNode, Callsite]) + + def addClass(classNode: ClassNode): Unit = { + for (m <- classNode.methods.asScala; callsite <- analyzeCallsites(m, classBTypeFromClassNode(classNode))) + callsites(callsite.callsiteInstruction) = callsite + } + + def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): List[Callsite] = { + // TODO: run dataflow analyses to make the call graph more precise + // - producers to get forwarded parameters (ForwardedParam) + // - typeAnalysis for more precise argument types, more precise callee + // - nullAnalysis to skip emitting the receiver-null-check when inlining + + // TODO: for now we run a basic analyzer to get the stack height at the call site. + // once we run a more elaborate analyzer (types, nullness), we can get the stack height out of there. + val analyzer = new BasicAnalyzer(methodNode, definingClass.internalName) + + methodNode.instructions.iterator.asScala.collect({ + case call: MethodInsnNode => + // TODO: log an inliner warning if the callee method cannot be found in the code repo? eg it's not on the classpath. + val callee = byteCodeRepository.methodNode(call.owner, call.name, call.desc) map { + case (method, declarationClass) => + val (declarationClassNode, source) = byteCodeRepository.classNodeAndSource(declarationClass) + val declarationClassBType = classBTypeFromClassNode(declarationClassNode) + val methodSignature = method.name + method.desc + val (safeToInline, annotatedInline, annotatedNoInline) = declarationClassBType.info.inlineInfos.get(methodSignature) match { + case Some(inlineInfo) => + val canInlineFromSource = inlineGlobalEnabled || source == ByteCodeRepository.CompilationUnit + // TODO: for now, we consider a callee safeToInline only if it's final + // type analysis can render more calls safeToInline (e.g. when the precise receiver type is known) + (canInlineFromSource && inlineInfo.effectivelyFinal, Some(inlineInfo.annotatedInline), Some(inlineInfo.annotatedNoInline)) + case None => + (false, None, None) + } + Callee( + callee = method, + calleeDeclarationClass = declarationClassBType, + safeToInline = safeToInline, + annotatedInline = annotatedInline, + annotatedNoInline = annotatedNoInline + ) + } + + val argInfos = if (callee.isEmpty) Nil else { + // TODO: for now it's Nil, because we don't run any data flow analysis + // there's no point in using the parameter types, that doesn't add any information. + // NOTE: need to run the same analyses after inlining, to re-compute the argInfos for the + // new duplicated callsites, see Inliner.inline + Nil + } + + Callsite( + callsiteInstruction = call, + callsiteMethod = methodNode, + callsiteClass = definingClass, + callee = callee, + argInfos = argInfos, + callsiteStackHeight = analyzer.frameAt(call).getStackSize + ) + }).toList + } + + /** + * A callsite in the call graph. + * @param callsiteInstruction The invocation instruction + * @param callsiteMethod The method containing the callsite + * @param callsiteClass The class containing the callsite + * @param callee The callee. For virtual calls, an override of the callee might be invoked. + * @param argInfos Information about the invocation receiver and arguments + * @param callsiteStackHeight The stack height at the callsite, required by the inliner + */ + final case class Callsite(callsiteInstruction: MethodInsnNode, callsiteMethod: MethodNode, callsiteClass: ClassBType, + callee: Option[Callee], argInfos: List[ArgInfo], + callsiteStackHeight: Int) { + override def toString = s"Invocation of ${callsiteInstruction.name + callsiteInstruction.desc}@${callsiteMethod.instructions.indexOf(callsiteInstruction)} in ${callsiteMethod.name}" + } + + /** + * Information about invocation arguments, obtained through data flow analysis of the callsite method. + */ + sealed trait ArgInfo + final case class ArgTypeInfo(argType: BType, isPrecise: Boolean, knownNotNull: Boolean) extends ArgInfo + final case class ForwardedParam(index: Int) extends ArgInfo + // can be extended, e.g., with constant types + + /** + * A callee in the call graph. + * @param callee The called method. For virtual calls, an override may actually be invoked. + * @param calleeDeclarationClass The class in which the callee is declared + * @param safeToInline True if the callee can be safely inlined: it cannot be overridden, + * and the inliner settings (project / global) allow inlining it. + * @param annotatedInline Defined if it is known whether the callee is annotated @inline + * @param annotatedNoInline Defined if it is known whether the callee is annotated @noinline + */ + final case class Callee(callee: MethodNode, calleeDeclarationClass: ClassBType, + safeToInline: Boolean, + annotatedInline: Option[Boolean], annotatedNoInline: Option[Boolean]) +} 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 f964b5b25d..7527491e9b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -19,7 +19,7 @@ import scala.tools.asm.tree.analysis._ class Inliner[BT <: BTypes](val btypes: BT) { import btypes._ - import btypes.byteCodeRepository + import callGraph._ /** * Copy and adapt the instructions of a method to a callsite. @@ -152,6 +152,29 @@ class Inliner[BT <: BTypes](val btypes: BT) { callsiteMethod.localVariables.addAll(cloneLocalVariableNodes(callee, labelsMap, callee.name + "_").asJava) callsiteMethod.tryCatchBlocks.addAll(cloneTryCatchBlockNodes(callee, labelsMap).asJava) + // Add all invocation instructions that were inlined to the call graph + callee.instructions.iterator().asScala foreach { + case originalCallsiteIns: MethodInsnNode => + callGraph.callsites.get(originalCallsiteIns) match { + case Some(originalCallsite) => + val newCallsiteIns = instructionMap(originalCallsiteIns).asInstanceOf[MethodInsnNode] + callGraph.callsites(newCallsiteIns) = Callsite( + callsiteInstruction = newCallsiteIns, + callsiteMethod = callsiteMethod, + callsiteClass = callsiteClass, + callee = originalCallsite.callee, + argInfos = Nil, // TODO: re-compute argInfos for new destination (once we actually compute them) + callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight + ) + + case None => + } + + case _ => + } + // Remove the elided invocation from the call graph + callGraph.callsites.remove(callsiteInstruction) + callsiteMethod.maxLocals += returnType.getSize + callee.maxLocals callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, callee.maxStack + callsiteStackHeight) 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 3a7250031a..23c8daa046 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala @@ -9,7 +9,7 @@ package opt import scala.annotation.switch import scala.tools.asm.Opcodes -import scala.tools.asm.tree.analysis.{Analyzer, BasicValue, BasicInterpreter} +import scala.tools.asm.tree.analysis.{Analyzer, BasicInterpreter} import scala.tools.asm.tree._ import scala.collection.convert.decorateAsScala._ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ @@ -149,7 +149,7 @@ class LocalOpt(settings: ScalaSettings) { def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: String): (Boolean, Set[LabelNode]) = { // The data flow analysis requires the maxLocals / maxStack fields of the method to be computed. computeMaxLocalsMaxStack(method) - val a = new Analyzer[BasicValue](new BasicInterpreter) + val a = new Analyzer(new BasicInterpreter) a.analyze(ownerClassName, method) val frames = a.getFrames diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index d7f4cca615..9674b4cfae 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -222,6 +222,8 @@ trait ScalaSettings extends AbsScalaSettings val emptyLineNumbers = Choice("empty-line-numbers", "Eliminate unnecessary line number information.") val emptyLabels = Choice("empty-labels", "Eliminate and collapse redundant labels in the bytecode.") val compactLocals = Choice("compact-locals", "Eliminate empty slots in the sequence of local variables.") + val inlineProject = Choice("inline-project", "Inline only methods defined in the files being compiled") + val inlineGlobal = Choice("inline-global", "Inline methods from any source, including classfiles on the compile classpath") val lNone = Choice("l:none", "Don't enable any optimizations.") @@ -231,10 +233,10 @@ trait ScalaSettings extends AbsScalaSettings private val methodChoices = List(unreachableCode, simplifyJumps, recurseUnreachableJumps, emptyLineNumbers, emptyLabels, compactLocals) val lMethod = Choice("l:method", "Enable intra-method optimizations: "+ methodChoices.mkString(","), expandsTo = methodChoices) - private val projectChoices = List(lMethod) + private val projectChoices = List(lMethod, inlineProject) val lProject = Choice("l:project", "Enable cross-method optimizations within the current project: "+ projectChoices.mkString(","), expandsTo = projectChoices) - private val classpathChoices = List(lProject) + private val classpathChoices = List(lProject, inlineGlobal) val lClasspath = Choice("l:classpath", "Enable cross-method optimizations across the entire classpath: "+ classpathChoices.mkString(","), expandsTo = classpathChoices) } @@ -252,6 +254,10 @@ trait ScalaSettings extends AbsScalaSettings def YoptEmptyLabels = Yopt.contains(YoptChoices.emptyLabels) def YoptCompactLocals = Yopt.contains(YoptChoices.compactLocals) + def YoptInlineProject = Yopt.contains(YoptChoices.inlineProject) + def YoptInlineGlobal = Yopt.contains(YoptChoices.inlineGlobal) + def YoptInlinerEnabled = YoptInlineProject || YoptInlineGlobal + private def removalIn212 = "This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug." object YstatisticsPhases extends MultiChoiceEnumeration { val parser, typer, patmat, erasure, cleanup, jvm = Value } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala index 4481fcd6be..65c96226ff 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala @@ -59,6 +59,15 @@ class BTypesFromClassfileTest { else (fromSym.flags | ACC_PRIVATE | ACC_PUBLIC) == (fromClassfile.flags | ACC_PRIVATE | ACC_PUBLIC) }, s"class flags differ\n$fromSym\n$fromClassfile") + // when parsing from classfile, the inline infos are obtained through the classSymbol, which + // is searched based on the classfile name. this lookup can fail. + assert(fromSym.inlineInfos.size == fromClassfile.inlineInfos.size || fromClassfile.inlineInfos.isEmpty, + s"wrong # of inline infos:\n${fromSym.inlineInfos.keys.toList.sorted}\n${fromClassfile.inlineInfos.keys.toList.sorted}") + fromClassfile.inlineInfos foreach { + case (signature, inlineInfo) => + assert(fromSym.inlineInfos(signature) == inlineInfo, s"inline infos differ for $signature:\n$inlineInfo\n${fromClassfile.inlineInfos(signature)}") + } + val chk1 = sameBTypes(fromSym.superClass, fromClassfile.superClass, checked) val chk2 = sameBTypes(fromSym.interfaces, fromClassfile.interfaces, chk1) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala new file mode 100644 index 0000000000..400fb6b00a --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -0,0 +1,146 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.collection.generic.Clearable +import scala.tools.asm.Opcodes._ +import org.junit.Assert._ + +import scala.tools.asm.tree._ +import scala.tools.asm.tree.analysis._ +import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.BasicAnalyzer +import scala.tools.testing.AssertUtil._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import AsmUtils._ + +import scala.collection.convert.decorateAsScala._ + +@RunWith(classOf[JUnit4]) +class CallGraphTest { + // no need to move the compiler instance to a companion: there's a single test method, so only a + // single instance created. + val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:inline-global") + import compiler.genBCode.bTypes._ + + // allows inspecting the caches after a compilation run + val notPerRun: List[Clearable] = List(classBTypeFromInternalName, byteCodeRepository.classes, callGraph.callsites) + notPerRun foreach compiler.perRunCaches.unrecordCache + + def compile(code: String): List[ClassNode] = { + notPerRun.foreach(_.clear()) + compileClasses(compiler)(code) + } + + def callsInMethod(methodNode: MethodNode): List[MethodInsnNode] = methodNode.instructions.iterator.asScala.collect({ + case call: MethodInsnNode => call + }).toList + + @Test + def callGraphStructure(): Unit = { + val code = + """class C { + | // try-catch prevents inlining - we want to analyze the callsite + | def f1 = try { 0 } catch { case _: Throwable => 1 } + | final def f2 = try { 0 } catch { case _: Throwable => 1 } + | + | @inline def f3 = try { 0 } catch { case _: Throwable => 1 } + | @inline final def f4 = try { 0 } catch { case _: Throwable => 1 } + | + | @noinline def f5 = try { 0 } catch { case _: Throwable => 1 } + | @noinline final def f6 = try { 0 } catch { case _: Throwable => 1 } + | + | @inline @noinline def f7 = try { 0 } catch { case _: Throwable => 1 } + |} + |class D extends C { + | @inline override def f1 = try { 0 } catch { case _: Throwable => 1 } + | override final def f3 = try { 0 } catch { case _: Throwable => 1 } + |} + |object C { + | def g1 = try { 0 } catch { case _: Throwable => 1 } + |} + |class Test { + | def t1(c: C) = c.f1 + c.f2 + c.f3 + c.f4 + c.f5 + c.f6 + c.f7 + C.g1 + | def t2(d: D) = d.f1 + d.f2 + d.f3 + d.f4 + d.f5 + d.f6 + d.f7 + C.g1 + |} + """.stripMargin + + // Get the ClassNodes from the code repo (don't use the unparsed ClassNodes returned by compile). + // The callGraph.callsites map is indexed by instructions of those ClassNodes. + val clss @ List(cCls, cMod, dCls, testCls) = compile(code).map(c => byteCodeRepository.classNode(c.name)) + clss.foreach(cls => { + // add classes to the call graph manually, the compiler doesn't do it yet. the next commit removes these lines. + cls.methods.asScala foreach BytecodeUtils.computeMaxLocalsMaxStack + callGraph.addClass(cls) + }) + + + val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = cCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name) + val List(df1, df3) = dCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name) + val g1 = cMod.methods.iterator.asScala.find(_.name == "g1").get + val List(t1, t2) = testCls.methods.iterator.asScala.filter(_.name.startsWith("t")).toList.sortBy(_.name) + + val List(cf1Call, cf2Call, cf3Call, cf4Call, cf5Call, cf6Call, cf7Call, cg1Call) = callsInMethod(t1) + val List(df1Call, df2Call, df3Call, df4Call, df5Call, df6Call, df7Call, dg1Call) = callsInMethod(t2) + + def checkCallsite(callsite: callGraph.Callsite, + call: MethodInsnNode, callsiteMethod: MethodNode, target: MethodNode, calleeDeclClass: ClassBType, + safeToInline: Boolean, atInline: Boolean, atNoInline: Boolean) = try { + assert(callsite.callsiteInstruction == call) + assert(callsite.callsiteMethod == callsiteMethod) + val callee = callsite.callee.get + assert(callee.callee == target) + assert(callee.calleeDeclarationClass == calleeDeclClass) + assert(callee.safeToInline == safeToInline) + assert(callee.annotatedInline.get == atInline) + assert(callee.annotatedNoInline.get == atNoInline) + + assert(callsite.argInfos == List()) // not defined yet + } catch { + case e: Throwable => println(callsite); throw e + } + + val cClassBType = classBTypeFromClassNode(cCls) + val cMClassBType = classBTypeFromClassNode(cMod) + val dClassBType = classBTypeFromClassNode(dCls) + + checkCallsite(callGraph.callsites(cf1Call), + cf1Call, t1, cf1, cClassBType, false, false, false) + checkCallsite(callGraph.callsites(cf2Call), + cf2Call, t1, cf2, cClassBType, true, false, false) + checkCallsite(callGraph.callsites(cf3Call), + cf3Call, t1, cf3, cClassBType, false, true, false) + checkCallsite(callGraph.callsites(cf4Call), + cf4Call, t1, cf4, cClassBType, true, true, false) + checkCallsite(callGraph.callsites(cf5Call), + cf5Call, t1, cf5, cClassBType, false, false, true) + checkCallsite(callGraph.callsites(cf6Call), + cf6Call, t1, cf6, cClassBType, true, false, true) + checkCallsite(callGraph.callsites(cf7Call), + cf7Call, t1, cf7, cClassBType, false, true, true) + checkCallsite(callGraph.callsites(cg1Call), + cg1Call, t1, g1, cMClassBType, true, false, false) + + checkCallsite(callGraph.callsites(df1Call), + df1Call, t2, df1, dClassBType, false, true, false) + checkCallsite(callGraph.callsites(df2Call), + df2Call, t2, cf2, cClassBType, true, false, false) + checkCallsite(callGraph.callsites(df3Call), + df3Call, t2, df3, dClassBType, true, false, false) + checkCallsite(callGraph.callsites(df4Call), + df4Call, t2, cf4, cClassBType, true, true, false) + checkCallsite(callGraph.callsites(df5Call), + df5Call, t2, cf5, cClassBType, false, false, true) + checkCallsite(callGraph.callsites(df6Call), + df6Call, t2, cf6, cClassBType, true, false, true) + checkCallsite(callGraph.callsites(df7Call), + df7Call, t2, cf7, cClassBType, false, true, true) + checkCallsite(callGraph.callsites(dg1Call), + dg1Call, t2, g1, cMClassBType, true, false, false) + } +} 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 2ec6853f13..6cd89e1323 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -26,7 +26,7 @@ object InlinerTest extends ClearAfterClass.Clearable { var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:project") // allows inspecting the caches after a compilation run - def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes) + def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes, compiler.genBCode.bTypes.callGraph.callsites) notPerRun foreach compiler.perRunCaches.unrecordCache def clear(): Unit = { compiler = null } @@ -41,11 +41,7 @@ class InlinerTest extends ClearAfterClass { def compile(code: String): List[ClassNode] = { InlinerTest.notPerRun.foreach(_.clear()) - val cls = compileClasses(compiler)(code) - // the compiler doesn't add classes being compiled to the code repo yet, so we do it manually. - // this line is removed in the next commit. - for (c <- cls) byteCodeRepository.classes(c.name) = (c, ByteCodeRepository.Classfile) - cls + compileClasses(compiler)(code) } // inline first invocation of f into g in class C -- cgit v1.2.3 From 37f7b76710c72360577250f07bd8b5cf55e527cc Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 19 Jan 2015 10:00:04 +0100 Subject: Integrate the inliner into the backend pipeline The current heuristics are simple: attempt to inline every method annotated `@inline`. Cycles in the inline request graph are broken in a determinisitc manner. Inlining is then performed starting at the leaves of the inline request graph, i.e., with those callsites where the target method has no callsites to inline. This expansion strategy can make a method grow arbitrarily. We will most likely have to add some thresholds and / or other measures to prevent size issues. --- .../scala/tools/nsc/backend/jvm/GenBCode.scala | 22 ++- .../scala/tools/nsc/backend/jvm/opt/Inliner.scala | 122 ++++++++++++ .../scala/tools/nsc/backend/jvm/opt/LocalOpt.scala | 38 +++- .../tools/nsc/backend/jvm/opt/CallGraphTest.scala | 10 +- .../tools/nsc/backend/jvm/opt/InlinerTest.scala | 211 ++++++++++++++++++++- 5 files changed, 389 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index 9b3bd7648d..173aa0ca30 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -217,11 +217,26 @@ abstract class GenBCode extends BCodeSyncAndTry { class Worker2 { lazy val localOpt = new LocalOpt(settings) + def runGlobalOptimizations(): Unit = { + import scala.collection.convert.decorateAsScala._ + q2.asScala foreach { + case Item2(_, _, plain, _, _) => + // skip mirror / bean: wd don't inline into tem, and they are not used in the plain class + if (plain != null) { + localOpt.minimalRemoveUnreachableCode(plain) + callGraph.addClass(plain) + } + } + bTypes.inliner.runInliner() + } + def localOptimizations(classNode: ClassNode): Unit = { BackendStats.timed(BackendStats.methodOptTimer)(localOpt.methodOptimizations(classNode)) } def run() { + if (settings.YoptInlinerEnabled) runGlobalOptimizations() + while (true) { val item = q2.poll if (item.isPoison) { @@ -269,7 +284,12 @@ abstract class GenBCode extends BCodeSyncAndTry { var arrivalPos = 0 - /* + /** + * The `run` method is overridden because the backend has a different data flow than the default + * phase: the backend does not transform compilation units one by one, but on all units in the + * same run. This allows cross-unit optimizations and running some stages of the backend + * concurrently on multiple units. + * * A run of the BCodePhase phase comprises: * * (a) set-up steps (most notably supporting maps in `BCodeTypes`, 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 7527491e9b..b74c7ba86c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -7,6 +7,7 @@ package scala.tools.nsc package backend.jvm package opt +import scala.annotation.tailrec import scala.tools.asm import asm.Opcodes._ import asm.tree._ @@ -16,11 +17,132 @@ import AsmUtils._ import BytecodeUtils._ import OptimizerReporting._ import scala.tools.asm.tree.analysis._ +import collection.mutable class Inliner[BT <: BTypes](val btypes: BT) { import btypes._ import callGraph._ + def runInliner(): Unit = { + for (request <- collectAndOrderInlineRequests) { + val Some(callee) = request.callee + inline(request.callsiteInstruction, request.callsiteStackHeight, request.callsiteMethod, request.callsiteClass, + callee.callee, callee.calleeDeclarationClass, + receiverKnownNotNull = false, keepLineNumbers = false) + } + } + + /** + * Ordering for inline requests. Required to make the inliner deterministic: + * - Always remove the same request when breaking inlining cycles + * - Perform inlinings in a consistent order + */ + object callsiteOrdering extends Ordering[Callsite] { + override def compare(x: Callsite, y: Callsite): Int = { + val cls = x.callsiteClass.internalName compareTo y.callsiteClass.internalName + if (cls != 0) return cls + + val name = x.callsiteMethod.name compareTo y.callsiteMethod.name + if (name != 0) return name + + val desc = x.callsiteMethod.desc compareTo y.callsiteMethod.desc + if (desc != 0) return desc + + def pos(c: Callsite) = c.callsiteMethod.instructions.indexOf(c.callsiteInstruction) + pos(x) - pos(y) + } + } + + /** + * Select callsites from the call graph that should be inlined. The resulting list of inlining + * requests is allowed to have cycles, and the callsites can appear in any order. + */ + def selectCallsitesForInlining: List[Callsite] = { + callsites.iterator.filter({ + case (_, callsite) => callsite.callee match { + case Some(Callee(callee, _, safeToInline, Some(annotatedInline), _)) => + // TODO: fix inlining from traits. + // For trait methods the callee is abstract: "trait T { @inline final def f = 1}". + // A callsite (t: T).f is `safeToInline` (effectivelyFinal is true), but the callee is the + // abstract method in the interface. + !isAbstractMethod(callee) && safeToInline && annotatedInline + case _ => false + } + case _ => false + }).map(_._2).toList + } + + /** + * Returns the callsites that can be inlined. Ensures that the returned inline request graph does + * not contain cycles. + * + * The resulting list is sorted such that the leaves of the inline request graph are on the left. + * Once these leaves are inlined, the successive elements will be leaves, etc. + */ + private def collectAndOrderInlineRequests: List[Callsite] = { + val requests = selectCallsitesForInlining + + // This map is an index to look up the inlining requests for a method. The value sets are mutable + // to allow removing elided requests (to break inlining cycles). The map itself is mutable to + // allow efficient building: requests.groupBy would build values as List[Callsite] that need to + // be transformed to mutable sets. + val inlineRequestsForMethod: mutable.Map[MethodNode, mutable.Set[Callsite]] = mutable.HashMap.empty.withDefaultValue(mutable.HashSet.empty) + for (r <- requests) inlineRequestsForMethod.getOrElseUpdate(r.callsiteMethod, mutable.HashSet.empty) += r + + /** + * Break cycles in the inline request graph by removing callsites. + * + * The list `requests` is traversed left-to-right, removing those callsites that are part of a + * cycle. Elided callsites are also removed from the `inlineRequestsForMethod` map. + */ + def breakInlineCycles(requests: List[Callsite]): List[Callsite] = { + // is there a path of inline requests from start to goal? + def isReachable(start: MethodNode, goal: MethodNode): Boolean = { + @tailrec def reachableImpl(check: List[MethodNode], visited: Set[MethodNode]): Boolean = check match { + case x :: xs => + if (x == goal) true + else if (visited(x)) reachableImpl(xs, visited) + else { + val callees = inlineRequestsForMethod(x).map(_.callee.get.callee) + reachableImpl(xs ::: callees.toList, visited + x) + } + + case Nil => + false + } + reachableImpl(List(start), Set.empty) + } + + val result = new mutable.ListBuffer[Callsite]() + // sort the inline requests to ensure that removing requests is deterministic + for (r <- requests.sorted(callsiteOrdering)) { + // is there a chain of inlining requests that would inline the callsite method into the callee? + if (isReachable(r.callee.get.callee, r.callsiteMethod)) + inlineRequestsForMethod(r.callsiteMethod) -= r + else + result += r + } + result.toList + } + + // sort the remaining inline requests such that the leaves appear first, then those requests + // that become leaves, etc. + def leavesFirst(requests: List[Callsite], visited: Set[Callsite] = Set.empty): List[Callsite] = { + if (requests.isEmpty) Nil + else { + val (leaves, others) = requests.partition(r => { + val inlineRequestsForCallee = inlineRequestsForMethod(r.callee.get.callee) + inlineRequestsForCallee.forall(visited) + }) + assert(leaves.nonEmpty, requests) + leaves ::: leavesFirst(others, visited ++ leaves) + } + } + + leavesFirst(breakInlineCycles(requests)) + } + + /** * Copy and adapt the instructions of a method to a callsite. * 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 23c8daa046..8c5a31658c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala @@ -12,6 +12,7 @@ import scala.tools.asm.Opcodes import scala.tools.asm.tree.analysis.{Analyzer, BasicInterpreter} import scala.tools.asm.tree._ import scala.collection.convert.decorateAsScala._ +import scala.tools.nsc.backend.jvm.BTypes.InternalName import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ import scala.tools.nsc.settings.ScalaSettings @@ -47,6 +48,37 @@ import scala.tools.nsc.settings.ScalaSettings * - 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 + } + } + + /** + * Remove unreachable code from a method. + * + * This implementation only removes instructions that are unreachable for an ASM analyzer / + * interpreter. This ensures that future analyses will not produce `null` frames. The inliner + * depends on this property. + */ + def minimalRemoveUnreachableCode(method: MethodNode, ownerClassName: InternalName): Boolean = { + if (method.instructions.size == 0) return false // fast path for abstract methods + + val (codeRemoved, _) = removeUnreachableCodeImpl(method, ownerClassName) + if (codeRemoved) { + // Required for correctness, see comment in class LocalOpt + removeEmptyExceptionHandlers(method) + removeUnusedLocalVariableNodes(method)() + } + codeRemoved + } + /** * Remove unreachable instructions from all (non-abstract) methods and apply various other * cleanups to the bytecode. @@ -73,7 +105,7 @@ class LocalOpt(settings: ScalaSettings) { * * Returns `true` if the bytecode of `method` was changed. */ - def methodOptimizations(method: MethodNode, ownerClassName: String): Boolean = { + def methodOptimizations(method: MethodNode, ownerClassName: InternalName): Boolean = { if (method.instructions.size == 0) return false // fast path for abstract methods // unreachable-code also removes unused local variable nodes and empty exception handlers. @@ -124,7 +156,7 @@ class LocalOpt(settings: ScalaSettings) { // (*) Removing stale local variable descriptors is required for correctness of unreachable-code val localsRemoved = - if (settings.YoptCompactLocals) compactLocalVariables(method) + if (settings.YoptCompactLocals) compactLocalVariables(method) // also removes unused else if (settings.YoptUnreachableCode) removeUnusedLocalVariableNodes(method)() // (*) else false @@ -146,7 +178,7 @@ class LocalOpt(settings: ScalaSettings) { * * TODO: rewrite, don't use computeMaxLocalsMaxStack (runs a ClassWriter) / Analyzer. Too slow. */ - def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: String): (Boolean, Set[LabelNode]) = { + def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: InternalName): (Boolean, Set[LabelNode]) = { // The data flow analysis requires the maxLocals / maxStack fields of the method to be computed. computeMaxLocalsMaxStack(method) val a = new Analyzer(new BasicInterpreter) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala index 400fb6b00a..5946f50f0c 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -23,8 +23,6 @@ import scala.collection.convert.decorateAsScala._ @RunWith(classOf[JUnit4]) class CallGraphTest { - // no need to move the compiler instance to a companion: there's a single test method, so only a - // single instance created. val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:inline-global") import compiler.genBCode.bTypes._ @@ -72,13 +70,7 @@ class CallGraphTest { // Get the ClassNodes from the code repo (don't use the unparsed ClassNodes returned by compile). // The callGraph.callsites map is indexed by instructions of those ClassNodes. - val clss @ List(cCls, cMod, dCls, testCls) = compile(code).map(c => byteCodeRepository.classNode(c.name)) - clss.foreach(cls => { - // add classes to the call graph manually, the compiler doesn't do it yet. the next commit removes these lines. - cls.methods.asScala foreach BytecodeUtils.computeMaxLocalsMaxStack - callGraph.addClass(cls) - }) - + val List(cCls, cMod, dCls, testCls) = compile(code).map(c => byteCodeRepository.classNode(c.name)) val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = cCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name) val List(df1, df3) = dCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name) 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 6cd89e1323..819252841e 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -23,7 +23,7 @@ import scala.collection.convert.decorateAsScala._ import scala.tools.testing.ClearAfterClass object InlinerTest extends ClearAfterClass.Clearable { - var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:project") + var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:classpath") // allows inspecting the caches after a compilation run def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes, compiler.genBCode.bTypes.callGraph.callsites) @@ -44,6 +44,15 @@ class InlinerTest extends ClearAfterClass { compileClasses(compiler)(code) } + def checkCallsite(callsite: callGraph.Callsite, callee: MethodNode) = { + assert(callsite.callsiteMethod.instructions.contains(callsite.callsiteInstruction), instructionsFromMethod(callsite.callsiteMethod)) + + val callsiteClassNode = byteCodeRepository.classNode(callsite.callsiteClass.internalName) + assert(callsiteClassNode.methods.contains(callsite.callsiteMethod), callsiteClassNode.methods.asScala.map(_.name).toList) + + assert(callsite.callee.get.callee == callee, callsite.callee.get.callee.name) + } + // inline first invocation of f into g in class C def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): (MethodNode, Option[String]) = { val List(cls) = compile(code) @@ -205,4 +214,204 @@ class InlinerTest extends ClearAfterClass { assert(r.get contains "would cause an IllegalAccessError", r) } + + @Test + def inlineSimpleAtInline(): Unit = { + val code = + """class C { + | @inline final def f = 0 + | final def g = 1 + | + | def test = f + g + |} + """.stripMargin + val List(cCls) = compile(code) + val instructions = instructionsFromMethod(cCls.methods.asScala.find(_.name == "test").get) + assert(instructions.contains(Op(ICONST_0)), instructions mkString "\n") + assert(!instructions.contains(Op(ICONST_1)), instructions) + } + + @Test + def cyclicInline(): Unit = { + val code = + """class C { + | @inline final def f: Int = g + | @inline final def g: Int = f + |} + """.stripMargin + val List(c) = compile(code) + val methods @ List(_, g) = c.methods.asScala.filter(_.name.length == 1).toList + val List(fIns, gIns) = methods.map(instructionsFromMethod(_).dropNonOp) + val invokeG = Invoke(INVOKEVIRTUAL, "C", "g", "()I", false) + assert(fIns contains invokeG, fIns) // no inlining into f, that request is elided + assert(gIns contains invokeG, gIns) // f is inlined into g, g invokes itself recursively + + assert(callGraph.callsites.size == 3, callGraph.callsites) + for (callsite <- callGraph.callsites.values if methods.contains(callsite.callsiteMethod)) { + checkCallsite(callsite, g) + } + } + + @Test + def cyclicInline2(): Unit = { + val code = + """class C { + | @inline final def h: Int = f + | @inline final def f: Int = g + g + | @inline final def g: Int = h + |} + """.stripMargin + val List(c) = compile(code) + val methods @ List(f, g, h) = c.methods.asScala.filter(_.name.length == 1).sortBy(_.name).toList + val List(fIns, gIns, hIns) = methods.map(instructionsFromMethod(_).dropNonOp) + val invokeG = Invoke(INVOKEVIRTUAL, "C", "g", "()I", false) + assert(fIns.count(_ == invokeG) == 2, fIns) // no inlining into f, these requests are elided + assert(gIns.count(_ == invokeG) == 2, gIns) + assert(hIns.count(_ == invokeG) == 2, hIns) + + assert(callGraph.callsites.size == 7, callGraph.callsites) + for (callsite <- callGraph.callsites.values if methods.contains(callsite.callsiteMethod)) { + checkCallsite(callsite, g) + } + } + + @Test + def arraycopy(): Unit = { + // also tests inlining of a void-returning method (no return value on the stack) + val code = + """class C { + | def f(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int): Unit = { + | compat.Platform.arraycopy(src, srcPos, dest, destPos, length) + | } + |} + """.stripMargin + val List(c) = compile(code) + val ins = instructionsFromMethod(c.methods.asScala.find(_.name == "f").get) + val invokeSysArraycopy = Invoke(INVOKESTATIC, "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", false) + assert(ins contains invokeSysArraycopy, ins mkString "\n") + } + + @Test + def arrayMemberMethod(): Unit = { + // This used to crash when building the call graph. The `owner` field of the MethodInsnNode + // for the invocation of `clone` is not an internal name, but a full array descriptor + // [Ljava.lang.Object; - the documentation in the ASM library didn't mention that possibility. + val code = + """class C { + | def f(a: Array[Object]) = { + | a.clone() + | } + |} + """.stripMargin + val List(c) = compile(code) + assert(callGraph.callsites.values exists (_.callsiteInstruction.name == "clone")) + } + + @Test + def atInlineInTraitDoesNotCrash(): Unit = { + val code = + """trait T { + | @inline final def f = 0 + |} + |class C { + | def g(t: T) = t.f + |} + """.stripMargin + val List(c, t, tClass) = compile(code) + val ins = instructionsFromMethod(c.methods.asScala.find(_.name == "g").get) + val invokeF = Invoke(INVOKEINTERFACE, "T", "f", "()I", true) + // no inlining yet + assert(ins contains invokeF, ins mkString "\n") + } + + @Test + def inlinePrivateMethodWithHandler(): Unit = { + val code = + """class C { + | @inline private def f = try { 0 } catch { case _: Throwable => 1 } + | def g = f + |} + """.stripMargin + val List(c) = compile(code) + val ins = instructionsFromMethod(c.methods.asScala.find(_.name == "g").get) + println(ins) + // no more invoke, f is inlined + assert(ins.count(_.isInstanceOf[Invoke]) == 0, ins mkString "\n") + } + + @Test + def inlineStaticCall(): Unit = { + val code = + """class C { + | def f = Integer.lowestOneBit(103) + |} + """.stripMargin + + val List(c) = compile(code) + val f = c.methods.asScala.find(_.name == "f").get + val callsiteIns = f.instructions.iterator().asScala.collect({ case c: MethodInsnNode => c }).next() + val clsBType = classBTypeFromParsedClassfile(c.name) + val analyzer = new BasicAnalyzer(f, clsBType.internalName) + + val integerClassBType = classBTypeFromInternalName("java/lang/Integer") + val lowestOneBitMethod = byteCodeRepository.methodNode(integerClassBType.internalName, "lowestOneBit", "(I)I").get._1 + + val r = inliner.inline( + callsiteIns, + analyzer.frameAt(callsiteIns).getStackSize, + f, + clsBType, + lowestOneBitMethod, + integerClassBType, + receiverKnownNotNull = false, + keepLineNumbers = false) + + assert(r.isEmpty, r) + val ins = instructionsFromMethod(f) + + // no invocations, lowestOneBit is inlined + assert(ins.count(_.isInstanceOf[Invoke]) == 0, ins mkString "\n") + + // no null check when inlining a static method + ins foreach { + case Jump(IFNONNULL, _) => assert(false, ins mkString "\n") + case _ => + } + } + + @Test + def maxLocalsMaxStackAfterInline(): Unit = { + val code = + """class C { + | @inline final def f1(x: Int): Int = { + | val a = x + 1 + | math.max(a, math.min(10, a - 1)) + | } + | + | @inline final def f2(x: Int): Unit = { + | val a = x + 1 + | println(math.max(a, 10)) + | } + | + | def g1 = println(f1(32)) + | def g2 = println(f2(32)) + |} + """.stripMargin + + val List(c) = compile(code) + val ms @ List(f1, f2, g1, g2) = c.methods.asScala.filter(_.name.length == 2).toList + + // stack height at callsite of f1 is 1, so max of g1 after inlining is max of f1 + 1 + assert(g1.maxStack == 7 && f1.maxStack == 6, s"${g1.maxStack} - ${f1.maxStack}") + + // locals in f1: this, x, a + // locals in g1 after inlining: this, this-of-f1, x, a, return value + assert(g1.maxLocals == 5 && f1.maxLocals == 3, s"${g1.maxLocals} - ${f1.maxLocals}") + + // like maxStack in g1 / f1 + assert(g2.maxStack == 5 && f2.maxStack == 4, s"${g2.maxStack} - ${f2.maxStack}") + + // like maxLocals for g1 / f1, but no return value + assert(g2.maxLocals == 4 && f2.maxLocals == 3, s"${g2.maxLocals} - ${f2.maxLocals}") + } } -- cgit v1.2.3 From ea10434ff3bf24ac61dd4f65edcf931b7e988c0a Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 19 Jan 2015 12:11:33 +0100 Subject: Looking up the ClassNode for an InternalName returns an Option The `ByteCodeRepository.classNode(InternalName)` method now returns an option. Concretely, in mixed compilation, the compiler does not create a ClassNode for Java classes, and they may not exist on the classpath either. --- .../tools/nsc/backend/jvm/BCodeSkelBuilder.scala | 2 +- .../scala/tools/nsc/backend/jvm/BTypes.scala | 16 +++++++-- .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 2 +- .../nsc/backend/jvm/opt/ByteCodeRepository.scala | 34 +++++++++--------- .../tools/nsc/backend/jvm/opt/CallGraph.scala | 41 ++++++++++++---------- .../nsc/backend/jvm/opt/OptimizerReporting.scala | 1 + .../tools/nsc/backend/jvm/opt/CallGraphTest.scala | 2 +- .../backend/jvm/opt/InlinerIllegalAccessTest.scala | 2 +- .../tools/nsc/backend/jvm/opt/InlinerTest.scala | 2 +- 9 files changed, 58 insertions(+), 44 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index b4de5cf52f..32a421c570 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -129,7 +129,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { if (settings.YoptInlinerEnabled) { // The inliner needs to find all classes in the code repo, also those being compiled - byteCodeRepository.classes(cnode.name) = (cnode, ByteCodeRepository.CompilationUnit) + byteCodeRepository.classes(cnode.name) = Some((cnode, ByteCodeRepository.CompilationUnit)) } assert(cd.symbol == claszSymbol, "Someone messed up BCodePhase.claszSymbol during genPlainClass().") diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index c93496fb49..f07c7b7764 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -106,7 +106,13 @@ abstract class BTypes { * Parse the classfile for `internalName` and construct the [[ClassBType]]. */ def classBTypeFromParsedClassfile(internalName: InternalName): ClassBType = { - classBTypeFromClassNode(byteCodeRepository.classNode(internalName)) + val classNode = byteCodeRepository.classNode(internalName) getOrElse { + // There's no way out, we need the ClassBType. I (lry) only know one case byteCodeRepository.classNode + // returns None: for Java classes in mixed compilation. In this case we should not end up here, + // because there exists a symbol for that Java class, the ClassBType should be built from the symbol. + assertionError(s"Could not find bytecode for class $internalName") + } + classBTypeFromClassNode(classNode) } /** @@ -141,11 +147,15 @@ abstract class BTypes { * For local and anonymous classes, innerClassNode.outerName is null. Such classes are required * to have an EnclosingMethod attribute declaring the outer class. So we keep those local and * anonymous classes whose outerClass is classNode.name. - * */ def nestedInCurrentClass(innerClassNode: InnerClassNode): Boolean = { (innerClassNode.outerName != null && innerClassNode.outerName == classNode.name) || - (innerClassNode.outerName == null && byteCodeRepository.classNode(innerClassNode.name).outerClass == classNode.name) + (innerClassNode.outerName == null && { + val classNodeForInnerClass = byteCodeRepository.classNode(innerClassNode.name) getOrElse { + assertionError(s"Could not find bytecode for class ${innerClassNode.name}") + } + classNodeForInnerClass.outerClass == classNode.name + }) } val nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({ diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 9ed7b3174b..d94bd77851 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -36,7 +36,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val coreBTypes = new CoreBTypesProxy[this.type](this) import coreBTypes._ - val byteCodeRepository = new ByteCodeRepository(global.classPath, recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, (ClassNode, Source)])) + val byteCodeRepository = new ByteCodeRepository(global.classPath, recordPerRunCache(collection.concurrent.TrieMap.empty)) val inliner: Inliner[this.type] = new Inliner(this) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala index b3ac06877b..ea4dd0c032 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala @@ -25,21 +25,23 @@ import BTypes.InternalName * @param classes Cache for parsed ClassNodes. Also stores the source of the bytecode: * [[Classfile]] if read from `classPath`, [[CompilationUnit]] if the bytecode * corresponds to a class being compiled. + * For Java classes in mixed compilation, the map contains `None`: there is no + * ClassNode generated by the backend and also no classfile that could be parsed. */ -class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val classes: collection.concurrent.Map[InternalName, (ClassNode, Source)]) { +class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val classes: collection.concurrent.Map[InternalName, Option[(ClassNode, Source)]]) { /** * The class node and source for an internal name. If the class node is not yet available, it is * parsed from the classfile on the compile classpath. */ - def classNodeAndSource(internalName: InternalName): (ClassNode, Source) = { - classes.getOrElseUpdate(internalName, (parseClass(internalName), Classfile)) + def classNodeAndSource(internalName: InternalName): Option[(ClassNode, Source)] = { + classes.getOrElseUpdate(internalName, parseClass(internalName).map((_, Classfile))) } /** * The class node for an internal name. If the class node is not yet available, it is parsed from * the classfile on the compile classpath. */ - def classNode(internalName: InternalName) = classNodeAndSource(internalName)._1 + def classNode(internalName: InternalName): Option[ClassNode] = classNodeAndSource(internalName).map(_._1) /** * The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`. @@ -48,10 +50,10 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class * @return The [[FieldNode]] of the requested field and the [[InternalName]] of its declaring class. */ def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Option[(FieldNode, InternalName)] = { - val c = classNode(classInternalName) - c.fields.asScala.find(f => f.name == name && f.desc == descriptor).map((_, classInternalName)) orElse { - Option(c.superName).flatMap(n => fieldNode(n, name, descriptor)) - } + classNode(classInternalName).flatMap(c => + c.fields.asScala.find(f => f.name == name && f.desc == descriptor).map((_, classInternalName)) orElse { + Option(c.superName).flatMap(n => fieldNode(n, name, descriptor)) + }) } /** @@ -64,16 +66,16 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class // In a MethodInsnNode, the `owner` field may be an array descriptor, for exmple when invoking `clone`. if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[') None else { - val c = classNode(ownerInternalNameOrArrayDescriptor) - c.methods.asScala.find(m => m.name == name && m.desc == descriptor).map((_, ownerInternalNameOrArrayDescriptor)) orElse { - val parents = Option(c.superName) ++ c.interfaces.asScala - // `view` to stop at the first result - parents.view.flatMap(methodNode(_, name, descriptor)).headOption - } + classNode(ownerInternalNameOrArrayDescriptor).flatMap(c => + c.methods.asScala.find(m => m.name == name && m.desc == descriptor).map((_, ownerInternalNameOrArrayDescriptor)) orElse { + val parents = Option(c.superName) ++ c.interfaces.asScala + // `view` to stop at the first result + parents.view.flatMap(methodNode(_, name, descriptor)).headOption + }) } } - private def parseClass(internalName: InternalName): ClassNode = { + private def parseClass(internalName: InternalName): Option[ClassNode] = { val fullName = internalName.replace('/', '.') classPath.findClassFile(fullName) map { classFile => val classNode = new asm.tree.ClassNode() @@ -90,8 +92,6 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class // https://jcp.org/aboutJava/communityprocess/final/jsr045/index.html removeLineNumberNodes(classNode) classNode - } getOrElse { - inlineFailure(s"Class file for class $fullName not found.") } } } 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 bfaa67004c..ac40ab8904 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -34,27 +34,30 @@ class CallGraph[BT <: BTypes](val btypes: BT) { methodNode.instructions.iterator.asScala.collect({ case call: MethodInsnNode => // TODO: log an inliner warning if the callee method cannot be found in the code repo? eg it's not on the classpath. - val callee = byteCodeRepository.methodNode(call.owner, call.name, call.desc) map { + val callee = byteCodeRepository.methodNode(call.owner, call.name, call.desc) flatMap { case (method, declarationClass) => - val (declarationClassNode, source) = byteCodeRepository.classNodeAndSource(declarationClass) - val declarationClassBType = classBTypeFromClassNode(declarationClassNode) - val methodSignature = method.name + method.desc - val (safeToInline, annotatedInline, annotatedNoInline) = declarationClassBType.info.inlineInfos.get(methodSignature) match { - case Some(inlineInfo) => - val canInlineFromSource = inlineGlobalEnabled || source == ByteCodeRepository.CompilationUnit - // TODO: for now, we consider a callee safeToInline only if it's final - // type analysis can render more calls safeToInline (e.g. when the precise receiver type is known) - (canInlineFromSource && inlineInfo.effectivelyFinal, Some(inlineInfo.annotatedInline), Some(inlineInfo.annotatedNoInline)) - case None => - (false, None, None) + // TODO: log inliner warning if callee decl class cannot be found? + byteCodeRepository.classNodeAndSource(declarationClass) map { + case (declarationClassNode, source) => + val declarationClassBType = classBTypeFromClassNode(declarationClassNode) + val methodSignature = method.name + method.desc + val (safeToInline, annotatedInline, annotatedNoInline) = declarationClassBType.info.inlineInfos.get(methodSignature) match { + case Some(inlineInfo) => + val canInlineFromSource = inlineGlobalEnabled || source == ByteCodeRepository.CompilationUnit + // TODO: for now, we consider a callee safeToInline only if it's final + // type analysis can render more calls safeToInline (e.g. when the precise receiver type is known) + (canInlineFromSource && inlineInfo.effectivelyFinal, Some(inlineInfo.annotatedInline), Some(inlineInfo.annotatedNoInline)) + case None => + (false, None, None) + } + Callee( + callee = method, + calleeDeclarationClass = declarationClassBType, + safeToInline = safeToInline, + annotatedInline = annotatedInline, + annotatedNoInline = annotatedNoInline + ) } - Callee( - callee = method, - calleeDeclarationClass = declarationClassBType, - safeToInline = safeToInline, - annotatedInline = annotatedInline, - annotatedNoInline = annotatedNoInline - ) } val argInfos = if (callee.isEmpty) Nil else { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala index a918e13534..53c00c7724 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala @@ -21,6 +21,7 @@ object OptimizerReporting { classInternalName + "::" + method.name + method.desc } + // TODO: clean up reporting of the inliner, test inline failure warnings, etc def inlineFailure(reason: String): Nothing = MissingRequirementError.signal(reason) def assertionError(message: String): Nothing = throw new AssertionError(message) } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala index 5946f50f0c..69bd92b4ba 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -70,7 +70,7 @@ class CallGraphTest { // Get the ClassNodes from the code repo (don't use the unparsed ClassNodes returned by compile). // The callGraph.callsites map is indexed by instructions of those ClassNodes. - val List(cCls, cMod, dCls, testCls) = compile(code).map(c => byteCodeRepository.classNode(c.name)) + val List(cCls, cMod, dCls, testCls) = compile(code).map(c => byteCodeRepository.classNode(c.name).get) val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = cCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name) val List(df1, df3) = dCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala index 36f297767e..40dc990c0f 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala @@ -31,7 +31,7 @@ class InlinerIllegalAccessTest extends ClearAfterClass { val compiler = InlinerIllegalAccessTest.compiler import compiler.genBCode.bTypes._ - def addToRepo(cls: List[ClassNode]): Unit = for (c <- cls) byteCodeRepository.classes(c.name) = (c, ByteCodeRepository.Classfile) + def addToRepo(cls: List[ClassNode]): Unit = for (c <- cls) byteCodeRepository.classes(c.name) = Some((c, ByteCodeRepository.Classfile)) def assertEmpty(ins: Option[AbstractInsnNode]) = for (i <- ins) throw new AssertionError(textify(i)) @Test 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 819252841e..240d106f5c 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -47,7 +47,7 @@ class InlinerTest extends ClearAfterClass { def checkCallsite(callsite: callGraph.Callsite, callee: MethodNode) = { assert(callsite.callsiteMethod.instructions.contains(callsite.callsiteInstruction), instructionsFromMethod(callsite.callsiteMethod)) - val callsiteClassNode = byteCodeRepository.classNode(callsite.callsiteClass.internalName) + val callsiteClassNode = byteCodeRepository.classNode(callsite.callsiteClass.internalName).get assert(callsiteClassNode.methods.contains(callsite.callsiteMethod), callsiteClassNode.methods.asScala.map(_.name).toList) assert(callsite.callee.get.callee == callee, callsite.callee.get.callee.name) -- cgit v1.2.3 From 37c91654433a12249ae125b9454ba17cef103327 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 19 Jan 2015 15:16:23 +0100 Subject: After removing a live handler, need to re-run unreachableCode Building the call graph and running the inliner require all code to be reachable (in terms of ASM's data flow analysis / interpreter). After removing unreachable code, empty exception handlers need to be removed for correctness (see comment in LocalOpt.methodOptimizations). Removing a live exception handler renders its handler unreachable and therefore requires running another round of removing unreachable code. --- .../scala/tools/nsc/backend/jvm/opt/LocalOpt.scala | 33 ++++++++++++++-------- .../scala/tools/nsc/settings/ScalaSettings.scala | 4 +-- 2 files changed, 22 insertions(+), 15 deletions(-) (limited to 'src') 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 8c5a31658c..f6cfc5598b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala @@ -65,17 +65,25 @@ 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 - * depends on this property. + * and call graph builder depend on this property. */ def minimalRemoveUnreachableCode(method: MethodNode, ownerClassName: InternalName): Boolean = { if (method.instructions.size == 0) return false // fast path for abstract methods - val (codeRemoved, _) = removeUnreachableCodeImpl(method, ownerClassName) - if (codeRemoved) { - // Required for correctness, see comment in class LocalOpt - removeEmptyExceptionHandlers(method) - removeUnusedLocalVariableNodes(method)() + // For correctness, after removing unreachable code, we have to eliminate empty exception + // handlers, see scaladoc of def methodOptimizations. Removing an live handler may render more + // code unreachable and therefore requires running another round. + def removalRound(): Boolean = { + val (codeRemoved, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName) + if (codeRemoved) { + val liveHandlerRemoved = removeEmptyExceptionHandlers(method).exists(h => liveLabels(h.start)) + if (liveHandlerRemoved) removalRound() + } + codeRemoved } + + val codeRemoved = removalRound() + if (codeRemoved) removeUnusedLocalVariableNodes(method)() codeRemoved } @@ -134,9 +142,7 @@ class LocalOpt(settings: ScalaSettings) { // This triggers "ClassFormatError: Illegal exception table range in class file C". Similar // for local variables in dead blocks. Maybe that's a bug in the ASM framework. - var recurse = true - var codeHandlersOrJumpsChanged = false - while (recurse) { + def removalRound(): Boolean = { // unreachable-code, empty-handlers and simplify-jumps run until reaching a fixpoint (see doc on class LocalOpt) val (codeRemoved, handlersRemoved, liveHandlerRemoved) = if (settings.YoptUnreachableCode) { val (codeRemoved, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName) @@ -148,12 +154,15 @@ class LocalOpt(settings: ScalaSettings) { val jumpsChanged = if (settings.YoptSimplifyJumps) simplifyJumps(method) else false - codeHandlersOrJumpsChanged ||= (codeRemoved || handlersRemoved || jumpsChanged) + // Eliminating live handlers and simplifying jump instructions may render more code + // unreachable, so we need to run another round. + if (liveHandlerRemoved || jumpsChanged) removalRound() - // The doc comment of class LocalOpt explains why we recurse if jumpsChanged || liveHandlerRemoved - recurse = settings.YoptRecurseUnreachableJumps && (jumpsChanged || liveHandlerRemoved) + codeRemoved || handlersRemoved || jumpsChanged } + val codeHandlersOrJumpsChanged = removalRound() + // (*) Removing stale local variable descriptors is required for correctness of unreachable-code val localsRemoved = if (settings.YoptCompactLocals) compactLocalVariables(method) // also removes unused diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 9674b4cfae..43b634eee1 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -218,7 +218,6 @@ trait ScalaSettings extends AbsScalaSettings object YoptChoices extends MultiChoiceEnumeration { val unreachableCode = Choice("unreachable-code", "Eliminate unreachable code, exception handlers protecting no instructions, debug information of eliminated variables.") val simplifyJumps = Choice("simplify-jumps", "Simplify branching instructions, eliminate unnecessary ones.") - val recurseUnreachableJumps = Choice("recurse-unreachable-jumps", "Recursively apply unreachable-code and simplify-jumps (if enabled) until reaching a fixpoint.") val emptyLineNumbers = Choice("empty-line-numbers", "Eliminate unnecessary line number information.") val emptyLabels = Choice("empty-labels", "Eliminate and collapse redundant labels in the bytecode.") val compactLocals = Choice("compact-locals", "Eliminate empty slots in the sequence of local variables.") @@ -230,7 +229,7 @@ trait ScalaSettings extends AbsScalaSettings private val defaultChoices = List(unreachableCode) val lDefault = Choice("l:default", "Enable default optimizations: "+ defaultChoices.mkString(","), expandsTo = defaultChoices) - private val methodChoices = List(unreachableCode, simplifyJumps, recurseUnreachableJumps, emptyLineNumbers, emptyLabels, compactLocals) + private val methodChoices = List(unreachableCode, simplifyJumps, emptyLineNumbers, emptyLabels, compactLocals) val lMethod = Choice("l:method", "Enable intra-method optimizations: "+ methodChoices.mkString(","), expandsTo = methodChoices) private val projectChoices = List(lMethod, inlineProject) @@ -249,7 +248,6 @@ trait ScalaSettings extends AbsScalaSettings def YoptNone = Yopt.isSetByUser && Yopt.value.isEmpty def YoptUnreachableCode = !Yopt.isSetByUser || Yopt.contains(YoptChoices.unreachableCode) def YoptSimplifyJumps = Yopt.contains(YoptChoices.simplifyJumps) - def YoptRecurseUnreachableJumps = Yopt.contains(YoptChoices.recurseUnreachableJumps) def YoptEmptyLineNumbers = Yopt.contains(YoptChoices.emptyLineNumbers) def YoptEmptyLabels = Yopt.contains(YoptChoices.emptyLabels) def YoptCompactLocals = Yopt.contains(YoptChoices.compactLocals) -- cgit v1.2.3 From 4e982451decdc3821febfe975e1b8e406a3741e8 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Tue, 20 Jan 2015 23:20:26 +0100 Subject: Don't crash the inliner in mixed compilation In mixed compilation, the bytecode of Java classes is not availalbe: the Scala compiler does not produce any, and there are no classfiles yet. When inlining a (Scala defined) method that contains an invocation to a Java method, we need the Java method's bytecode in order to check whether that invocation can be transplanted to the new location without causing an IllegalAccessError. If the bytecode cannot be found, inlining won't be allowed. --- .../scala/tools/nsc/backend/jvm/BTypes.scala | 60 ++++++++++++---------- .../scala/tools/nsc/backend/jvm/opt/Inliner.scala | 41 +++++++++++---- test/files/run/bcodeInlinerMixed.flags | 1 + test/files/run/bcodeInlinerMixed/A_1.java | 3 ++ test/files/run/bcodeInlinerMixed/B_1.scala | 20 ++++++++ test/files/run/bcodeInlinerMixed/Test.scala | 16 ++++++ .../backend/jvm/opt/BTypesFromClassfileTest.scala | 2 +- .../backend/jvm/opt/InlinerIllegalAccessTest.scala | 4 +- .../tools/nsc/backend/jvm/opt/InlinerTest.scala | 43 ++++++++++++++-- 9 files changed, 146 insertions(+), 44 deletions(-) create mode 100644 test/files/run/bcodeInlinerMixed.flags create mode 100644 test/files/run/bcodeInlinerMixed/A_1.java create mode 100644 test/files/run/bcodeInlinerMixed/B_1.scala create mode 100644 test/files/run/bcodeInlinerMixed/Test.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index f07c7b7764..e617c86b23 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -87,32 +87,29 @@ abstract class BTypes { * * This method supports both descriptors and internal names. */ - def bTypeForDescriptorOrInternalNameFromClassfile(desc: String): BType = (desc(0): @switch) match { - case 'V' => UNIT - case 'Z' => BOOL - case 'C' => CHAR - case 'B' => BYTE - case 'S' => SHORT - case 'I' => INT - case 'F' => FLOAT - case 'J' => LONG - case 'D' => DOUBLE - case '[' => ArrayBType(bTypeForDescriptorOrInternalNameFromClassfile(desc.substring(1))) + def bTypeForDescriptorOrInternalNameFromClassfile(desc: String): Option[BType] = (desc(0): @switch) match { + case 'V' => Some(UNIT) + case 'Z' => Some(BOOL) + case 'C' => Some(CHAR) + case 'B' => Some(BYTE) + case 'S' => Some(SHORT) + case 'I' => Some(INT) + case 'F' => Some(FLOAT) + case 'J' => Some(LONG) + case 'D' => Some(DOUBLE) + case '[' => bTypeForDescriptorOrInternalNameFromClassfile(desc.substring(1)) map ArrayBType case 'L' if desc.last == ';' => classBTypeFromParsedClassfile(desc.substring(1, desc.length - 1)) case _ => classBTypeFromParsedClassfile(desc) } /** - * Parse the classfile for `internalName` and construct the [[ClassBType]]. + * Parse the classfile for `internalName` and construct the [[ClassBType]]. Returns `None` if the + * classfile cannot be found in the `byteCodeRepository`. */ - def classBTypeFromParsedClassfile(internalName: InternalName): ClassBType = { - val classNode = byteCodeRepository.classNode(internalName) getOrElse { - // There's no way out, we need the ClassBType. I (lry) only know one case byteCodeRepository.classNode - // returns None: for Java classes in mixed compilation. In this case we should not end up here, - // because there exists a symbol for that Java class, the ClassBType should be built from the symbol. - assertionError(s"Could not find bytecode for class $internalName") + def classBTypeFromParsedClassfile(internalName: InternalName): Option[ClassBType] = { + classBTypeFromInternalName.get(internalName) orElse { + byteCodeRepository.classNode(internalName) map classBTypeFromClassNode } - classBTypeFromClassNode(classNode) } /** @@ -120,20 +117,31 @@ abstract class BTypes { */ def classBTypeFromClassNode(classNode: ClassNode): ClassBType = { classBTypeFromInternalName.getOrElse(classNode.name, { - setClassInfo(classNode, ClassBType(classNode.name)) + setClassInfoFromParsedClassfile(classNode, ClassBType(classNode.name)) }) } - private def setClassInfo(classNode: ClassNode, classBType: ClassBType): ClassBType = { + private def setClassInfoFromParsedClassfile(classNode: ClassNode, classBType: ClassBType): ClassBType = { + def ensureClassBTypeFromParsedClassfile(internalName: InternalName): ClassBType = { + classBTypeFromParsedClassfile(internalName) getOrElse { + // When building a ClassBType from a parsed classfile, we need the ClassBTypes for all + // referenced types. + // TODO: make this more robust with respect to incomplete classpaths. + // Maybe not those parts of the ClassBType that require the missing class are not actually + // queried during the backend, so every part of a ClassBType that requires parsing a + // (potentially missing) classfile should be computed lazily. + assertionError(s"Could not find bytecode for class $internalName") + } + } val superClass = classNode.superName match { case null => assert(classNode.name == ObjectReference.internalName, s"class with missing super type: ${classNode.name}") None case superName => - Some(classBTypeFromParsedClassfile(superName)) + Some(ensureClassBTypeFromParsedClassfile(superName)) } - val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) + val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(ensureClassBTypeFromParsedClassfile)(collection.breakOut) val flags = classNode.access @@ -159,7 +167,7 @@ abstract class BTypes { } val nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({ - case i if nestedInCurrentClass(i) => classBTypeFromParsedClassfile(i.name) + case i if nestedInCurrentClass(i) => ensureClassBTypeFromParsedClassfile(i.name) })(collection.breakOut) // if classNode is a nested class, it has an innerClass attribute for itself. in this @@ -169,11 +177,11 @@ abstract class BTypes { val enclosingClass = if (innerEntry.outerName != null) { // if classNode is a member class, the outerName is non-null - classBTypeFromParsedClassfile(innerEntry.outerName) + ensureClassBTypeFromParsedClassfile(innerEntry.outerName) } else { // for anonymous or local classes, the outerName is null, but the enclosing class is // stored in the EnclosingMethod attribute (which ASM encodes in classNode.outerClass). - classBTypeFromParsedClassfile(classNode.outerClass) + ensureClassBTypeFromParsedClassfile(classNode.outerClass) } val staticFlag = (innerEntry.access & Opcodes.ACC_STATIC) != 0 NestedInfo(enclosingClass, Option(innerEntry.outerName), Option(innerEntry.innerName), staticFlag) 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 b74c7ba86c..2ca8e8b8c4 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -353,6 +353,11 @@ class Inliner[BT <: BTypes](val btypes: BT) { } } + /** + * Returns the first instruction in the `instructions` list that would cause a + * [[java.lang.IllegalAccessError]] when inlined into the `destinationClass`. Returns `None` if + * all instructions can be legally transplanted. + */ def findIllegalAccess(instructions: InsnList, destinationClass: ClassBType): Option[AbstractInsnNode] = { /** @@ -413,36 +418,50 @@ class Inliner[BT <: BTypes](val btypes: BT) { } } + /** + * Check if `instruction` can be transplanted to `destinationClass`. + * + * If the instruction references a class, method or field that cannot be found in the + * byteCodeRepository, it is considered as not legal. This is known to happen in mixed + * compilation: for Java classes there is no classfile that could be parsed, nor does the + * compiler generate any bytecode. + */ def isLegal(instruction: AbstractInsnNode): Boolean = instruction match { case ti: TypeInsnNode => // NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. For these instructions, the reference // "must be a symbolic reference to a class, array, or interface type" (JVMS 6), so // it can be an internal name, or a full array descriptor. - classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ti.desc)) + bTypeForDescriptorOrInternalNameFromClassfile(ti.desc).exists(classIsAccessible(_)) case ma: MultiANewArrayInsnNode => // "a symbolic reference to a class, array, or interface type" - classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ma.desc)) + bTypeForDescriptorOrInternalNameFromClassfile(ma.desc).exists(classIsAccessible(_)) case fi: FieldInsnNode => - val fieldRefClass = classBTypeFromParsedClassfile(fi.owner) - val (fieldNode, fieldDeclClass) = byteCodeRepository.fieldNode(fieldRefClass.internalName, fi.name, fi.desc).get - memberIsAccessible(fieldNode.access, classBTypeFromParsedClassfile(fieldDeclClass), fieldRefClass) + (for { + fieldRefClass <- classBTypeFromParsedClassfile(fi.owner) + (fieldNode, fieldDeclClassNode) <- byteCodeRepository.fieldNode(fieldRefClass.internalName, fi.name, fi.desc) + fieldDeclClass <- classBTypeFromParsedClassfile(fieldDeclClassNode) + } yield { + memberIsAccessible(fieldNode.access, fieldDeclClass, fieldRefClass) + }) getOrElse false case mi: MethodInsnNode => if (mi.owner.charAt(0) == '[') true // array methods are accessible - else { - val methodRefClass = classBTypeFromParsedClassfile(mi.owner) - val (methodNode, methodDeclClass) = byteCodeRepository.methodNode(methodRefClass.internalName, mi.name, mi.desc).get - memberIsAccessible(methodNode.access, classBTypeFromParsedClassfile(methodDeclClass), methodRefClass) - } + else (for { + methodRefClass <- classBTypeFromParsedClassfile(mi.owner) + (methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, mi.name, mi.desc) + methodDeclClass <- classBTypeFromParsedClassfile(methodDeclClassNode) + } yield { + memberIsAccessible(methodNode.access, methodDeclClass, methodRefClass) + }) getOrElse false case ivd: InvokeDynamicInsnNode => // TODO @lry check necessary conditions to inline an indy, instead of giving up false case ci: LdcInsnNode => ci.cst match { - case t: asm.Type => classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName)) + case t: asm.Type => bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName).exists(classIsAccessible(_)) case _ => true } diff --git a/test/files/run/bcodeInlinerMixed.flags b/test/files/run/bcodeInlinerMixed.flags new file mode 100644 index 0000000000..63b5558cfd --- /dev/null +++ b/test/files/run/bcodeInlinerMixed.flags @@ -0,0 +1 @@ +-Ybackend:GenBCode -Yopt:l:classpath \ No newline at end of file diff --git a/test/files/run/bcodeInlinerMixed/A_1.java b/test/files/run/bcodeInlinerMixed/A_1.java new file mode 100644 index 0000000000..44d7d88eeb --- /dev/null +++ b/test/files/run/bcodeInlinerMixed/A_1.java @@ -0,0 +1,3 @@ +public class A_1 { + public static final int bar() { return 100; } +} diff --git a/test/files/run/bcodeInlinerMixed/B_1.scala b/test/files/run/bcodeInlinerMixed/B_1.scala new file mode 100644 index 0000000000..2aadeccb82 --- /dev/null +++ b/test/files/run/bcodeInlinerMixed/B_1.scala @@ -0,0 +1,20 @@ +// Partest does proper mixed compilation: +// 1. scalac *.scala *.java +// 2. javac *.java +// 3. scalc *.scala +// +// In the second scalc round, the classfile for A_1 is on the classpath. +// Therefore the inliner has access to the bytecode of `bar`, which means +// it can verify that the invocation to `bar` can be safely inlined. +// +// So both callsites of `flop` are inlined. +// +// In a single mixed compilation, `flop` cannot be inlined, see JUnit InlinerTest.scala, def mixedCompilationNoInline. + +class B { + @inline final def flop = A_1.bar + def g = flop +} +class C { + def h(b: B) = b.flop +} diff --git a/test/files/run/bcodeInlinerMixed/Test.scala b/test/files/run/bcodeInlinerMixed/Test.scala new file mode 100644 index 0000000000..c8c7a9fe2a --- /dev/null +++ b/test/files/run/bcodeInlinerMixed/Test.scala @@ -0,0 +1,16 @@ +import scala.tools.partest.{BytecodeTest, ASMConverters} +import ASMConverters._ + +object Test extends BytecodeTest { + def show: Unit = { + val gIns = instructionsFromMethod(getMethod(loadClassNode("B"), "g")) + val hIns = instructionsFromMethod(getMethod(loadClassNode("C"), "h")) + // val invocation = Invoke(INVOKESTATIC, A_1, bar, ()I, false) + for (i <- List(gIns, hIns)) { + assert(i exists { + case Invoke(_, _, "bar", "()I", _) => true + case _ => false + }, i mkString "\n") + } + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala index 65c96226ff..f7c9cab284 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala @@ -90,7 +90,7 @@ class BTypesFromClassfileTest { clearCache() val fromSymbol = classBTypeFromSymbol(classSym) clearCache() - val fromClassfile = bTypes.classBTypeFromParsedClassfile(fromSymbol.internalName) + val fromClassfile = bTypes.classBTypeFromParsedClassfile(fromSymbol.internalName).get sameBType(fromSymbol, fromClassfile) } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala index 40dc990c0f..ef0f6bcd77 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala @@ -59,7 +59,7 @@ class InlinerIllegalAccessTest extends ClearAfterClass { def check(classNode: ClassNode, test: Option[AbstractInsnNode] => Unit) = { for (m <- methods) - test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(classNode.name))) + test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(classNode.name).get)) } check(cClass, assertEmpty) @@ -153,7 +153,7 @@ class InlinerIllegalAccessTest extends ClearAfterClass { val List(rbD, rcD, rfD, rgD) = dCl.methods.asScala.toList.filter(_.name(0) == 'r').sortBy(_.name) def check(method: MethodNode, dest: ClassNode, test: Option[AbstractInsnNode] => Unit): Unit = { - test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(dest.name))) + test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(dest.name).get)) } val cOrDOwner = (_: Option[AbstractInsnNode] @unchecked) match { 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 240d106f5c..4e7a2399a2 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -6,12 +6,15 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.Test import scala.collection.generic.Clearable +import scala.collection.mutable.ListBuffer +import scala.reflect.internal.util.BatchSourceFile import scala.tools.asm.Opcodes._ import org.junit.Assert._ import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis._ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.BasicAnalyzer +import scala.tools.nsc.io._ import scala.tools.testing.AssertUtil._ import CodeGenTools._ @@ -57,7 +60,7 @@ class InlinerTest extends ClearAfterClass { def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): (MethodNode, Option[String]) = { val List(cls) = compile(code) mod(cls) - val clsBType = classBTypeFromParsedClassfile(cls.name) + val clsBType = classBTypeFromParsedClassfile(cls.name).get val List(f, g) = cls.methods.asScala.filter(m => Set("f", "g")(m.name)).toList.sortBy(_.name) val fCall = g.instructions.iterator.asScala.collect({ case i: MethodInsnNode if i.name == "f" => i }).next() @@ -191,8 +194,8 @@ class InlinerTest extends ClearAfterClass { val List(c, d) = compile(code) - val cTp = classBTypeFromParsedClassfile(c.name) - val dTp = classBTypeFromParsedClassfile(d.name) + val cTp = classBTypeFromParsedClassfile(c.name).get + val dTp = classBTypeFromParsedClassfile(d.name).get val g = c.methods.asScala.find(_.name == "g").get val h = d.methods.asScala.find(_.name == "h").get @@ -350,7 +353,7 @@ class InlinerTest extends ClearAfterClass { val List(c) = compile(code) val f = c.methods.asScala.find(_.name == "f").get val callsiteIns = f.instructions.iterator().asScala.collect({ case c: MethodInsnNode => c }).next() - val clsBType = classBTypeFromParsedClassfile(c.name) + val clsBType = classBTypeFromParsedClassfile(c.name).get val analyzer = new BasicAnalyzer(f, clsBType.internalName) val integerClassBType = classBTypeFromInternalName("java/lang/Integer") @@ -414,4 +417,36 @@ class InlinerTest extends ClearAfterClass { // like maxLocals for g1 / f1, but no return value assert(g2.maxLocals == 4 && f2.maxLocals == 3, s"${g2.maxLocals} - ${f2.maxLocals}") } + + @Test + def mixedCompilationNoInline(): Unit = { + // The inliner checks if the invocation `A.bar` can be safely inlined. For that it needs to have + // the bytecode of the invoked method. In mixed compilation, there's no classfile available for + // A, so `flop` cannot be inlined, we cannot check if it's safe. + + val javaCode = + """public class A { + | public static final int bar() { return 100; } + |} + """.stripMargin + + val scalaCode = + """class B { + | @inline final def flop = A.bar + | def g = flop + |} + """.stripMargin + + InlinerTest.notPerRun.foreach(_.clear()) + compiler.reporter.reset() + compiler.settings.outputDirs.setSingleOutput(new VirtualDirectory("(memory)", None)) + val run = new compiler.Run() + run.compileSources(List(new BatchSourceFile("A.java", javaCode), new BatchSourceFile("B.scala", scalaCode))) + val outDir = compiler.settings.outputDirs.getSingleOutput.get + + val List(b) = outDir.iterator.map(f => AsmUtils.readClass(f.toByteArray)).toList.sortBy(_.name) + val ins = getSingleMethod(b, "g").instructions + val invokeFlop = Invoke(INVOKEVIRTUAL, "B", "flop", "()I", false) + assert(ins contains invokeFlop, ins mkString "\n") + } } -- cgit v1.2.3 From 027e97981d9b6a3783e9ab247cc898017b3de821 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 11 Mar 2015 11:34:08 -0700 Subject: Workaround for SI-9111 The inliner forces some method symbols to complete that would not be completed otherwise. This triggers SI-9111, in which the completer of a valid Java method definition reports an error in mixed compilation. The workaround disables error reporting while completing lazy method and class symbols in the backend. --- .../scala/tools/nsc/backend/jvm/BTypes.scala | 6 +- .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 69 +++++++++++++++++----- .../nsc/backend/jvm/opt/OptimizerReporting.scala | 1 + test/files/pos/t9111-inliner-workaround.flags | 1 + test/files/pos/t9111-inliner-workaround/A_1.java | 13 ++++ .../pos/t9111-inliner-workaround/Test_1.scala | 10 ++++ 6 files changed, 84 insertions(+), 16 deletions(-) create mode 100644 test/files/pos/t9111-inliner-workaround.flags create mode 100644 test/files/pos/t9111-inliner-workaround/A_1.java create mode 100644 test/files/pos/t9111-inliner-workaround/Test_1.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index e617c86b23..81d8adb7de 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -12,7 +12,7 @@ import asm.Opcodes import scala.tools.asm.tree.{InnerClassNode, ClassNode} import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo} import scala.tools.nsc.backend.jvm.opt.{CallGraph, ByteCodeRepository, Inliner} -import OptimizerReporting._ +import opt.OptimizerReporting._ import scala.collection.convert.decorateAsScala._ /** @@ -736,8 +736,10 @@ abstract class BTypes { /** * A ClassBType represents a class or interface type. The necessary information to build a * ClassBType is extracted from compiler symbols and types, see BTypesFromSymbols. + * + * Currently non-final due to SI-9111 */ - final case class ClassBType(internalName: InternalName) extends RefBType { + /*final*/ case class ClassBType(internalName: InternalName) extends RefBType { /** * Write-once variable allows initializing a cyclic graph of infos. This is required for * nested classes. Example: for the definition `class A { class B }` we have diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index d94bd77851..9fdb92b47c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -146,13 +146,48 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { else { val internalName = classSym.javaBinaryName.toString classBTypeFromInternalName.getOrElse(internalName, { - // The new ClassBType is added to the map in its constructor, before we set its info. This - // allows initializing cyclic dependencies, see the comment on variable ClassBType._info. - setClassInfo(classSym, ClassBType(internalName)) + if (completeSilentlyAndCheckErroneous(classSym)) { + new ErroneousClassBType(internalName) + } else { + // The new ClassBType is added to the map in its constructor, before we set its info. This + // allows initializing cyclic dependencies, see the comment on variable ClassBType._info. + setClassInfo(classSym, ClassBType(internalName)) + } }) } } + /** + * Part of the workaround for SI-9111. Makes sure that the compiler only fails if the ClassInfo + * of the symbol that could not be completed is actually required. + */ + private class ErroneousClassBType(internalName: InternalName) extends ClassBType(internalName) { + def msg = s"The class info for $internalName could not be completed due to SI-9111." + override def info: ClassInfo = opt.OptimizerReporting.assertionError(msg) + override def info_=(i: ClassInfo): Unit = opt.OptimizerReporting.assertionError(msg) + } + + /** + * This is a hack to work around SI-9111. The completer of `methodSym` may report type errors. We + * cannot change the typer context of the completer at this point and make it silent: the context + * captured when creating the completer in the namer. However, we can temporarily replace + * global.reporter (it's a var) to store errors. + */ + def completeSilentlyAndCheckErroneous(sym: Symbol): Boolean = { + if (sym.rawInfo.isComplete) false + else { + val originalReporter = global.reporter + val storeReporter = new reporters.StoreReporter() + try { + global.reporter = storeReporter + sym.info + } finally { + global.reporter = originalReporter + } + storeReporter.infos.exists(_.severity == storeReporter.ERROR) + } + } + /** * Builds a [[MethodBType]] for a method symbol. */ @@ -428,18 +463,24 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { else { // Primitve methods cannot be inlined, so there's no point in building an InlineInfo. Also, some // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]]. - classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).map({ + classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({ case methodSym => - val methodBType = methodBTypeFromSymbol(methodSym) - val name = methodSym.javaSimpleName.toString // same as in genDefDef - val signature = name + methodBType.descriptor - val info = MethodInlineInfo( - effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden, - traitMethodWithStaticImplementation = false, // temporary, fixed in future commit - annotatedInline = methodSym.hasAnnotation(ScalaInlineClass), - annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass) - ) - (signature, info) + if (completeSilentlyAndCheckErroneous(methodSym)) { + // Happens due to SI-9111. Just don't provide any InlineInfo for that method, we don't + // need fail the compiler. + None + } else { + val methodBType = methodBTypeFromSymbol(methodSym) + val name = methodSym.javaSimpleName.toString // same as in genDefDef + val signature = name + methodBType.descriptor + val info = MethodInlineInfo( + effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden, + traitMethodWithStaticImplementation = false, // temporary, fixed in future commit + annotatedInline = methodSym.hasAnnotation(ScalaInlineClass), + annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass) + ) + Some((signature, info)) + } }).toMap } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala index 53c00c7724..5b47bc88c2 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala @@ -5,6 +5,7 @@ package scala.tools.nsc package backend.jvm +package opt import scala.tools.asm import asm.tree._ diff --git a/test/files/pos/t9111-inliner-workaround.flags b/test/files/pos/t9111-inliner-workaround.flags new file mode 100644 index 0000000000..63b5558cfd --- /dev/null +++ b/test/files/pos/t9111-inliner-workaround.flags @@ -0,0 +1 @@ +-Ybackend:GenBCode -Yopt:l:classpath \ No newline at end of file diff --git a/test/files/pos/t9111-inliner-workaround/A_1.java b/test/files/pos/t9111-inliner-workaround/A_1.java new file mode 100644 index 0000000000..bc60b68ea6 --- /dev/null +++ b/test/files/pos/t9111-inliner-workaround/A_1.java @@ -0,0 +1,13 @@ +public class A_1 { + public static class T { } + + public static class Inner { + public static class T { } + + public void foo(T t) { } + + public T t = null; + + public class Deeper extends T { } + } +} diff --git a/test/files/pos/t9111-inliner-workaround/Test_1.scala b/test/files/pos/t9111-inliner-workaround/Test_1.scala new file mode 100644 index 0000000000..1a00fff833 --- /dev/null +++ b/test/files/pos/t9111-inliner-workaround/Test_1.scala @@ -0,0 +1,10 @@ +object Test extends App { + println(new A_1.Inner()) + + // Accessing foo or Deeper triggers the error of SI-9111. + // However, when not referring to those definitions, compilation should + // succeed, also if the inliner is enabled. + + // println(i.foo(null)) + // new i.Deeper() +} -- cgit v1.2.3 From f8bb3d5289e5eb84ccd94386e5c3df1bdf8b91bc Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 11 Mar 2015 12:09:21 -0700 Subject: Inline final methods defined in traits In order to inline a final trait method, callsites of such methods are first re-written from interface calls to static calls of the trait's implementation class. Then inlining proceeds as ususal. One problem that came up during development was that mixin methods are added to class symbols only for classes being compiled, but not for others. In order to inline a mixin method, we need the InlineInfo, which so far was built using the class (and method) symbols. So we had a problem with separate compilation. Looking up the symbol from a given classfile name was already known to be brittle (it's also one of the weak points of the current inliner), so we changed the strategy. Now the InlineInfo for every class is encoded in a new classfile attribute. This classfile attribute is relatively small, because all strings it references (class internal names, method names, method descriptors) would exist anyway in the constant pool, so it just adds a few references. When building the InlineInfo for a class symbol, we only look at the symbol properties for symbols being compiled in the current run. For unpickled symbols, we build the InlineInfo by reading the classfile attribute. This change also adds delambdafy:method classes to currentRun.symSource. Otherwise, currentRun.compiles(lambdaClass) is false. --- .../tools/nsc/backend/jvm/BCodeSkelBuilder.scala | 6 +- .../scala/tools/nsc/backend/jvm/BTypes.scala | 76 +++-- .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 134 +++------ .../nsc/backend/jvm/opt/ByteCodeRepository.scala | 8 +- .../tools/nsc/backend/jvm/opt/BytecodeUtils.scala | 4 + .../tools/nsc/backend/jvm/opt/CallGraph.scala | 69 ++++- .../scala/tools/nsc/backend/jvm/opt/Inliner.scala | 81 ++++- .../scala/tools/nsc/transform/Delambdafy.scala | 2 + .../scala/tools/nsc/backend/jvm/CodeGenTools.scala | 64 +++- .../tools/nsc/backend/jvm/DirectCompileTest.scala | 13 + .../backend/jvm/opt/BTypesFromClassfileTest.scala | 13 +- .../tools/nsc/backend/jvm/opt/CallGraphTest.scala | 4 +- .../jvm/opt/CompactLocalVariablesTest.scala | 4 +- .../tools/nsc/backend/jvm/opt/InlineInfoTest.scala | 65 +++++ .../jvm/opt/InlinerSeparateCompilationTest.scala | 114 ++++++++ .../tools/nsc/backend/jvm/opt/InlinerTest.scala | 325 +++++++++++++++++++-- .../nsc/backend/jvm/opt/UnreachableCodeTest.scala | 4 +- test/junit/scala/tools/testing/TempDir.scala | 18 ++ 18 files changed, 805 insertions(+), 199 deletions(-) create mode 100644 test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala create mode 100644 test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala create mode 100644 test/junit/scala/tools/testing/TempDir.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 32a421c570..e40e928761 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -103,6 +103,8 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { isCZRemote = isRemote(claszSymbol) thisName = internalName(claszSymbol) + val classBType = classBTypeFromSymbol(claszSymbol) + cnode = new asm.tree.ClassNode() initJClass(cnode) @@ -120,10 +122,12 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { addClassFields() - innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.nestedClasses + innerClassBufferASM ++= classBType.info.nestedClasses gen(cd.impl) addInnerClassesASM(cnode, innerClassBufferASM.toList) + cnode.visitAttribute(classBType.inlineInfoAttribute) + if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern)) AsmUtils.traceClass(cnode) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 81d8adb7de..51a17b7fe4 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -10,8 +10,8 @@ import scala.annotation.switch import scala.tools.asm import asm.Opcodes import scala.tools.asm.tree.{InnerClassNode, ClassNode} -import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo} -import scala.tools.nsc.backend.jvm.opt.{CallGraph, ByteCodeRepository, Inliner} +import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo} +import scala.tools.nsc.backend.jvm.opt._ import opt.OptimizerReporting._ import scala.collection.convert.decorateAsScala._ @@ -47,6 +47,9 @@ abstract class BTypes { // 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 + /** * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its * construction. @@ -60,23 +63,6 @@ abstract class BTypes { */ val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType]) - /** - * Build the [[InlineInfo]] for the methods of a class, given its internal name. - * - * The InlineInfo is part of the ClassBType's [[ClassInfo]]. Note that there are two ways to build - * a ClassBType: from a class symbol (methods in [[BTypesFromSymbols]]) or from a [[ClassNode]]. - * The InlineInfo however contains information that can only be retrieved from the symbol of - * the class (e.g., is a method annotated @inline). - * - * This method (implemented in [[BTypesFromSymbols]]) looks up the class symbol in the symbol - * table, using the classfile name of the class. - * - * The method tries to undo some of the name mangling, but the lookup does not succeed for all - * classes. In case it fails, the resulting ClassBType will simply not have an InlineInfo, and - * we won't be able to inline its methods. - */ - def inlineInfosFromSymbolLookup(internalName: InternalName): Map[String, MethodInlineInfo] - /** * Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType * is constructed by parsing the corresponding classfile. @@ -187,10 +173,52 @@ abstract class BTypes { NestedInfo(enclosingClass, Option(innerEntry.outerName), Option(innerEntry.innerName), staticFlag) } - classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfosFromSymbolLookup(classBType.internalName)) + val inlineInfo = inlineInfoFromClassfile(classNode) + + classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo) classBType } + /** + * Build the InlineInfo for a class. For Scala classes, the information is stored in the + * ScalaInlineInfo attribute. If the attribute is missing, the InlineInfo is built using the + * metadata available in the classfile (ACC_FINAL flags, etc). + */ + def inlineInfoFromClassfile(classNode: ClassNode): InlineInfo = { + def fromClassfileAttribute: Option[InlineInfo] = { + // TODO: if this is a scala class and there's no attribute, emit an inliner warning if the InlineInfo is used + if (classNode.attrs == null) None + else classNode.attrs.asScala.collect({ case a: InlineInfoAttribute => a}).headOption.map(_.inlineInfo) + } + + def fromClassfileWithoutAttribute = { + // when building MethodInlineInfos for the members of a ClassSymbol, we exclude those methods + // in scalaPrimitives. This is necessary because some of them have non-erased types, which would + // require special handling. Excluding is OK because they are never inlined. + // Here we are parsing from a classfile and we don't need to do anything special. Many of these + // primitives don't even exist, for example Any.isInstanceOf. + val methodInfos = classNode.methods.asScala.map(methodNode => { + val info = MethodInlineInfo( + effectivelyFinal = BytecodeUtils.isFinalMethod(methodNode), + traitMethodWithStaticImplementation = false, + annotatedInline = false, + annotatedNoInline = false) + (methodNode.name + methodNode.desc, info) + }).toMap + InlineInfo( + traitImplClassSelfType = None, + isEffectivelyFinal = BytecodeUtils.isFinalClass(classNode), + methodInfos = methodInfos, + warning = None) + } + + // The InlineInfo is built from the classfile (not from the symbol) for all classes that are NOT + // being compiled. For those classes, the info is only needed if the inliner is enabled, othewise + // we can save the memory. + if (!inlinerEnabled) BTypes.EmptyInlineInfo + else fromClassfileAttribute getOrElse fromClassfileWithoutAttribute + } + /** * A BType is either a primitive type, a ClassBType, an ArrayBType of one of these, or a MethodType * referring to BTypes. @@ -831,6 +859,8 @@ abstract class BTypes { ) } + def inlineInfoAttribute: InlineInfoAttribute = InlineInfoAttribute(info.inlineInfo) + def isSubtypeOf(other: ClassBType): Boolean = { if (this == other) return true @@ -945,13 +975,11 @@ abstract class BTypes { * @param nestedClasses Classes nested in this class. Those need to be added to the * InnerClass table, see the InnerClass spec summary above. * @param nestedInfo If this describes a nested class, information for the InnerClass table. - * @param inlineInfos The [[InlineInfo]]s for the methods declared in this class. The map is - * indexed by the string s"$name$descriptor" (to disambiguate overloads). - * Entries may be missing, see comment on [[inlineInfosFromSymbolLookup]]. + * @param inlineInfo Information about this class for the inliner. */ final case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int, nestedClasses: List[ClassBType], nestedInfo: Option[NestedInfo], - inlineInfos: Map[String, MethodInlineInfo]) + inlineInfo: InlineInfo) /** * Information required to add a class to an InnerClass table. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 9fdb92b47c..a217e54ed8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -7,10 +7,8 @@ package scala.tools.nsc package backend.jvm import scala.tools.asm -import scala.tools.asm.tree.ClassNode -import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository.Source import scala.tools.nsc.backend.jvm.opt.{CallGraph, Inliner, ByteCodeRepository} -import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo, InternalName} +import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo, InternalName} /** * This class mainly contains the method classBTypeFromSymbol, which extracts the necessary @@ -42,48 +40,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val callGraph: CallGraph[this.type] = new CallGraph(this) - /** - * See doc in [[BTypes.inlineInfosFromSymbolLookup]]. - * TODO: once the optimzier uses parallelism, lock before symbol table accesses - */ - def inlineInfosFromSymbolLookup(internalName: InternalName): Map[String, MethodInlineInfo] = { - val name = internalName.replace('/', '.') - - // TODO: de-mangle more class names - - def inEmptyPackage = name.indexOf('.') == -1 - def isModule = name.endsWith("$") - def isTopLevel = { - // TODO: this is conservative, there's also $'s introduced by name mangling, e.g., $colon$colon - // for this, use NameTransformer.decode - if (isModule) name.indexOf('$') == (name.length - 1) - else name.indexOf('$') == -1 - } - - val lookupName = { - if (isModule) newTermName(name.substring(0, name.length - 1)) - else newTypeName(name) - } - - // for now we only try classes that look like top-level - val classSym = if (!isTopLevel) NoSymbol else { - val member = { - if (inEmptyPackage) { - // rootMirror.getClassIfDefined fails for classes / modules in the empty package. - // maybe that should be fixed. - rootMirror.EmptyPackageClass.info.member(lookupName) - } else { - if (isModule) rootMirror.getModuleIfDefined(lookupName) - else rootMirror.getClassIfDefined(lookupName) - } - } - if (isModule) member.moduleClass else member - } - - if (classSym == NoSymbol) Map.empty - else buildInlineInfos(classSym) - } - final def initializeCoreBTypes(): Unit = { coreBTypes.setBTypes(new CoreBTypes[this.type](this)) } @@ -92,6 +48,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { def inlineGlobalEnabled: Boolean = settings.YoptInlineGlobal + def inlinerEnabled: Boolean = settings.YoptInlinerEnabled + // helpers that need access to global. // TODO @lry create a separate component, they don't belong to BTypesFromSymbols @@ -167,27 +125,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { override def info_=(i: ClassInfo): Unit = opt.OptimizerReporting.assertionError(msg) } - /** - * This is a hack to work around SI-9111. The completer of `methodSym` may report type errors. We - * cannot change the typer context of the completer at this point and make it silent: the context - * captured when creating the completer in the namer. However, we can temporarily replace - * global.reporter (it's a var) to store errors. - */ - def completeSilentlyAndCheckErroneous(sym: Symbol): Boolean = { - if (sym.rawInfo.isComplete) false - else { - val originalReporter = global.reporter - val storeReporter = new reporters.StoreReporter() - try { - global.reporter = storeReporter - sym.info - } finally { - global.reporter = originalReporter - } - storeReporter.infos.exists(_.severity == storeReporter.ERROR) - } - } - /** * Builds a [[MethodBType]] for a method symbol. */ @@ -401,9 +338,9 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val nestedInfo = buildNestedInfo(classSym) - val inlineInfos = buildInlineInfos(classSym) + val inlineInfo = buildInlineInfo(classSym, classBType.internalName) - classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfos) + classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo) classBType } @@ -458,30 +395,39 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } } - private def buildInlineInfos(classSym: Symbol): Map[String, MethodInlineInfo] = { - if (!settings.YoptInlinerEnabled) Map.empty + /** + * Build the InlineInfo for a ClassBType from the class symbol. + * + * Note that the InlineInfo is only built from the symbolic information for classes that are being + * compiled. For all other classes we delegate to inlineInfoFromClassfile. The reason is that + * mixed-in methods are only added to class symbols being compiled, but not to other classes + * extending traits. Creating the InlineInfo from the symbol would prevent these mixins from being + * inlined. + * + * So for classes being compiled, the InlineInfo is created here and stored in the ScalaInlineInfo + * classfile attribute. + */ + private def buildInlineInfo(classSym: Symbol, internalName: InternalName): InlineInfo = { + def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym, classBTypeFromSymbol(_).internalName, methodBTypeFromSymbol(_).descriptor) + + // phase travel required, see implementation of `compiles`. for nested classes, it checks if the + // enclosingTopLevelClass is being compiled. after flatten, all classes are considered top-level, + // so `compiles` would return `false`. + if (exitingPickler(currentRun.compiles(classSym))) buildFromSymbol else { - // Primitve methods cannot be inlined, so there's no point in building an InlineInfo. Also, some - // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]]. - classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({ - case methodSym => - if (completeSilentlyAndCheckErroneous(methodSym)) { - // Happens due to SI-9111. Just don't provide any InlineInfo for that method, we don't - // need fail the compiler. - None - } else { - val methodBType = methodBTypeFromSymbol(methodSym) - val name = methodSym.javaSimpleName.toString // same as in genDefDef - val signature = name + methodBType.descriptor - val info = MethodInlineInfo( - effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden, - traitMethodWithStaticImplementation = false, // temporary, fixed in future commit - annotatedInline = methodSym.hasAnnotation(ScalaInlineClass), - annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass) - ) - Some((signature, info)) - } - }).toMap + // For classes not being compiled, the InlineInfo is read from the classfile attribute. This + // fixes an issue with mixed-in methods: the mixin phase enters mixin methods only to class + // symbols being compiled. For non-compiled classes, we could not build MethodInlineInfos + // for those mixin members, which prevents inlining. + byteCodeRepository.classNode(internalName) match { + case Some(classNode) => + inlineInfoFromClassfile(classNode) + case None => + // TODO: inliner warning if the InlineInfo for that class is being used + // We can still use the inline information built from the symbol, even though mixin + // members will be missing. + buildFromSymbol + } } } @@ -503,7 +449,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL, nestedClasses = nested, nestedInfo = None, - Map.empty // no InlineInfo needed, scala never invokes methods on the mirror class + InlineInfo(None, true, Map.empty, None) // no InlineInfo needed, scala never invokes methods on the mirror class ) c }) @@ -538,8 +484,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } // legacy, to be removed when the @remote annotation gets removed - final def isRemote(s: Symbol) = (s hasAnnotation definitions.RemoteAttr) - final def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0) + final def isRemote(s: Symbol) = s hasAnnotation definitions.RemoteAttr + final def hasPublicBitSet(flags: Int) = (flags & asm.Opcodes.ACC_PUBLIC) != 0 /** * Return the Java modifiers for the given symbol. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala index ea4dd0c032..fb58f1b189 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala @@ -10,6 +10,7 @@ package opt import scala.tools.asm import asm.tree._ import scala.collection.convert.decorateAsScala._ +import scala.tools.asm.Attribute import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.util.ClassFileLookup import OptimizerReporting._ @@ -64,6 +65,7 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class */ def methodNode(ownerInternalNameOrArrayDescriptor: String, name: String, descriptor: String): Option[(MethodNode, InternalName)] = { // In a MethodInsnNode, the `owner` field may be an array descriptor, for exmple when invoking `clone`. + // We don't inline array methods (they are native anyway), so just return None. if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[') None else { classNode(ownerInternalNameOrArrayDescriptor).flatMap(c => @@ -80,9 +82,13 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class classPath.findClassFile(fullName) map { classFile => val classNode = new asm.tree.ClassNode() val classReader = new asm.ClassReader(classFile.toByteArray) + + // Passing the InlineInfoAttributePrototype makes the ClassReader invoke the specific `read` + // method of the InlineInfoAttribute class, instead of putting the byte array into a generic + // Attribute. // We don't need frames when inlining, but we want to keep the local variable table, so we // don't use SKIP_DEBUG. - classReader.accept(classNode, asm.ClassReader.SKIP_FRAMES) + classReader.accept(classNode, Array[Attribute](InlineInfoAttributePrototype), asm.ClassReader.SKIP_FRAMES) // SKIP_FRAMES leaves line number nodes. Remove them because they are not correct after // inlining. // TODO: we need to remove them also for classes that are not parsed from classfiles, why not simplify and do it once when inlining? 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 74f46d04f9..e221eef636 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -83,6 +83,10 @@ object BytecodeUtils { def isSynchronizedMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_SYNCHRONIZED) != 0 + def isFinalClass(classNode: ClassNode): Boolean = (classNode.access & Opcodes.ACC_FINAL) != 0 + + def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE)) != 0 + def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = { var result = instruction do { result = result.getNext } 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 ac40ab8904..020db738e8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -9,7 +9,9 @@ package opt 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.BasicAnalyzer +import ByteCodeRepository.{Source, CompilationUnit} class CallGraph[BT <: BTypes](val btypes: BT) { import btypes._ @@ -22,6 +24,43 @@ class CallGraph[BT <: BTypes](val btypes: BT) { } def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): List[Callsite] = { + + /** + * Analyze a callsite and gather meta-data that can be used for inlining decisions. + * + * @return Three booleans indicating whether + * 1. the callsite can be safely inlined + * 2. the callee is annotated `@inline` + * 3. the callee is annotated `@noinline` + */ + def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, receiverTypeInternalName: InternalName, calleeSource: Source): (Boolean, Boolean, Boolean) = { + val methodSignature = calleeMethodNode.name + calleeMethodNode.desc + + // The inlineInfo.methodInfos of a ClassBType holds an InlineInfo for each method *declared* + // within a class (not for inherited methods). Since we already have the classBType of the + // callee, we only check there for the methodInlineInfo, we should find it there. + calleeDeclarationClassBType.info.inlineInfo.methodInfos.find(_._1 == methodSignature) match { + case Some((_, methodInlineInfo)) => + val canInlineFromSource = inlineGlobalEnabled || calleeSource == CompilationUnit + // A non-final method can be 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 + def isStaticallyResolved: Boolean = { + // 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. + methodInlineInfo.effectivelyFinal || { + // TODO: inline warning when the receiver class cannot be found on the classpath + classBTypeFromParsedClassfile(receiverTypeInternalName).exists(_.info.inlineInfo.isEffectivelyFinal) + } + } + + (canInlineFromSource && isStaticallyResolved, methodInlineInfo.annotatedInline, methodInlineInfo.annotatedNoInline) + + case None => + // TODO: issue inliner warning + (false, false, false) + } + } + // TODO: run dataflow analyses to make the call graph more precise // - producers to get forwarded parameters (ForwardedParam) // - typeAnalysis for more precise argument types, more precise callee @@ -40,16 +79,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { byteCodeRepository.classNodeAndSource(declarationClass) map { case (declarationClassNode, source) => val declarationClassBType = classBTypeFromClassNode(declarationClassNode) - val methodSignature = method.name + method.desc - val (safeToInline, annotatedInline, annotatedNoInline) = declarationClassBType.info.inlineInfos.get(methodSignature) match { - case Some(inlineInfo) => - val canInlineFromSource = inlineGlobalEnabled || source == ByteCodeRepository.CompilationUnit - // TODO: for now, we consider a callee safeToInline only if it's final - // type analysis can render more calls safeToInline (e.g. when the precise receiver type is known) - (canInlineFromSource && inlineInfo.effectivelyFinal, Some(inlineInfo.annotatedInline), Some(inlineInfo.annotatedNoInline)) - case None => - (false, None, None) - } + val (safeToInline, annotatedInline, annotatedNoInline) = analyzeCallsite(method, declarationClassBType, call.owner, source) Callee( callee = method, calleeDeclarationClass = declarationClassBType, @@ -81,17 +111,21 @@ class CallGraph[BT <: BTypes](val btypes: BT) { /** * A callsite in the call graph. + * * @param callsiteInstruction The invocation instruction * @param callsiteMethod The method containing the callsite * @param callsiteClass The class containing the callsite - * @param callee The callee. For virtual calls, an override of the callee might be invoked. + * @param callee The callee, as it appears in the invocation instruction. For virtual + * calls, an override of the callee might be invoked. Also, the callee + * can be abstract. `None` if the callee MethodNode cannot be found in + * the bytecode repository. * @param argInfos Information about the invocation receiver and arguments * @param callsiteStackHeight The stack height at the callsite, required by the inliner */ final case class Callsite(callsiteInstruction: MethodInsnNode, callsiteMethod: MethodNode, callsiteClass: ClassBType, callee: Option[Callee], argInfos: List[ArgInfo], callsiteStackHeight: Int) { - override def toString = s"Invocation of ${callsiteInstruction.name + callsiteInstruction.desc}@${callsiteMethod.instructions.indexOf(callsiteInstruction)} in ${callsiteMethod.name}" + override def toString = s"Invocation of ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}@${callsiteMethod.instructions.indexOf(callsiteInstruction)} in ${callsiteClass.internalName}.${callsiteMethod.name}" } /** @@ -104,14 +138,17 @@ class CallGraph[BT <: BTypes](val btypes: BT) { /** * A callee in the call graph. - * @param callee The called method. For virtual calls, an override may actually be invoked. + * + * @param callee The callee, as it appears in the invocation instruction. For + * virtual calls, an override of the callee might be invoked. Also, + * the callee can be abstract. * @param calleeDeclarationClass The class in which the callee is declared * @param safeToInline True if the callee can be safely inlined: it cannot be overridden, * and the inliner settings (project / global) allow inlining it. - * @param annotatedInline Defined if it is known whether the callee is annotated @inline - * @param annotatedNoInline Defined if it is known whether the callee is annotated @noinline + * @param annotatedInline True if the callee is annotated @inline + * @param annotatedNoInline True if the callee is annotated @noinline */ final case class Callee(callee: MethodNode, calleeDeclarationClass: ClassBType, safeToInline: Boolean, - annotatedInline: Option[Boolean], annotatedNoInline: Option[Boolean]) + annotatedInline: Boolean, annotatedNoInline: Boolean) } 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 2ca8e8b8c4..970cc6803a 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,6 @@ import scala.collection.convert.decorateAsJava._ import AsmUtils._ import BytecodeUtils._ import OptimizerReporting._ -import scala.tools.asm.tree.analysis._ import collection.mutable class Inliner[BT <: BTypes](val btypes: BT) { @@ -24,6 +23,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { import callGraph._ def runInliner(): Unit = { + rewriteFinalTraitMethodInvocations() + for (request <- collectAndOrderInlineRequests) { val Some(callee) = request.callee inline(request.callsiteInstruction, request.callsiteStackHeight, request.callsiteMethod, request.callsiteClass, @@ -58,18 +59,74 @@ class Inliner[BT <: BTypes](val btypes: BT) { * requests is allowed to have cycles, and the callsites can appear in any order. */ def selectCallsitesForInlining: List[Callsite] = { - callsites.iterator.filter({ - case (_, callsite) => callsite.callee match { - case Some(Callee(callee, _, safeToInline, Some(annotatedInline), _)) => - // TODO: fix inlining from traits. - // For trait methods the callee is abstract: "trait T { @inline final def f = 1}". - // A callsite (t: T).f is `safeToInline` (effectivelyFinal is true), but the callee is the - // abstract method in the interface. - !isAbstractMethod(callee) && safeToInline && annotatedInline - case _ => false - } + callsites.valuesIterator.filter({ + case Callsite(_, _, _, Some(Callee(callee, _, safeToInline, annotatedInline, _)), _, _) => + // For trait methods the callee is abstract: "trait T { @inline final def f = 1}". + // A callsite (t: T).f is `safeToInline` (effectivelyFinal is true), but the callee is the + // abstract method in the interface. + // Even though we such invocations are re-written using `rewriteFinalTraitMethodInvocation`, + // the guard is kept here for the cases where the rewrite fails. + !isAbstractMethod(callee) && safeToInline && annotatedInline + case _ => false - }).map(_._2).toList + }).toList + } + + def rewriteFinalTraitMethodInvocations(): Unit = { + // Rewriting final trait method callsites to the implementation class enables inlining. + // We cannot just iterate over the values of the `callsites` map because the rewrite changes the + // map. Therefore we first copy the values to a list. + callsites.values.toList.foreach(rewriteFinalTraitMethodInvocation) + } + + /** + * Rewrite the INVOKEINTERFACE callsite of a final trait method invocation to INVOKESTATIC of the + * corresponding method in the implementation class. This enables inlining final trait methods. + * + * In a final trait method callsite, the callee is safeToInline and the callee method is abstract + * (the receiver type is the interface, so the method is abstract). + */ + def rewriteFinalTraitMethodInvocation(callsite: Callsite): Unit = callsite.callee match { + case Some(Callee(callee, calleeDeclarationClass, true, true, annotatedNoInline)) if isAbstractMethod(callee) => + assert(calleeDeclarationClass.isInterface, s"expected interface call (final trait method) when inlining abstract method: $callsite") + + val traitMethodArgumentTypes = asm.Type.getArgumentTypes(callee.desc) + + val selfParamTypeName = calleeDeclarationClass.info.inlineInfo.traitImplClassSelfType.getOrElse(calleeDeclarationClass.internalName) + val selfParamType = asm.Type.getObjectType(selfParamTypeName) + + val implClassMethodDescriptor = asm.Type.getMethodDescriptor(asm.Type.getReturnType(callee.desc), selfParamType +: traitMethodArgumentTypes: _*) + val implClassInternalName = calleeDeclarationClass.internalName + "$class" + + // The rewrite reading the implementation class and the implementation method from the bytecode + // repository. If either of the two fails, the rewrite is not performed. + for { + // TODO: inline warnings if impl class or method cannot be found + (implClassMethod, _) <- byteCodeRepository.methodNode(implClassInternalName, callee.name, implClassMethodDescriptor) + implClassBType <- classBTypeFromParsedClassfile(implClassInternalName) + } yield { + val newCallsiteInstruction = new MethodInsnNode(INVOKESTATIC, implClassInternalName, callee.name, implClassMethodDescriptor, false) + callsite.callsiteMethod.instructions.insert(callsite.callsiteInstruction, newCallsiteInstruction) + callsite.callsiteMethod.instructions.remove(callsite.callsiteInstruction) + + callGraph.callsites.remove(callsite.callsiteInstruction) + val staticCallsite = Callsite( + callsiteInstruction = newCallsiteInstruction, + callsiteMethod = callsite.callsiteMethod, + callsiteClass = callsite.callsiteClass, + callee = Some(Callee( + callee = implClassMethod, + calleeDeclarationClass = implClassBType, + safeToInline = true, + annotatedInline = true, + annotatedNoInline = annotatedNoInline)), + argInfos = Nil, + callsiteStackHeight = callsite.callsiteStackHeight + ) + callGraph.callsites(newCallsiteInstruction) = staticCallsite + } + + case _ => } /** diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 1f832ba81e..94e88589f5 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -255,6 +255,8 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre val name = unit.freshTypeName(s"$oldClassPart$suffix".replace("$anon", "$nestedInAnon")) val lambdaClass = pkg newClassSymbol(name, originalFunction.pos, FINAL | SYNTHETIC) addAnnotation SerialVersionUIDAnnotation + // make sure currentRun.compiles(lambdaClass) is true (AddInterfaces does the same for trait impl classes) + currentRun.symSource(lambdaClass) = funOwner.sourceFile lambdaClass setInfo ClassInfoType(parents, newScope, lambdaClass) assert(!lambdaClass.isAnonymousClass && !lambdaClass.isAnonymousFunction, "anonymous class name: "+ lambdaClass.name) assert(lambdaClass.isDelambdafyFunction, "not lambda class name: " + lambdaClass.name) diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala index e94f33db3d..c64f6e7f10 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala @@ -14,6 +14,7 @@ import scala.tools.nsc.settings.{MutableSettings, ScalaSettings} import scala.tools.nsc.{Settings, Global} import scala.tools.partest.ASMConverters import scala.collection.JavaConverters._ +import scala.tools.testing.TempDir object CodeGenTools { import ASMConverters._ @@ -42,20 +43,27 @@ object CodeGenTools { } def newCompiler(defaultArgs: String = "-usejavacp", extraArgs: String = ""): Global = { + val compiler = newCompilerWithoutVirtualOutdir(defaultArgs, extraArgs) + resetOutput(compiler) + compiler + } + + def newCompilerWithoutVirtualOutdir(defaultArgs: String = "-usejavacp", extraArgs: String = ""): Global = { val settings = new Settings() val args = (CommandLineParser tokenize defaultArgs) ++ (CommandLineParser tokenize extraArgs) settings.processArguments(args, processAll = true) - val compiler = new Global(settings) - resetOutput(compiler) - compiler + new Global(settings) } - def compile(compiler: Global)(code: String): List[(String, Array[Byte])] = { + def newRun(compiler: Global): compiler.Run = { compiler.reporter.reset() resetOutput(compiler) - val run = new compiler.Run() - run.compileSources(List(new BatchSourceFile("unitTestSource.scala", code))) - val outDir = compiler.settings.outputDirs.getSingleOutput.get + new compiler.Run() + } + + def makeSourceFile(code: String, filename: String): BatchSourceFile = new BatchSourceFile(filename, code) + + def getGeneratedClassfiles(outDir: AbstractFile): List[(String, Array[Byte])] = { def files(dir: AbstractFile): List[(String, Array[Byte])] = { val res = ListBuffer.empty[(String, Array[Byte])] for (f <- dir.iterator) { @@ -67,8 +75,46 @@ object CodeGenTools { files(outDir) } - def compileClasses(compiler: Global)(code: String): List[ClassNode] = { - compile(compiler)(code).map(p => AsmUtils.readClass(p._2)).sortBy(_.name) + def compile(compiler: Global)(scalaCode: String, javaCode: List[(String, String)] = Nil): List[(String, Array[Byte])] = { + val run = newRun(compiler) + run.compileSources(makeSourceFile(scalaCode, "unitTestSource.scala") :: javaCode.map(p => makeSourceFile(p._1, p._2))) + getGeneratedClassfiles(compiler.settings.outputDirs.getSingleOutput.get) + } + + /** + * Compile multiple Scala files separately into a single output directory. + * + * Note that a new compiler instance is created for compiling each file because symbols survive + * across runs. This makes separate compilation slower. + * + * The output directory is a physical directory, I have not figured out if / how it's possible to + * add a VirtualDirectory to the classpath of a compiler. + */ + def compileSeparately(codes: List[String], extraArgs: String = ""): List[(String, Array[Byte])] = { + val outDir = AbstractFile.getDirectory(TempDir.createTempDir()) + val outDirPath = outDir.canonicalPath + val argsWithOutDir = extraArgs + s" -d $outDirPath -cp $outDirPath" + + for (code <- codes) { + val compiler = newCompilerWithoutVirtualOutdir(extraArgs = argsWithOutDir) + new compiler.Run().compileSources(List(makeSourceFile(code, "unitTestSource.scala"))) + } + + val classfiles = getGeneratedClassfiles(outDir) + outDir.delete() + classfiles + } + + def compileClassesSeparately(codes: List[String], extraArgs: String = "") = { + readAsmClasses(compileSeparately(codes, extraArgs)) + } + + def readAsmClasses(classfiles: List[(String, Array[Byte])]) = { + classfiles.map(p => AsmUtils.readClass(p._2)).sortBy(_.name) + } + + def compileClasses(compiler: Global)(code: String, javaCode: List[(String, String)] = Nil): List[ClassNode] = { + readAsmClasses(compile(compiler)(code, javaCode)) } def compileMethods(compiler: Global)(code: String): List[MethodNode] = { diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala index 3b1b009037..94877fb037 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala @@ -78,4 +78,17 @@ class DirectCompileTest extends ClearAfterClass { Label(11) )) } + + @Test + def testSeparateCompilation(): Unit = { + val codeA = "class A { def f = 1 }" + val codeB = "class B extends A { def g = f }" + val List(a, b) = compileClassesSeparately(List(codeA, codeB)) + val ins = getSingleMethod(b, "g").instructions + assert(ins exists { + case Invoke(_, "B", "f", _, _) => true + case _ => false + }, ins) + + } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala index f7c9cab284..761f214f82 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala @@ -59,17 +59,12 @@ class BTypesFromClassfileTest { else (fromSym.flags | ACC_PRIVATE | ACC_PUBLIC) == (fromClassfile.flags | ACC_PRIVATE | ACC_PUBLIC) }, s"class flags differ\n$fromSym\n$fromClassfile") - // when parsing from classfile, the inline infos are obtained through the classSymbol, which - // is searched based on the classfile name. this lookup can fail. - assert(fromSym.inlineInfos.size == fromClassfile.inlineInfos.size || fromClassfile.inlineInfos.isEmpty, - s"wrong # of inline infos:\n${fromSym.inlineInfos.keys.toList.sorted}\n${fromClassfile.inlineInfos.keys.toList.sorted}") - fromClassfile.inlineInfos foreach { - case (signature, inlineInfo) => - assert(fromSym.inlineInfos(signature) == inlineInfo, s"inline infos differ for $signature:\n$inlineInfo\n${fromClassfile.inlineInfos(signature)}") - } + // we don't compare InlineInfos in this test: in both cases (from symbol and from classfile) they + // are actually created by looking at the classfile members, not the symbol's. InlineInfos are only + // built from symbols for classes that are being compiled, which is not the case here. Instead + // there's a separate InlineInfoTest. val chk1 = sameBTypes(fromSym.superClass, fromClassfile.superClass, checked) - val chk2 = sameBTypes(fromSym.interfaces, fromClassfile.interfaces, chk1) // The fromSym info has only member classes, no local or anonymous. The symbol is read from the diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala index 69bd92b4ba..16f09db189 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -89,8 +89,8 @@ class CallGraphTest { assert(callee.callee == target) assert(callee.calleeDeclarationClass == calleeDeclClass) assert(callee.safeToInline == safeToInline) - assert(callee.annotatedInline.get == atInline) - assert(callee.annotatedNoInline.get == atNoInline) + assert(callee.annotatedInline == atInline) + assert(callee.annotatedNoInline == atNoInline) assert(callsite.argInfos == List()) // not defined yet } catch { diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala index fc748196d0..76492cfa23 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala @@ -17,8 +17,8 @@ class CompactLocalVariablesTest { // recurse-unreachable-jumps is required for eliminating catch blocks, in the first dce round they // are still live.only after eliminating the empty handler the catch blocks become unreachable. - val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,recurse-unreachable-jumps,compact-locals") - val noCompactVarsCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,recurse-unreachable-jumps") + val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,compact-locals") + val noCompactVarsCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code") @Test def compactUnused(): Unit = { diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala new file mode 100644 index 0000000000..4e12ed757e --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala @@ -0,0 +1,65 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.collection.generic.Clearable +import org.junit.Assert._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import AsmUtils._ +import scala.tools.testing.ClearAfterClass + +import scala.collection.convert.decorateAsScala._ + +object InlineInfoTest extends ClearAfterClass.Clearable { + var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:classpath") + def clear(): Unit = { compiler = null } + + def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes) + notPerRun foreach compiler.perRunCaches.unrecordCache +} + +@RunWith(classOf[JUnit4]) +class InlineInfoTest { + val compiler = InlineInfoTest.compiler + + def compile(code: String) = { + InlineInfoTest.notPerRun.foreach(_.clear()) + compileClasses(compiler)(code) + } + + @Test + def inlineInfosFromSymbolAndAttribute(): Unit = { + val code = + """trait T { + | @inline def f: Int + | @noinline final def g = 0 + |} + |trait U { self: T => + | @inline def f = 0 + | final def h = 0 + | final class K { + | @inline def i = 0 + | } + |} + |sealed trait V { + | @inline def j = 0 + |} + |class C extends T with U + """.stripMargin + val classes = compile(code) + val fromSyms = classes.map(c => compiler.genBCode.bTypes.classBTypeFromInternalName(c.name).info.inlineInfo) + + val fromAttrs = classes.map(c => { + assert(c.attrs.asScala.exists(_.isInstanceOf[InlineInfoAttribute]), c.attrs) + compiler.genBCode.bTypes.inlineInfoFromClassfile(c) + }) + + assert(fromSyms == fromAttrs) + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala new file mode 100644 index 0000000000..58a262c401 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala @@ -0,0 +1,114 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.tools.asm.Opcodes._ +import org.junit.Assert._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import AsmUtils._ + +import scala.collection.convert.decorateAsScala._ + +object InlinerSeparateCompilationTest { + val args = "-Ybackend:GenBCode -Yopt:l:classpath" +} + +@RunWith(classOf[JUnit4]) +class InlinerSeparateCompilationTest { + import InlinerSeparateCompilationTest._ + import InlinerTest.{listStringLines, assertInvoke, assertNoInvoke} + + @Test + def inlnieMixedinMember(): Unit = { + val codeA = + """trait T { + | @inline def f = 0 + |} + |object O extends T { + | @inline def g = 1 + |} + """.stripMargin + + val codeB = + """class C { + | def t1(t: T) = t.f + | def t2 = O.f + | def t3 = O.g + |} + """.stripMargin + + val List(c, o, oMod, t, tCls) = compileClassesSeparately(List(codeA, codeB), args) + assertInvoke(getSingleMethod(c, "t1"), "T", "f") + assertNoInvoke(getSingleMethod(c, "t2")) + assertNoInvoke(getSingleMethod(c, "t3")) + } + + @Test + def inlineSealedMember(): Unit = { + val codeA = + """sealed trait T { + | @inline def f = 1 + |} + """.stripMargin + + val codeB = + """class C { + | def t1(t: T) = t.f + |} + """.stripMargin + + val List(c, t, tCls) = compileClassesSeparately(List(codeA, codeB), args) + assertNoInvoke(getSingleMethod(c, "t1")) + } + + @Test + def inlineInheritedMember(): Unit = { + val codeA = + """trait T { + | @inline final def f = 1 + |} + |trait U extends T { + | @inline final def g = f + |} + """.stripMargin + + val codeB = + """class C extends U { + | def t1 = this.f + | def t2 = this.g + | def t3(t: T) = t.f + |} + """.stripMargin + + val List(c, t, tCls, u, uCls) = compileClassesSeparately(List(codeA, codeB), args) + for (m <- List("t1", "t2", "t3")) assertNoInvoke(getSingleMethod(c, m)) + } + + @Test + def inlineWithSelfType(): Unit = { + val assembly = + """trait Assembly extends T { + | @inline final def g = 1 + | @inline final def n = m + |} + """.stripMargin + + val codeA = + s"""trait T { self: Assembly => + | @inline final def f = g + | @inline final def m = 1 + |} + |$assembly + """.stripMargin + + val List(a, aCls, t, tCls) = compileClassesSeparately(List(codeA, assembly), args) + assertNoInvoke(getSingleMethod(tCls, "f")) + assertNoInvoke(getSingleMethod(aCls, "n")) + } +} 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 4e7a2399a2..694dff8dee 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -33,18 +33,37 @@ object InlinerTest extends ClearAfterClass.Clearable { notPerRun foreach compiler.perRunCaches.unrecordCache def clear(): Unit = { compiler = null } + + implicit class listStringLines[T](val l: List[T]) extends AnyVal { + def stringLines = l.mkString("\n") + } + + def assertNoInvoke(m: Method): Unit = assertNoInvoke(m.instructions) + def assertNoInvoke(ins: List[Instruction]): Unit = { + assert(!ins.exists(_.isInstanceOf[Invoke]), ins.stringLines) + } + + def assertInvoke(m: Method, receiver: String, method: String): Unit = assertInvoke(m.instructions, receiver, method) + def assertInvoke(l: List[Instruction], receiver: String, method: String): Unit = { + assert(l.exists { + case Invoke(_, `receiver`, `method`, _, _) => true + case _ => false + }, l.stringLines) + } } @RunWith(classOf[JUnit4]) class InlinerTest extends ClearAfterClass { ClearAfterClass.stateToClear = InlinerTest + import InlinerTest.{listStringLines, assertInvoke, assertNoInvoke} + val compiler = InlinerTest.compiler import compiler.genBCode.bTypes._ - def compile(code: String): List[ClassNode] = { + def compile(scalaCode: String, javaCode: List[(String, String)] = Nil): List[ClassNode] = { InlinerTest.notPerRun.foreach(_.clear()) - compileClasses(compiler)(code) + compileClasses(compiler)(scalaCode, javaCode) } def checkCallsite(callsite: callGraph.Callsite, callee: MethodNode) = { @@ -229,8 +248,8 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin val List(cCls) = compile(code) - val instructions = instructionsFromMethod(cCls.methods.asScala.find(_.name == "test").get) - assert(instructions.contains(Op(ICONST_0)), instructions mkString "\n") + val instructions = getSingleMethod(cCls, "test").instructions + assert(instructions.contains(Op(ICONST_0)), instructions.stringLines) assert(!instructions.contains(Op(ICONST_1)), instructions) } @@ -282,16 +301,22 @@ class InlinerTest extends ClearAfterClass { def arraycopy(): Unit = { // also tests inlining of a void-returning method (no return value on the stack) val code = - """class C { + """// can't use the `compat.Platform.arraycopy` from the std lib for now, because the classfile doesn't have a ScalaInlineInfo attribute + |object Platform { + | @inline def arraycopy(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int) { + | System.arraycopy(src, srcPos, dest, destPos, length) + | } + |} + |class C { | def f(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int): Unit = { - | compat.Platform.arraycopy(src, srcPos, dest, destPos, length) + | Platform.arraycopy(src, srcPos, dest, destPos, length) | } |} """.stripMargin - val List(c) = compile(code) - val ins = instructionsFromMethod(c.methods.asScala.find(_.name == "f").get) + val List(c, _, _) = compile(code) + val ins = getSingleMethod(c, "f").instructions val invokeSysArraycopy = Invoke(INVOKESTATIC, "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", false) - assert(ins contains invokeSysArraycopy, ins mkString "\n") + assert(ins contains invokeSysArraycopy, ins.stringLines) } @Test @@ -311,7 +336,7 @@ class InlinerTest extends ClearAfterClass { } @Test - def atInlineInTraitDoesNotCrash(): Unit = { + def atInlineInTrait(): Unit = { val code = """trait T { | @inline final def f = 0 @@ -321,10 +346,7 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin val List(c, t, tClass) = compile(code) - val ins = instructionsFromMethod(c.methods.asScala.find(_.name == "g").get) - val invokeF = Invoke(INVOKEINTERFACE, "T", "f", "()I", true) - // no inlining yet - assert(ins contains invokeF, ins mkString "\n") + assertNoInvoke(getSingleMethod(c, "g")) } @Test @@ -336,10 +358,8 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin val List(c) = compile(code) - val ins = instructionsFromMethod(c.methods.asScala.find(_.name == "g").get) - println(ins) // no more invoke, f is inlined - assert(ins.count(_.isInstanceOf[Invoke]) == 0, ins mkString "\n") + assertNoInvoke(getSingleMethod(c, "g")) } @Test @@ -373,11 +393,11 @@ class InlinerTest extends ClearAfterClass { val ins = instructionsFromMethod(f) // no invocations, lowestOneBit is inlined - assert(ins.count(_.isInstanceOf[Invoke]) == 0, ins mkString "\n") + assertNoInvoke(ins) // no null check when inlining a static method ins foreach { - case Jump(IFNONNULL, _) => assert(false, ins mkString "\n") + case Jump(IFNONNULL, _) => assert(false, ins.stringLines) case _ => } } @@ -437,16 +457,267 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin - InlinerTest.notPerRun.foreach(_.clear()) - compiler.reporter.reset() - compiler.settings.outputDirs.setSingleOutput(new VirtualDirectory("(memory)", None)) - val run = new compiler.Run() - run.compileSources(List(new BatchSourceFile("A.java", javaCode), new BatchSourceFile("B.scala", scalaCode))) - val outDir = compiler.settings.outputDirs.getSingleOutput.get - val List(b) = outDir.iterator.map(f => AsmUtils.readClass(f.toByteArray)).toList.sortBy(_.name) + val List(b) = compile(scalaCode, List((javaCode, "A.java"))) val ins = getSingleMethod(b, "g").instructions val invokeFlop = Invoke(INVOKEVIRTUAL, "B", "flop", "()I", false) - assert(ins contains invokeFlop, ins mkString "\n") + assert(ins contains invokeFlop, ins.stringLines) + } + + @Test + def inlineFromTraits(): Unit = { + val code = + """trait T { + | @inline final def f = g + | @inline final def g = 1 + |} + | + |class C extends T { + | def t1(t: T) = t.f + | def t2(c: C) = c.f + |} + """.stripMargin + val List(c, t, tClass) = compile(code) + // both are just `return 1`, no more calls + assertNoInvoke(getSingleMethod(c, "t1")) + assertNoInvoke(getSingleMethod(c, "t2")) + } + + @Test + def inlineMixinMethods(): Unit = { + val code = + """trait T { + | @inline final def f = 1 + |} + |class C extends T + """.stripMargin + val List(c, t, tClass) = compile(code) + // the static implementaiton method is inlined into the mixin, so there's no invocation in the mixin + assertNoInvoke(getSingleMethod(c, "f")) + } + + @Test + def inlineTraitInherited(): Unit = { + val code = + """trait T { + | @inline final def f = 1 + |} + |trait U extends T { + | @inline final def g = f + |} + |class C extends U { + | def t1 = f + | def t2 = g + |} + """.stripMargin + val List(c, t, tClass, u, uClass) = compile(code) + assertNoInvoke(getSingleMethod(c, "t1")) + assertNoInvoke(getSingleMethod(c, "t2")) + } + + @Test + def virtualTraitNoInline(): Unit = { + val code = + """trait T { + | @inline def f = 1 + |} + |class C extends T { + | def t1(t: T) = t.f + | def t2 = this.f + |} + """.stripMargin + val List(c, t, tClass) = compile(code) + assertInvoke(getSingleMethod(c, "t1"), "T", "f") + assertInvoke(getSingleMethod(c, "t2"), "C", "f") + } + + @Test + def sealedTraitInline(): Unit = { + val code = + """sealed trait T { + | @inline def f = 1 + |} + |class C { + | def t1(t: T) = t.f + |} + """.stripMargin + val List(c, t, tClass) = compile(code) + assertNoInvoke(getSingleMethod(c, "t1")) + } + + @Test + def inlineFromObject(): Unit = { + val code = + """trait T { + | @inline def f = 0 + |} + |object O extends T { + | @inline def g = 1 + | // mixin generates `def f = T$class.f(this)`, which is inlined here (we get ICONST_0) + |} + |class C { + | def t1 = O.f // the mixin method of O is inlined, so we directly get the ICONST_0 + | def t2 = O.g // object members are inlined + | def t3(t: T) = t.f // no inlining here + |} + """.stripMargin + val List(c, oMirror, oModule, t, tClass) = compile(code) + + assertNoInvoke(getSingleMethod(oModule, "f")) + + assertNoInvoke(getSingleMethod(c, "t1")) + assertNoInvoke(getSingleMethod(c, "t2")) + assertInvoke(getSingleMethod(c, "t3"), "T", "f") + } + + @Test + def selfTypeInline(): Unit = { + val code = + """trait T { self: Assembly => + | @inline final def f = g + | @inline final def m = 1 + |} + |trait Assembly extends T { + | @inline final def g = 1 + | @inline final def n = m // inlined. (*) + | // (*) the declaration class of m is T. the signature of T$class.m is m(LAssembly;)I. so we need the self type to build the + | // signature. then we can look up the MethodNode of T$class.m and then rewrite the INVOKEINTERFACE to INVOKESTATIC. + |} + |class C { + | def t1(a: Assembly) = a.f // like above, decl class is T, need self-type of T to rewrite the interface call to static. + | def t2(a: Assembly) = a.n + |} + """.stripMargin + + val List(assembly, assemblyClass, c, t, tClass) = compile(code) + + assertNoInvoke(getSingleMethod(tClass, "f")) + + assertNoInvoke(getSingleMethod(assemblyClass, "n")) + + assertNoInvoke(getSingleMethod(c, "t1")) + assertNoInvoke(getSingleMethod(c, "t2")) + } + + @Test + def selfTypeInline2(): Unit = { + // There are some interesting things going on here with the self types. Here's a short version: + // + // trait T1 { def f = 1 } + // trait T2a { self: T1 with T2a => // self type in the backend: T1 + // def f = 2 + // def g = f // resolved to T2a.f + // } + // trait T2b { self: T2b with T1 => // self type in the backend: T2b + // def f = 2 + // def g = f // resolved to T1.f + // } + // + // scala> val t = typeOf[T2a]; exitingMixin(t.typeOfThis.typeSymbol) // self type of T2a is T1 + // res28: $r.intp.global.Symbol = trait T1 + // + // scala> typeOf[T2a].typeOfThis.member(newTermName("f")).owner // f in T2a is resolved as T2a.f + // res29: $r.intp.global.Symbol = trait T2a + // + // scala> val t = typeOf[T2b]; exitingMixin(t.typeOfThis.typeSymbol) // self type of T2b is T1 + // res30: $r.intp.global.Symbol = trait T2b + // + // scala> typeOf[T2b].typeOfThis.member(newTermName("f")).owner // f in T2b is resolved as T1.f + // res31: $r.intp.global.Symbol = trait T1 + + val code = + """trait T1 { + | @inline def f: Int = 0 + | @inline def g1 = f // not inlined: f not final, so T1$class.g1 has an interface call T1.f + |} + | + |// erased self-type (used in impl class for `self` parameter): T1 + |trait T2a { self: T1 with T2a => + | @inline override final def f = 1 + | @inline def g2a = f // inlined: resolved as T2a.f, which is re-written to T2a$class.f, so T2a$class.g2a has ICONST_1 + |} + | + |final class Ca extends T1 with T2a { + | // mixin generates accessors like `def g1 = T1$class.g1`, the impl class method call is inlined into the accessor. + | + | def m1a = g1 // call to accessor, inlined, we get the interface call T1.f + | def m2a = g2a // call to accessor, inlined, we get ICONST_1 + | def m3a = f // call to accessor, inlined, we get ICONST_1 + | + | def m4a(t: T1) = t.f // T1.f is not final, so not inlined, interface call to T1.f + | def m5a(t: T2a) = t.f // re-written to T2a$class.f, inlined, ICONST_1 + |} + | + |// erased self-type: T2b + |trait T2b { self: T2b with T1 => + | @inline override final def f = 1 + | @inline def g2b = f // not inlined: resolved as T1.f, so T2b$class.g2b has an interface call T1.f + |} + | + |final class Cb extends T1 with T2b { + | def m1b = g1 // inlined, we get the interface call to T1.f + | def m2b = g2b // inlined, we get the interface call to T1.f + | def m3b = f // inlined, we get ICONST_1 + | + | def m4b(t: T1) = t.f // T1.f is not final, so not inlined, interface call to T1.f + | def m5b(t: T2b) = t.f // re-written to T2b$class.f, inlined, ICONST_1 + |} + """.stripMargin + val List(ca, cb, t1, t1C, t2a, t2aC, t2b, t2bC) = compile(code) + + val t2aCfDesc = t2aC.methods.asScala.find(_.name == "f").get.desc + assert(t2aCfDesc == "(LT1;)I", t2aCfDesc) // self-type of T2a is T1 + + val t2bCfDesc = t2bC.methods.asScala.find(_.name == "f").get.desc + assert(t2bCfDesc == "(LT2b;)I", t2bCfDesc) // self-type of T2b is T2b + + assertNoInvoke(getSingleMethod(t2aC, "g2a")) + assertInvoke(getSingleMethod(t2bC, "g2b"), "T1", "f") + + assertInvoke(getSingleMethod(ca, "m1a"), "T1", "f") + assertNoInvoke(getSingleMethod(ca, "m2a")) // no invoke, see comment on def g2a + assertNoInvoke(getSingleMethod(ca, "m3a")) + assertInvoke(getSingleMethod(ca, "m4a"), "T1", "f") + assertNoInvoke(getSingleMethod(ca, "m5a")) + + assertInvoke(getSingleMethod(cb, "m1b"), "T1", "f") + assertInvoke(getSingleMethod(cb, "m2b"), "T1", "f") // invoke, see comment on def g2b + assertNoInvoke(getSingleMethod(cb, "m3b")) + assertInvoke(getSingleMethod(cb, "m4b"), "T1", "f") + assertNoInvoke(getSingleMethod(cb, "m5b")) + } + + @Test + def finalSubclassInline(): Unit = { + val code = + """class C { + | @inline def f = 0 + | @inline final def g = 1 + |} + |final class D extends C + |object E extends C + |class T { + | def t1(d: D) = d.f + d.g + E.f + E.g // d.f can be inlined because the reciever type is D, which is final. + |} // so d.f can be resolved statically. same for E.f + """.stripMargin + val List(c, d, e, eModule, t) = compile(code) + assertNoInvoke(getSingleMethod(t, "t1")) + } + + @Test + def inlineFromNestedClasses(): Unit = { + val code = + """class C { + | trait T { @inline final def f = 1 } + | class D extends T{ + | def m(t: T) = t.f + | } + | + | def m(d: D) = d.f + |} + """.stripMargin + val List(c, d, t, tC) = compile(code) + assertNoInvoke(getSingleMethod(d, "m")) + assertNoInvoke(getSingleMethod(c, "m")) } } 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 7c9636b8b7..c2e2a1b883 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala @@ -22,8 +22,8 @@ object UnreachableCodeTest extends ClearAfterClass.Clearable { var dceCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code") var noOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:none") - // jvm-1.5 disables computing stack map frames, and it emits dead code as-is. - var noOptNoFramesCompiler = newCompiler(extraArgs = "-target:jvm-1.5 -Ybackend:GenBCode -Yopt:l:none") + // jvm-1.5 disables computing stack map frames, and it emits dead code as-is. note that this flag triggers a deprecation warning + var noOptNoFramesCompiler = newCompiler(extraArgs = "-target:jvm-1.5 -Ybackend:GenBCode -Yopt:l:none -deprecation") def clear(): Unit = { methodOptCompiler = null diff --git a/test/junit/scala/tools/testing/TempDir.scala b/test/junit/scala/tools/testing/TempDir.scala new file mode 100644 index 0000000000..475de8c4a2 --- /dev/null +++ b/test/junit/scala/tools/testing/TempDir.scala @@ -0,0 +1,18 @@ +package scala.tools.testing + +import java.io.{IOException, File} + +object TempDir { + final val TEMP_DIR_ATTEMPTS = 10000 + def createTempDir(): File = { + val baseDir = new File(System.getProperty("java.io.tmpdir")) + val baseName = System.currentTimeMillis() + "-" + var c = 0 + while (c < TEMP_DIR_ATTEMPTS) { + val tempDir = new File(baseDir, baseName + c) + if (tempDir.mkdir()) return tempDir + c += 1 + } + throw new IOException(s"Failed to create directory") + } +} -- cgit v1.2.3 From a4e71b188fe8069b4de3a0753defb624b8b1eb8c Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 9 Feb 2015 19:33:20 +0100 Subject: Cast receiver if necessary when rewriting trait calls to impl method The self parameter type may be incompatible with the trait type. trait T { self: S => def foo = 1 } The $self parameter type of T$class.foo is S, which may be unrelated to T. If we re-write a call to T.foo to T$class.foo, we need to cast the receiver to S, otherwise we get a VerifyError. --- .../scala/tools/nsc/backend/jvm/AsmUtils.scala | 18 ++++++++++-- .../scala/tools/nsc/backend/jvm/BTypes.scala | 4 +-- .../tools/nsc/backend/jvm/opt/BytecodeUtils.scala | 9 ++++-- .../tools/nsc/backend/jvm/opt/CallGraph.scala | 4 +-- .../scala/tools/nsc/backend/jvm/opt/Inliner.scala | 34 +++++++++++++++++----- .../tools/nsc/backend/jvm/opt/CallGraphTest.scala | 1 - .../tools/nsc/backend/jvm/opt/InlinerTest.scala | 25 +++++++++++++--- 7 files changed, 74 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala index d3f09217cd..0df1b2029d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala @@ -7,8 +7,8 @@ package scala.tools.nsc.backend.jvm import scala.tools.asm.tree.{InsnList, AbstractInsnNode, ClassNode, MethodNode} import java.io.{StringWriter, PrintWriter} -import scala.tools.asm.util.{TraceClassVisitor, TraceMethodVisitor, Textifier} -import scala.tools.asm.{Attribute, ClassReader} +import scala.tools.asm.util.{CheckClassAdapter, TraceClassVisitor, TraceMethodVisitor, Textifier} +import scala.tools.asm.{ClassWriter, Attribute, ClassReader} import scala.collection.convert.decorateAsScala._ import scala.tools.nsc.backend.jvm.opt.InlineInfoAttributePrototype @@ -106,4 +106,18 @@ object AsmUtils { * Returns a human-readable representation of the given instruction sequence. */ def textify(insns: InsnList): String = textify(insns.iterator().asScala) + + /** + * Run ASM's CheckClassAdapter over a class. Returns None if no problem is found, otherwise + * Some(msg) with the verifier's error message. + */ + def checkClass(classNode: ClassNode): Option[String] = { + val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS) + classNode.accept(cw) + val sw = new StringWriter() + val pw = new PrintWriter(sw) + CheckClassAdapter.verify(new ClassReader(cw.toByteArray), false, pw) + val res = sw.toString + if (res.isEmpty) None else Some(res) + } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 51a17b7fe4..872d1cc522 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -16,11 +16,11 @@ import opt.OptimizerReporting._ import scala.collection.convert.decorateAsScala._ /** - * The BTypes component defines The BType class hierarchy. BTypes encapsulate all type information + * The BTypes component defines The BType class hierarchy. A BType stores all type information * that is required after building the ASM nodes. This includes optimizations, generation of * InnerClass attributes and generation of stack map frames. * - * This representation is immutable and independent of the compiler data structures, hence it can + * The representation is immutable and independent of the compiler data structures, hence it can * be queried by concurrent threads. */ abstract class BTypes { 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 e221eef636..d2658bcd2a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -296,10 +296,13 @@ object BytecodeUtils { )).toList } - class BasicAnalyzer(methodNode: MethodNode, classInternalName: InternalName) { - val analyzer = new Analyzer(new BasicInterpreter) + /** + * A wrapper to make ASM's Analyzer a bit easier to use. + */ + class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, interpreter: Interpreter[V] = new BasicInterpreter) { + val analyzer = new Analyzer(interpreter) analyzer.analyze(classInternalName, methodNode) - def frameAt(instruction: AbstractInsnNode): Frame[BasicValue] = analyzer.getFrames()(methodNode.instructions.indexOf(instruction)) + def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.getFrames()(methodNode.instructions.indexOf(instruction)) } implicit class `frame extensions`[V <: Value](val frame: Frame[V]) extends AnyVal { 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 020db738e8..18b95184e5 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -10,7 +10,7 @@ package opt 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.BasicAnalyzer +import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer import ByteCodeRepository.{Source, CompilationUnit} class CallGraph[BT <: BTypes](val btypes: BT) { @@ -68,7 +68,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. - val analyzer = new BasicAnalyzer(methodNode, definingClass.internalName) + val analyzer = new AsmAnalyzer(methodNode, definingClass.internalName) methodNode.instructions.iterator.asScala.collect({ case call: MethodInsnNode => 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 970cc6803a..b2459862ea 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -17,6 +17,7 @@ import AsmUtils._ import BytecodeUtils._ import OptimizerReporting._ import collection.mutable +import scala.tools.asm.tree.analysis.{SourceInterpreter, Analyzer} class Inliner[BT <: BTypes](val btypes: BT) { import btypes._ @@ -92,19 +93,38 @@ class Inliner[BT <: BTypes](val btypes: BT) { val traitMethodArgumentTypes = asm.Type.getArgumentTypes(callee.desc) - val selfParamTypeName = calleeDeclarationClass.info.inlineInfo.traitImplClassSelfType.getOrElse(calleeDeclarationClass.internalName) - val selfParamType = asm.Type.getObjectType(selfParamTypeName) + val selfParamType = calleeDeclarationClass.info.inlineInfo.traitImplClassSelfType match { + case Some(internalName) => classBTypeFromParsedClassfile(internalName) + case None => Some(calleeDeclarationClass) + } - val implClassMethodDescriptor = asm.Type.getMethodDescriptor(asm.Type.getReturnType(callee.desc), selfParamType +: traitMethodArgumentTypes: _*) val implClassInternalName = calleeDeclarationClass.internalName + "$class" // The rewrite reading the implementation class and the implementation method from the bytecode // repository. If either of the two fails, the rewrite is not performed. for { - // TODO: inline warnings if impl class or method cannot be found - (implClassMethod, _) <- byteCodeRepository.methodNode(implClassInternalName, callee.name, implClassMethodDescriptor) - implClassBType <- classBTypeFromParsedClassfile(implClassInternalName) + // TODO: inline warnings if selfClassType, impl class or impl method cannot be found + selfType <- selfParamType + implClassMethodDescriptor = asm.Type.getMethodDescriptor(asm.Type.getReturnType(callee.desc), selfType.toASMType +: traitMethodArgumentTypes: _*) + (implClassMethod, _) <- byteCodeRepository.methodNode(implClassInternalName, callee.name, implClassMethodDescriptor) + implClassBType <- classBTypeFromParsedClassfile(implClassInternalName) } yield { + + // The self parameter type may be incompatible with the trait type. + // trait T { self: S => def foo = 1 } + // The $self parameter type of T$class.foo is S, which may be unrelated to T. If we re-write + // a call to T.foo to T$class.foo, we need to cast the receiver to S, otherwise we get a + // VerifyError. We run a `SourceInterpreter` to find all producer instructions of the + // receiver value and add a cast to the self type after each. + if (!calleeDeclarationClass.isSubtypeOf(selfType)) { + val analyzer = new AsmAnalyzer(callsite.callsiteMethod, callsite.callsiteClass.internalName, new SourceInterpreter) + val receiverValue = analyzer.frameAt(callsite.callsiteInstruction).peekDown(traitMethodArgumentTypes.length) + for (i <- receiverValue.insns.asScala) { + val cast = new TypeInsnNode(CHECKCAST, selfType.internalName) + callsite.callsiteMethod.instructions.insert(i, cast) + } + } + val newCallsiteInstruction = new MethodInsnNode(INVOKESTATIC, implClassInternalName, callee.name, implClassMethodDescriptor, false) callsite.callsiteMethod.instructions.insert(callsite.callsiteInstruction, newCallsiteInstruction) callsite.callsiteMethod.instructions.remove(callsite.callsiteInstruction) @@ -291,7 +311,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { // We run an interpreter to know the stack height at each xRETURN instruction and the sizes // of the values on the stack. - val analyzer = new BasicAnalyzer(callee, calleeDeclarationClass.internalName) + val analyzer = new AsmAnalyzer(callee, calleeDeclarationClass.internalName) for (originalReturn <- callee.instructions.iterator().asScala if isReturn(originalReturn)) { val frame = analyzer.frameAt(originalReturn) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala index 16f09db189..d7344ae61f 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -11,7 +11,6 @@ import org.junit.Assert._ import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis._ -import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.BasicAnalyzer import scala.tools.testing.AssertUtil._ import CodeGenTools._ 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 694dff8dee..7f58f77b15 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -13,7 +13,7 @@ import org.junit.Assert._ import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis._ -import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.BasicAnalyzer +import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer import scala.tools.nsc.io._ import scala.tools.testing.AssertUtil._ @@ -84,7 +84,7 @@ class InlinerTest extends ClearAfterClass { val List(f, g) = cls.methods.asScala.filter(m => Set("f", "g")(m.name)).toList.sortBy(_.name) val fCall = g.instructions.iterator.asScala.collect({ case i: MethodInsnNode if i.name == "f" => i }).next() - val analyzer = new BasicAnalyzer(g, clsBType.internalName) + val analyzer = new AsmAnalyzer(g, clsBType.internalName) val r = inliner.inline( fCall, @@ -222,7 +222,7 @@ class InlinerTest extends ClearAfterClass { case m: MethodInsnNode if m.name == "g" => m }).next() - val analyzer = new BasicAnalyzer(h, dTp.internalName) + val analyzer = new AsmAnalyzer(h, dTp.internalName) val r = inliner.inline( gCall, @@ -374,7 +374,7 @@ class InlinerTest extends ClearAfterClass { val f = c.methods.asScala.find(_.name == "f").get val callsiteIns = f.instructions.iterator().asScala.collect({ case c: MethodInsnNode => c }).next() val clsBType = classBTypeFromParsedClassfile(c.name).get - val analyzer = new BasicAnalyzer(f, clsBType.internalName) + val analyzer = new AsmAnalyzer(f, clsBType.internalName) val integerClassBType = classBTypeFromInternalName("java/lang/Integer") val lowestOneBitMethod = byteCodeRepository.methodNode(integerClassBType.internalName, "lowestOneBit", "(I)I").get._1 @@ -720,4 +720,21 @@ class InlinerTest extends ClearAfterClass { assertNoInvoke(getSingleMethod(d, "m")) assertNoInvoke(getSingleMethod(c, "m")) } + + @Test + def inlineTraitCastReceiverToSelf(): Unit = { + val code = + """class C { def foo(x: Int) = x } + |trait T { self: C => + | @inline final def f(x: Int) = foo(x) + | def t1 = f(1) + | def t2(t: T) = t.f(2) + |} + """.stripMargin + val List(c, t, tc) = compile(code) + val t1 = getSingleMethod(tc, "t1") + val t2 = getSingleMethod(tc, "t2") + val cast = TypeOp(CHECKCAST, "C") + Set(t1, t2).foreach(m => assert(m.instructions.contains(cast), m.instructions)) + } } -- cgit v1.2.3 From 57c07204ca452564b930085cfa9e8b099e45b2a9 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 6 Feb 2015 13:55:49 +0100 Subject: Limit the size of the ByteCodeRepository cache I observed cases (eg Scaladoc tests) where we end up with 17k+ ClassNodes, which makes 500 MB. --- .../tools/nsc/backend/jvm/BCodeSkelBuilder.scala | 2 +- .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 3 +- .../nsc/backend/jvm/opt/ByteCodeRepository.scala | 37 ++++++++++++++++++++-- .../backend/jvm/opt/InlinerIllegalAccessTest.scala | 2 +- 4 files changed, 39 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index e40e928761..61606419bd 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -133,7 +133,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { if (settings.YoptInlinerEnabled) { // The inliner needs to find all classes in the code repo, also those being compiled - byteCodeRepository.classes(cnode.name) = Some((cnode, ByteCodeRepository.CompilationUnit)) + byteCodeRepository.add(cnode, ByteCodeRepository.CompilationUnit) } assert(cd.symbol == claszSymbol, "Someone messed up BCodePhase.claszSymbol during genPlainClass().") diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index a217e54ed8..b90030dd8c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -413,7 +413,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 + if (exitingPickler(currentRun.compiles(classSym))) buildFromSymbol // InlineInfo required for classes being compiled, we have to create the classfile attribute + else if (!inlinerEnabled) BTypes.EmptyInlineInfo // For other classes, we need the InlineInfo only inf the inliner is enabled. else { // For classes not being compiled, the InlineInfo is read from the classfile attribute. This // fixes an issue with mixed-in methods: the mixin phase enters mixin methods only to class diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala index fb58f1b189..0958601d73 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala @@ -17,6 +17,7 @@ import OptimizerReporting._ import BytecodeUtils._ import ByteCodeRepository._ import BTypes.InternalName +import java.util.concurrent.atomic.AtomicLong /** * The ByteCodeRepository provides utilities to read the bytecode of classfiles from the compilation @@ -26,16 +27,48 @@ import BTypes.InternalName * @param classes Cache for parsed ClassNodes. Also stores the source of the bytecode: * [[Classfile]] if read from `classPath`, [[CompilationUnit]] if the bytecode * corresponds to a class being compiled. + * The `Long` field encodes the age of the node in the map, which allows removing + * old entries when the map grows too large. * For Java classes in mixed compilation, the map contains `None`: there is no * ClassNode generated by the backend and also no classfile that could be parsed. */ -class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val classes: collection.concurrent.Map[InternalName, Option[(ClassNode, Source)]]) { +class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val classes: collection.concurrent.Map[InternalName, Option[(ClassNode, Source, Long)]]) { + + private val maxCacheSize = 1500 + private val targetSize = 500 + + private val idCounter = new AtomicLong(0) + + /** + * Prevent the code repository from growing too large. Profiling reveals that the average size + * of a ClassNode is about 30 kb. I observed having 17k+ classes in the cache, i.e., 500 mb. + * + * We can only remove classes with `Source == Classfile`, those can be parsed again if requested. + */ + private def limitCacheSize(): Unit = { + if (classes.count(c => c._2.isDefined && c._2.get._2 == Classfile) > maxCacheSize) { + val removeId = idCounter.get - targetSize + val toRemove = classes.iterator.collect({ + case (name, Some((_, Classfile, id))) if id < removeId => name + }).toList + toRemove foreach classes.remove + } + } + + def add(classNode: ClassNode, source: Source) = { + classes(classNode.name) = Some((classNode, source, idCounter.incrementAndGet())) + } + /** * The class node and source for an internal name. If the class node is not yet available, it is * parsed from the classfile on the compile classpath. */ def classNodeAndSource(internalName: InternalName): Option[(ClassNode, Source)] = { - classes.getOrElseUpdate(internalName, parseClass(internalName).map((_, Classfile))) + val r = classes.getOrElseUpdate(internalName, { + limitCacheSize() + parseClass(internalName).map((_, Classfile, idCounter.incrementAndGet())) + }) + r.map(v => (v._1, v._2)) } /** diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala index ef0f6bcd77..91404acba7 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala @@ -31,7 +31,7 @@ class InlinerIllegalAccessTest extends ClearAfterClass { val compiler = InlinerIllegalAccessTest.compiler import compiler.genBCode.bTypes._ - def addToRepo(cls: List[ClassNode]): Unit = for (c <- cls) byteCodeRepository.classes(c.name) = Some((c, ByteCodeRepository.Classfile)) + def addToRepo(cls: List[ClassNode]): Unit = for (c <- cls) byteCodeRepository.add(c, ByteCodeRepository.Classfile) def assertEmpty(ins: Option[AbstractInsnNode]) = for (i <- ins) throw new AssertionError(textify(i)) @Test -- cgit v1.2.3 From f8731c5b17274d68de3469e34727e24a937ffc84 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 11 Mar 2015 11:38:17 -0700 Subject: Issue inliner warnings for callsites that cannot be inlined Issue precise warnings when the inliner fails to inline or analyze a callsite. Inline failures may have various causes, for example because some class cannot be found on the classpath when building the call graph. So we need to store problems that happen early in the optimizer (when building the necessary data structures, call graph, ClassBTypes) to be able to report them later in case the inliner accesses the related data. We use Either to store these warning messages. The commit introduces an implicit class `RightBiasedEither` to make Either easier to use for error propagation. This would be subsumed by a biased either in the standard library (or could use a Validation). The `info` of each ClassBType is now an Either. There are two cases where the info is not available: - The type info should be parsed from a classfile, but the class cannot be found on the classpath - SI-9111, the type of a Java source originating class symbol cannot be completed This means that the operations on ClassBType that query the info now return an Either, too. Each Callsite in the call graph now stores the source position of the call instruction. Since the call graph is built after code generation, we build a map from invocation nodes to positions during code gen and query it when building the call graph. The new inliner can report a large number of precise warnings when a callsite cannot be inlined, or if the inlining metadata cannot be computed precisely, for example due to a missing classfile. The new -Yopt-warnings multi-choice option allows configuring inliner warnings. By default (no option provided), a one-line summary is issued in case there were callsites annotated @inline that could not be inlined. --- src/compiler/scala/tools/nsc/Reporting.scala | 12 +- .../tools/nsc/backend/jvm/BCodeAsmCommon.scala | 7 +- .../tools/nsc/backend/jvm/BCodeBodyBuilder.scala | 55 +++-- .../scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 19 +- .../tools/nsc/backend/jvm/BCodeIdiomatic.scala | 38 +-- .../tools/nsc/backend/jvm/BCodeSkelBuilder.scala | 15 +- .../scala/tools/nsc/backend/jvm/BTypes.scala | 271 ++++++++++++--------- .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 53 ++-- .../tools/nsc/backend/jvm/BackendReporting.scala | 265 ++++++++++++++++++++ .../scala/tools/nsc/backend/jvm/CoreBTypes.scala | 5 +- .../scala/tools/nsc/backend/jvm/GenBCode.scala | 4 + .../nsc/backend/jvm/opt/ByteCodeRepository.scala | 83 ++++--- .../tools/nsc/backend/jvm/opt/BytecodeUtils.scala | 2 +- .../tools/nsc/backend/jvm/opt/CallGraph.scala | 111 +++++---- .../nsc/backend/jvm/opt/InlineInfoAttribute.scala | 3 +- .../scala/tools/nsc/backend/jvm/opt/Inliner.scala | 262 +++++++++++++------- .../nsc/backend/jvm/opt/OptimizerReporting.scala | 28 --- .../scala/tools/nsc/settings/ScalaSettings.scala | 19 ++ test/files/run/colltest1.scala | 2 +- test/files/run/compiler-asSeenFrom.scala | 2 +- test/files/run/existentials-in-compiler.scala | 2 +- test/files/run/is-valid-num.scala | 2 +- test/files/run/iterator-from.scala | 2 +- test/files/run/mapConserve.scala | 2 +- test/files/run/pc-conversions.scala | 2 +- test/files/run/stringinterpolation_macro-run.scala | 2 +- test/files/run/synchronized.check | 4 + test/files/run/t7096.scala | 2 +- test/files/run/t7582.check | 4 + test/files/run/t7582b.check | 4 + .../scala/tools/nsc/backend/jvm/CodeGenTools.scala | 44 ++-- .../tools/nsc/backend/jvm/DirectCompileTest.scala | 4 + .../backend/jvm/opt/BTypesFromClassfileTest.scala | 8 +- .../tools/nsc/backend/jvm/opt/CallGraphTest.scala | 23 +- .../tools/nsc/backend/jvm/opt/InlineInfoTest.scala | 4 +- .../nsc/backend/jvm/opt/InlineWarningTest.scala | 146 +++++++++++ .../backend/jvm/opt/InlinerIllegalAccessTest.scala | 4 +- .../jvm/opt/InlinerSeparateCompilationTest.scala | 3 +- .../tools/nsc/backend/jvm/opt/InlinerTest.scala | 84 +++++-- .../nsc/backend/jvm/opt/MethodLevelOpts.scala | 3 +- .../nsc/backend/jvm/opt/UnreachableCodeTest.scala | 3 +- 41 files changed, 1157 insertions(+), 451 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala delete mode 100644 src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala create mode 100644 test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/Reporting.scala b/src/compiler/scala/tools/nsc/Reporting.scala index 4d7e9e753f..72a4b69536 100644 --- a/src/compiler/scala/tools/nsc/Reporting.scala +++ b/src/compiler/scala/tools/nsc/Reporting.scala @@ -26,7 +26,7 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w protected def PerRunReporting = new PerRunReporting class PerRunReporting extends PerRunReportingBase { /** Collects for certain classes of warnings during this run. */ - private class ConditionalWarning(what: String, option: Settings#BooleanSetting) { + private class ConditionalWarning(what: String, option: Settings#BooleanSetting)(reRunFlag: String = option.name) { val warnings = mutable.LinkedHashMap[Position, String]() def warn(pos: Position, msg: String) = if (option) reporter.warning(pos, msg) @@ -37,16 +37,16 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w val warningVerb = if (numWarnings == 1) "was" else "were" val warningCount = countElementsAsString(numWarnings, s"$what warning") - reporter.warning(NoPosition, s"there $warningVerb $warningCount; re-run with ${option.name} for details") + reporter.warning(NoPosition, s"there $warningVerb $warningCount; re-run with $reRunFlag for details") } } // This change broke sbt; I gave it the thrilling name of uncheckedWarnings0 so // as to recover uncheckedWarnings for its ever-fragile compiler interface. - private val _deprecationWarnings = new ConditionalWarning("deprecation", settings.deprecation) - private val _uncheckedWarnings = new ConditionalWarning("unchecked", settings.unchecked) - private val _featureWarnings = new ConditionalWarning("feature", settings.feature) - private val _inlinerWarnings = new ConditionalWarning("inliner", settings.YinlinerWarnings) + private val _deprecationWarnings = new ConditionalWarning("deprecation", settings.deprecation)() + private val _uncheckedWarnings = new ConditionalWarning("unchecked", settings.unchecked)() + private val _featureWarnings = new ConditionalWarning("feature", settings.feature)() + private val _inlinerWarnings = new ConditionalWarning("inliner", settings.YinlinerWarnings)(if (settings.isBCodeAskedFor) settings.YoptWarnings.name else settings.YinlinerWarnings.name) private val _allConditionalWarnings = List(_deprecationWarnings, _uncheckedWarnings, _featureWarnings, _inlinerWarnings) // TODO: remove in favor of the overload that takes a Symbol, give that argument a default (NoSymbol) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala index 2ebf338f5e..162da4236a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -7,7 +7,8 @@ package scala.tools.nsc package backend.jvm import scala.tools.nsc.Global -import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo, InternalName} +import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo, InlineInfo} +import BackendReporting.ClassSymbolInfoFailureSI9111 /** * This trait contains code shared between GenBCode and GenASM that depends on types defined in @@ -336,7 +337,7 @@ final class BCodeAsmCommon[G <: Global](val global: G) { val isEffectivelyFinal = classSym.isEffectivelyFinal - var warning = Option.empty[String] + var warning = Option.empty[ClassSymbolInfoFailureSI9111] // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]]. @@ -345,7 +346,7 @@ final class BCodeAsmCommon[G <: Global](val global: G) { if (completeSilentlyAndCheckErroneous(methodSym)) { // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler. if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes") - warning = Some(s"Failed to get the type of a method of class symbol ${classSym.fullName} due to SI-9111") + warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName)) None } else { val name = methodSym.javaSimpleName.toString // same as in genDefDef diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 1b3f124dd8..15b014bdd3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -13,6 +13,7 @@ import scala.annotation.switch import scala.tools.asm import GenBCode._ +import BackendReporting._ /* * @@ -93,7 +94,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val thrownKind = tpeTK(expr) // `throw null` is valid although scala.Null (as defined in src/libray-aux) isn't a subtype of Throwable. // Similarly for scala.Nothing (again, as defined in src/libray-aux). - assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference)) + assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference).get) genLoad(expr, thrownKind) lineNumber(expr) emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level. @@ -230,7 +231,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { if (isArithmeticOp(code)) genArithmeticOp(tree, code) else if (code == scalaPrimitives.CONCAT) genStringConcat(tree) - else if (code == scalaPrimitives.HASH) genScalaHash(receiver) + else if (code == scalaPrimitives.HASH) genScalaHash(receiver, tree.pos) else if (isArrayOp(code)) genArrayOp(tree, code, expectedType) else if (isLogicalOp(code) || isComparisonOp(code)) { val success, failure, after = new asm.Label @@ -584,7 +585,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // if (fun.symbol.isConstructor) Static(true) else SuperCall(mix); mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) genLoadArguments(args, paramTKs(app)) - genCallMethod(fun.symbol, invokeStyle, pos = app.pos) + genCallMethod(fun.symbol, invokeStyle, app.pos) generatedType = asmMethodType(fun.symbol).returnType // 'new' constructor call: Note: since constructors are @@ -626,7 +627,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { mnode.visitTypeInsn(asm.Opcodes.NEW, rt.internalName) bc dup generatedType genLoadArguments(args, paramTKs(app)) - genCallMethod(ctor, icodes.opcodes.Static(onInstance = true)) + genCallMethod(ctor, icodes.opcodes.Static(onInstance = true), app.pos) case _ => abort(s"Cannot instantiate $tpt of kind: $generatedType") @@ -636,7 +637,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val nativeKind = tpeTK(expr) genLoad(expr, nativeKind) val MethodNameAndType(mname, methodType) = asmBoxTo(nativeKind) - bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor) + bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos) generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType) case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) => @@ -644,7 +645,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) generatedType = boxType val MethodNameAndType(mname, methodType) = asmUnboxTo(boxType) - bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor) + bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos) case app @ Apply(fun, args) => val sym = fun.symbol @@ -695,10 +696,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // descriptor (instead of a class internal name): // invokevirtual #2; //Method "[I".clone:()Ljava/lang/Object val target: String = targetTypeKind.asRefBType.classOrArrayType - bc.invokevirtual(target, "clone", "()Ljava/lang/Object;") + bc.invokevirtual(target, "clone", "()Ljava/lang/Object;", app.pos) } else { - genCallMethod(sym, invokeStyle, hostClass, app.pos) + genCallMethod(sym, invokeStyle, app.pos, hostClass) } } // end of genNormalMethodCall() @@ -810,7 +811,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } def adapt(from: BType, to: BType) { - if (!from.conformsTo(to)) { + if (!from.conformsTo(to).get) { to match { case UNIT => bc drop from case _ => bc.emitT2T(from, to) @@ -976,23 +977,23 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // Optimization for expressions of the form "" + x. We can avoid the StringBuilder. case List(Literal(Constant("")), arg) => genLoad(arg, ObjectReference) - genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false)) + genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false), arg.pos) case concatenations => - bc.genStartConcat + bc.genStartConcat(tree.pos) for (elem <- concatenations) { val kind = tpeTK(elem) genLoad(elem, kind) - bc.genStringConcat(kind) + bc.genStringConcat(kind, elem.pos) } - bc.genEndConcat + bc.genEndConcat(tree.pos) } StringReference } - def genCallMethod(method: Symbol, style: InvokeStyle, hostClass0: Symbol = null, pos: Position = NoPosition) { + def genCallMethod(method: Symbol, style: InvokeStyle, pos: Position, hostClass0: Symbol = null) { val siteSymbol = claszSymbol val hostSymbol = if (hostClass0 == null) method.owner else hostClass0 @@ -1036,26 +1037,26 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } if (style.isStatic) { - if (style.hasInstance) { bc.invokespecial (jowner, jname, mdescr) } - else { bc.invokestatic (jowner, jname, mdescr) } + if (style.hasInstance) { bc.invokespecial (jowner, jname, mdescr, pos) } + else { bc.invokestatic (jowner, jname, mdescr, pos) } } else if (style.isDynamic) { - if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr) } - else { bc.invokevirtual (jowner, jname, mdescr) } + if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr, pos) } + else { bc.invokevirtual (jowner, jname, mdescr, pos) } } else { assert(style.isSuper, s"An unknown InvokeStyle: $style") - bc.invokespecial(jowner, jname, mdescr) + bc.invokespecial(jowner, jname, mdescr, pos) initModule() } } // end of genCallMethod() /* Generate the scala ## method. */ - def genScalaHash(tree: Tree): BType = { + def genScalaHash(tree: Tree, applyPos: Position): BType = { genLoadModule(ScalaRunTimeModule) // TODO why load ScalaRunTimeModule if ## has InvokeStyle of Static(false) ? genLoad(tree, ObjectReference) - genCallMethod(hashMethodSym, icodes.opcodes.Static(onInstance = false)) + genCallMethod(hashMethodSym, icodes.opcodes.Static(onInstance = false), applyPos) INT } @@ -1187,8 +1188,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // TODO !!!!!!!!!! isReferenceType, in the sense of TypeKind? (ie non-array, non-boxed, non-nothing, may be null) if (scalaPrimitives.isUniversalEqualityOp(code) && tpeTK(lhs).isClass) { // `lhs` has reference type - if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure) - else genEqEqPrimitive(lhs, rhs, failure, success) + if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure, tree.pos) + else genEqEqPrimitive(lhs, rhs, failure, success, tree.pos) } else if (scalaPrimitives.isComparisonOp(code)) genComparisonOp(lhs, rhs, code) @@ -1208,7 +1209,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { * @param l left-hand-side of the '==' * @param r right-hand-side of the '==' */ - def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label) { + def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label, pos: Position) { /* True if the equality comparison is between values that require the use of the rich equality * comparator (scala.runtime.Comparator.equals). This is the case when either side of the @@ -1232,7 +1233,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } genLoad(l, ObjectReference) genLoad(r, ObjectReference) - genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false)) + genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false), pos) genCZJUMP(success, failure, icodes.NE, BOOL) } else { @@ -1248,7 +1249,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // SI-7852 Avoid null check if L is statically non-null. genLoad(l, ObjectReference) genLoad(r, ObjectReference) - genCallMethod(Object_equals, icodes.opcodes.Dynamic) + genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos) genCZJUMP(success, failure, icodes.NE, BOOL) } else { // l == r -> if (l eq null) r eq null else l.equals(r) @@ -1269,7 +1270,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { markProgramPoint(lNonNull) locals.load(eqEqTempLocal) - genCallMethod(Object_equals, icodes.opcodes.Dynamic) + genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos) genCZJUMP(success, failure, icodes.NE, BOOL) } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 246d565987..3b7dbc18da 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -11,6 +11,7 @@ import scala.tools.asm import scala.collection.mutable import scala.tools.nsc.io.AbstractFile import GenBCode._ +import BackendReporting._ /* * Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes. @@ -67,7 +68,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { override def getCommonSuperClass(inameA: String, inameB: String): String = { val a = classBTypeFromInternalName(inameA) val b = classBTypeFromInternalName(inameB) - val lub = a.jvmWiseLUB(b) + val lub = a.jvmWiseLUB(b).get val lubName = lub.internalName assert(lubName != "scala/Any") lubName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things. @@ -205,12 +206,12 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * can-multi-thread */ final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) { - val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain).distinct + val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain.get).distinct // sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler for (nestedClass <- allNestedClasses.sortBy(_.internalName.toString)) { // Extract the innerClassEntry - we know it exists, enclosingNestedClassesChain only returns nested classes. - val Some(e) = nestedClass.innerClassAttributeEntry + val Some(e) = nestedClass.innerClassAttributeEntry.get jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.flags) } } @@ -341,7 +342,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { */ final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = { val r = classBTypeFromSymbol(sym) - if (r.isNestedClass) innerClassBufferASM += r + if (r.isNestedClass.get) innerClassBufferASM += r r } @@ -351,7 +352,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * TODO: clean up the way we track referenced inner classes. */ final def toTypeKind(t: Type): BType = typeToBType(t) match { - case c: ClassBType if c.isNestedClass => + case c: ClassBType if c.isNestedClass.get => innerClassBufferASM += c c case r => r @@ -364,7 +365,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { final def asmMethodType(msym: Symbol): MethodBType = { val r = methodBTypeFromSymbol(msym) (r.returnType :: r.argumentTypes) foreach { - case c: ClassBType if c.isNestedClass => innerClassBufferASM += c + case c: ClassBType if c.isNestedClass.get => innerClassBufferASM += c case _ => } r @@ -714,7 +715,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { val mirrorClass = new asm.tree.ClassNode mirrorClass.visit( classfileVersion, - bType.info.flags, + bType.info.get.flags, bType.internalName, null /* no java-generic-signature */, ObjectReference.internalName, @@ -730,7 +731,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass) - innerClassBufferASM ++= bType.info.nestedClasses + innerClassBufferASM ++= bType.info.get.nestedClasses addInnerClassesASM(mirrorClass, innerClassBufferASM.toList) mirrorClass.visitEnd() @@ -846,7 +847,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments constructor.visitEnd() - innerClassBufferASM ++= classBTypeFromSymbol(cls).info.nestedClasses + innerClassBufferASM ++= classBTypeFromSymbol(cls).info.get.nestedClasses addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList) beanInfoClass.visitEnd() diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index c743ebd16f..9993357eee 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -11,6 +11,7 @@ import scala.tools.asm import scala.annotation.switch import scala.collection.mutable import GenBCode._ +import scala.tools.asm.tree.MethodInsnNode /* * A high-level facade to the ASM API for bytecode generation. @@ -105,7 +106,7 @@ abstract class BCodeIdiomatic extends SubComponent { */ abstract class JCodeMethodN { - def jmethod: asm.MethodVisitor + def jmethod: asm.tree.MethodNode import asm.Opcodes; import icodes.opcodes.{ Static, Dynamic, SuperCall } @@ -205,20 +206,21 @@ abstract class BCodeIdiomatic extends SubComponent { /* * can-multi-thread */ - final def genStartConcat { + final def genStartConcat(pos: Position): Unit = { jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName) jmethod.visitInsn(Opcodes.DUP) invokespecial( StringBuilderClassName, INSTANCE_CONSTRUCTOR_NAME, - "()V" + "()V", + pos ) } /* * can-multi-thread */ - final def genStringConcat(el: BType) { + final def genStringConcat(el: BType, pos: Position): Unit = { val jtype = if (el.isArray || el.isClass) ObjectReference @@ -226,14 +228,14 @@ abstract class BCodeIdiomatic extends SubComponent { val bt = MethodBType(List(jtype), StringBuilderReference) - invokevirtual(StringBuilderClassName, "append", bt.descriptor) + invokevirtual(StringBuilderClassName, "append", bt.descriptor, pos) } /* * can-multi-thread */ - final def genEndConcat { - invokevirtual(StringBuilderClassName, "toString", "()Ljava/lang/String;") + final def genEndConcat(pos: Position): Unit = { + invokevirtual(StringBuilderClassName, "toString", "()Ljava/lang/String;", pos) } /* @@ -389,20 +391,26 @@ abstract class BCodeIdiomatic extends SubComponent { final def rem(tk: BType) { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread // can-multi-thread - final def invokespecial(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, false) + final def invokespecial(owner: String, name: String, desc: String, pos: Position) { + addInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, false, pos) } // can-multi-thread - final def invokestatic(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, false) + final def invokestatic(owner: String, name: String, desc: String, pos: Position) { + addInvoke(Opcodes.INVOKESTATIC, owner, name, desc, false, pos) } // can-multi-thread - final def invokeinterface(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc, true) + final def invokeinterface(owner: String, name: String, desc: String, pos: Position) { + addInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, true, pos) } // can-multi-thread - final def invokevirtual(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false) + final def invokevirtual(owner: String, name: String, desc: String, pos: Position) { + addInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, false, pos) + } + + private def addInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position) = { + val node = new MethodInsnNode(opcode, owner, name, desc, itf) + jmethod.instructions.add(node) + if (settings.YoptInlinerEnabled) callsitePositions(node) = pos } // can-multi-thread diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 61606419bd..2a06c62e37 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -4,20 +4,17 @@ */ -package scala -package tools.nsc +package scala.tools.nsc package backend package jvm import scala.collection.{ mutable, immutable } import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository import scala.tools.nsc.symtab._ -import scala.annotation.switch import scala.tools.asm -import scala.tools.asm.util.{TraceMethodVisitor, ASMifier} -import java.io.PrintWriter import GenBCode._ +import BackendReporting._ /* * @@ -122,11 +119,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { addClassFields() - innerClassBufferASM ++= classBType.info.nestedClasses + innerClassBufferASM ++= classBType.info.get.nestedClasses gen(cd.impl) addInnerClassesASM(cnode, innerClassBufferASM.toList) - cnode.visitAttribute(classBType.inlineInfoAttribute) + cnode.visitAttribute(classBType.inlineInfoAttribute.get) if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern)) AsmUtils.traceClass(cnode) @@ -146,9 +143,9 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val ps = claszSymbol.info.parents val superClass: String = if (ps.isEmpty) ObjectReference.internalName else internalName(ps.head.typeSymbol) - val interfaceNames = classBTypeFromSymbol(claszSymbol).info.interfaces map { + val interfaceNames = classBTypeFromSymbol(claszSymbol).info.get.interfaces map { case classBType => - if (classBType.isNestedClass) { innerClassBufferASM += classBType } + if (classBType.isNestedClass.get) { innerClassBufferASM += classBType } classBType.internalName } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 872d1cc522..d2ee944916 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -7,12 +7,15 @@ package scala.tools.nsc package backend.jvm import scala.annotation.switch +import scala.collection.concurrent.TrieMap +import scala.reflect.internal.util.Position import scala.tools.asm import asm.Opcodes -import scala.tools.asm.tree.{InnerClassNode, ClassNode} +import scala.tools.asm.tree.{MethodInsnNode, InnerClassNode, ClassNode} import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo} +import scala.tools.nsc.backend.jvm.BackendReporting._ +import BackendReporting.RightBiasedEither import scala.tools.nsc.backend.jvm.opt._ -import opt.OptimizerReporting._ import scala.collection.convert.decorateAsScala._ /** @@ -41,6 +44,8 @@ abstract class BTypes { val callGraph: CallGraph[this.type] + val backendReporting: BackendReporting + // Allows to define per-run caches here and in the CallGraph component, which don't have a global def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T @@ -50,6 +55,9 @@ abstract class BTypes { // When the inliner is not enabled, there's no point in adding InlineInfos to all ClassBTypes def inlinerEnabled: Boolean + // Settings that define what kind of optimizer warnings are emitted. + def warnSettings: WarnSettings + /** * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its * construction. @@ -61,7 +69,19 @@ abstract class BTypes { * Concurrent because stack map frames are computed when in the class writer, which might run * on multiple classes concurrently. */ - val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType]) + val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty) + + /** + * Store the position of every MethodInsnNode during code generation. This allows each callsite + * in the call graph to remember its source position, which is required for inliner warnings. + */ + val callsitePositions: collection.concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty) + + /** + * Contains the internal names of all classes that are defined in Java source files of the current + * compilation run (mixed compilation). Used for more detailed error reporting. + */ + val javaDefinedClasses: collection.mutable.Set[InternalName] = recordPerRunCache(collection.mutable.Set.empty) /** * Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType @@ -73,29 +93,33 @@ abstract class BTypes { * * This method supports both descriptors and internal names. */ - def bTypeForDescriptorOrInternalNameFromClassfile(desc: String): Option[BType] = (desc(0): @switch) match { - case 'V' => Some(UNIT) - case 'Z' => Some(BOOL) - case 'C' => Some(CHAR) - case 'B' => Some(BYTE) - case 'S' => Some(SHORT) - case 'I' => Some(INT) - case 'F' => Some(FLOAT) - case 'J' => Some(LONG) - case 'D' => Some(DOUBLE) - case '[' => bTypeForDescriptorOrInternalNameFromClassfile(desc.substring(1)) map ArrayBType + def bTypeForDescriptorOrInternalNameFromClassfile(desc: String): BType = (desc(0): @switch) match { + case 'V' => UNIT + case 'Z' => BOOL + case 'C' => CHAR + case 'B' => BYTE + case 'S' => SHORT + case 'I' => INT + case 'F' => FLOAT + case 'J' => LONG + case 'D' => DOUBLE + case '[' => ArrayBType(bTypeForDescriptorOrInternalNameFromClassfile(desc.substring(1))) case 'L' if desc.last == ';' => classBTypeFromParsedClassfile(desc.substring(1, desc.length - 1)) case _ => classBTypeFromParsedClassfile(desc) } /** - * Parse the classfile for `internalName` and construct the [[ClassBType]]. Returns `None` if the - * classfile cannot be found in the `byteCodeRepository`. + * Parse the classfile for `internalName` and construct the [[ClassBType]]. If the classfile cannot + * be found in the `byteCodeRepository`, the `info` of the resulting ClassBType is undefined. */ - def classBTypeFromParsedClassfile(internalName: InternalName): Option[ClassBType] = { - classBTypeFromInternalName.get(internalName) orElse { - byteCodeRepository.classNode(internalName) map classBTypeFromClassNode - } + def classBTypeFromParsedClassfile(internalName: InternalName): ClassBType = { + classBTypeFromInternalName.getOrElse(internalName, { + val res = ClassBType(internalName) + byteCodeRepository.classNode(internalName) match { + case Left(msg) => res.info = Left(NoClassBTypeInfoMissingBytecode(msg)); res + case Right(c) => setClassInfoFromParsedClassfile(c, res) + } + }) } /** @@ -108,26 +132,15 @@ abstract class BTypes { } private def setClassInfoFromParsedClassfile(classNode: ClassNode, classBType: ClassBType): ClassBType = { - def ensureClassBTypeFromParsedClassfile(internalName: InternalName): ClassBType = { - classBTypeFromParsedClassfile(internalName) getOrElse { - // When building a ClassBType from a parsed classfile, we need the ClassBTypes for all - // referenced types. - // TODO: make this more robust with respect to incomplete classpaths. - // Maybe not those parts of the ClassBType that require the missing class are not actually - // queried during the backend, so every part of a ClassBType that requires parsing a - // (potentially missing) classfile should be computed lazily. - assertionError(s"Could not find bytecode for class $internalName") - } - } val superClass = classNode.superName match { case null => assert(classNode.name == ObjectReference.internalName, s"class with missing super type: ${classNode.name}") None case superName => - Some(ensureClassBTypeFromParsedClassfile(superName)) + Some(classBTypeFromParsedClassfile(superName)) } - val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(ensureClassBTypeFromParsedClassfile)(collection.breakOut) + val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) val flags = classNode.access @@ -145,15 +158,13 @@ abstract class BTypes { def nestedInCurrentClass(innerClassNode: InnerClassNode): Boolean = { (innerClassNode.outerName != null && innerClassNode.outerName == classNode.name) || (innerClassNode.outerName == null && { - val classNodeForInnerClass = byteCodeRepository.classNode(innerClassNode.name) getOrElse { - assertionError(s"Could not find bytecode for class ${innerClassNode.name}") - } + val classNodeForInnerClass = byteCodeRepository.classNode(innerClassNode.name).get // TODO: don't get here, but set the info to Left at the end classNodeForInnerClass.outerClass == classNode.name }) } val nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({ - case i if nestedInCurrentClass(i) => ensureClassBTypeFromParsedClassfile(i.name) + case i if nestedInCurrentClass(i) => classBTypeFromParsedClassfile(i.name) })(collection.breakOut) // if classNode is a nested class, it has an innerClass attribute for itself. in this @@ -163,11 +174,11 @@ abstract class BTypes { val enclosingClass = if (innerEntry.outerName != null) { // if classNode is a member class, the outerName is non-null - ensureClassBTypeFromParsedClassfile(innerEntry.outerName) + classBTypeFromParsedClassfile(innerEntry.outerName) } else { // for anonymous or local classes, the outerName is null, but the enclosing class is // stored in the EnclosingMethod attribute (which ASM encodes in classNode.outerClass). - ensureClassBTypeFromParsedClassfile(classNode.outerClass) + classBTypeFromParsedClassfile(classNode.outerClass) } val staticFlag = (innerEntry.access & Opcodes.ACC_STATIC) != 0 NestedInfo(enclosingClass, Option(innerEntry.outerName), Option(innerEntry.innerName), staticFlag) @@ -175,7 +186,7 @@ abstract class BTypes { val inlineInfo = inlineInfoFromClassfile(classNode) - classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo) + classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo)) classBType } @@ -186,12 +197,16 @@ abstract class BTypes { */ def inlineInfoFromClassfile(classNode: ClassNode): InlineInfo = { def fromClassfileAttribute: Option[InlineInfo] = { - // TODO: if this is a scala class and there's no attribute, emit an inliner warning if the InlineInfo is used if (classNode.attrs == null) None else classNode.attrs.asScala.collect({ case a: InlineInfoAttribute => a}).headOption.map(_.inlineInfo) } def fromClassfileWithoutAttribute = { + val warning = { + val isScala = classNode.attrs != null && classNode.attrs.asScala.exists(a => a.`type` == BTypes.ScalaAttributeName || a.`type` == BTypes.ScalaSigAttributeName) + if (isScala) Some(NoInlineInfoAttribute(classNode.name)) + else None + } // when building MethodInlineInfos for the members of a ClassSymbol, we exclude those methods // in scalaPrimitives. This is necessary because some of them have non-erased types, which would // require special handling. Excluding is OK because they are never inlined. @@ -209,7 +224,7 @@ abstract class BTypes { traitImplClassSelfType = None, isEffectivelyFinal = BytecodeUtils.isFinalClass(classNode), methodInfos = methodInfos, - warning = None) + warning) } // The InlineInfo is built from the classfile (not from the symbol) for all classes that are NOT @@ -283,7 +298,7 @@ abstract class BTypes { * promotions (e.g. BYTE to INT). Its operation can be visualized more easily in terms of the * Java bytecode type hierarchy. */ - final def conformsTo(other: BType): Boolean = { + final def conformsTo(other: BType): Either[NoClassBTypeInfo, Boolean] = tryEither(Right({ assert(isRef || isPrimitive, s"conformsTo cannot handle $this") assert(other.isRef || other.isPrimitive, s"conformsTo cannot handle $other") @@ -291,7 +306,7 @@ abstract class BTypes { case ArrayBType(component) => if (other == ObjectReference || other == jlCloneableReference || other == jioSerializableReference) true else other match { - case ArrayBType(otherComponoent) => component.conformsTo(otherComponoent) + case ArrayBType(otherComponoent) => component.conformsTo(otherComponoent).orThrow case _ => false } @@ -300,7 +315,7 @@ abstract class BTypes { if (other.isBoxed) this == other else if (other == ObjectReference) true else other match { - case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) // e.g., java/lang/Double conforms to java/lang/Number + case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow // e.g., java/lang/Double conforms to java/lang/Number case _ => false } } else if (isNullType) { @@ -310,7 +325,7 @@ abstract class BTypes { } else if (isNothingType) { true } else other match { - case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) + case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow // case ArrayBType(_) => this.isNullType // documentation only, because `if (isNullType)` above covers this case case _ => // isNothingType || // documentation only, because `if (isNothingType)` above covers this case @@ -325,7 +340,7 @@ abstract class BTypes { assert(isPrimitive && other.isPrimitive, s"Expected primitive types $this - $other") this == other } - } + })) /** * Compute the upper bound of two types. @@ -765,9 +780,22 @@ abstract class BTypes { * A ClassBType represents a class or interface type. The necessary information to build a * ClassBType is extracted from compiler symbols and types, see BTypesFromSymbols. * - * Currently non-final due to SI-9111 + * The `info` field contains either the class information on an error message why the info could + * not be computed. There are two reasons for an erroneous info: + * 1. The ClassBType was built from a class symbol that stems from a java source file, and the + * symbol's type could not be completed successfully (SI-9111) + * 2. The ClassBType should be built from a classfile, but the class could not be found on the + * compilation classpath. + * + * Note that all ClassBTypes required in a non-optimzied run are built during code generation from + * the class symbols referenced by the ASTs, so they have a valid info. Therefore the backend + * often invokes `info.get` (which asserts the info to exist) when reading data from the ClassBType. + * + * The inliner on the other hand uses ClassBTypes that are built from classfiles, which may have + * a missing info. In order not to crash the compiler unnecessarily, the inliner does not force + * infos using `get`, but it reports inliner warnings for missing infos that prevent inlining. */ - /*final*/ case class ClassBType(internalName: InternalName) extends RefBType { + final case class ClassBType(internalName: InternalName) extends RefBType { /** * Write-once variable allows initializing a cyclic graph of infos. This is required for * nested classes. Example: for the definition `class A { class B }` we have @@ -775,14 +803,14 @@ abstract class BTypes { * B.info.nestedInfo.outerClass == A * A.info.nestedClasses contains B */ - private var _info: ClassInfo = null + private var _info: Either[NoClassBTypeInfo, ClassInfo] = null - def info: ClassInfo = { + def info: Either[NoClassBTypeInfo, ClassInfo] = { assert(_info != null, s"ClassBType.info not yet assigned: $this") _info } - def info_=(i: ClassInfo): Unit = { + def info_=(i: Either[NoClassBTypeInfo, ClassInfo]): Unit = { assert(_info == null, s"Cannot set ClassBType.info multiple times: $this") _info = i checkInfoConsistency() @@ -791,27 +819,29 @@ abstract class BTypes { classBTypeFromInternalName(internalName) = this private def checkInfoConsistency(): Unit = { + if (info.isLeft) return + // we assert some properties. however, some of the linked ClassBType (members, superClass, // interfaces) may not yet have an `_info` (initialization of cyclic structures). so we do a - // best-effort verification. - def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || p(c) + // best-effort verification. also we don't report an error if the info is a Left. + def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || c.info.isLeft || p(c) def isJLO(t: ClassBType) = t.internalName == ObjectReference.internalName assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this") assert( - if (info.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) } - else if (isInterface) isJLO(info.superClass.get) - else !isJLO(this) && ifInit(info.superClass.get)(!_.isInterface), - s"Invalid superClass in $this: ${info.superClass}" + if (info.get.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) } + else if (isInterface.get) isJLO(info.get.superClass.get) + else !isJLO(this) && ifInit(info.get.superClass.get)(!_.isInterface.get), + s"Invalid superClass in $this: ${info.get.superClass}" ) assert( - info.interfaces.forall(c => ifInit(c)(_.isInterface)), - s"Invalid interfaces in $this: ${info.interfaces}" + info.get.interfaces.forall(c => ifInit(c)(_.isInterface.get)), + s"Invalid interfaces in $this: ${info.get.interfaces}" ) - assert(info.nestedClasses.forall(c => ifInit(c)(_.isNestedClass)), info.nestedClasses) + assert(info.get.nestedClasses.forall(c => ifInit(c)(_.isNestedClass.get)), info.get.nestedClasses) } /** @@ -819,12 +849,12 @@ abstract class BTypes { */ def simpleName: String = internalName.split("/").last - def isInterface = (info.flags & asm.Opcodes.ACC_INTERFACE) != 0 + def isInterface: Either[NoClassBTypeInfo, Boolean] = info.map(i => (i.flags & asm.Opcodes.ACC_INTERFACE) != 0) - def superClassesTransitive: List[ClassBType] = info.superClass match { - case None => Nil - case Some(sc) => sc :: sc.superClassesTransitive - } + def superClassesTransitive: Either[NoClassBTypeInfo, List[ClassBType]] = info.flatMap(i => i.superClass match { + case None => Right(Nil) + case Some(sc) => sc.superClassesTransitive.map(sc :: _) + }) /** * The prefix of the internal name until the last '/', or the empty string. @@ -837,15 +867,19 @@ abstract class BTypes { } } - def isPublic = (info.flags & asm.Opcodes.ACC_PUBLIC) != 0 + def isPublic: Either[NoClassBTypeInfo, Boolean] = info.map(i => (i.flags & asm.Opcodes.ACC_PUBLIC) != 0) - def isNestedClass = info.nestedInfo.isDefined + def isNestedClass: Either[NoClassBTypeInfo, Boolean] = info.map(_.nestedInfo.isDefined) - def enclosingNestedClassesChain: List[ClassBType] = - if (isNestedClass) this :: info.nestedInfo.get.enclosingClass.enclosingNestedClassesChain - else Nil + def enclosingNestedClassesChain: Either[NoClassBTypeInfo, List[ClassBType]] = { + isNestedClass.flatMap(isNested => { + // if isNested is true, we know that info.get is defined, and nestedInfo.get is also defined. + if (isNested) info.get.nestedInfo.get.enclosingClass.enclosingNestedClassesChain.map(this :: _) + else Right(Nil) + }) + } - def innerClassAttributeEntry: Option[InnerClassEntry] = info.nestedInfo map { + def innerClassAttributeEntry: Either[NoClassBTypeInfo, Option[InnerClassEntry]] = info.map(i => i.nestedInfo map { case NestedInfo(_, outerName, innerName, isStaticNestedClass) => InnerClassEntry( internalName, @@ -853,30 +887,39 @@ abstract class BTypes { innerName.orNull, GenBCode.mkFlags( // the static flag in the InnerClass table has a special meaning, see InnerClass comment - info.flags & ~Opcodes.ACC_STATIC, + i.flags & ~Opcodes.ACC_STATIC, if (isStaticNestedClass) Opcodes.ACC_STATIC else 0 ) & ClassBType.INNER_CLASSES_FLAGS ) - } - - def inlineInfoAttribute: InlineInfoAttribute = InlineInfoAttribute(info.inlineInfo) + }) - def isSubtypeOf(other: ClassBType): Boolean = { - if (this == other) return true + def inlineInfoAttribute: Either[NoClassBTypeInfo, InlineInfoAttribute] = info.map(i => { + // InlineInfos are serialized for classes being compiled. For those the info was built by + // buildInlineInfoFromClassSymbol, which only adds a warning under SI-9111, which in turn + // only happens for class symbols of java source files. + // we could put this assertion into InlineInfoAttribute, but it is more safe to put it here + // where it affect only GenBCode, and not add any assertion to GenASM in 2.11.6. + assert(i.inlineInfo.warning.isEmpty, i.inlineInfo.warning) + InlineInfoAttribute(i.inlineInfo) + }) - if (isInterface) { - if (other == ObjectReference) return true // interfaces conform to Object - if (!other.isInterface) return false // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false. + def isSubtypeOf(other: ClassBType): Either[NoClassBTypeInfo, Boolean] = try { + if (this == other) return Right(true) + if (isInterface.orThrow) { + if (other == ObjectReference) return Right(true) // interfaces conform to Object + if (!other.isInterface.orThrow) return Right(false) // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false. // else: this and other are both interfaces. continue to (*) } else { - val sc = info.superClass - if (sc.isDefined && sc.get.isSubtypeOf(other)) return true // the superclass of this class conforms to other - if (!other.isInterface) return false // this and other are both classes, and the superclass of this does not conform + val sc = info.orThrow.superClass + if (sc.isDefined && sc.get.isSubtypeOf(other).orThrow) return Right(true) // the superclass of this class conforms to other + if (!other.isInterface.orThrow) return Right(false) // this and other are both classes, and the superclass of this does not conform // else: this is a class, the other is an interface. continue to (*) } // (*) check if some interface of this class conforms to other. - info.interfaces.exists(_.isSubtypeOf(other)) + Right(info.orThrow.interfaces.exists(_.isSubtypeOf(other).orThrow)) + } catch { + case Invalid(noInfo: NoClassBTypeInfo) => Left(noInfo) } /** @@ -886,34 +929,36 @@ abstract class BTypes { * http://comments.gmane.org/gmane.comp.java.vm.languages/2293 * https://issues.scala-lang.org/browse/SI-3872 */ - def jvmWiseLUB(other: ClassBType): ClassBType = { + def jvmWiseLUB(other: ClassBType): Either[NoClassBTypeInfo, ClassBType] = { def isNotNullOrNothing(c: ClassBType) = !c.isNullType && !c.isNothingType assert(isNotNullOrNothing(this) && isNotNullOrNothing(other), s"jvmWiseLub for null or nothing: $this - $other") - val res: ClassBType = (this.isInterface, other.isInterface) match { - case (true, true) => - // exercised by test/files/run/t4761.scala - if (other.isSubtypeOf(this)) this - else if (this.isSubtypeOf(other)) other - else ObjectReference - - case (true, false) => - if (other.isSubtypeOf(this)) this else ObjectReference - - case (false, true) => - if (this.isSubtypeOf(other)) other else ObjectReference + tryEither { + val res: ClassBType = (this.isInterface.orThrow, other.isInterface.orThrow) match { + case (true, true) => + // exercised by test/files/run/t4761.scala + if (other.isSubtypeOf(this).orThrow) this + else if (this.isSubtypeOf(other).orThrow) other + else ObjectReference + + case (true, false) => + if (other.isSubtypeOf(this).orThrow) this else ObjectReference + + case (false, true) => + if (this.isSubtypeOf(other).orThrow) other else ObjectReference + + case _ => + // TODO @lry I don't really understand the reasoning here. + // Both this and other are classes. The code takes (transitively) all superclasses and + // finds the first common one. + // MOST LIKELY the answer can be found here, see the comments and links by Miguel: + // - https://issues.scala-lang.org/browse/SI-3872 + firstCommonSuffix(this :: this.superClassesTransitive.orThrow, other :: other.superClassesTransitive.orThrow) + } - case _ => - // TODO @lry I don't really understand the reasoning here. - // Both this and other are classes. The code takes (transitively) all superclasses and - // finds the first common one. - // MOST LIKELY the answer can be found here, see the comments and links by Miguel: - // - https://issues.scala-lang.org/browse/SI-3872 - firstCommonSuffix(this :: this.superClassesTransitive, other :: other.superClassesTransitive) + assert(isNotNullOrNothing(res), s"jvmWiseLub computed: $res") + Right(res) } - - assert(isNotNullOrNothing(res), s"jvmWiseLub computed: $res") - res } private def firstCommonSuffix(as: List[ClassBType], bs: List[ClassBType]): ClassBType = { @@ -1080,11 +1125,15 @@ object BTypes { * @param methodInfos The [[MethodInlineInfo]]s for the methods declared in this class. * The map is indexed by the string s"$name$descriptor" (to * disambiguate overloads). + * + * @param warning Contains an warning message if an error occured when building this + * InlineInfo, for example if some classfile could not be found on + * the classpath. This warning can be reported later by the inliner. */ final case class InlineInfo(traitImplClassSelfType: Option[InternalName], isEffectivelyFinal: Boolean, methodInfos: Map[String, MethodInlineInfo], - warning: Option[String]) + warning: Option[ClassInlineInfoWarning]) val EmptyInlineInfo = InlineInfo(None, false, Map.empty, None) @@ -1102,4 +1151,8 @@ object BTypes { traitMethodWithStaticImplementation: Boolean, annotatedInline: Boolean, annotatedNoInline: Boolean) + + // no static way (without symbol table instance) to get to nme.ScalaATTR / ScalaSignatureATTR + val ScalaAttributeName = "Scala" + val ScalaSigAttributeName = "ScalaSig" } \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index b90030dd8c..eeb6ed24a2 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -9,6 +9,7 @@ package backend.jvm import scala.tools.asm import scala.tools.nsc.backend.jvm.opt.{CallGraph, Inliner, ByteCodeRepository} import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo, InternalName} +import BackendReporting._ /** * This class mainly contains the method classBTypeFromSymbol, which extracts the necessary @@ -34,12 +35,14 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val coreBTypes = new CoreBTypesProxy[this.type](this) import coreBTypes._ - val byteCodeRepository = new ByteCodeRepository(global.classPath, recordPerRunCache(collection.concurrent.TrieMap.empty)) + val byteCodeRepository = new ByteCodeRepository(global.classPath, javaDefinedClasses, recordPerRunCache(collection.concurrent.TrieMap.empty)) val inliner: Inliner[this.type] = new Inliner(this) val callGraph: CallGraph[this.type] = new CallGraph(this) + val backendReporting: BackendReporting = new BackendReportingImpl(global) + final def initializeCoreBTypes(): Unit = { coreBTypes.setBTypes(new CoreBTypes[this.type](this)) } @@ -50,6 +53,16 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { def inlinerEnabled: Boolean = settings.YoptInlinerEnabled + def warnSettings: WarnSettings = { + val c = settings.YoptWarningsChoices + // cannot extract settings.YoptWarnings into a local val due to some dependent typing issue. + WarnSettings( + !settings.YoptWarnings.isSetByUser || settings.YoptWarnings.contains(c.atInlineFailedSummary.name) || settings.YoptWarnings.contains(c.atInlineFailed.name), + settings.YoptWarnings.contains(c.noInlineMixed.name), + settings.YoptWarnings.contains(c.noInlineMissingBytecode.name), + settings.YoptWarnings.contains(c.noInlineMissingScalaInlineInfoAttr.name)) + } + // helpers that need access to global. // TODO @lry create a separate component, they don't belong to BTypesFromSymbols @@ -104,27 +117,19 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { else { val internalName = classSym.javaBinaryName.toString classBTypeFromInternalName.getOrElse(internalName, { + // The new ClassBType is added to the map in its constructor, before we set its info. This + // allows initializing cyclic dependencies, see the comment on variable ClassBType._info. + val res = ClassBType(internalName) if (completeSilentlyAndCheckErroneous(classSym)) { - new ErroneousClassBType(internalName) + res.info = Left(NoClassBTypeInfoClassSymbolInfoFailedSI9111(classSym.fullName)) + res } else { - // The new ClassBType is added to the map in its constructor, before we set its info. This - // allows initializing cyclic dependencies, see the comment on variable ClassBType._info. - setClassInfo(classSym, ClassBType(internalName)) + setClassInfo(classSym, res) } }) } } - /** - * Part of the workaround for SI-9111. Makes sure that the compiler only fails if the ClassInfo - * of the symbol that could not be completed is actually required. - */ - private class ErroneousClassBType(internalName: InternalName) extends ClassBType(internalName) { - def msg = s"The class info for $internalName could not be completed due to SI-9111." - override def info: ClassInfo = opt.OptimizerReporting.assertionError(msg) - override def info_=(i: ClassInfo): Unit = opt.OptimizerReporting.assertionError(msg) - } - /** * Builds a [[MethodBType]] for a method symbol. */ @@ -202,7 +207,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { case ThisType(sym) => classBTypeFromSymbol(sym) case SingleType(_, sym) => primitiveOrClassToBType(sym) case ConstantType(_) => typeToBType(t.underlying) - case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b)) + case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b).get) } } } @@ -340,7 +345,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val inlineInfo = buildInlineInfo(classSym, classBType.internalName) - classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo) + classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo)) classBType } @@ -421,13 +426,10 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { // symbols being compiled. For non-compiled classes, we could not build MethodInlineInfos // for those mixin members, which prevents inlining. byteCodeRepository.classNode(internalName) match { - case Some(classNode) => + case Right(classNode) => inlineInfoFromClassfile(classNode) - case None => - // TODO: inliner warning if the InlineInfo for that class is being used - // We can still use the inline information built from the symbol, even though mixin - // members will be missing. - buildFromSymbol + case Left(missingClass) => + InlineInfo(None, false, Map.empty, Some(ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass))) } } } @@ -444,14 +446,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val c = ClassBType(internalName) // class info consistent with BCodeHelpers.genMirrorClass val nested = exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol - c.info = ClassInfo( + c.info = Right(ClassInfo( superClass = Some(ObjectReference), interfaces = Nil, flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL, nestedClasses = nested, nestedInfo = None, - InlineInfo(None, true, Map.empty, None) // no InlineInfo needed, scala never invokes methods on the mirror class - ) + InlineInfo(None, true, Map.empty, None))) // no InlineInfo needed, scala never invokes methods on the mirror class c }) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala new file mode 100644 index 0000000000..a06fb4bab8 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala @@ -0,0 +1,265 @@ +package scala.tools.nsc +package backend.jvm + +import scala.tools.asm.tree.{AbstractInsnNode, MethodNode} +import scala.tools.nsc.backend.jvm.BTypes.InternalName +import scala.reflect.internal.util.Position + +/** + * Interface for emitting inline warnings. The interface is required because the implementation + * depends on Global, which is not available in BTypes (only in BTypesFromSymbols). + */ +sealed abstract class BackendReporting { + def inlinerWarning(pos: Position, message: String): Unit +} + +final class BackendReportingImpl(val global: Global) extends BackendReporting { + import global._ + + def inlinerWarning(pos: Position, message: String): Unit = { + currentRun.reporting.inlinerWarning(pos, message) + } +} + +/** + * Utilities for error reporting. + * + * Defines some tools to make error reporting with Either easier. Would be subsumed by a right-biased + * Either in the standard library (or scalaz \/) (Validation is different, it accumulates multiple + * errors). + */ +object BackendReporting { + def methodSignature(classInternalName: InternalName, name: String, desc: String) = { + classInternalName + "::" + name + desc + } + + def methodSignature(classInternalName: InternalName, method: MethodNode): String = { + methodSignature(classInternalName, method.name, method.desc) + } + + def assertionError(message: String): Nothing = throw new AssertionError(message) + + implicit class RightBiasedEither[A, B](val v: Either[A, B]) extends AnyVal { + def map[U](f: B => U) = v.right.map(f) + def flatMap[BB](f: B => Either[A, BB]) = v.right.flatMap(f) + def filter(f: B => Boolean)(implicit empty: A): Either[A, B] = v match { + case Left(_) => v + case Right(e) => if (f(e)) v else Left(empty) // scalaz.\/ requires an implicit Monoid m to get m.empty + } + def foreach[U](f: B => U) = v.right.foreach(f) + + def getOrElse[BB >: B](alt: => BB): BB = v.right.getOrElse(alt) + + /** + * Get the value, fail with an assertion if this is an error. + */ + def get: B = { + assert(v.isRight, v.left.get) + v.right.get + } + + /** + * Get the right value of an `Either` by throwing a potential error message. Can simplify the + * implementation of methods that act on multiple `Either` instances. Instead of flat-mapping, + * the first error can be collected as + * + * tryEither { + * eitherOne.orThrow .... eitherTwo.orThrow ... eitherThree.orThrow + * } + */ + def orThrow: B = v match { + case Left(m) => throw Invalid(m) + case Right(t) => t + } + } + + case class Invalid[A](e: A) extends Exception + + /** + * See documentation of orThrow above. + */ + def tryEither[A, B](op: => Either[A, B]): Either[A, B] = try { op } catch { case Invalid(e) => Left(e.asInstanceOf[A]) } + + final case class WarnSettings(atInlineFailed: Boolean, noInlineMixed: Boolean, noInlineMissingBytecode: Boolean, noInlineMissingScalaInlineInfoAttr: Boolean) + + sealed trait OptimizerWarning { + def emitWarning(settings: WarnSettings): Boolean + } + + // Method filter in RightBiasedEither requires an implicit empty value. Taking the value here + // in scope allows for-comprehensions that desugar into filter calls (for example when using a + // tuple de-constructor). + implicit object emptyOptimizerWarning extends OptimizerWarning { + def emitWarning(settings: WarnSettings): Boolean = false + } + + sealed trait MissingBytecodeWarning extends OptimizerWarning { + override def toString = this match { + case ClassNotFound(internalName, definedInJavaSource) => + s"The classfile for $internalName could not be found on the compilation classpath." + { + if (definedInJavaSource) "\nThe class is defined in a Java source file that is being compiled (mixed compilation), therefore no bytecode is available." + else "" + } + + case MethodNotFound(name, descriptor, ownerInternalName, missingClasses) => + val (javaDef, others) = missingClasses.partition(_.definedInJavaSource) + s"The method $name$descriptor could not be found in the class $ownerInternalName or any of its parents." + + (if (others.isEmpty) "" else others.map(_.internalName).mkString("\nNote that the following parent classes could not be found on the classpath: ", ", ", "")) + + (if (javaDef.isEmpty) "" else javaDef.map(_.internalName).mkString("\nNote that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: ", ",", "")) + + case FieldNotFound(name, descriptor, ownerInternalName, missingClass) => + s"The field node $name$descriptor could not be found because the classfile $ownerInternalName cannot be found on the classpath." + + missingClass.map(c => s" Reason:\n$c").getOrElse("") + } + + def emitWarning(settings: WarnSettings): Boolean = this match { + case ClassNotFound(_, javaDefined) => + if (javaDefined) settings.noInlineMixed + else settings.noInlineMissingBytecode + + case m @ MethodNotFound(_, _, _, missing) => + if (m.isArrayMethod) false + else settings.noInlineMissingBytecode || missing.exists(_.emitWarning(settings)) + + case FieldNotFound(_, _, _, missing) => + settings.noInlineMissingBytecode || missing.exists(_.emitWarning(settings)) + } + } + + case class ClassNotFound(internalName: InternalName, definedInJavaSource: Boolean) extends MissingBytecodeWarning + case class MethodNotFound(name: String, descriptor: String, ownerInternalNameOrArrayDescriptor: InternalName, missingClasses: List[ClassNotFound]) extends MissingBytecodeWarning { + def isArrayMethod = ownerInternalNameOrArrayDescriptor.charAt(0) == '[' + } + case class FieldNotFound(name: String, descriptor: String, ownerInternalName: InternalName, missingClass: Option[ClassNotFound]) extends MissingBytecodeWarning + + sealed trait NoClassBTypeInfo extends OptimizerWarning { + override def toString = this match { + case NoClassBTypeInfoMissingBytecode(cause) => + cause.toString + + case NoClassBTypeInfoClassSymbolInfoFailedSI9111(classFullName) => + s"Failed to get the type of class symbol $classFullName due to SI-9111." + } + + def emitWarning(settings: WarnSettings): Boolean = this match { + case NoClassBTypeInfoMissingBytecode(cause) => cause.emitWarning(settings) + case NoClassBTypeInfoClassSymbolInfoFailedSI9111(_) => settings.noInlineMissingBytecode + } + } + + case class NoClassBTypeInfoMissingBytecode(cause: MissingBytecodeWarning) extends NoClassBTypeInfo + case class NoClassBTypeInfoClassSymbolInfoFailedSI9111(classFullName: String) extends NoClassBTypeInfo + + /** + * Used in the CallGraph for nodes where an issue occurred determining the callee information. + */ + sealed trait CalleeInfoWarning extends OptimizerWarning { + def declarationClass: InternalName + def name: String + def descriptor: String + + def warningMessageSignature = BackendReporting.methodSignature(declarationClass, name, descriptor) + + override def toString = this match { + case MethodInlineInfoIncomplete(_, _, _, cause) => + s"The inline information for $warningMessageSignature may be incomplete:\n" + cause + + case MethodInlineInfoMissing(_, _, _, cause) => + s"No inline information for method $warningMessageSignature could be found." + + cause.map(" Possible reason:\n" + _).getOrElse("") + + case MethodInlineInfoError(_, _, _, cause) => + s"Error while computing the inline information for method $warningMessageSignature:\n" + cause + + case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) => + cause.toString + } + + def emitWarning(settings: WarnSettings): Boolean = this match { + case MethodInlineInfoIncomplete(_, _, _, cause) => cause.emitWarning(settings) + + case MethodInlineInfoMissing(_, _, _, Some(cause)) => cause.emitWarning(settings) + case MethodInlineInfoMissing(_, _, _, None) => settings.noInlineMissingBytecode + + case MethodInlineInfoError(_, _, _, cause) => cause.emitWarning(settings) + + case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) => cause.emitWarning(settings) + } + } + + case class MethodInlineInfoIncomplete(declarationClass: InternalName, name: String, descriptor: String, cause: ClassInlineInfoWarning) extends CalleeInfoWarning + case class MethodInlineInfoMissing(declarationClass: InternalName, name: String, descriptor: String, cause: Option[ClassInlineInfoWarning]) extends CalleeInfoWarning + case class MethodInlineInfoError(declarationClass: InternalName, name: String, descriptor: String, cause: NoClassBTypeInfo) extends CalleeInfoWarning + case class RewriteTraitCallToStaticImplMethodFailed(declarationClass: InternalName, name: String, descriptor: String, cause: OptimizerWarning) extends CalleeInfoWarning + + sealed trait CannotInlineWarning extends OptimizerWarning { + def calleeDeclarationClass: InternalName + def name: String + def descriptor: String + + def calleeMethodSig = BackendReporting.methodSignature(calleeDeclarationClass, name, descriptor) + + override def toString = this match { + case IllegalAccessInstruction(_, _, _, callsiteClass, instruction) => + s"The callee $calleeMethodSig contains the instruction ${AsmUtils.textify(instruction)}" + + s"\nthat would cause an IllegalAccessError when inlined into class $callsiteClass." + + case IllegalAccessCheckFailed(_, _, _, callsiteClass, instruction, cause) => + s"Failed to check if $calleeMethodSig can be safely inlined to $callsiteClass without causing an IllegalAccessError. Checking instruction ${AsmUtils.textify(instruction)} failed:\n" + cause + + case MethodWithHandlerCalledOnNonEmptyStack(_, _, _, callsiteClass, callsiteName, callsiteDesc) => + s"""The operand stack at the callsite in ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)} contains more values than the + |arguments expected by the callee $calleeMethodSig. These values would be discarded + |when entering an exception handler declared in the inlined method.""".stripMargin + + case SynchronizedMethod(_, _, _) => + s"Method $calleeMethodSig cannot be inlined because it is synchronized." + } + + def emitWarning(settings: WarnSettings): Boolean = this match { + case _: IllegalAccessInstruction | _: MethodWithHandlerCalledOnNonEmptyStack | _: SynchronizedMethod => + settings.atInlineFailed + + case IllegalAccessCheckFailed(_, _, _, _, _, cause) => + cause.emitWarning(settings) + } + } + case class IllegalAccessInstruction(calleeDeclarationClass: InternalName, name: String, descriptor: String, + callsiteClass: InternalName, instruction: AbstractInsnNode) extends CannotInlineWarning + case class IllegalAccessCheckFailed(calleeDeclarationClass: InternalName, name: String, descriptor: String, + callsiteClass: InternalName, instruction: AbstractInsnNode, cause: OptimizerWarning) extends CannotInlineWarning + case class MethodWithHandlerCalledOnNonEmptyStack(calleeDeclarationClass: InternalName, name: String, descriptor: String, + callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning + case class SynchronizedMethod(calleeDeclarationClass: InternalName, name: String, descriptor: String) extends CannotInlineWarning + + /** + * Used in the InlineInfo of a ClassBType, when some issue occurred obtaining the inline information. + */ + sealed trait ClassInlineInfoWarning extends OptimizerWarning { + override def toString = this match { + case NoInlineInfoAttribute(internalName) => + s"The Scala classfile $internalName does not have a ScalaInlineInfo attribute." + + case ClassSymbolInfoFailureSI9111(classFullName) => + s"Failed to get the type of a method of class symbol $classFullName due to SI-9111." + + case ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass) => + s"Failed to build the inline information: $missingClass." + + case UnknownScalaInlineInfoVersion(internalName, version) => + s"Cannot read ScalaInlineInfo version $version in classfile $internalName. Use a more recent compiler." + } + + def emitWarning(settings: WarnSettings): Boolean = this match { + case NoInlineInfoAttribute(_) => settings.noInlineMissingScalaInlineInfoAttr + case ClassNotFoundWhenBuildingInlineInfoFromSymbol(cause) => cause.emitWarning(settings) + case ClassSymbolInfoFailureSI9111(_) => settings.noInlineMissingBytecode + case UnknownScalaInlineInfoVersion(_, _) => settings.noInlineMissingScalaInlineInfoAttr + } + } + + case class NoInlineInfoAttribute(internalName: InternalName) extends ClassInlineInfoWarning + case class ClassSymbolInfoFailureSI9111(classFullName: String) extends ClassInlineInfoWarning + case class ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass: ClassNotFound) extends ClassInlineInfoWarning + case class UnknownScalaInlineInfoVersion(internalName: InternalName, version: Int) extends ClassInlineInfoWarning +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala index 246235f395..492fe3ae79 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala @@ -99,10 +99,9 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { * * Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal * names of NothingClass and NullClass can't be emitted as-is. - * TODO @lry Once there's a 2.11.3 starr, use the commented argument list. The current starr crashes on the type literal `scala.runtime.Nothing$` */ - lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(rootMirror.getRequiredClass("scala.runtime.Nothing$")) // (requiredClass[scala.runtime.Nothing$]) - lazy val RT_NULL : ClassBType = classBTypeFromSymbol(rootMirror.getRequiredClass("scala.runtime.Null$")) // (requiredClass[scala.runtime.Null$]) + lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Nothing$]) + lazy val RT_NULL : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Null$]) lazy val ObjectReference : ClassBType = classBTypeFromSymbol(ObjectClass) lazy val objArrayReference : ArrayBType = ArrayBType(ObjectReference) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index 173aa0ca30..be1595dc29 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -307,6 +307,10 @@ abstract class GenBCode extends BCodeSyncAndTry { arrivalPos = 0 // just in case scalaPrimitives.init() bTypes.initializeCoreBTypes() + bTypes.javaDefinedClasses.clear() + bTypes.javaDefinedClasses ++= currentRun.symSource collect { + case (sym, _) if sym.isJavaDefined => sym.javaBinaryName.toString + } Statistics.stopTimer(BackendStats.bcodeInitTimer, initStart) // initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala index 0958601d73..607b7145d6 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala @@ -11,9 +11,9 @@ import scala.tools.asm import asm.tree._ import scala.collection.convert.decorateAsScala._ import scala.tools.asm.Attribute +import scala.tools.nsc.backend.jvm.BackendReporting._ import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.util.ClassFileLookup -import OptimizerReporting._ import BytecodeUtils._ import ByteCodeRepository._ import BTypes.InternalName @@ -29,10 +29,10 @@ import java.util.concurrent.atomic.AtomicLong * corresponds to a class being compiled. * The `Long` field encodes the age of the node in the map, which allows removing * old entries when the map grows too large. - * For Java classes in mixed compilation, the map contains `None`: there is no - * ClassNode generated by the backend and also no classfile that could be parsed. + * For Java classes in mixed compilation, the map contains an error message: no + * ClassNode is generated by the backend and also no classfile that could be parsed. */ -class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val classes: collection.concurrent.Map[InternalName, Option[(ClassNode, Source, Long)]]) { +class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJavaSourceDefined: InternalName => Boolean, val classes: collection.concurrent.Map[InternalName, Either[ClassNotFound, (ClassNode, Source, Long)]]) { private val maxCacheSize = 1500 private val targetSize = 500 @@ -46,24 +46,24 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class * We can only remove classes with `Source == Classfile`, those can be parsed again if requested. */ private def limitCacheSize(): Unit = { - if (classes.count(c => c._2.isDefined && c._2.get._2 == Classfile) > maxCacheSize) { + if (classes.count(c => c._2.isRight && c._2.right.get._2 == Classfile) > maxCacheSize) { val removeId = idCounter.get - targetSize val toRemove = classes.iterator.collect({ - case (name, Some((_, Classfile, id))) if id < removeId => name + case (name, Right((_, Classfile, id))) if id < removeId => name }).toList toRemove foreach classes.remove } } def add(classNode: ClassNode, source: Source) = { - classes(classNode.name) = Some((classNode, source, idCounter.incrementAndGet())) + classes(classNode.name) = Right((classNode, source, idCounter.incrementAndGet())) } /** * The class node and source for an internal name. If the class node is not yet available, it is * parsed from the classfile on the compile classpath. */ - def classNodeAndSource(internalName: InternalName): Option[(ClassNode, Source)] = { + def classNodeAndSource(internalName: InternalName): Either[ClassNotFound, (ClassNode, Source)] = { val r = classes.getOrElseUpdate(internalName, { limitCacheSize() parseClass(internalName).map((_, Classfile, idCounter.incrementAndGet())) @@ -75,42 +75,66 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class * The class node for an internal name. If the class node is not yet available, it is parsed from * the classfile on the compile classpath. */ - def classNode(internalName: InternalName): Option[ClassNode] = classNodeAndSource(internalName).map(_._1) + def classNode(internalName: InternalName): Either[ClassNotFound, ClassNode] = classNodeAndSource(internalName).map(_._1) /** * The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`. * The declaration of the field may be in one of the superclasses. * - * @return The [[FieldNode]] of the requested field and the [[InternalName]] of its declaring class. + * @return The [[FieldNode]] of the requested field and the [[InternalName]] of its declaring + * class, or an error message if the field could not be found */ - def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Option[(FieldNode, InternalName)] = { - classNode(classInternalName).flatMap(c => - c.fields.asScala.find(f => f.name == name && f.desc == descriptor).map((_, classInternalName)) orElse { - Option(c.superName).flatMap(n => fieldNode(n, name, descriptor)) - }) + def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Either[FieldNotFound, (FieldNode, InternalName)] = { + def fieldNodeImpl(parent: InternalName): Either[FieldNotFound, (FieldNode, InternalName)] = { + def msg = s"The field node $name$descriptor could not be found in class $classInternalName or any of its superclasses." + classNode(parent) match { + case Left(e) => Left(FieldNotFound(name, descriptor, classInternalName, Some(e))) + case Right(c) => + c.fields.asScala.find(f => f.name == name && f.desc == descriptor) match { + case Some(f) => Right((f, parent)) + case None => + if (c.superName == null) Left(FieldNotFound(name, descriptor, classInternalName, None)) + else fieldNode(c.superName, name, descriptor) + } + } + } + fieldNodeImpl(classInternalName) } /** * The method node for a method matching `name` and `descriptor`, accessed in class `classInternalName`. * The declaration of the method may be in one of the parents. * - * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring class. + * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring + * class, or an error message if the method could not be found. */ - def methodNode(ownerInternalNameOrArrayDescriptor: String, name: String, descriptor: String): Option[(MethodNode, InternalName)] = { - // In a MethodInsnNode, the `owner` field may be an array descriptor, for exmple when invoking `clone`. - // We don't inline array methods (they are native anyway), so just return None. - if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[') None - else { - classNode(ownerInternalNameOrArrayDescriptor).flatMap(c => - c.methods.asScala.find(m => m.name == name && m.desc == descriptor).map((_, ownerInternalNameOrArrayDescriptor)) orElse { - val parents = Option(c.superName) ++ c.interfaces.asScala - // `view` to stop at the first result - parents.view.flatMap(methodNode(_, name, descriptor)).headOption - }) + def methodNode(ownerInternalNameOrArrayDescriptor: String, name: String, descriptor: String): Either[MethodNotFound, (MethodNode, InternalName)] = { + // on failure, returns a list of class names that could not be found on the classpath + def methodNodeImpl(ownerInternalName: InternalName): Either[List[ClassNotFound], (MethodNode, InternalName)] = { + classNode(ownerInternalName) match { + case Left(e) => Left(List(e)) + case Right(c) => + c.methods.asScala.find(m => m.name == name && m.desc == descriptor) match { + case Some(m) => Right((m, ownerInternalName)) + case None => findInParents(Option(c.superName) ++: c.interfaces.asScala.toList, Nil) + } + } + } + + // find the MethodNode in one of the parent classes + def findInParents(parents: List[InternalName], failedClasses: List[ClassNotFound]): Either[List[ClassNotFound], (MethodNode, InternalName)] = parents match { + case x :: xs => methodNodeImpl(x).left.flatMap(failed => findInParents(xs, failed ::: failedClasses)) + case Nil => Left(failedClasses) } + + // In a MethodInsnNode, the `owner` field may be an array descriptor, for exmple when invoking `clone`. We don't have a method node to return in this case. + if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[') + Left(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, Nil)) + else + methodNodeImpl(ownerInternalNameOrArrayDescriptor).left.map(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, _)) } - private def parseClass(internalName: InternalName): Option[ClassNode] = { + private def parseClass(internalName: InternalName): Either[ClassNotFound, ClassNode] = { val fullName = internalName.replace('/', '.') classPath.findClassFile(fullName) map { classFile => val classNode = new asm.tree.ClassNode() @@ -131,6 +155,9 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class // https://jcp.org/aboutJava/communityprocess/final/jsr045/index.html removeLineNumberNodes(classNode) classNode + } match { + case Some(node) => Right(node) + case None => Left(ClassNotFound(internalName, isJavaSourceDefined(internalName))) } } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala index d2658bcd2a..14e8cccc60 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -85,7 +85,7 @@ object BytecodeUtils { def isFinalClass(classNode: ClassNode): Boolean = (classNode.access & Opcodes.ACC_FINAL) != 0 - def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE)) != 0 + def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) != 0 def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = { var result = instruction 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 18b95184e5..cd204e8043 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -7,9 +7,11 @@ package scala.tools.nsc package backend.jvm package opt +import scala.reflect.internal.util.{NoPosition, Position} import scala.tools.asm.tree._ import scala.collection.convert.decorateAsScala._ -import scala.tools.nsc.backend.jvm.BTypes.InternalName +import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InternalName} +import scala.tools.nsc.backend.jvm.BackendReporting._ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer import ByteCodeRepository.{Source, CompilationUnit} @@ -25,39 +27,40 @@ class CallGraph[BT <: BTypes](val btypes: BT) { def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): List[Callsite] = { + case class CallsiteInfo(safeToInline: Boolean, annotatedInline: Boolean, annotatedNoInline: Boolean, warning: Option[CalleeInfoWarning]) + /** * Analyze a callsite and gather meta-data that can be used for inlining decisions. - * - * @return Three booleans indicating whether - * 1. the callsite can be safely inlined - * 2. the callee is annotated `@inline` - * 3. the callee is annotated `@noinline` */ - def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, receiverTypeInternalName: InternalName, calleeSource: Source): (Boolean, Boolean, Boolean) = { + def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, receiverTypeInternalName: InternalName, calleeSource: Source): CallsiteInfo = { val methodSignature = calleeMethodNode.name + calleeMethodNode.desc - // The inlineInfo.methodInfos of a ClassBType holds an InlineInfo for each method *declared* - // within a class (not for inherited methods). Since we already have the classBType of the - // callee, we only check there for the methodInlineInfo, we should find it there. - calleeDeclarationClassBType.info.inlineInfo.methodInfos.find(_._1 == methodSignature) match { - case Some((_, methodInlineInfo)) => - val canInlineFromSource = inlineGlobalEnabled || calleeSource == CompilationUnit - // A non-final method can be 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 - def isStaticallyResolved: Boolean = { - // 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. - methodInlineInfo.effectivelyFinal || { - // TODO: inline warning when the receiver class cannot be found on the classpath - classBTypeFromParsedClassfile(receiverTypeInternalName).exists(_.info.inlineInfo.isEffectivelyFinal) + try { + // The inlineInfo.methodInfos of a ClassBType holds an InlineInfo for each method *declared* + // within a class (not for inherited methods). Since we already have the classBType of the + // callee, we only check there for the methodInlineInfo, we should find it there. + calleeDeclarationClassBType.info.orThrow.inlineInfo.methodInfos.get(methodSignature) match { + case Some(methodInlineInfo) => + val canInlineFromSource = inlineGlobalEnabled || calleeSource == CompilationUnit + // A non-final method can be 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 + def isStaticallyResolved: Boolean = { + // 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. + methodInlineInfo.effectivelyFinal || classBTypeFromParsedClassfile(receiverTypeInternalName).info.orThrow.inlineInfo.isEffectivelyFinal } - } - - (canInlineFromSource && isStaticallyResolved, methodInlineInfo.annotatedInline, methodInlineInfo.annotatedNoInline) + val warning = calleeDeclarationClassBType.info.orThrow.inlineInfo.warning.map( + MethodInlineInfoIncomplete(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, _)) + CallsiteInfo(canInlineFromSource && isStaticallyResolved, methodInlineInfo.annotatedInline, methodInlineInfo.annotatedNoInline, warning) - case None => - // TODO: issue inliner warning - (false, false, false) + case None => + val warning = MethodInlineInfoMissing(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, calleeDeclarationClassBType.info.orThrow.inlineInfo.warning) + CallsiteInfo(false, false, false, Some(warning)) + } + } catch { + case Invalid(noInfo: NoClassBTypeInfo) => + val warning = MethodInlineInfoError(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, noInfo) + CallsiteInfo(false, false, false, Some(warning)) } } @@ -72,25 +75,22 @@ class CallGraph[BT <: BTypes](val btypes: BT) { methodNode.instructions.iterator.asScala.collect({ case call: MethodInsnNode => - // TODO: log an inliner warning if the callee method cannot be found in the code repo? eg it's not on the classpath. - val callee = byteCodeRepository.methodNode(call.owner, call.name, call.desc) flatMap { - case (method, declarationClass) => - // TODO: log inliner warning if callee decl class cannot be found? - byteCodeRepository.classNodeAndSource(declarationClass) map { - case (declarationClassNode, source) => - val declarationClassBType = classBTypeFromClassNode(declarationClassNode) - val (safeToInline, annotatedInline, annotatedNoInline) = analyzeCallsite(method, declarationClassBType, call.owner, source) - Callee( - callee = method, - calleeDeclarationClass = declarationClassBType, - safeToInline = safeToInline, - annotatedInline = annotatedInline, - annotatedNoInline = annotatedNoInline - ) - } + val callee: Either[OptimizerWarning, Callee] = for { + (method, declarationClass) <- byteCodeRepository.methodNode(call.owner, call.name, call.desc): Either[OptimizerWarning, (MethodNode, InternalName)] + (declarationClassNode, source) <- byteCodeRepository.classNodeAndSource(declarationClass): Either[OptimizerWarning, (ClassNode, Source)] + declarationClassBType = classBTypeFromClassNode(declarationClassNode) + } yield { + val CallsiteInfo(safeToInline, annotatedInline, annotatedNoInline, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source) + Callee( + callee = method, + calleeDeclarationClass = declarationClassBType, + safeToInline = safeToInline, + annotatedInline = annotatedInline, + annotatedNoInline = annotatedNoInline, + calleeInfoWarning = warning) } - val argInfos = if (callee.isEmpty) Nil else { + val argInfos = if (callee.isLeft) Nil else { // TODO: for now it's Nil, because we don't run any data flow analysis // there's no point in using the parameter types, that doesn't add any information. // NOTE: need to run the same analyses after inlining, to re-compute the argInfos for the @@ -104,7 +104,8 @@ class CallGraph[BT <: BTypes](val btypes: BT) { callsiteClass = definingClass, callee = callee, argInfos = argInfos, - callsiteStackHeight = analyzer.frameAt(call).getStackSize + callsiteStackHeight = analyzer.frameAt(call).getStackSize, + callsitePosition = callsitePositions.getOrElse(call, NoPosition) ) }).toList } @@ -117,15 +118,20 @@ class CallGraph[BT <: BTypes](val btypes: BT) { * @param callsiteClass The class containing the callsite * @param callee The callee, as it appears in the invocation instruction. For virtual * calls, an override of the callee might be invoked. Also, the callee - * can be abstract. `None` if the callee MethodNode cannot be found in - * the bytecode repository. + * can be abstract. Contains a warning message if the callee MethodNode + * cannot be found in the bytecode repository. * @param argInfos Information about the invocation receiver and arguments * @param callsiteStackHeight The stack height at the callsite, required by the inliner + * @param callsitePosition The source position of the callsite, used for inliner warnings. */ final case class Callsite(callsiteInstruction: MethodInsnNode, callsiteMethod: MethodNode, callsiteClass: ClassBType, - callee: Option[Callee], argInfos: List[ArgInfo], - callsiteStackHeight: Int) { - override def toString = s"Invocation of ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}@${callsiteMethod.instructions.indexOf(callsiteInstruction)} in ${callsiteClass.internalName}.${callsiteMethod.name}" + callee: Either[OptimizerWarning, Callee], argInfos: List[ArgInfo], + callsiteStackHeight: Int, callsitePosition: Position) { + override def toString = + "Invocation of" + + s" ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}" + + s"@${callsiteMethod.instructions.indexOf(callsiteInstruction)}" + + s" in ${callsiteClass.internalName}.${callsiteMethod.name}" } /** @@ -147,8 +153,11 @@ class CallGraph[BT <: BTypes](val btypes: BT) { * and the inliner settings (project / global) allow inlining it. * @param annotatedInline True if the callee is annotated @inline * @param annotatedNoInline True if the callee is annotated @noinline + * @param calleeInfoWarning An inliner warning if some information was not available while + * gathering the information about this callee. */ final case class Callee(callee: MethodNode, calleeDeclarationClass: ClassBType, safeToInline: Boolean, - annotatedInline: Boolean, annotatedNoInline: Boolean) + annotatedInline: Boolean, annotatedNoInline: Boolean, + calleeInfoWarning: Option[CalleeInfoWarning]) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala index 4812f2290f..e7dd5abc57 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala @@ -9,6 +9,7 @@ package opt import scala.tools.asm._ import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo} +import scala.tools.nsc.backend.jvm.BackendReporting.UnknownScalaInlineInfoVersion /** * This attribute stores the InlineInfo for a ClassBType as an independent classfile attribute. @@ -119,7 +120,7 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI InlineInfoAttribute(InlineInfo(self, isFinal, infos, None)) } else { - val msg = s"Cannot read ScalaInlineInfo version $version in classfile ${cr.getClassName}. Use a more recent compiler." + val msg = UnknownScalaInlineInfoVersion(cr.getClassName, version) InlineInfoAttribute(BTypes.EmptyInlineInfo.copy(warning = Some(msg))) } } 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 b2459862ea..7ce98ecff1 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -15,9 +15,10 @@ import scala.collection.convert.decorateAsScala._ import scala.collection.convert.decorateAsJava._ import AsmUtils._ import BytecodeUtils._ -import OptimizerReporting._ import collection.mutable import scala.tools.asm.tree.analysis.{SourceInterpreter, Analyzer} +import BackendReporting._ +import scala.tools.nsc.backend.jvm.BTypes.InternalName class Inliner[BT <: BTypes](val btypes: BT) { import btypes._ @@ -27,10 +28,19 @@ class Inliner[BT <: BTypes](val btypes: BT) { rewriteFinalTraitMethodInvocations() for (request <- collectAndOrderInlineRequests) { - val Some(callee) = request.callee - inline(request.callsiteInstruction, request.callsiteStackHeight, request.callsiteMethod, request.callsiteClass, + 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) + } + } } } @@ -61,18 +71,51 @@ class Inliner[BT <: BTypes](val btypes: BT) { */ def selectCallsitesForInlining: List[Callsite] = { callsites.valuesIterator.filter({ - case Callsite(_, _, _, Some(Callee(callee, _, safeToInline, annotatedInline, _)), _, _) => - // For trait methods the callee is abstract: "trait T { @inline final def f = 1}". - // A callsite (t: T).f is `safeToInline` (effectivelyFinal is true), but the callee is the - // abstract method in the interface. - // Even though we such invocations are re-written using `rewriteFinalTraitMethodInvocation`, - // the guard is kept here for the cases where the rewrite fails. - !isAbstractMethod(callee) && safeToInline && annotatedInline - - case _ => false + case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, annotatedInline, _, warning)), _, _, pos) => + val res = doInlineCallsite(callsite) + + if (!res) { + if (annotatedInline && btypes.warnSettings.atInlineFailed) { + // if the callsite is annotated @inline, we report an inline warning even if the underlying + // reason is, for example, mixed compilation (which has a separate -Yopt-warning flag). + def initMsg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)} is annotated @inline but cannot be inlined" + def warnMsg = warning.map(" Possible reason:\n" + _).getOrElse("") + if (!safeToInline) + backendReporting.inlinerWarning(pos, s"$initMsg: the method is not final and may be overridden." + warnMsg) + else if (doRewriteTraitCallsite(callsite) && isAbstractMethod(callee)) + backendReporting.inlinerWarning(pos, s"$initMsg: the trait method call could not be rewritten to the static implementation method." + warnMsg) + else + backendReporting.inlinerWarning(pos, s"$initMsg." + warnMsg) + } else if (warning.isDefined && warning.get.emitWarning(warnSettings)) { + // when annotatedInline is false, and there is some warning, the callsite metadata is possibly incomplete. + backendReporting.inlinerWarning(pos, s"there was a problem determining if method ${callee.name} can be inlined: \n"+ warning.get) + } + } + + res + + case Callsite(ins, _, _, Left(warning), _, _, pos) => + if (warning.emitWarning(warnSettings)) + backendReporting.inlinerWarning(pos, s"failed to determine if ${ins.name} should be inlined:\n$warning") + false }).toList } + /** + * The current inlining heuristics are simple: inline calls to methods annotated @inline. + */ + def doInlineCallsite(callsite: Callsite): Boolean = callsite match { + case Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, annotatedInline, _, warning)), _, _, pos) => + // Usually, safeToInline implies that the callee is not abstract. + // But for final trait methods, the callee is abstract: "trait T { @inline final def f = 1}". + // A callsite (t: T).f is `safeToInline`, but the callee is the abstract method in the interface. + // We try to rewrite these calls to the static impl method, but that may not always succeed, + // in which case we cannot inline the call. + annotatedInline && safeToInline && !isAbstractMethod(callee) + + case _ => false + } + def rewriteFinalTraitMethodInvocations(): Unit = { // Rewriting final trait method callsites to the implementation class enables inlining. // We cannot just iterate over the values of the `callsites` map because the rewrite changes the @@ -80,6 +123,20 @@ class Inliner[BT <: BTypes](val btypes: BT) { callsites.values.toList.foreach(rewriteFinalTraitMethodInvocation) } + /** + * True for statically resolved trait callsites that should be rewritten to the static implementation method. + */ + def doRewriteTraitCallsite(callsite: Callsite) = callsite.callee match { + case Right(Callee(callee, calleeDeclarationClass, true, annotatedInline, annotatedNoInline, infoWarning)) if isAbstractMethod(callee) => + // The pattern matches abstract methods that are `safeToInline`. This can only match the interface method of a final, concrete + // trait method. An abstract method (in a trait or abstract class) is never `safeToInline` (abstract methods cannot be final). + // See also comment in `doInlineCallsite` + for (i <- calleeDeclarationClass.isInterface) assert(i, s"expected interface call (final trait method) when inlining abstract method: $callsite") + true + + case _ => false + } + /** * Rewrite the INVOKEINTERFACE callsite of a final trait method invocation to INVOKESTATIC of the * corresponding method in the implementation class. This enables inlining final trait methods. @@ -87,27 +144,31 @@ class Inliner[BT <: BTypes](val btypes: BT) { * In a final trait method callsite, the callee is safeToInline and the callee method is abstract * (the receiver type is the interface, so the method is abstract). */ - def rewriteFinalTraitMethodInvocation(callsite: Callsite): Unit = callsite.callee match { - case Some(Callee(callee, calleeDeclarationClass, true, true, annotatedNoInline)) if isAbstractMethod(callee) => - assert(calleeDeclarationClass.isInterface, s"expected interface call (final trait method) when inlining abstract method: $callsite") + def rewriteFinalTraitMethodInvocation(callsite: Callsite): Unit = { + if (doRewriteTraitCallsite(callsite)) { + val Right(Callee(callee, calleeDeclarationClass, safeToInline, annotatedInline, annotatedNoInline, infoWarning)) = callsite.callee val traitMethodArgumentTypes = asm.Type.getArgumentTypes(callee.desc) - val selfParamType = calleeDeclarationClass.info.inlineInfo.traitImplClassSelfType match { + val implClassInternalName = calleeDeclarationClass.internalName + "$class" + + val selfParamTypeV: Either[OptimizerWarning, ClassBType] = calleeDeclarationClass.info.map(_.inlineInfo.traitImplClassSelfType match { case Some(internalName) => classBTypeFromParsedClassfile(internalName) - case None => Some(calleeDeclarationClass) - } + case None => calleeDeclarationClass + }) - val implClassInternalName = calleeDeclarationClass.internalName + "$class" + def implClassMethodV(implMethodDescriptor: String): Either[OptimizerWarning, MethodNode] = { + byteCodeRepository.methodNode(implClassInternalName, callee.name, implMethodDescriptor).map(_._1) + } // The rewrite reading the implementation class and the implementation method from the bytecode // repository. If either of the two fails, the rewrite is not performed. - for { - // TODO: inline warnings if selfClassType, impl class or impl method cannot be found - selfType <- selfParamType - implClassMethodDescriptor = asm.Type.getMethodDescriptor(asm.Type.getReturnType(callee.desc), selfType.toASMType +: traitMethodArgumentTypes: _*) - (implClassMethod, _) <- byteCodeRepository.methodNode(implClassInternalName, callee.name, implClassMethodDescriptor) - implClassBType <- classBTypeFromParsedClassfile(implClassInternalName) + val res = for { + selfParamType <- selfParamTypeV + implMethodDescriptor = asm.Type.getMethodDescriptor(asm.Type.getReturnType(callee.desc), selfParamType.toASMType +: traitMethodArgumentTypes: _*) + implClassMethod <- implClassMethodV(implMethodDescriptor) + implClassBType = classBTypeFromParsedClassfile(implClassInternalName) + selfTypeOk <- calleeDeclarationClass.isSubtypeOf(selfParamType) } yield { // The self parameter type may be incompatible with the trait type. @@ -116,16 +177,16 @@ class Inliner[BT <: BTypes](val btypes: BT) { // a call to T.foo to T$class.foo, we need to cast the receiver to S, otherwise we get a // VerifyError. We run a `SourceInterpreter` to find all producer instructions of the // receiver value and add a cast to the self type after each. - if (!calleeDeclarationClass.isSubtypeOf(selfType)) { + if (!selfTypeOk) { val analyzer = new AsmAnalyzer(callsite.callsiteMethod, callsite.callsiteClass.internalName, new SourceInterpreter) val receiverValue = analyzer.frameAt(callsite.callsiteInstruction).peekDown(traitMethodArgumentTypes.length) for (i <- receiverValue.insns.asScala) { - val cast = new TypeInsnNode(CHECKCAST, selfType.internalName) + val cast = new TypeInsnNode(CHECKCAST, selfParamType.internalName) callsite.callsiteMethod.instructions.insert(i, cast) } } - val newCallsiteInstruction = new MethodInsnNode(INVOKESTATIC, implClassInternalName, callee.name, implClassMethodDescriptor, false) + val newCallsiteInstruction = new MethodInsnNode(INVOKESTATIC, implClassInternalName, callee.name, implMethodDescriptor, false) callsite.callsiteMethod.instructions.insert(callsite.callsiteInstruction, newCallsiteInstruction) callsite.callsiteMethod.instructions.remove(callsite.callsiteInstruction) @@ -134,19 +195,26 @@ class Inliner[BT <: BTypes](val btypes: BT) { callsiteInstruction = newCallsiteInstruction, callsiteMethod = callsite.callsiteMethod, callsiteClass = callsite.callsiteClass, - callee = Some(Callee( + callee = Right(Callee( callee = implClassMethod, calleeDeclarationClass = implClassBType, - safeToInline = true, - annotatedInline = true, - annotatedNoInline = annotatedNoInline)), + safeToInline = safeToInline, + annotatedInline = annotatedInline, + annotatedNoInline = annotatedNoInline, + calleeInfoWarning = infoWarning)), argInfos = Nil, - callsiteStackHeight = callsite.callsiteStackHeight + callsiteStackHeight = callsite.callsiteStackHeight, + callsitePosition = callsite.callsitePosition ) callGraph.callsites(newCallsiteInstruction) = staticCallsite } - case _ => + for (warning <- res.left) { + val Right(callee) = callsite.callee + val newCallee = callee.copy(calleeInfoWarning = Some(RewriteTraitCallToStaticImplMethodFailed(calleeDeclarationClass.internalName, callee.callee.name, callee.callee.desc, warning))) + callGraph.callsites(callsite.callsiteInstruction) = callsite.copy(callee = Right(newCallee)) + } + } } /** @@ -240,7 +308,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { */ def inline(callsiteInstruction: MethodInsnNode, callsiteStackHeight: Int, callsiteMethod: MethodNode, callsiteClass: ClassBType, callee: MethodNode, calleeDeclarationClass: ClassBType, - receiverKnownNotNull: Boolean, keepLineNumbers: Boolean): Option[String] = { + receiverKnownNotNull: Boolean, keepLineNumbers: Boolean): Option[CannotInlineWarning] = { canInline(callsiteInstruction, callsiteStackHeight, callsiteMethod, callsiteClass, callee, calleeDeclarationClass) orElse { // New labels for the cloned instructions val labelsMap = cloneLabels(callee) @@ -363,7 +431,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { callsiteClass = callsiteClass, callee = originalCallsite.callee, argInfos = Nil, // TODO: re-compute argInfos for new destination (once we actually compute them) - callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight + callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight, + callsitePosition = originalCallsite.callsitePosition ) case None => @@ -386,7 +455,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { * @return `Some(message)` if inlining cannot be performed, `None` otherwise */ def canInline(callsiteInstruction: MethodInsnNode, callsiteStackHeight: Int, callsiteMethod: MethodNode, callsiteClass: ClassBType, - callee: MethodNode, calleeDeclarationClass: ClassBType): Option[String] = { + callee: MethodNode, calleeDeclarationClass: ClassBType): Option[CannotInlineWarning] = { def calleeDesc = s"${callee.name} of type ${callee.desc} in ${calleeDeclarationClass.internalName}" def methodMismatch = s"Wrong method node for inlining ${textify(callsiteInstruction)}: $calleeDesc" @@ -416,37 +485,43 @@ class Inliner[BT <: BTypes](val btypes: BT) { if (isSynchronizedMethod(callee)) { // Could be done by locking on the receiver, wrapping the inlined code in a try and unlocking // in finally. But it's probably not worth the effort, scala never emits synchronized methods. - Some(s"Method ${methodSignature(calleeDeclarationClass.internalName, callee)} is not inlined because it is synchronized") + Some(SynchronizedMethod(calleeDeclarationClass.internalName, callee.name, callee.desc)) } else if (!callee.tryCatchBlocks.isEmpty && stackHasNonParameters) { - Some( - s"""The operand stack at the callsite in ${methodSignature(callsiteClass.internalName, callsiteMethod)} contains more values than the - |arguments expected by the callee ${methodSignature(calleeDeclarationClass.internalName, callee)}. These values would be discarded - |when entering an exception handler declared in the inlined method.""".stripMargin - ) + Some(MethodWithHandlerCalledOnNonEmptyStack( + calleeDeclarationClass.internalName, callee.name, callee.desc, + callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) } else findIllegalAccess(callee.instructions, callsiteClass) map { - case illegalAccessIns => - s"""The callee ${methodSignature(calleeDeclarationClass.internalName, callee)} contains the instruction ${AsmUtils.textify(illegalAccessIns)} - |that would cause an IllegalAccessError when inlined into class ${callsiteClass.internalName}""".stripMargin + case (illegalAccessIns, None) => + IllegalAccessInstruction( + calleeDeclarationClass.internalName, callee.name, callee.desc, + callsiteClass.internalName, illegalAccessIns) + + case (illegalAccessIns, Some(warning)) => + IllegalAccessCheckFailed( + calleeDeclarationClass.internalName, callee.name, callee.desc, + callsiteClass.internalName, illegalAccessIns, warning) } } /** * Returns the first instruction in the `instructions` list that would cause a - * [[java.lang.IllegalAccessError]] when inlined into the `destinationClass`. Returns `None` if - * all instructions can be legally transplanted. + * [[java.lang.IllegalAccessError]] when inlined into the `destinationClass`. + * + * If validity of some instruction could not be checked because an error occurred, the instruction + * is returned together with a warning message that describes the problem. */ - def findIllegalAccess(instructions: InsnList, destinationClass: ClassBType): Option[AbstractInsnNode] = { + def findIllegalAccess(instructions: InsnList, destinationClass: ClassBType): Option[(AbstractInsnNode, Option[OptimizerWarning])] = { /** * Check if a type is accessible to some class, as defined in JVMS 5.4.4. * (A1) C is public * (A2) C and D are members of the same run-time package */ - def classIsAccessible(accessed: BType, from: ClassBType = destinationClass): Boolean = (accessed: @unchecked) match { + def classIsAccessible(accessed: BType, from: ClassBType = destinationClass): Either[OptimizerWarning, Boolean] = (accessed: @unchecked) match { // TODO: A2 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok? - case c: ClassBType => c.isPublic || c.packageInternalName == from.packageInternalName + case c: ClassBType => c.isPublic.map(_ || c.packageInternalName == from.packageInternalName) case a: ArrayBType => classIsAccessible(a.elementType, from) - case _: PrimitiveBType => true + case _: PrimitiveBType => Right(true) } /** @@ -471,27 +546,29 @@ class Inliner[BT <: BTypes](val btypes: BT) { * run-time package as D. * (B4) R is private and is declared in D. */ - def memberIsAccessible(memberFlags: Int, memberDeclClass: ClassBType, memberRefClass: ClassBType): Boolean = { + def memberIsAccessible(memberFlags: Int, memberDeclClass: ClassBType, memberRefClass: ClassBType): Either[OptimizerWarning, Boolean] = { // TODO: B3 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok? def samePackageAsDestination = memberDeclClass.packageInternalName == destinationClass.packageInternalName val key = (ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE) & memberFlags key match { case ACC_PUBLIC => // B1 - true + Right(true) case ACC_PROTECTED => // B2 - val condB2 = destinationClass.isSubtypeOf(memberDeclClass) && { - val isStatic = (ACC_STATIC & memberFlags) != 0 - isStatic || memberRefClass.isSubtypeOf(destinationClass) || destinationClass.isSubtypeOf(memberRefClass) + tryEither { + val condB2 = destinationClass.isSubtypeOf(memberDeclClass).orThrow && { + val isStatic = (ACC_STATIC & memberFlags) != 0 + isStatic || memberRefClass.isSubtypeOf(destinationClass).orThrow || destinationClass.isSubtypeOf(memberRefClass).orThrow + } + Right(condB2 || samePackageAsDestination) // B3 (protected) } - condB2 || samePackageAsDestination // B3 (protected) - case 0 => // B3 (default access) - samePackageAsDestination + case 0 => // B3 (default access) + Right(samePackageAsDestination) case ACC_PRIVATE => // B4 - memberDeclClass == destinationClass + Right(memberDeclClass == destinationClass) } } @@ -502,49 +579,68 @@ class Inliner[BT <: BTypes](val btypes: BT) { * byteCodeRepository, it is considered as not legal. This is known to happen in mixed * compilation: for Java classes there is no classfile that could be parsed, nor does the * compiler generate any bytecode. + * + * Returns a warning message describing the problem if checking the legality for the instruction + * failed. */ - def isLegal(instruction: AbstractInsnNode): Boolean = instruction match { + def isLegal(instruction: AbstractInsnNode): Either[OptimizerWarning, Boolean] = instruction match { case ti: TypeInsnNode => // NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. For these instructions, the reference // "must be a symbolic reference to a class, array, or interface type" (JVMS 6), so // it can be an internal name, or a full array descriptor. - bTypeForDescriptorOrInternalNameFromClassfile(ti.desc).exists(classIsAccessible(_)) + classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ti.desc)) case ma: MultiANewArrayInsnNode => // "a symbolic reference to a class, array, or interface type" - bTypeForDescriptorOrInternalNameFromClassfile(ma.desc).exists(classIsAccessible(_)) + classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ma.desc)) case fi: FieldInsnNode => - (for { - fieldRefClass <- classBTypeFromParsedClassfile(fi.owner) - (fieldNode, fieldDeclClassNode) <- byteCodeRepository.fieldNode(fieldRefClass.internalName, fi.name, fi.desc) - fieldDeclClass <- classBTypeFromParsedClassfile(fieldDeclClassNode) + val fieldRefClass = classBTypeFromParsedClassfile(fi.owner) + for { + (fieldNode, fieldDeclClassNode) <- byteCodeRepository.fieldNode(fieldRefClass.internalName, fi.name, fi.desc): Either[OptimizerWarning, (FieldNode, InternalName)] + fieldDeclClass = classBTypeFromParsedClassfile(fieldDeclClassNode) + res <- memberIsAccessible(fieldNode.access, fieldDeclClass, fieldRefClass) } yield { - memberIsAccessible(fieldNode.access, fieldDeclClass, fieldRefClass) - }) getOrElse false + res + } case mi: MethodInsnNode => - if (mi.owner.charAt(0) == '[') true // array methods are accessible - else (for { - methodRefClass <- classBTypeFromParsedClassfile(mi.owner) - (methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, mi.name, mi.desc) - methodDeclClass <- classBTypeFromParsedClassfile(methodDeclClassNode) - } yield { - memberIsAccessible(methodNode.access, methodDeclClass, methodRefClass) - }) getOrElse false + if (mi.owner.charAt(0) == '[') Right(true) // array methods are accessible + else { + val methodRefClass = classBTypeFromParsedClassfile(mi.owner) + for { + (methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, mi.name, mi.desc): Either[OptimizerWarning, (MethodNode, InternalName)] + methodDeclClass = classBTypeFromParsedClassfile(methodDeclClassNode) + res <- memberIsAccessible(methodNode.access, methodDeclClass, methodRefClass) + } yield { + res + } + } case ivd: InvokeDynamicInsnNode => // TODO @lry check necessary conditions to inline an indy, instead of giving up - false + Right(false) case ci: LdcInsnNode => ci.cst match { - case t: asm.Type => bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName).exists(classIsAccessible(_)) - case _ => true + case t: asm.Type => classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName)) + case _ => Right(true) } - case _ => true + case _ => Right(true) } - instructions.iterator.asScala.find(!isLegal(_)) + val it = instructions.iterator.asScala + @tailrec def find: Option[(AbstractInsnNode, Option[OptimizerWarning])] = { + if (!it.hasNext) None // all instructions are legal + else { + val i = it.next() + isLegal(i) match { + case Left(warning) => Some((i, Some(warning))) // checking isLegal for i failed + case Right(false) => Some((i, None)) // an illegal instruction was found + case _ => find + } + } + } + find } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala deleted file mode 100644 index 5b47bc88c2..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala +++ /dev/null @@ -1,28 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2014 LAMP/EPFL - * @author Martin Odersky - */ - -package scala.tools.nsc -package backend.jvm -package opt - -import scala.tools.asm -import asm.tree._ -import scala.tools.nsc.backend.jvm.BTypes.InternalName - -/** - * Reporting utilities used in the optimizer. - * - * TODO: move out of opt package, rename: it's already used outside the optimizer. - * Centralize backend reporting here. - */ -object OptimizerReporting { - def methodSignature(classInternalName: InternalName, method: MethodNode): String = { - classInternalName + "::" + method.name + method.desc - } - - // TODO: clean up reporting of the inliner, test inline failure warnings, etc - def inlineFailure(reason: String): Nothing = MissingRequirementError.signal(reason) - def assertionError(message: String): Nothing = throw new AssertionError(message) -} diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 43b634eee1..d273995e6e 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -256,6 +256,25 @@ trait ScalaSettings extends AbsScalaSettings def YoptInlineGlobal = Yopt.contains(YoptChoices.inlineGlobal) def YoptInlinerEnabled = YoptInlineProject || YoptInlineGlobal + object YoptWarningsChoices extends MultiChoiceEnumeration { + val none = Choice("none" , "No optimizer warnings.") + val atInlineFailedSummary = Choice("at-inline-failed-summary" , "One-line summary if there were @inline method calls that could not be inlined.") + val atInlineFailed = Choice("at-inline-failed" , "A detailed warning for each @inline method call that could not be inlined.") + val noInlineMixed = Choice("no-inline-mixed" , "In mixed compilation, warn at callsites methods defined in java sources (the inlining decision cannot be made without bytecode).") + val noInlineMissingBytecode = Choice("no-inline-missing-bytecode" , "Warn if an inlining decision cannot be made because a the bytecode of a class or member cannot be found on the compilation classpath.") + val noInlineMissingScalaInlineInfoAttr = Choice("no-inline-missing-attribute", "Warn if an inlining decision cannot be made because a Scala classfile does not have a ScalaInlineInfo attribute.") + } + + val YoptWarnings = MultiChoiceSetting( + name = "-Yopt-warnings", + helpArg = "warnings", + descr = "Enable optimizer warnings", + domain = YoptWarningsChoices, + default = Some(List(YoptWarningsChoices.atInlineFailed.name))) withPostSetHook (self => { + if (self.value subsetOf Set(YoptWarningsChoices.none, YoptWarningsChoices.atInlineFailedSummary)) YinlinerWarnings.value = false + else YinlinerWarnings.value = true + }) + private def removalIn212 = "This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug." object YstatisticsPhases extends MultiChoiceEnumeration { val parser, typer, patmat, erasure, cleanup, jvm = Value } diff --git a/test/files/run/colltest1.scala b/test/files/run/colltest1.scala index e0ec378585..de8780a050 100644 --- a/test/files/run/colltest1.scala +++ b/test/files/run/colltest1.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warnings; re-run with -Yinline-warnings for details + * filter: inliner warnings; re-run with */ import scala.collection._ import scala.language.postfixOps diff --git a/test/files/run/compiler-asSeenFrom.scala b/test/files/run/compiler-asSeenFrom.scala index 677dd40ddc..a60c2e8925 100644 --- a/test/files/run/compiler-asSeenFrom.scala +++ b/test/files/run/compiler-asSeenFrom.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warning; re-run with -Yinline-warnings for details + * filter: inliner warning; re-run with */ import scala.tools.nsc._ import scala.tools.partest.DirectTest diff --git a/test/files/run/existentials-in-compiler.scala b/test/files/run/existentials-in-compiler.scala index dfc7048b31..e516eddf95 100644 --- a/test/files/run/existentials-in-compiler.scala +++ b/test/files/run/existentials-in-compiler.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warnings; re-run with -Yinline-warnings for details + * filter: inliner warnings; re-run with */ import scala.tools.nsc._ import scala.tools.partest.CompilerTest diff --git a/test/files/run/is-valid-num.scala b/test/files/run/is-valid-num.scala index 4ab2fac8dd..156121cab5 100644 --- a/test/files/run/is-valid-num.scala +++ b/test/files/run/is-valid-num.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warnings; re-run with -Yinline-warnings for details + * filter: inliner warnings; re-run with */ object Test { def x = BigInt("10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") diff --git a/test/files/run/iterator-from.scala b/test/files/run/iterator-from.scala index e2ca5864ea..e7ba1aeb28 100644 --- a/test/files/run/iterator-from.scala +++ b/test/files/run/iterator-from.scala @@ -1,5 +1,5 @@ /* This file tests iteratorFrom, keysIteratorFrom, and valueIteratorFrom on various sorted sets and maps - * filter: inliner warnings; re-run with -Yinline-warnings for details + * filter: inliner warnings; re-run with */ import scala.util.{Random => R} diff --git a/test/files/run/mapConserve.scala b/test/files/run/mapConserve.scala index f52af3b9f4..c17754283a 100644 --- a/test/files/run/mapConserve.scala +++ b/test/files/run/mapConserve.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warnings; re-run with -Yinline-warnings for details + * filter: inliner warnings; re-run with */ import scala.annotation.tailrec import scala.collection.mutable.ListBuffer diff --git a/test/files/run/pc-conversions.scala b/test/files/run/pc-conversions.scala index 5fecac9d94..d4ae305aa7 100644 --- a/test/files/run/pc-conversions.scala +++ b/test/files/run/pc-conversions.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warning; re-run with -Yinline-warnings for details + * filter: inliner warning; re-run with */ import collection._ diff --git a/test/files/run/stringinterpolation_macro-run.scala b/test/files/run/stringinterpolation_macro-run.scala index e18375d521..ae7c0e5d7a 100644 --- a/test/files/run/stringinterpolation_macro-run.scala +++ b/test/files/run/stringinterpolation_macro-run.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warnings; re-run with -Yinline-warnings for details + * filter: inliner warnings; re-run with */ object Test extends App { diff --git a/test/files/run/synchronized.check b/test/files/run/synchronized.check index eab191b4ed..9add05ea0c 100644 --- a/test/files/run/synchronized.check +++ b/test/files/run/synchronized.check @@ -1,4 +1,8 @@ +#partest !-Ybackend:GenBCode warning: there were 14 inliner warnings; re-run with -Yinline-warnings for details +#partest -Ybackend:GenBCode +warning: there were 14 inliner warnings; re-run with -Yopt-warnings for details +#partest .|. c1.f1: OK .|. c1.fi: OK .|... c1.fv: OK diff --git a/test/files/run/t7096.scala b/test/files/run/t7096.scala index 872562dd4d..f723d70abe 100644 --- a/test/files/run/t7096.scala +++ b/test/files/run/t7096.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warning; re-run with -Yinline-warnings for details + * filter: inliner warning; re-run with */ import scala.tools.partest._ import scala.tools.nsc._ diff --git a/test/files/run/t7582.check b/test/files/run/t7582.check index cd951d8d4f..2a11210000 100644 --- a/test/files/run/t7582.check +++ b/test/files/run/t7582.check @@ -1,2 +1,6 @@ +#partest !-Ybackend:GenBCode warning: there was one inliner warning; re-run with -Yinline-warnings for details +#partest -Ybackend:GenBCode +warning: there was one inliner warning; re-run with -Yopt-warnings for details +#partest 2 diff --git a/test/files/run/t7582b.check b/test/files/run/t7582b.check index cd951d8d4f..2a11210000 100644 --- a/test/files/run/t7582b.check +++ b/test/files/run/t7582b.check @@ -1,2 +1,6 @@ +#partest !-Ybackend:GenBCode warning: there was one inliner warning; re-run with -Yinline-warnings for details +#partest -Ybackend:GenBCode +warning: there was one inliner warning; re-run with -Yopt-warnings for details +#partest 2 diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala index c64f6e7f10..5d5215d887 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala @@ -6,11 +6,12 @@ import scala.collection.mutable.ListBuffer import scala.reflect.internal.util.BatchSourceFile import scala.reflect.io.VirtualDirectory import scala.tools.asm.Opcodes -import scala.tools.asm.tree.{AbstractInsnNode, LabelNode, ClassNode, MethodNode} +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.settings.{MutableSettings, ScalaSettings} +import scala.tools.nsc.reporters.StoreReporter +import scala.tools.nsc.settings.MutableSettings import scala.tools.nsc.{Settings, Global} import scala.tools.partest.ASMConverters import scala.collection.JavaConverters._ @@ -52,7 +53,7 @@ object CodeGenTools { val settings = new Settings() val args = (CommandLineParser tokenize defaultArgs) ++ (CommandLineParser tokenize extraArgs) settings.processArguments(args, processAll = true) - new Global(settings) + new Global(settings, new StoreReporter) } def newRun(compiler: Global): compiler.Run = { @@ -61,6 +62,8 @@ object CodeGenTools { new compiler.Run() } + def reporter(compiler: Global) = compiler.reporter.asInstanceOf[StoreReporter] + def makeSourceFile(code: String, filename: String): BatchSourceFile = new BatchSourceFile(filename, code) def getGeneratedClassfiles(outDir: AbstractFile): List[(String, Array[Byte])] = { @@ -75,9 +78,18 @@ object CodeGenTools { files(outDir) } - def compile(compiler: Global)(scalaCode: String, javaCode: List[(String, String)] = Nil): List[(String, Array[Byte])] = { + def checkReport(compiler: Global, allowMessage: StoreReporter#Info => Boolean = _ => false): Unit = { + val disallowed = reporter(compiler).infos.toList.filter(!allowMessage(_)) // toList prevents an infer-non-wildcard-existential warning. + if (disallowed.nonEmpty) { + val msg = disallowed.mkString("\n") + assert(false, "The compiler issued non-allowed warnings or errors:\n" + msg) + } + } + + def compile(compiler: Global)(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[(String, Array[Byte])] = { val run = newRun(compiler) run.compileSources(makeSourceFile(scalaCode, "unitTestSource.scala") :: javaCode.map(p => makeSourceFile(p._1, p._2))) + checkReport(compiler, allowMessage) getGeneratedClassfiles(compiler.settings.outputDirs.getSingleOutput.get) } @@ -90,7 +102,7 @@ object CodeGenTools { * The output directory is a physical directory, I have not figured out if / how it's possible to * add a VirtualDirectory to the classpath of a compiler. */ - def compileSeparately(codes: List[String], extraArgs: String = ""): List[(String, Array[Byte])] = { + def compileSeparately(codes: List[String], extraArgs: String = "", allowMessage: StoreReporter#Info => Boolean = _ => false, afterEach: AbstractFile => Unit = _ => ()): List[(String, Array[Byte])] = { val outDir = AbstractFile.getDirectory(TempDir.createTempDir()) val outDirPath = outDir.canonicalPath val argsWithOutDir = extraArgs + s" -d $outDirPath -cp $outDirPath" @@ -98,6 +110,8 @@ object CodeGenTools { for (code <- codes) { val compiler = newCompilerWithoutVirtualOutdir(extraArgs = argsWithOutDir) new compiler.Run().compileSources(List(makeSourceFile(code, "unitTestSource.scala"))) + checkReport(compiler, allowMessage) + afterEach(outDir) } val classfiles = getGeneratedClassfiles(outDir) @@ -105,29 +119,29 @@ object CodeGenTools { classfiles } - def compileClassesSeparately(codes: List[String], extraArgs: String = "") = { - readAsmClasses(compileSeparately(codes, extraArgs)) + def compileClassesSeparately(codes: List[String], extraArgs: String = "", allowMessage: StoreReporter#Info => Boolean = _ => false, afterEach: AbstractFile => Unit = _ => ()) = { + readAsmClasses(compileSeparately(codes, extraArgs, allowMessage, afterEach)) } def readAsmClasses(classfiles: List[(String, Array[Byte])]) = { classfiles.map(p => AsmUtils.readClass(p._2)).sortBy(_.name) } - def compileClasses(compiler: Global)(code: String, javaCode: List[(String, String)] = Nil): List[ClassNode] = { - readAsmClasses(compile(compiler)(code, javaCode)) + def compileClasses(compiler: Global)(code: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = { + readAsmClasses(compile(compiler)(code, javaCode, allowMessage)) } - def compileMethods(compiler: Global)(code: String): List[MethodNode] = { - compileClasses(compiler)(s"class C { $code }").head.methods.asScala.toList.filterNot(_.name == "") + def compileMethods(compiler: Global)(code: String, allowMessage: StoreReporter#Info => Boolean = _ => false): List[MethodNode] = { + compileClasses(compiler)(s"class C { $code }", allowMessage = allowMessage).head.methods.asScala.toList.filterNot(_.name == "") } - def singleMethodInstructions(compiler: Global)(code: String): List[Instruction] = { - val List(m) = compileMethods(compiler)(code) + def singleMethodInstructions(compiler: Global)(code: String, allowMessage: StoreReporter#Info => Boolean = _ => false): List[Instruction] = { + val List(m) = compileMethods(compiler)(code, allowMessage = allowMessage) instructionsFromMethod(m) } - def singleMethod(compiler: Global)(code: String): Method = { - val List(m) = compileMethods(compiler)(code) + def singleMethod(compiler: Global)(code: String, allowMessage: StoreReporter#Info => Boolean = _ => false): Method = { + val List(m) = compileMethods(compiler)(code, allowMessage = allowMessage) convertMethod(m) } diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala index 94877fb037..4086f7dd7b 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala @@ -89,6 +89,10 @@ class DirectCompileTest extends ClearAfterClass { case Invoke(_, "B", "f", _, _) => true case _ => false }, ins) + } + @Test + def compileErroneous(): Unit = { + compileClasses(compiler)("class C { def f: String = 1 }", allowMessage = _.msg contains "type mismatch") } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala index 761f214f82..1b6c080234 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala @@ -15,6 +15,8 @@ import CodeGenTools._ import scala.tools.partest.ASMConverters import ASMConverters._ +import BackendReporting._ + import scala.collection.convert.decorateAsScala._ @RunWith(classOf[JUnit4]) @@ -39,7 +41,7 @@ class BTypesFromClassfileTest { if (checked(fromSym.internalName)) checked else { assert(fromSym == fromClassfile, s"$fromSym != $fromClassfile") - sameInfo(fromSym.info, fromClassfile.info, checked + fromSym.internalName) + sameInfo(fromSym.info.get, fromClassfile.info.get, checked + fromSym.internalName) } } @@ -73,7 +75,7 @@ class BTypesFromClassfileTest { // and anonymous classes as members of the outer class. But not for unpickled symbols). // The fromClassfile info has all nested classes, including anonymous and local. So we filter // them out: member classes are identified by having the `outerName` defined. - val memberClassesFromClassfile = fromClassfile.nestedClasses.filter(_.info.nestedInfo.get.outerName.isDefined) + val memberClassesFromClassfile = fromClassfile.nestedClasses.filter(_.info.get.nestedInfo.get.outerName.isDefined) // Sorting is required: the backend sorts all InnerClass entries by internalName before writing // them to the classfile (to make it deterministic: the entries are collected in a Set during // code generation). @@ -85,7 +87,7 @@ class BTypesFromClassfileTest { clearCache() val fromSymbol = classBTypeFromSymbol(classSym) clearCache() - val fromClassfile = bTypes.classBTypeFromParsedClassfile(fromSymbol.internalName).get + val fromClassfile = bTypes.classBTypeFromParsedClassfile(fromSymbol.internalName) sameBType(fromSymbol, fromClassfile) } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala index d7344ae61f..9fda034a04 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -11,27 +11,29 @@ import org.junit.Assert._ import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis._ +import scala.tools.nsc.reporters.StoreReporter import scala.tools.testing.AssertUtil._ import CodeGenTools._ import scala.tools.partest.ASMConverters import ASMConverters._ import AsmUtils._ +import BackendReporting._ import scala.collection.convert.decorateAsScala._ @RunWith(classOf[JUnit4]) class CallGraphTest { - val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:inline-global") + val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:inline-global -Yopt-warnings") import compiler.genBCode.bTypes._ // allows inspecting the caches after a compilation run val notPerRun: List[Clearable] = List(classBTypeFromInternalName, byteCodeRepository.classes, callGraph.callsites) notPerRun foreach compiler.perRunCaches.unrecordCache - def compile(code: String): List[ClassNode] = { + def compile(code: String, allowMessage: StoreReporter#Info => Boolean): List[ClassNode] = { notPerRun.foreach(_.clear()) - compileClasses(compiler)(code) + compileClasses(compiler)(code, allowMessage = allowMessage) } def callsInMethod(methodNode: MethodNode): List[MethodInsnNode] = methodNode.instructions.iterator.asScala.collect({ @@ -69,7 +71,20 @@ class CallGraphTest { // Get the ClassNodes from the code repo (don't use the unparsed ClassNodes returned by compile). // The callGraph.callsites map is indexed by instructions of those ClassNodes. - val List(cCls, cMod, dCls, testCls) = compile(code).map(c => byteCodeRepository.classNode(c.name).get) + + val ok = Set( + "D::f1()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // only one warning for D.f1: C.f1 is not annotated @inline + "C::f3()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // only one warning for C.f3: D.f3 does not have @inline (and it would also be safe to inline) + "C::f7()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // two warnings (the error message mentions C.f7 even if the receiver type is D, because f7 is inherited from C) + "operand stack at the callsite in Test::t1(LC;)I contains more values", + "operand stack at the callsite in Test::t2(LD;)I contains more values") + var msgCount = 0 + val checkMsg = (m: StoreReporter#Info) => { + msgCount += 1 + ok exists (m.msg contains _) + } + val List(cCls, cMod, dCls, testCls) = compile(code, checkMsg).map(c => byteCodeRepository.classNode(c.name).get) + assert(msgCount == 6, msgCount) val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = cCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name) val List(df1, df3) = dCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala index 4e12ed757e..57088bdd2f 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala @@ -14,6 +14,8 @@ import ASMConverters._ import AsmUtils._ import scala.tools.testing.ClearAfterClass +import BackendReporting._ + import scala.collection.convert.decorateAsScala._ object InlineInfoTest extends ClearAfterClass.Clearable { @@ -53,7 +55,7 @@ class InlineInfoTest { |class C extends T with U """.stripMargin val classes = compile(code) - val fromSyms = classes.map(c => compiler.genBCode.bTypes.classBTypeFromInternalName(c.name).info.inlineInfo) + val fromSyms = classes.map(c => compiler.genBCode.bTypes.classBTypeFromInternalName(c.name).info.get.inlineInfo) val fromAttrs = classes.map(c => { assert(c.attrs.asScala.exists(_.isInstanceOf[InlineInfoAttribute]), c.attrs) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala new file mode 100644 index 0000000000..fedc074a15 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala @@ -0,0 +1,146 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.collection.generic.Clearable +import scala.collection.mutable.ListBuffer +import scala.reflect.internal.util.BatchSourceFile +import scala.tools.asm.Opcodes._ +import org.junit.Assert._ + +import scala.tools.asm.tree._ +import scala.tools.asm.tree.analysis._ +import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer +import scala.tools.nsc.io._ +import scala.tools.nsc.reporters.StoreReporter +import scala.tools.testing.AssertUtil._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import AsmUtils._ + +import BackendReporting._ + +import scala.collection.convert.decorateAsScala._ +import scala.tools.testing.ClearAfterClass + +object InlineWarningTest extends ClearAfterClass.Clearable { + val argsNoWarn = "-Ybackend:GenBCode -Yopt:l:classpath" + val args = argsNoWarn + " -Yopt-warnings" + var compiler = newCompiler(extraArgs = args) + def clear(): Unit = { compiler = null } +} + +@RunWith(classOf[JUnit4]) +class InlineWarningTest extends ClearAfterClass { + ClearAfterClass.stateToClear = InlineWarningTest + + val compiler = InlineWarningTest.compiler + + def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = { + compileClasses(compiler)(scalaCode, javaCode, allowMessage) + } + + @Test + def nonFinal(): Unit = { + val code = + """class C { + | @inline def m1 = 1 + |} + |trait T { + | @inline def m2 = 1 + |} + |class D extends C with T + | + |class Test { + | def t1(c: C, t: T, d: D) = c.m1 + t.m2 + d.m1 + d.m2 + |} + """.stripMargin + var count = 0 + val warns = Set( + "C::m1()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", + "T::m2()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", + "D::m2()I is annotated @inline but cannot be inlined: the method is not final and may be overridden") + compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)}) + assert(count == 4, count) + } + + @Test + def traitMissingImplClass(): Unit = { + val codeA = "trait T { @inline final def f = 1 }" + val codeB = "class C { def t1(t: T) = t.f }" + + val removeImpl = (outDir: AbstractFile) => { + val f = outDir.lookupName("T$class.class", directory = false) + if (f != null) f.delete() + } + + val warn = + """T::f()I is annotated @inline but cannot be inlined: the trait method call could not be rewritten to the static implementation method. Possible reason: + |The method f(LT;)I could not be found in the class T$class or any of its parents. + |Note that the following parent classes could not be found on the classpath: T$class""".stripMargin + + var c = 0 + compileSeparately(List(codeA, codeB), extraArgs = InlineWarningTest.args, afterEach = removeImpl, allowMessage = i => {c += 1; i.msg contains warn}) + assert(c == 1, c) + + // only summary here + compileSeparately(List(codeA, codeB), extraArgs = InlineWarningTest.argsNoWarn, afterEach = removeImpl, allowMessage = _.msg contains "there was one inliner warning") + } + + @Test + def handlerNonEmptyStack(): Unit = { + val code = + """class C { + | @noinline def q = 0 + | @inline final def foo = try { q } catch { case e: Exception => 2 } + | def t1 = println(foo) // inline warning here: foo cannot be inlined on top of a non-empty stack + |} + """.stripMargin + + var c = 0 + compile(code, allowMessage = i => {c += 1; i.msg contains "operand stack at the callsite in C::t1()V contains more values"}) + assert(c == 1, c) + } + + @Test + def mixedWarnings(): Unit = { + val javaCode = + """public class A { + | public static final int bar() { return 100; } + |} + """.stripMargin + + val scalaCode = + """class B { + | @inline final def flop = A.bar + | def g = flop + |} + """.stripMargin + + val warns = List( + """failed to determine if bar should be inlined: + |The method bar()I could not be found in the class A or any of its parents. + |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin, + + """B::flop()I is annotated @inline but could not be inlined: + |Failed to check if B::flop()I can be safely inlined to B without causing an IllegalAccessError. Checking instruction INVOKESTATIC A.bar ()I failed: + |The method bar()I could not be found in the class A or any of its parents. + |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin) + + var c = 0 + val List(b) = compile(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; warns.tail.exists(i.msg contains _)}) + assert(c == 1, c) + + // no warnings here + compileClasses(newCompiler(extraArgs = InlineWarningTest.argsNoWarn + " -Yopt-warnings:none"))(scalaCode, List((javaCode, "A.java"))) + + c = 0 + 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) + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala index 91404acba7..03d2f2f108 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala @@ -59,7 +59,7 @@ class InlinerIllegalAccessTest extends ClearAfterClass { def check(classNode: ClassNode, test: Option[AbstractInsnNode] => Unit) = { for (m <- methods) - test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(classNode.name).get)) + test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(classNode.name)).map(_._1)) } check(cClass, assertEmpty) @@ -153,7 +153,7 @@ class InlinerIllegalAccessTest extends ClearAfterClass { val List(rbD, rcD, rfD, rgD) = dCl.methods.asScala.toList.filter(_.name(0) == 'r').sortBy(_.name) def check(method: MethodNode, dest: ClassNode, test: Option[AbstractInsnNode] => Unit): Unit = { - test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(dest.name).get)) + test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(dest.name)).map(_._1)) } val cOrDOwner = (_: Option[AbstractInsnNode] @unchecked) match { diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala index 58a262c401..5c9bd1c188 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala @@ -43,7 +43,8 @@ class InlinerSeparateCompilationTest { |} """.stripMargin - val List(c, o, oMod, t, tCls) = compileClassesSeparately(List(codeA, codeB), args) + val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + val List(c, o, oMod, t, tCls) = compileClassesSeparately(List(codeA, codeB), args + " -Yopt-warnings", _.msg contains warn) assertInvoke(getSingleMethod(c, "t1"), "T", "f") assertNoInvoke(getSingleMethod(c, "t2")) assertNoInvoke(getSingleMethod(c, "t3")) 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 7f58f77b15..d32c1b2958 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -15,6 +15,7 @@ import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis._ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer import scala.tools.nsc.io._ +import scala.tools.nsc.reporters.StoreReporter import scala.tools.testing.AssertUtil._ import CodeGenTools._ @@ -22,11 +23,13 @@ import scala.tools.partest.ASMConverters import ASMConverters._ import AsmUtils._ +import BackendReporting._ + import scala.collection.convert.decorateAsScala._ import scala.tools.testing.ClearAfterClass object InlinerTest extends ClearAfterClass.Clearable { - var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:classpath") + var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:classpath -Yopt-warnings") // allows inspecting the caches after a compilation run def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes, compiler.genBCode.bTypes.callGraph.callsites) @@ -61,9 +64,9 @@ class InlinerTest extends ClearAfterClass { val compiler = InlinerTest.compiler import compiler.genBCode.bTypes._ - def compile(scalaCode: String, javaCode: List[(String, String)] = Nil): List[ClassNode] = { + def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = { InlinerTest.notPerRun.foreach(_.clear()) - compileClasses(compiler)(scalaCode, javaCode) + compileClasses(compiler)(scalaCode, javaCode, allowMessage) } def checkCallsite(callsite: callGraph.Callsite, callee: MethodNode) = { @@ -76,10 +79,10 @@ class InlinerTest extends ClearAfterClass { } // inline first invocation of f into g in class C - def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): (MethodNode, Option[String]) = { + def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): (MethodNode, Option[CannotInlineWarning]) = { val List(cls) = compile(code) mod(cls) - val clsBType = classBTypeFromParsedClassfile(cls.name).get + val clsBType = classBTypeFromParsedClassfile(cls.name) val List(f, g) = cls.methods.asScala.filter(m => Set("f", "g")(m.name)).toList.sortBy(_.name) val fCall = g.instructions.iterator.asScala.collect({ case i: MethodInsnNode if i.name == "f" => i }).next() @@ -166,7 +169,7 @@ class InlinerTest extends ClearAfterClass { val f = cls.methods.asScala.find(_.name == "f").get f.access |= ACC_SYNCHRONIZED }) - assert(can.get contains "synchronized", can) + assert(can.get.isInstanceOf[SynchronizedMethod], can) } @Test @@ -192,7 +195,7 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin val (_, r) = inlineTest(code) - assert(r.get contains "operand stack at the callsite", r) + assert(r.get.isInstanceOf[MethodWithHandlerCalledOnNonEmptyStack], r) } @Test @@ -213,8 +216,8 @@ class InlinerTest extends ClearAfterClass { val List(c, d) = compile(code) - val cTp = classBTypeFromParsedClassfile(c.name).get - val dTp = classBTypeFromParsedClassfile(d.name).get + val cTp = classBTypeFromParsedClassfile(c.name) + val dTp = classBTypeFromParsedClassfile(d.name) val g = c.methods.asScala.find(_.name == "g").get val h = d.methods.asScala.find(_.name == "h").get @@ -234,7 +237,7 @@ class InlinerTest extends ClearAfterClass { receiverKnownNotNull = true, keepLineNumbers = true) - assert(r.get contains "would cause an IllegalAccessError", r) + assert(r.get.isInstanceOf[IllegalAccessInstruction], r) } @Test @@ -373,7 +376,7 @@ class InlinerTest extends ClearAfterClass { val List(c) = compile(code) val f = c.methods.asScala.find(_.name == "f").get val callsiteIns = f.instructions.iterator().asScala.collect({ case c: MethodInsnNode => c }).next() - val clsBType = classBTypeFromParsedClassfile(c.name).get + val clsBType = classBTypeFromParsedClassfile(c.name) val analyzer = new AsmAnalyzer(f, clsBType.internalName) val integerClassBType = classBTypeFromInternalName("java/lang/Integer") @@ -457,8 +460,15 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin + val warn = + """B::flop()I is annotated @inline but could not be inlined: + |Failed to check if B::flop()I can be safely inlined to B without causing an IllegalAccessError. Checking instruction INVOKESTATIC A.bar ()I failed: + |The method bar()I could not be found in the class A or any of its parents. + |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin - val List(b) = compile(scalaCode, List((javaCode, "A.java"))) + var c = 0 + val List(b) = compile(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; i.msg contains warn}) + assert(c == 1, c) val ins = getSingleMethod(b, "g").instructions val invokeFlop = Invoke(INVOKEVIRTUAL, "B", "flop", "()I", false) assert(ins contains invokeFlop, ins.stringLines) @@ -526,7 +536,12 @@ class InlinerTest extends ClearAfterClass { | def t2 = this.f |} """.stripMargin - val List(c, t, tClass) = compile(code) + val warns = Set( + "C::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", + "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden") + var count = 0 + val List(c, t, tClass) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)}) + assert(count == 2, count) assertInvoke(getSingleMethod(c, "t1"), "T", "f") assertInvoke(getSingleMethod(c, "t2"), "C", "f") } @@ -561,7 +576,10 @@ class InlinerTest extends ClearAfterClass { | def t3(t: T) = t.f // no inlining here |} """.stripMargin - val List(c, oMirror, oModule, t, tClass) = compile(code) + val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + var count = 0 + val List(c, oMirror, oModule, t, tClass) = compile(code, allowMessage = i => {count += 1; i.msg contains warn}) + assert(count == 1, count) assertNoInvoke(getSingleMethod(oModule, "f")) @@ -663,7 +681,11 @@ class InlinerTest extends ClearAfterClass { | def m5b(t: T2b) = t.f // re-written to T2b$class.f, inlined, ICONST_1 |} """.stripMargin - val List(ca, cb, t1, t1C, t2a, t2aC, t2b, t2bC) = compile(code) + + val warning = "T1::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + var count = 0 + val List(ca, cb, t1, t1C, t2a, t2aC, t2b, t2bC) = compile(code, allowMessage = i => {count += 1; i.msg contains warning}) + assert(count == 4, count) // see comments, f is not inlined 4 times val t2aCfDesc = t2aC.methods.asScala.find(_.name == "f").get.desc assert(t2aCfDesc == "(LT1;)I", t2aCfDesc) // self-type of T2a is T1 @@ -737,4 +759,36 @@ class InlinerTest extends ClearAfterClass { val cast = TypeOp(CHECKCAST, "C") Set(t1, t2).foreach(m => assert(m.instructions.contains(cast), m.instructions)) } + + @Test + def abstractMethodWarning(): Unit = { + val code = + """abstract class C { + | @inline def foo: Int + |} + |class T { + | def t1(c: C) = c.foo + |} + """.stripMargin + val warn = "C::foo()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + var c = 0 + compile(code, allowMessage = i => {c += 1; i.msg contains warn}) + assert(c == 1, c) + } + + @Test + def abstractFinalMethodError(): Unit = { + val code = + """abstract class C { + | @inline final def foo: Int + |} + |trait T { + | @inline final def bar: Int + |} + """.stripMargin + val err = "abstract member may not have final modifier" + var i = 0 + compile(code, allowMessage = info => {i += 1; info.msg contains err}) + assert(i == 2, i) + } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala index 2c71e9d533..1ce1b88ff2 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala @@ -31,7 +31,8 @@ class MethodLevelOpts extends ClearAfterClass { @Test def eliminateEmptyTry(): Unit = { val code = "def f = { try {} catch { case _: Throwable => 0; () }; 1 }" - assertSameCode(singleMethodInstructions(methodOptCompiler)(code), wrapInDefault(Op(ICONST_1), Op(IRETURN))) + val warn = "a pure expression does nothing in statement position" + assertSameCode(singleMethodInstructions(methodOptCompiler)(code, allowMessage = _.msg contains warn), wrapInDefault(Op(ICONST_1), Op(IRETURN))) } @Test 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 c2e2a1b883..da9853148b 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala @@ -154,7 +154,8 @@ class UnreachableCodeTest extends ClearAfterClass { assertSameCode(noDce.dropNonOp, List(Op(ICONST_1), Op(IRETURN), Op(ATHROW), Op(ATHROW))) // when NOT computing stack map frames, ASM's ClassWriter does not replace dead code by NOP/ATHROW - val noDceNoFrames = singleMethodInstructions(noOptNoFramesCompiler)(code) + val warn = "target:jvm-1.5 is deprecated" + val noDceNoFrames = singleMethodInstructions(noOptNoFramesCompiler)(code, allowMessage = _.msg contains warn) assertSameCode(noDceNoFrames.dropNonOp, List(Op(ICONST_1), Op(IRETURN), Op(ICONST_2), Op(IRETURN))) } -- cgit v1.2.3 From 2d88143f37144f3db5a1d1d27806518bea13ba47 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 11 Mar 2015 10:39:40 -0700 Subject: Ensure to re-write only trait method calls of actual trait methods The inliner would incorrectly treat trait field accessors like ordinary trait member methods and try to re-write invocations to the corresponding static method in the implementation class. This rewrite usually failed because no method was found in the impl class. However, for lazy val fields, there exists a member in the impl class with the same name, and the rewrite was performed. The result was that every field access would execute the lazy initializer instead of reading the field. This commit checks the traitMethodWithStaticImplementation field of the ScalaInlineInfo classfile attribute and puts an explicit `safeToRewrite` flag to each call site in the call graph. This cleans up the code in the inliner that deals with rewriting trait callsites. --- .../scala/tools/nsc/backend/jvm/BTypes.scala | 1 - .../tools/nsc/backend/jvm/opt/CallGraph.scala | 52 ++++++++++++---- .../scala/tools/nsc/backend/jvm/opt/Inliner.scala | 30 ++++------ .../tools/nsc/backend/jvm/opt/InlinerTest.scala | 70 ++++++++++++++++++++++ 4 files changed, 119 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index d2ee944916..d8a17e975e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -14,7 +14,6 @@ import asm.Opcodes import scala.tools.asm.tree.{MethodInsnNode, InnerClassNode, ClassNode} import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo} import scala.tools.nsc.backend.jvm.BackendReporting._ -import BackendReporting.RightBiasedEither import scala.tools.nsc.backend.jvm.opt._ import scala.collection.convert.decorateAsScala._ 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 cd204e8043..47d32c94cb 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -27,7 +27,9 @@ class CallGraph[BT <: BTypes](val btypes: BT) { def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): List[Callsite] = { - case class CallsiteInfo(safeToInline: Boolean, annotatedInline: Boolean, annotatedNoInline: Boolean, warning: Option[CalleeInfoWarning]) + case class CallsiteInfo(safeToInline: Boolean, safeToRewrite: Boolean, + annotatedInline: Boolean, annotatedNoInline: Boolean, + warning: Option[CalleeInfoWarning]) /** * Analyze a callsite and gather meta-data that can be used for inlining decisions. @@ -42,25 +44,44 @@ class CallGraph[BT <: BTypes](val btypes: BT) { calleeDeclarationClassBType.info.orThrow.inlineInfo.methodInfos.get(methodSignature) match { case Some(methodInlineInfo) => val canInlineFromSource = inlineGlobalEnabled || calleeSource == CompilationUnit - // A non-final method can be 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 - def isStaticallyResolved: Boolean = { - // 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. - methodInlineInfo.effectivelyFinal || classBTypeFromParsedClassfile(receiverTypeInternalName).info.orThrow.inlineInfo.isEffectivelyFinal + + val isAbstract = BytecodeUtils.isAbstractMethod(calleeMethodNode) + + // (1) A non-final method can be safe to inline if the receiver type is a final subclass. Example: + // class A { @inline def f = 1 }; object B extends A; B.f // can be inlined + // + // TODO: type analysis can render more calls statically resolved. Example˜∫ + // new A.f // can be inlined, the receiver type is known to be exactly A. + val isStaticallyResolved: Boolean = { + methodInlineInfo.effectivelyFinal || + classBTypeFromParsedClassfile(receiverTypeInternalName).info.orThrow.inlineInfo.isEffectivelyFinal // (1) } + + val isRewritableTraitCall = isStaticallyResolved && methodInlineInfo.traitMethodWithStaticImplementation + val warning = calleeDeclarationClassBType.info.orThrow.inlineInfo.warning.map( MethodInlineInfoIncomplete(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, _)) - CallsiteInfo(canInlineFromSource && isStaticallyResolved, methodInlineInfo.annotatedInline, methodInlineInfo.annotatedNoInline, warning) + + // (1) For invocations of final trait methods, the callee isStaticallyResolved but also + // abstract. Such a callee is not safe to inline - it needs to be re-written to the + // static impl method first (safeToRewrite). + // (2) Final trait methods can be rewritten from the interface to the static implementation + // method to enable inlining. + CallsiteInfo( + safeToInline = canInlineFromSource && isStaticallyResolved && !isAbstract, // (1) + safeToRewrite = canInlineFromSource && isRewritableTraitCall, // (2) + annotatedInline = methodInlineInfo.annotatedInline, + annotatedNoInline = methodInlineInfo.annotatedNoInline, + warning = warning) case None => val warning = MethodInlineInfoMissing(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, calleeDeclarationClassBType.info.orThrow.inlineInfo.warning) - CallsiteInfo(false, false, false, Some(warning)) + CallsiteInfo(false, false, false, false, Some(warning)) } } catch { case Invalid(noInfo: NoClassBTypeInfo) => val warning = MethodInlineInfoError(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, noInfo) - CallsiteInfo(false, false, false, Some(warning)) + CallsiteInfo(false, false, false, false, Some(warning)) } } @@ -80,11 +101,12 @@ class CallGraph[BT <: BTypes](val btypes: BT) { (declarationClassNode, source) <- byteCodeRepository.classNodeAndSource(declarationClass): Either[OptimizerWarning, (ClassNode, Source)] declarationClassBType = classBTypeFromClassNode(declarationClassNode) } yield { - val CallsiteInfo(safeToInline, annotatedInline, annotatedNoInline, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source) + val CallsiteInfo(safeToInline, safeToRewrite, annotatedInline, annotatedNoInline, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source) Callee( callee = method, calleeDeclarationClass = declarationClassBType, safeToInline = safeToInline, + safeToRewrite = safeToRewrite, annotatedInline = annotatedInline, annotatedNoInline = annotatedNoInline, calleeInfoWarning = warning) @@ -151,13 +173,17 @@ class CallGraph[BT <: BTypes](val btypes: BT) { * @param calleeDeclarationClass The class in which the callee is declared * @param safeToInline True if the callee can be safely inlined: it cannot be overridden, * and the inliner settings (project / global) allow inlining it. + * @param safeToRewrite True if the callee the interface method of a concrete trait method + * that can be safely re-written to the static implementation method. * @param annotatedInline True if the callee is annotated @inline * @param annotatedNoInline True if the callee is annotated @noinline * @param calleeInfoWarning An inliner warning if some information was not available while * gathering the information about this callee. */ final case class Callee(callee: MethodNode, calleeDeclarationClass: ClassBType, - safeToInline: Boolean, + safeToInline: Boolean, safeToRewrite: Boolean, annotatedInline: Boolean, annotatedNoInline: Boolean, - calleeInfoWarning: Option[CalleeInfoWarning]) + calleeInfoWarning: Option[CalleeInfoWarning]) { + assert(!(safeToInline && safeToRewrite), s"A callee of ${callee.name} can be either safeToInline or safeToRewrite, but not both.") + } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala index 7ce98ecff1..0f4c7d5287 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -71,7 +71,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { */ def selectCallsitesForInlining: List[Callsite] = { callsites.valuesIterator.filter({ - case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, annotatedInline, _, warning)), _, _, pos) => + case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, annotatedInline, _, warning)), _, _, pos) => val res = doInlineCallsite(callsite) if (!res) { @@ -80,10 +80,10 @@ class Inliner[BT <: BTypes](val btypes: BT) { // reason is, for example, mixed compilation (which has a separate -Yopt-warning flag). def initMsg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)} is annotated @inline but cannot be inlined" def warnMsg = warning.map(" Possible reason:\n" + _).getOrElse("") - if (!safeToInline) - backendReporting.inlinerWarning(pos, s"$initMsg: the method is not final and may be overridden." + warnMsg) - else if (doRewriteTraitCallsite(callsite) && isAbstractMethod(callee)) + if (doRewriteTraitCallsite(callsite)) backendReporting.inlinerWarning(pos, s"$initMsg: the trait method call could not be rewritten to the static implementation method." + warnMsg) + else if (!safeToInline) + backendReporting.inlinerWarning(pos, s"$initMsg: the method is not final and may be overridden." + warnMsg) else backendReporting.inlinerWarning(pos, s"$initMsg." + warnMsg) } else if (warning.isDefined && warning.get.emitWarning(warnSettings)) { @@ -105,13 +105,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { * The current inlining heuristics are simple: inline calls to methods annotated @inline. */ def doInlineCallsite(callsite: Callsite): Boolean = callsite match { - case Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, annotatedInline, _, warning)), _, _, pos) => - // Usually, safeToInline implies that the callee is not abstract. - // But for final trait methods, the callee is abstract: "trait T { @inline final def f = 1}". - // A callsite (t: T).f is `safeToInline`, but the callee is the abstract method in the interface. - // We try to rewrite these calls to the static impl method, but that may not always succeed, - // in which case we cannot inline the call. - annotatedInline && safeToInline && !isAbstractMethod(callee) + case Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, annotatedInline, _, warning)), _, _, pos) => + annotatedInline && safeToInline case _ => false } @@ -127,13 +122,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { * True for statically resolved trait callsites that should be rewritten to the static implementation method. */ def doRewriteTraitCallsite(callsite: Callsite) = callsite.callee match { - case Right(Callee(callee, calleeDeclarationClass, true, annotatedInline, annotatedNoInline, infoWarning)) if isAbstractMethod(callee) => - // The pattern matches abstract methods that are `safeToInline`. This can only match the interface method of a final, concrete - // trait method. An abstract method (in a trait or abstract class) is never `safeToInline` (abstract methods cannot be final). - // See also comment in `doInlineCallsite` - for (i <- calleeDeclarationClass.isInterface) assert(i, s"expected interface call (final trait method) when inlining abstract method: $callsite") - true - + case Right(Callee(callee, calleeDeclarationClass, safeToInline, true, annotatedInline, annotatedNoInline, infoWarning)) => true case _ => false } @@ -146,7 +135,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { */ def rewriteFinalTraitMethodInvocation(callsite: Callsite): Unit = { if (doRewriteTraitCallsite(callsite)) { - val Right(Callee(callee, calleeDeclarationClass, safeToInline, annotatedInline, annotatedNoInline, infoWarning)) = callsite.callee + val Right(Callee(callee, calleeDeclarationClass, _, _, annotatedInline, annotatedNoInline, infoWarning)) = callsite.callee val traitMethodArgumentTypes = asm.Type.getArgumentTypes(callee.desc) @@ -198,7 +187,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { callee = Right(Callee( callee = implClassMethod, calleeDeclarationClass = implClassBType, - safeToInline = safeToInline, + safeToInline = true, + safeToRewrite = false, annotatedInline = annotatedInline, annotatedNoInline = annotatedNoInline, calleeInfoWarning = infoWarning)), 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 d32c1b2958..caaa65bf7e 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -791,4 +791,74 @@ class InlinerTest extends ClearAfterClass { compile(code, allowMessage = info => {i += 1; info.msg contains err}) assert(i == 2, i) } + + @Test + def noInlineTraitFieldAccessors(): Unit = { + val code = + """sealed trait T { + | lazy val a = 0 + | val b = 1 + | final lazy val c = 2 + | final val d = 3 + | final val d1: Int = 3 + | + | @noinline def f = 5 // re-written to T$class + | @noinline final def g = 6 // re-written + | + | @noinline def h: Int + | @inline def i: Int + |} + | + |trait U { // not sealed + | lazy val a = 0 + | val b = 1 + | final lazy val c = 2 + | final val d = 3 + | final val d1: Int = 3 + | + | @noinline def f = 5 // not re-written (not final) + | @noinline final def g = 6 // re-written + | + | @noinline def h: Int + | @inline def i: Int + |} + | + |class C { + | def m1(t: T) = t.a + t.b + t.c + t.d1 + | def m2(t: T) = t.d // inlined by the type-checker's constant folding + | def m3(t: T) = t.f + t.g + t.h + t.i + | + | def m4(u: U) = u.a + u.b + u.c + u.d1 + | def m5(u: U) = u.d + | def m6(u: U) = u.f + u.g + u.h + u.i + |} + """.stripMargin + + val List(c, t, tClass, u, uClass) = compile(code, allowMessage = _.msg contains "i()I is annotated @inline but cannot be inlined") + val m1 = getSingleMethod(c, "m1") + assertInvoke(m1, "T", "a") + assertInvoke(m1, "T", "b") + assertInvoke(m1, "T", "c") + + assertNoInvoke(getSingleMethod(c, "m2")) + + val m3 = getSingleMethod(c, "m3") + assertInvoke(m3, "T$class", "f") + assertInvoke(m3, "T$class", "g") + assertInvoke(m3, "T", "h") + assertInvoke(m3, "T", "i") + + val m4 = getSingleMethod(c, "m4") + assertInvoke(m4, "U", "a") + assertInvoke(m4, "U", "b") + assertInvoke(m4, "U", "c") + + assertNoInvoke(getSingleMethod(c, "m5")) + + val m6 = getSingleMethod(c, "m6") + assertInvoke(m6, "U", "f") + assertInvoke(m6, "U$class", "g") + assertInvoke(m6, "U", "h") + assertInvoke(m6, "U", "i") + } } -- cgit v1.2.3 From bf01acd6143552be8a173472a839dfcdee27697c Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 12 Mar 2015 15:25:14 -0700 Subject: SI-9020 Avoid spurious value discarding warning post-typer Typechecking during the specialization phase was emitting a bogus warning about value discarding. Such warnings in the typer should be guarded by `!isPastTyper` to restrict the analysis to the code the user originally wrote, rather than the results of later typechecking. I've made this change to the value discarding warning. I've also changed a numeric widening warning in the vicinity, although I do not have a test case for that. --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 4 ++-- test/files/pos/t9020.flags | 1 + test/files/pos/t9020.scala | 10 ++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 test/files/pos/t9020.flags create mode 100644 test/files/pos/t9020.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 3a85d16f55..7ef739e711 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1039,11 +1039,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // to non-continuation types. if (tree.tpe <:< AnyTpe) pt.dealias match { case TypeRef(_, UnitClass, _) => // (12) - if (settings.warnValueDiscard) + if (!isPastTyper && settings.warnValueDiscard) context.warning(tree.pos, "discarded non-Unit value") return typedPos(tree.pos, mode, pt)(Block(List(tree), Literal(Constant(())))) case TypeRef(_, sym, _) if isNumericValueClass(sym) && isNumericSubType(tree.tpe, pt) => - if (settings.warnNumericWiden) + if (!isPastTyper && settings.warnNumericWiden) context.warning(tree.pos, "implicit numeric widening") return typedPos(tree.pos, mode, pt)(Select(tree, "to" + sym.name)) case _ => diff --git a/test/files/pos/t9020.flags b/test/files/pos/t9020.flags new file mode 100644 index 0000000000..efb2dd3e6f --- /dev/null +++ b/test/files/pos/t9020.flags @@ -0,0 +1 @@ +-Ywarn-value-discard -Xfatal-warnings diff --git a/test/files/pos/t9020.scala b/test/files/pos/t9020.scala new file mode 100644 index 0000000000..16e31e2572 --- /dev/null +++ b/test/files/pos/t9020.scala @@ -0,0 +1,10 @@ +trait ValueDiscard[@specialized U] { + def u: U +} +/* Was: +scalac-hash v2.11.5 -Ywarn-value-discard test/files/pos/t9020.scala +test/files/pos/t9020.scala:2: warning: discarded non-Unit value + def u: U + ^ +one warning found +*/ -- cgit v1.2.3 From c2ab768287cc02b5e01342ac993d6c2b6e7ee2aa Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Thu, 12 Mar 2015 15:32:46 -0700 Subject: Don't inline methods containing super calls into other classes Method bodies that contain a super call cannot be inlined into other classes. The same goes for methods containing calls to private methods, but that was already ensured before by accessibility checks. The last case of `invokespecial` instructions is constructor calls. Those can be safely moved into different classes (as long as the constructor is accessible at the new location). Note that scalac never emits methods / constructors as private in bytecode. --- .../scala/tools/nsc/backend/jvm/opt/Inliner.scala | 18 ++++++-- .../backend/jvm/opt/InlinerIllegalAccessTest.scala | 26 ++++++------ .../tools/nsc/backend/jvm/opt/InlinerTest.scala | 48 ++++++++++++++++++++++ 3 files changed, 76 insertions(+), 16 deletions(-) (limited to 'src') 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 0f4c7d5287..e14e57d3ab 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -480,7 +480,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { Some(MethodWithHandlerCalledOnNonEmptyStack( calleeDeclarationClass.internalName, callee.name, callee.desc, callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) - } else findIllegalAccess(callee.instructions, callsiteClass) map { + } else findIllegalAccess(callee.instructions, calleeDeclarationClass, callsiteClass) map { case (illegalAccessIns, None) => IllegalAccessInstruction( calleeDeclarationClass.internalName, callee.name, callee.desc, @@ -500,7 +500,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { * If validity of some instruction could not be checked because an error occurred, the instruction * is returned together with a warning message that describes the problem. */ - def findIllegalAccess(instructions: InsnList, destinationClass: ClassBType): Option[(AbstractInsnNode, Option[OptimizerWarning])] = { + def findIllegalAccess(instructions: InsnList, calleeDeclarationClass: ClassBType, destinationClass: ClassBType): Option[(AbstractInsnNode, Option[OptimizerWarning])] = { /** * Check if a type is accessible to some class, as defined in JVMS 5.4.4. @@ -597,11 +597,23 @@ class Inliner[BT <: BTypes](val btypes: BT) { case mi: MethodInsnNode => if (mi.owner.charAt(0) == '[') Right(true) // array methods are accessible else { + def canInlineCall(opcode: Int, methodFlags: Int, methodDeclClass: ClassBType, methodRefClass: ClassBType): Either[OptimizerWarning, Boolean] = { + opcode match { + case INVOKESPECIAL if mi.name != GenBCode.INSTANCE_CONSTRUCTOR_NAME => + // invokespecial is used for private method calls, super calls and instance constructor calls. + // private method and super calls can only be inlined into the same class. + Right(destinationClass == calleeDeclarationClass) + + case _ => // INVOKEVIRTUAL, INVOKESTATIC, INVOKEINTERFACE and INVOKESPECIAL of constructors + memberIsAccessible(methodFlags, methodDeclClass, methodRefClass) + } + } + val methodRefClass = classBTypeFromParsedClassfile(mi.owner) for { (methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, mi.name, mi.desc): Either[OptimizerWarning, (MethodNode, InternalName)] methodDeclClass = classBTypeFromParsedClassfile(methodDeclClassNode) - res <- memberIsAccessible(methodNode.access, methodDeclClass, methodRefClass) + res <- canInlineCall(mi.getOpcode, methodNode.access, methodDeclClass, methodRefClass) } yield { res } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala index 03d2f2f108..b4839dcec8 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala @@ -59,7 +59,7 @@ class InlinerIllegalAccessTest extends ClearAfterClass { def check(classNode: ClassNode, test: Option[AbstractInsnNode] => Unit) = { for (m <- methods) - test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(classNode.name)).map(_._1)) + test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(cClass.name), classBTypeFromParsedClassfile(classNode.name)).map(_._1)) } check(cClass, assertEmpty) @@ -152,8 +152,8 @@ class InlinerIllegalAccessTest extends ClearAfterClass { val List(rbD, rcD, rfD, rgD) = dCl.methods.asScala.toList.filter(_.name(0) == 'r').sortBy(_.name) - def check(method: MethodNode, dest: ClassNode, test: Option[AbstractInsnNode] => Unit): Unit = { - test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(dest.name)).map(_._1)) + def check(method: MethodNode, decl: ClassNode, dest: ClassNode, test: Option[AbstractInsnNode] => Unit): Unit = { + test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(decl.name), classBTypeFromParsedClassfile(dest.name)).map(_._1)) } val cOrDOwner = (_: Option[AbstractInsnNode] @unchecked) match { @@ -164,35 +164,35 @@ class InlinerIllegalAccessTest extends ClearAfterClass { // PUBLIC // public methods allowed everywhere - for (m <- Set(raC, reC); c <- allClasses) check(m, c, assertEmpty) + for (m <- Set(raC, reC); c <- allClasses) check(m, cCl, c, assertEmpty) // DEFAULT ACCESS // default access OK in same package - for (m <- Set(rbC, rfC, rbD, rfD); c <- allClasses) { - if (c.name startsWith "a/") check(m, c, assertEmpty) - else check(m, c, cOrDOwner) + for ((m, declCls) <- Set((rbC, cCl), (rfC, cCl), (rbD, dCl), (rfD, dCl)); c <- allClasses) { + if (c.name startsWith "a/") check(m, declCls, c, assertEmpty) + else check(m, declCls, c, cOrDOwner) } // PROTECTED // protected accessed in same class, or protected static accessed in subclass(rgD). // can be inlined to subclasses, and classes in the same package (gCl) - for (m <- Set(rcC, rgC, rgD); c <- Set(cCl, dCl, eCl, fCl, gCl, hCl)) check(m, c, assertEmpty) + for ((m, declCls) <- Set((rcC, cCl), (rgC, cCl), (rgD, dCl)); c <- Set(cCl, dCl, eCl, fCl, gCl, hCl)) check(m, declCls, c, assertEmpty) // protected in non-subclass and different package - for (m <- Set(rcC, rgC)) check(m, iCl, cOrDOwner) + for (m <- Set(rcC, rgC)) check(m, cCl, iCl, cOrDOwner) // non-static protected accessed in subclass (rcD). can be inlined to related class, or classes in the same package - for (c <- Set(cCl, dCl, eCl, fCl, gCl)) check(rcD, c, assertEmpty) + for (c <- Set(cCl, dCl, eCl, fCl, gCl)) check(rcD, dCl, c, assertEmpty) // rcD cannot be inlined into non-related classes, if the declaration and destination are not in the same package - for (c <- Set(hCl, iCl)) check(rcD, c, cOrDOwner) + for (c <- Set(hCl, iCl)) check(rcD, dCl, c, cOrDOwner) // PRIVATE // privated method accesses can only be inlined in the same class - for (m <- Set(rdC, rhC)) check(m, cCl, assertEmpty) - for (m <- Set(rdC, rhC); c <- allClasses.tail) check(m, c, cOrDOwner) + for (m <- Set(rdC, rhC)) check(m, cCl, cCl, assertEmpty) + for (m <- Set(rdC, rhC); c <- allClasses.tail) check(m, cCl, c, cOrDOwner) } } 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 0581bd24fe..39fb28570e 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -902,4 +902,52 @@ class InlinerTest extends ClearAfterClass { allowMessage = i => {c += 1; i.msg contains warn}) assert(c == 1, c) } + + @Test + def inlineInvokeSpecial(): Unit = { + val code = + """class Aa { + | def f1 = 0 + |} + |class B extends Aa { + | @inline final override def f1 = 1 + super.f1 // invokespecial Aa.f1 + | + | private def f2m = 0 // public B$$f2m in bytecode + | @inline final def f2 = f2m // invokevirtual B.B$$f2m + | + | private def this(x: Int) = this() // public in bytecode + | @inline final def f3 = new B() // invokespecial B.() + | @inline final def f4 = new B(1) // invokespecial B.(I) + | + | def t1 = f1 // inlined + | def t2 = f2 // inlined + | def t3 = f3 // inlined + | def t4 = f4 // inlined + |} + |class T { + | def t1(b: B) = b.f1 // cannot inline: contains a super call + | def t2(b: B) = b.f2 // inlined + | def t3(b: B) = b.f3 // inlined + | def t4(b: B) = b.f4 // inlined + |} + """.stripMargin + + val warn = + """B::f1()I is annotated @inline but could not be inlined: + |The callee B::f1()I contains the instruction INVOKESPECIAL Aa.f1 ()I + |that would cause an IllegalAccessError when inlined into class T.""".stripMargin + var c = 0 + val List(a, b, t) = compile(code, allowMessage = i => {c += 1; i.msg contains warn}) + assert(c == 1, c) + + assertInvoke(getSingleMethod(b, "t1"), "Aa", "f1") + assertInvoke(getSingleMethod(b, "t2"), "B", "B$$f2m") + assertInvoke(getSingleMethod(b, "t3"), "B", "") + assertInvoke(getSingleMethod(b, "t4"), "B", "") + + assertInvoke(getSingleMethod(t, "t1"), "B", "f1") + assertInvoke(getSingleMethod(t, "t2"), "B", "B$$f2m") + assertInvoke(getSingleMethod(t, "t3"), "B", "") + assertInvoke(getSingleMethod(t, "t4"), "B", "") + } } -- cgit v1.2.3 From 8e0a386508d6cb655db0a29a23fb26170b262548 Mon Sep 17 00:00:00 2001 From: Szabolcs Berecz Date: Sat, 14 Mar 2015 01:27:02 +0100 Subject: SI-9219 Stream toString returns unexpected result - Cursor was not advanced before appending the second element when only the first two elements of the stream were known. - When there is no cycle in the stream, the "scout" (and "cursor") ends up pointing to a stream where tailDefined is false. This means that cursor is either empty, or cursor.tail is not yet evaluated. The former case is handled properly, but in the latter case, one more element (cursor.head) needs to be appended. --- src/library/scala/collection/immutable/Stream.scala | 21 +++++++++++++-------- test/files/run/t9219.check | 3 +++ test/files/run/t9219.scala | 11 +++++++++++ 3 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 test/files/run/t9219.check create mode 100644 test/files/run/t9219.scala (limited to 'src') diff --git a/src/library/scala/collection/immutable/Stream.scala b/src/library/scala/collection/immutable/Stream.scala index 9ed5162061..f303e79bb3 100644 --- a/src/library/scala/collection/immutable/Stream.scala +++ b/src/library/scala/collection/immutable/Stream.scala @@ -743,16 +743,18 @@ self => b append end return b } - if ((cursor ne scout) && scout.tailDefined) { + if (cursor ne scout) { cursor = scout - scout = scout.tail - // Use 2x 1x iterator trick for cycle detection; slow iterator can add strings - while ((cursor ne scout) && scout.tailDefined) { - b append sep append cursor.head - n += 1 - cursor = cursor.tail + if (scout.tailDefined) { scout = scout.tail - if (scout.tailDefined) scout = scout.tail + // Use 2x 1x iterator trick for cycle detection; slow iterator can add strings + while ((cursor ne scout) && scout.tailDefined) { + b append sep append cursor.head + n += 1 + cursor = cursor.tail + scout = scout.tail + if (scout.tailDefined) scout = scout.tail + } } } if (!scout.tailDefined) { // Not a cycle, scout hit an end @@ -761,6 +763,9 @@ self => n += 1 cursor = cursor.tail } + if (cursor.nonEmpty) { + b append sep append cursor.head + } } else { // Cycle. diff --git a/test/files/run/t9219.check b/test/files/run/t9219.check new file mode 100644 index 0000000000..3509ece003 --- /dev/null +++ b/test/files/run/t9219.check @@ -0,0 +1,3 @@ +Stream(1, 2, ?) +Stream(1, 2, 3, 4, ?) +Stream(1, 2, 3, 4, 5, 6, ?) diff --git a/test/files/run/t9219.scala b/test/files/run/t9219.scala new file mode 100644 index 0000000000..c15f55faac --- /dev/null +++ b/test/files/run/t9219.scala @@ -0,0 +1,11 @@ +object Test extends App { + def check[U](f: Stream[Int] => U) = { + val s = Stream.from(1) + f(s) + println(s) + } + + check(_.tail) + check(_.take(4).force) + check(_(5)) +} -- cgit v1.2.3 From 992b58f2fffd49c6a7ff56a3106f3a4fcb0b02c9 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Sat, 14 Mar 2015 10:31:47 -0700 Subject: Clarifications to src/asm/README --- src/asm/README | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/asm/README b/src/asm/README index 3ceac88098..e70911e17c 100644 --- a/src/asm/README +++ b/src/asm/README @@ -6,11 +6,16 @@ Git SVN repo: https://github.com/lrytz/asm Upgrading ASM ------------- +Check the commit history of src/asm: https://github.com/scala/scala/commits/2.11.x/src/asm. +Find the previous commit that upgraded ASM and take a look at its commit message. It should +be a squashed version of a pull request that shows the precise procedure how the last upgrade +was made. + Start by deleting all source files in src/asm/ and copy the ones from the latest ASM release. Excluded Files (don't copy): - package.html files - - org/objectweb/asm/commons + - org/objectweb/asm/commons, but keep CodeSizeEvaluator.java - org/objectweb/asm/optimizer - org/objectweb/asm/xml @@ -27,4 +32,6 @@ Re-packaging and cosmetic changes: - remove trailing whitespace find src/asm/scala/tools/asm -name '*.java' | xargs sed -i '' -e 's/[ ]*$//' -Actual changes: check the git log for [asm-cherry-pick] after the previous upgrade. +Include the actual changes that we have in our repostiory + - Include the commits labelled [asm-cherry-pick] in the non-squashed PR of the previous upgrade + - Include the changes that were added to src/asm since the last upgrade and label them [asm-cherry-pick] -- cgit v1.2.3 From 139a05c708f4c1fbeeeb8129a40719987fa7d8b6 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Sat, 14 Mar 2015 10:39:59 -0700 Subject: Upgrade ASM to 5.0.3 This commit is a squashed version of all commits in PR #4382. For the next upgrade, consult the src/asm/README and check the commits in https://github.com/scala/scala/pull/4382/commits. --- src/asm/README | 2 +- src/asm/scala/tools/asm/Label.java | 2 +- src/asm/scala/tools/asm/MethodWriter.java | 28 +-- src/asm/scala/tools/asm/Type.java | 6 +- .../scala/tools/asm/commons/CodeSizeEvaluator.java | 238 +++++++++++++++++++++ src/asm/scala/tools/asm/tree/analysis/Frame.java | 10 +- .../scala/tools/asm/util/CheckClassAdapter.java | 16 +- 7 files changed, 270 insertions(+), 32 deletions(-) create mode 100644 src/asm/scala/tools/asm/commons/CodeSizeEvaluator.java (limited to 'src') diff --git a/src/asm/README b/src/asm/README index e70911e17c..58d555acde 100644 --- a/src/asm/README +++ b/src/asm/README @@ -1,4 +1,4 @@ -Version 5.0.2, SVN r1741, tags/ASM_5_0_2 +Version 5.0.3, SVN r1748, tags/ASM_5_0_3 Git SVN repo: https://github.com/lrytz/asm - git svn howto: https://github.com/lrytz/asm/issues/1 diff --git a/src/asm/scala/tools/asm/Label.java b/src/asm/scala/tools/asm/Label.java index c094eba408..22b6798fb5 100644 --- a/src/asm/scala/tools/asm/Label.java +++ b/src/asm/scala/tools/asm/Label.java @@ -473,7 +473,7 @@ public class Label { void addToSubroutine(final long id, final int nbSubroutines) { if ((status & VISITED) == 0) { status |= VISITED; - srcAndRefPositions = new int[(nbSubroutines - 1) / 32 + 1]; + srcAndRefPositions = new int[nbSubroutines / 32 + 1]; } srcAndRefPositions[(int) (id >>> 32)] |= (int) id; } diff --git a/src/asm/scala/tools/asm/MethodWriter.java b/src/asm/scala/tools/asm/MethodWriter.java index d30e04c625..9c72ead61d 100644 --- a/src/asm/scala/tools/asm/MethodWriter.java +++ b/src/asm/scala/tools/asm/MethodWriter.java @@ -1974,43 +1974,43 @@ public class MethodWriter extends MethodVisitor { stackMap.putByte(v); } } else { - StringBuffer buf = new StringBuffer(); + StringBuilder sb = new StringBuilder(); d >>= 28; while (d-- > 0) { - buf.append('['); + sb.append('['); } if ((t & Frame.BASE_KIND) == Frame.OBJECT) { - buf.append('L'); - buf.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1); - buf.append(';'); + sb.append('L'); + sb.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1); + sb.append(';'); } else { switch (t & 0xF) { case 1: - buf.append('I'); + sb.append('I'); break; case 2: - buf.append('F'); + sb.append('F'); break; case 3: - buf.append('D'); + sb.append('D'); break; case 9: - buf.append('Z'); + sb.append('Z'); break; case 10: - buf.append('B'); + sb.append('B'); break; case 11: - buf.append('C'); + sb.append('C'); break; case 12: - buf.append('S'); + sb.append('S'); break; default: - buf.append('J'); + sb.append('J'); } } - stackMap.putByte(7).putShort(cw.newClass(buf.toString())); + stackMap.putByte(7).putShort(cw.newClass(sb.toString())); } } } diff --git a/src/asm/scala/tools/asm/Type.java b/src/asm/scala/tools/asm/Type.java index 7887080dee..c8f0048588 100644 --- a/src/asm/scala/tools/asm/Type.java +++ b/src/asm/scala/tools/asm/Type.java @@ -556,11 +556,11 @@ public class Type { case DOUBLE: return "double"; case ARRAY: - StringBuffer b = new StringBuffer(getElementType().getClassName()); + StringBuilder sb = new StringBuilder(getElementType().getClassName()); for (int i = getDimensions(); i > 0; --i) { - b.append("[]"); + sb.append("[]"); } - return b.toString(); + return sb.toString(); case OBJECT: return new String(buf, off, len).replace('/', '.'); default: diff --git a/src/asm/scala/tools/asm/commons/CodeSizeEvaluator.java b/src/asm/scala/tools/asm/commons/CodeSizeEvaluator.java new file mode 100644 index 0000000000..80c07bdae0 --- /dev/null +++ b/src/asm/scala/tools/asm/commons/CodeSizeEvaluator.java @@ -0,0 +1,238 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.commons; + +import scala.tools.asm.Handle; +import scala.tools.asm.Label; +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; + +/** + * A {@link MethodVisitor} that can be used to approximate method size. + * + * @author Eugene Kuleshov + */ +public class CodeSizeEvaluator extends MethodVisitor implements Opcodes { + + private int minSize; + + private int maxSize; + + public CodeSizeEvaluator(final MethodVisitor mv) { + this(Opcodes.ASM5, mv); + } + + protected CodeSizeEvaluator(final int api, final MethodVisitor mv) { + super(api, mv); + } + + public int getMinSize() { + return this.minSize; + } + + public int getMaxSize() { + return this.maxSize; + } + + @Override + public void visitInsn(final int opcode) { + minSize += 1; + maxSize += 1; + if (mv != null) { + mv.visitInsn(opcode); + } + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + if (opcode == SIPUSH) { + minSize += 3; + maxSize += 3; + } else { + minSize += 2; + maxSize += 2; + } + if (mv != null) { + mv.visitIntInsn(opcode, operand); + } + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + if (var < 4 && opcode != RET) { + minSize += 1; + maxSize += 1; + } else if (var >= 256) { + minSize += 4; + maxSize += 4; + } else { + minSize += 2; + maxSize += 2; + } + if (mv != null) { + mv.visitVarInsn(opcode, var); + } + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + minSize += 3; + maxSize += 3; + if (mv != null) { + mv.visitTypeInsn(opcode, type); + } + } + + @Override + public void visitFieldInsn(final int opcode, final String owner, + final String name, final String desc) { + minSize += 3; + maxSize += 3; + if (mv != null) { + mv.visitFieldInsn(opcode, owner, name, desc); + } + } + + @Deprecated + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc) { + if (api >= Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, + opcode == Opcodes.INVOKEINTERFACE); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { + if (api < Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, itf); + } + + private void doVisitMethodInsn(int opcode, final String owner, + final String name, final String desc, final boolean itf) { + if (opcode == INVOKEINTERFACE) { + minSize += 5; + maxSize += 5; + } else { + minSize += 3; + maxSize += 3; + } + if (mv != null) { + mv.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, + Object... bsmArgs) { + minSize += 5; + maxSize += 5; + if (mv != null) { + mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + minSize += 3; + if (opcode == GOTO || opcode == JSR) { + maxSize += 5; + } else { + maxSize += 8; + } + if (mv != null) { + mv.visitJumpInsn(opcode, label); + } + } + + @Override + public void visitLdcInsn(final Object cst) { + if (cst instanceof Long || cst instanceof Double) { + minSize += 3; + maxSize += 3; + } else { + minSize += 2; + maxSize += 3; + } + if (mv != null) { + mv.visitLdcInsn(cst); + } + } + + @Override + public void visitIincInsn(final int var, final int increment) { + if (var > 255 || increment > 127 || increment < -128) { + minSize += 6; + maxSize += 6; + } else { + minSize += 3; + maxSize += 3; + } + if (mv != null) { + mv.visitIincInsn(var, increment); + } + } + + @Override + public void visitTableSwitchInsn(final int min, final int max, + final Label dflt, final Label... labels) { + minSize += 13 + labels.length * 4; + maxSize += 16 + labels.length * 4; + if (mv != null) { + mv.visitTableSwitchInsn(min, max, dflt, labels); + } + } + + @Override + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, + final Label[] labels) { + minSize += 9 + keys.length * 8; + maxSize += 12 + keys.length * 8; + if (mv != null) { + mv.visitLookupSwitchInsn(dflt, keys, labels); + } + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + minSize += 4; + maxSize += 4; + if (mv != null) { + mv.visitMultiANewArrayInsn(desc, dims); + } + } +} diff --git a/src/asm/scala/tools/asm/tree/analysis/Frame.java b/src/asm/scala/tools/asm/tree/analysis/Frame.java index 44a07ee27c..0b7f4ba53b 100644 --- a/src/asm/scala/tools/asm/tree/analysis/Frame.java +++ b/src/asm/scala/tools/asm/tree/analysis/Frame.java @@ -725,14 +725,14 @@ public class Frame { */ @Override public String toString() { - StringBuffer b = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (int i = 0; i < getLocals(); ++i) { - b.append(getLocal(i)); + sb.append(getLocal(i)); } - b.append(' '); + sb.append(' '); for (int i = 0; i < getStackSize(); ++i) { - b.append(getStack(i).toString()); + sb.append(getStack(i).toString()); } - return b.toString(); + return sb.toString(); } } diff --git a/src/asm/scala/tools/asm/util/CheckClassAdapter.java b/src/asm/scala/tools/asm/util/CheckClassAdapter.java index 9909208cc4..88afdb0441 100644 --- a/src/asm/scala/tools/asm/util/CheckClassAdapter.java +++ b/src/asm/scala/tools/asm/util/CheckClassAdapter.java @@ -269,26 +269,26 @@ public class CheckClassAdapter extends ClassVisitor { for (int j = 0; j < method.instructions.size(); ++j) { method.instructions.get(j).accept(mv); - StringBuffer s = new StringBuffer(); + StringBuilder sb = new StringBuilder(); Frame f = frames[j]; if (f == null) { - s.append('?'); + sb.append('?'); } else { for (int k = 0; k < f.getLocals(); ++k) { - s.append(getShortName(f.getLocal(k).toString())) + sb.append(getShortName(f.getLocal(k).toString())) .append(' '); } - s.append(" : "); + sb.append(" : "); for (int k = 0; k < f.getStackSize(); ++k) { - s.append(getShortName(f.getStack(k).toString())) + sb.append(getShortName(f.getStack(k).toString())) .append(' '); } } - while (s.length() < method.maxStack + method.maxLocals + 1) { - s.append(' '); + while (sb.length() < method.maxStack + method.maxLocals + 1) { + sb.append(' '); } pw.print(Integer.toString(j + 100000).substring(1)); - pw.print(" " + s + " : " + t.text.get(t.text.size() - 1)); + pw.print(" " + sb + " : " + t.text.get(t.text.size() - 1)); } for (int j = 0; j < method.tryCatchBlocks.size(); ++j) { method.tryCatchBlocks.get(j).accept(mv); -- cgit v1.2.3 From edf954ff10b0f1fb7668018d43680646f8503b27 Mon Sep 17 00:00:00 2001 From: Simon Schäfer Date: Mon, 16 Mar 2015 08:11:48 +0100 Subject: SI-9226 Fix NPE in GenASM.writeIfNotTooBig On 2.11.6 line 535 (as reported in the issue) points to the changed line. Exception.getMessage can return null. Checking for null on the matched variable is not necessary. No tests, because I have no idea on how to reproduce the problem. --- src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 62268e1907..86a69b92ea 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -533,7 +533,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => } bytecodeWriter.writeClass(label, jclassName, arr, outF) } catch { - case e: java.lang.RuntimeException if e != null && (e.getMessage contains "too large!") => + case e: java.lang.RuntimeException if e.getMessage != null && (e.getMessage contains "too large!") => reporter.error(sym.pos, s"Could not write class $jclassName because it exceeds JVM code size limits. ${e.getMessage}") } -- cgit v1.2.3 From 2dc3b19fb3fe18dada28c3506a14dd9602065e06 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 17 Mar 2015 15:46:07 -0700 Subject: SI-9231 Don't attempt implicit search for erroneous parameter If the instantiated type of an implicit parameter is erroneous, we should not attempt the implicit search. This avoids the following useless error message in the enclosed test: error: ambiguous implicit values: ... match expected type M[] --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 2 +- test/files/neg/t9231.check | 4 ++++ test/files/neg/t9231.scala | 9 +++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 test/files/neg/t9231.check create mode 100644 test/files/neg/t9231.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 3a85d16f55..377404b564 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -151,7 +151,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper for(ar <- argResultsBuff) paramTp = paramTp.subst(ar.subst.from, ar.subst.to) - val res = if (paramFailed || (paramTp.isError && {paramFailed = true; true})) SearchFailure else inferImplicit(fun, paramTp, context.reportErrors, isView = false, context) + val res = if (paramFailed || (paramTp.isErroneous && {paramFailed = true; true})) SearchFailure else inferImplicit(fun, paramTp, context.reportErrors, isView = false, context) argResultsBuff += res if (res.isSuccess) { diff --git a/test/files/neg/t9231.check b/test/files/neg/t9231.check new file mode 100644 index 0000000000..43c14f53ca --- /dev/null +++ b/test/files/neg/t9231.check @@ -0,0 +1,4 @@ +t9231.scala:8: error: not found: type DoesNotExist + foo[DoesNotExist] + ^ +one error found diff --git a/test/files/neg/t9231.scala b/test/files/neg/t9231.scala new file mode 100644 index 0000000000..05b1d24e9a --- /dev/null +++ b/test/files/neg/t9231.scala @@ -0,0 +1,9 @@ +class M[A] +class C { + implicit def M1: M[Int] = null + implicit def M2: M[String] = null + + def foo[A](implicit M: M[A]) = null + + foo[DoesNotExist] +} -- cgit v1.2.3 From 79c053738b8907e461e5087f63d247a64271509a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 17 Mar 2015 16:17:04 -0700 Subject: SI-9223 By-name constructor params should not be aliased Usually, the compiler tries avoids creating a field in a subclass for a constructor parameter that is known to be stored in a field in one of its superclasses. However, in the enclosed test `run/t9223.scala`, this mechanism confuses `=> A` with `A`, which results in a runtime `ClassCastException`. This commit avoids using param aliases for by-name parameters. I have also added a test for something close the opposite case, where the subclass has a strict parameter and the superclass has a by-name parameter. This was working correctly before this patch. --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 2 +- test/files/run/t9223.scala | 8 ++++++++ test/files/run/t9223b.scala | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 test/files/run/t9223.scala create mode 100644 test/files/run/t9223b.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 3a85d16f55..758425aad5 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2053,7 +2053,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case acc => acc } ownAcc match { - case acc: TermSymbol if !acc.isVariable => + case acc: TermSymbol if !acc.isVariable && !isByNameParamType(acc.info) => debuglog(s"$acc has alias ${alias.fullLocationString}") acc setAlias alias case _ => diff --git a/test/files/run/t9223.scala b/test/files/run/t9223.scala new file mode 100644 index 0000000000..78767b158d --- /dev/null +++ b/test/files/run/t9223.scala @@ -0,0 +1,8 @@ +class X(val x: String) +class Y(y: => String) extends X(y) { def f = y } + +object Test { + def main(args: Array[String]): Unit = { + assert(new Y("hi").f == "hi") + } +} diff --git a/test/files/run/t9223b.scala b/test/files/run/t9223b.scala new file mode 100644 index 0000000000..2afc7ddfe0 --- /dev/null +++ b/test/files/run/t9223b.scala @@ -0,0 +1,8 @@ +class X(x: => String) { def xx = x } +class Y(y: String) extends X(y) { def f = y } + +object Test { + def main(args: Array[String]): Unit = { + assert(new Y("hi").f == "hi") + } +} -- cgit v1.2.3 From b19c675efb98a17fbe5edd8ad89e7da02c3646dd Mon Sep 17 00:00:00 2001 From: RobertZK Date: Wed, 18 Mar 2015 01:30:37 -0500 Subject: Fix typo in brutallyResetAttrs comment --- src/compiler/scala/tools/nsc/ast/Trees.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/ast/Trees.scala b/src/compiler/scala/tools/nsc/ast/Trees.scala index 3652f51153..3a502bdb7a 100644 --- a/src/compiler/scala/tools/nsc/ast/Trees.scala +++ b/src/compiler/scala/tools/nsc/ast/Trees.scala @@ -178,7 +178,7 @@ trait Trees extends scala.reflect.internal.Trees { self: Global => } } - // Finally, noone resetAllAttrs it anymore, so I'm removing it from the compiler. + // Finally, no one uses resetAllAttrs anymore, so I'm removing it from the compiler. // Even though it's with great pleasure I'm doing that, I'll leave its body here to warn future generations about what happened in the past. // // So what actually happened in the past is that we used to have two flavors of resetAttrs: resetAllAttrs and resetLocalAttrs. -- cgit v1.2.3 From bdc0b6313e116d9627c423f2267a423fa666334e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 24 Mar 2015 15:18:56 +1000 Subject: Improve diagnostic error on failed genLoadIdent This error has been haunting us recently, firstly on Greg's machine when compiling the compiler using the new SBT build, and more recently during PR validation in #4316. This commit will output an error like: symbol value c#16812 does not exist in Macro.impl, which contains locals value a#16810, value b#16811 I've included symbol IDs in the hope that they will prove useful. It seems that synthetic identifiers generated by the pattern matcher are often seen in the vicinity of this bug. --- .../scala/tools/nsc/backend/icode/GenICode.scala | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala index cf52ad6636..185fd93501 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala @@ -876,13 +876,15 @@ abstract class GenICode extends SubComponent { genLoadModule(ctx, tree) generatedType = toTypeKind(sym.info) } else { - try { - val Some(l) = ctx.method.lookupLocal(sym) - ctx.bb.emit(LOAD_LOCAL(l), tree.pos) - generatedType = l.kind - } catch { - case ex: MatchError => - abort("symbol " + sym + " does not exist in " + ctx.method) + ctx.method.lookupLocal(sym) match { + case Some(l) => + ctx.bb.emit(LOAD_LOCAL(l), tree.pos) + generatedType = l.kind + case None => + val saved = settings.uniqid + settings.uniqid.value = true + try abort(s"symbol $sym does not exist in ${ctx.method}, which contains locals ${ctx.method.locals.mkString(",")}") + finally settings.uniqid.value = saved } } } -- cgit v1.2.3 From 7c162d41ad87c390e341aec5d3819c4527fee47f Mon Sep 17 00:00:00 2001 From: Alessandro Lacava Date: Tue, 24 Mar 2015 12:13:18 +0100 Subject: fix typo in scaladoc --- src/library/scala/collection/GenTraversableOnce.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/library/scala/collection/GenTraversableOnce.scala b/src/library/scala/collection/GenTraversableOnce.scala index 8c7c754af8..f77462ce88 100644 --- a/src/library/scala/collection/GenTraversableOnce.scala +++ b/src/library/scala/collection/GenTraversableOnce.scala @@ -278,7 +278,7 @@ trait GenTraversableOnce[+A] extends Any { * * @param op the binary operator. * @tparam B the result type of the binary operator. - * @return an option value containing the result of `reduceLeft(op)` is this $coll is nonempty, + * @return an option value containing the result of `reduceLeft(op)` if this $coll is nonempty, * `None` otherwise. */ def reduceLeftOption[B >: A](op: (B, A) => B): Option[B] @@ -290,7 +290,7 @@ trait GenTraversableOnce[+A] extends Any { * * @param op the binary operator. * @tparam B the result type of the binary operator. - * @return an option value containing the result of `reduceRight(op)` is this $coll is nonempty, + * @return an option value containing the result of `reduceRight(op)` if this $coll is nonempty, * `None` otherwise. */ def reduceRightOption[B >: A](op: (A, B) => B): Option[B] -- cgit v1.2.3 From e5bfc1c097e8dea9e0643e4a43743196209e9ba8 Mon Sep 17 00:00:00 2001 From: Simon Ochsenreither Date: Sat, 29 Nov 2014 03:44:05 +0100 Subject: Deprecations: Use of isPackage, hasSymbol, getter, setter... ... replaced by hasPackageFlag, hasSymbolIn, getterIn, setterIn. --- .../scala/reflect/reify/codegen/GenSymbols.scala | 2 +- .../scala/reflect/reify/codegen/GenTrees.scala | 4 ++-- src/compiler/scala/tools/nsc/ast/Trees.scala | 2 +- .../scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 4 ++-- .../scala/tools/nsc/backend/jvm/GenASM.scala | 4 ++-- .../scala/tools/nsc/transform/ExplicitOuter.scala | 2 +- src/compiler/scala/tools/nsc/transform/Mixin.scala | 16 ++++++------- .../tools/nsc/transform/SpecializeTypes.scala | 8 +++---- .../tools/nsc/typechecker/MethodSynthesis.scala | 4 ++-- .../scala/tools/nsc/typechecker/Namers.scala | 6 ++--- .../scala/tools/nsc/typechecker/RefChecks.scala | 2 +- .../scala/tools/nsc/typechecker/TreeCheckers.scala | 6 ++--- .../tools/nsc/typechecker/TypeDiagnostics.scala | 4 ++-- .../scala/tools/nsc/typechecker/Typers.scala | 6 ++--- src/reflect/scala/reflect/internal/Printers.scala | 4 ++-- src/reflect/scala/reflect/internal/Symbols.scala | 26 ++++++++++------------ src/reflect/scala/reflect/internal/Types.scala | 2 +- 17 files changed, 50 insertions(+), 52 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/reflect/reify/codegen/GenSymbols.scala b/src/compiler/scala/reflect/reify/codegen/GenSymbols.scala index 52ddcb154b..e41fbf042a 100644 --- a/src/compiler/scala/reflect/reify/codegen/GenSymbols.scala +++ b/src/compiler/scala/reflect/reify/codegen/GenSymbols.scala @@ -39,7 +39,7 @@ trait GenSymbols { else if (sym.isModuleClass) if (sym.sourceModule.isLocatable) Select(Select(reify(sym.sourceModule), nme.asModule), nme.moduleClass) else reifySymDef(sym) - else if (sym.isPackage) + else if (sym.hasPackageFlag) mirrorMirrorCall(nme.staticPackage, reify(sym.fullName)) else if (sym.isLocatable) { /* This is a fancy conundrum that stems from the fact that Scala allows diff --git a/src/compiler/scala/reflect/reify/codegen/GenTrees.scala b/src/compiler/scala/reflect/reify/codegen/GenTrees.scala index 743fe131c4..f34d75140b 100644 --- a/src/compiler/scala/reflect/reify/codegen/GenTrees.scala +++ b/src/compiler/scala/reflect/reify/codegen/GenTrees.scala @@ -153,7 +153,7 @@ trait GenTrees { else mirrorCall(nme.Ident, reify(name)) case Select(qual, name) => - if (qual.symbol != null && qual.symbol.isPackage) { + if (qual.symbol != null && qual.symbol.hasPackageFlag) { mirrorBuildCall(nme.mkIdent, reify(sym)) } else { val effectiveName = if (sym != null && sym != NoSymbol) sym.name else name @@ -199,7 +199,7 @@ trait GenTrees { } } else tree match { - case Select(qual, name) if !qual.symbol.isPackage => + case Select(qual, name) if !qual.symbol.hasPackageFlag => if (reifyDebug) println(s"reifying Select($qual, $name)") mirrorCall(nme.Select, reify(qual), reify(name)) case SelectFromTypeTree(qual, name) => diff --git a/src/compiler/scala/tools/nsc/ast/Trees.scala b/src/compiler/scala/tools/nsc/ast/Trees.scala index 3a502bdb7a..934257092f 100644 --- a/src/compiler/scala/tools/nsc/ast/Trees.scala +++ b/src/compiler/scala/tools/nsc/ast/Trees.scala @@ -308,7 +308,7 @@ trait Trees extends scala.reflect.internal.Trees { self: Global => // Erasing locally-defined symbols is useful to prevent tree corruption, but erasing external bindings is not, // therefore we want to retain those bindings, especially given that restoring them can be impossible // if we move these trees into lexical contexts different from their original locations. - if (dupl.hasSymbol) { + if (dupl.hasSymbolField) { val sym = dupl.symbol val vetoScope = !brutally && !(locals contains sym) && !(locals contains sym.deSkolemize) val vetoThis = dupl.isInstanceOf[This] && sym.isPackageClass diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 3b7dbc18da..18468f5ae3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -780,8 +780,8 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { var fieldList = List[String]() for (f <- fieldSymbols if f.hasGetter; - g = f.getter(cls); - s = f.setter(cls); + g = f.getterIn(cls); + s = f.setterIn(cls); if g.isPublic && !(f.name startsWith "$") ) { // inserting $outer breaks the bean diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 86a69b92ea..d7abe2be4b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -2883,8 +2883,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => var fieldList = List[String]() for (f <- clasz.fields if f.symbol.hasGetter; - g = f.symbol.getter(clasz.symbol); - s = f.symbol.setter(clasz.symbol) + g = f.symbol.getterIn(clasz.symbol); + s = f.symbol.setterIn(clasz.symbol) if g.isPublic && !(f.symbol.name startsWith "$") ) { // inserting $outer breaks the bean diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index 6225b486c2..540de2cfe1 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -207,7 +207,7 @@ abstract class ExplicitOuter extends InfoTransform // class needs to have a common naming scheme, independently of whether // the field was accessed from an inner class or not. See #2946 if (sym.owner.isTrait && sym.isLocalToThis && - (sym.getter(sym.owner.toInterface) == NoSymbol)) + (sym.getterIn(sym.owner.toInterface) == NoSymbol)) sym.makeNotPrivate(sym.owner) tp } diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 7927875583..b5ffc8bc58 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -232,13 +232,13 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { for (member <- impl.info.decls) { if (!member.isMethod && !member.isModule && !member.isModuleVar) { assert(member.isTerm && !member.isDeferred, member) - if (member.getter(impl).isPrivate) { + if (member.getterIn(impl).isPrivate) { member.makeNotPrivate(clazz) // this will also make getter&setter not private } - val getter = member.getter(clazz) + val getter = member.getterIn(clazz) if (getter == NoSymbol) addMember(clazz, newGetter(member)) if (!member.tpe.isInstanceOf[ConstantType] && !member.isLazy) { - val setter = member.setter(clazz) + val setter = member.setterIn(clazz) if (setter == NoSymbol) addMember(clazz, newSetter(member)) } } @@ -872,7 +872,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } def mkCheckedAccessor(clazz: Symbol, retVal: Tree, offset: Int, pos: Position, fieldSym: Symbol): Tree = { - val sym = fieldSym.getter(fieldSym.owner) + val sym = fieldSym.getterIn(fieldSym.owner) val bitmapSym = bitmapFor(clazz, offset, sym) val kind = bitmapKind(sym) val mask = maskForOffset(offset, sym, kind) @@ -921,7 +921,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { deriveDefDef(stat)(addInitBits(clazz, _)) } else if (settings.checkInit && !clazz.isTrait && sym.isSetter) { - val getter = sym.getter(clazz) + val getter = sym.getterIn(clazz) if (needsInitFlag(getter) && fieldOffset.isDefinedAt(getter)) deriveDefDef(stat)(rhs => Block(List(rhs, localTyper.typed(mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter)))), UNIT)) else stat @@ -1057,7 +1057,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { def isOverriddenSetter(sym: Symbol) = nme.isTraitSetterName(sym.name) && { val other = sym.nextOverriddenSymbol - isOverriddenAccessor(other.getter(other.owner), clazz.info.baseClasses) + isOverriddenAccessor(other.getterIn(other.owner), clazz.info.baseClasses) } // for all symbols `sym` in the class definition, which are mixed in: @@ -1232,7 +1232,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // refer to fields in some implementation class via an abstract // getter in the interface. val iface = toInterface(sym.owner.tpe).typeSymbol - val ifaceGetter = sym getter iface + val ifaceGetter = sym getterIn iface if (ifaceGetter == NoSymbol) abort("No getter for " + sym + " in " + iface) else typedPos(tree.pos)((qual DOT ifaceGetter)()) @@ -1240,7 +1240,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { case Assign(Apply(lhs @ Select(qual, _), List()), rhs) => // assign to fields in some implementation class via an abstract // setter in the interface. - def setter = lhs.symbol.setter(toInterface(lhs.symbol.owner.tpe).typeSymbol) setPos lhs.pos + def setter = lhs.symbol.setterIn(toInterface(lhs.symbol.owner.tpe).typeSymbol) setPos lhs.pos typedPos(tree.pos)((qual DOT setter)(rhs)) diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index 1691b01e3e..086512677e 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -699,7 +699,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } else if (m.isValue && !m.isMethod && !m.hasFlag(LAZY)) { // concrete value definition def mkAccessor(field: Symbol, name: Name) = { - val newFlags = (SPECIALIZED | m.getter(clazz).flags) & ~(LOCAL | CASEACCESSOR | PARAMACCESSOR) + val newFlags = (SPECIALIZED | m.getterIn(clazz).flags) & ~(LOCAL | CASEACCESSOR | PARAMACCESSOR) // we rely on the super class to initialize param accessors val sym = sClass.newMethod(name.toTermName, field.pos, newFlags) info(sym) = SpecializedAccessor(field) @@ -720,7 +720,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { if (nme.isLocalName(m.name)) { val specGetter = mkAccessor(specVal, specVal.getterName) setInfo MethodType(Nil, specVal.info) - val origGetter = overrideIn(sClass, m.getter(clazz)) + val origGetter = overrideIn(sClass, m.getterIn(clazz)) info(origGetter) = Forward(specGetter) enterMember(specGetter) enterMember(origGetter) @@ -733,12 +733,12 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { debuglog("override case field accessor %s -> %s".format(m.name.decode, cfaGetter.name.decode)) } - if (specVal.isVariable && m.setter(clazz) != NoSymbol) { + if (specVal.isVariable && m.setterIn(clazz) != NoSymbol) { val specSetter = mkAccessor(specVal, specGetter.setterName) .resetFlag(STABLE) specSetter.setInfo(MethodType(specSetter.newSyntheticValueParams(List(specVal.info)), UnitTpe)) - val origSetter = overrideIn(sClass, m.setter(clazz)) + val origSetter = overrideIn(sClass, m.setterIn(clazz)) info(origSetter) = Forward(specSetter) enterMember(specSetter) enterMember(origSetter) diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index 0aa62d771e..1f00403f3a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -384,7 +384,7 @@ trait MethodSynthesis { } } case class Getter(tree: ValDef) extends BaseGetter(tree) { - override def derivedSym = if (mods.isDeferred) basisSym else basisSym.getter(enclClass) + override def derivedSym = if (mods.isDeferred) basisSym else basisSym.getterIn(enclClass) private def derivedRhs = if (mods.isDeferred) EmptyTree else fieldSelection private def derivedTpt = { // For existentials, don't specify a type for the getter, even one derived @@ -451,7 +451,7 @@ trait MethodSynthesis { def flagsMask = SetterFlags def flagsExtra = ACCESSOR - override def derivedSym = basisSym.setter(enclClass) + override def derivedSym = basisSym.setterIn(enclClass) } case class Field(tree: ValDef) extends DerivedFromValDef { def name = tree.localName diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 711cfba24d..4b942e28a6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -464,7 +464,7 @@ trait Namers extends MethodSynthesis { def enterModuleSymbol(tree : ModuleDef): Symbol = { var m: Symbol = context.scope lookupModule tree.name val moduleFlags = tree.mods.flags | MODULE - if (m.isModule && !m.isPackage && inCurrentScope(m) && (currentRun.canRedefine(m) || m.isSynthetic)) { + if (m.isModule && !m.hasPackageFlag && inCurrentScope(m) && (currentRun.canRedefine(m) || m.isSynthetic)) { // This code accounts for the way the package objects found in the classpath are opened up // early by the completer of the package itself. If the `packageobjects` phase then finds // the same package object in sources, we have to clean the slate and remove package object @@ -487,7 +487,7 @@ trait Namers extends MethodSynthesis { m.moduleClass setFlag moduleClassFlags(moduleFlags) setPrivateWithin(tree, m.moduleClass) } - if (m.isTopLevel && !m.isPackage) { + if (m.isTopLevel && !m.hasPackageFlag) { m.moduleClass.associatedFile = contextFile currentRun.symSource(m) = m.moduleClass.sourceFile registerTopLevelSym(m) @@ -844,7 +844,7 @@ trait Namers extends MethodSynthesis { private def widenIfNecessary(sym: Symbol, tpe: Type, pt: Type): Type = { val getter = if (sym.isValue && sym.owner.isClass && sym.isPrivate) - sym.getter(sym.owner) + sym.getterIn(sym.owner) else sym def isHidden(tp: Type): Boolean = tp match { case SingleType(pre, sym) => diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index d2931ff9e1..dfc0a433a9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -718,7 +718,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans // Check the remainder for invalid absoverride. for (member <- rest ; if (member.isAbstractOverride && member.isIncompleteIn(clazz))) { - val other = member.superSymbol(clazz) + val other = member.superSymbolIn(clazz) val explanation = if (other != NoSymbol) " and overrides incomplete superclass member " + infoString(other) else ", but no concrete implementation could be found in a base class" diff --git a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala index 02356580cc..a7d48ceb89 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala @@ -300,8 +300,8 @@ abstract class TreeCheckers extends Analyzer { checkSym(tree) /* XXX: lots of syms show up here with accessed == NoSymbol. */ if (accessed != NoSymbol) { - val agetter = accessed.getter(sym.owner) - val asetter = accessed.setter(sym.owner) + val agetter = accessed.getterIn(sym.owner) + val asetter = accessed.setterIn(sym.owner) assertFn(agetter == sym || asetter == sym, sym + " is getter or setter, but accessed sym " + accessed + " shows " + agetter + " and " + asetter @@ -311,7 +311,7 @@ abstract class TreeCheckers extends Analyzer { } case ValDef(_, _, _, _) => if (sym.hasGetter && !sym.isOuterField && !sym.isOuterAccessor) { - assertFn(sym.getter(sym.owner) != NoSymbol, ownerstr(sym) + " has getter but cannot be found. " + sym.ownerChain) + assertFn(sym.getterIn(sym.owner) != NoSymbol, ownerstr(sym) + " has getter but cannot be found. " + sym.ownerChain) } case Apply(fn, args) => if (args exists (_ == EmptyTree)) diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index 0f90c6a478..0c36d74847 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -141,8 +141,8 @@ trait TypeDiagnostics { if (!member.hasAccessorFlag) member else if (!member.isDeferred) member.accessed else { - val getter = if (member.isSetter) member.getter(member.owner) else member - val flags = if (getter.setter(member.owner) != NoSymbol) DEFERRED.toLong | MUTABLE else DEFERRED + val getter = if (member.isSetter) member.getterIn(member.owner) else member + val flags = if (getter.setterIn(member.owner) != NoSymbol) DEFERRED.toLong | MUTABLE else DEFERRED getter.owner.newValue(getter.name.toTermName, getter.pos, flags) setInfo getter.tpe.resultType } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index d23b50c3eb..1afb45289f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2044,7 +2044,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (mexists(vparamss)(_.symbol == superArg.symbol)) { val alias = ( superAcc.initialize.alias - orElse (superAcc getter superAcc.owner) + orElse (superAcc getterIn superAcc.owner) filter (alias => superClazz.info.nonPrivateMember(alias.name) == alias) ) if (alias.exists && !alias.accessed.isVariable && !isRepeatedParamType(alias.accessed.info)) { @@ -4365,7 +4365,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def narrowRhs(tp: Type) = { val sym = context.tree.symbol context.tree match { case ValDef(mods, _, _, Apply(Select(`tree`, _), _)) if !mods.isMutable && sym != null && sym != NoSymbol => - val sym1 = if (sym.owner.isClass && sym.getter(sym.owner) != NoSymbol) sym.getter(sym.owner) + val sym1 = if (sym.owner.isClass && sym.getterIn(sym.owner) != NoSymbol) sym.getterIn(sym.owner) else sym.lazyAccessorOrSelf val pre = if (sym1.owner.isClass) sym1.owner.thisType else NoPrefix intersectionType(List(tp, singleType(pre, sym1))) @@ -5382,7 +5382,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def runTyper(): Tree = { if (retypingOk) { tree.tpe = null - if (tree.hasSymbol) tree.symbol = NoSymbol + if (tree.hasSymbolField) tree.symbol = NoSymbol } val alreadyTyped = tree.tpe ne null val shouldPrint = !alreadyTyped && !phase.erasedTypes diff --git a/src/reflect/scala/reflect/internal/Printers.scala b/src/reflect/scala/reflect/internal/Printers.scala index 98b2c48379..b44c4022f6 100644 --- a/src/reflect/scala/reflect/internal/Printers.scala +++ b/src/reflect/scala/reflect/internal/Printers.scala @@ -1006,7 +1006,7 @@ trait Printers extends api.Printers { self: SymbolTable => printSuper(st, printedName(qual), checkSymbol = false) case th @ This(qual) => - if (tree.hasExistingSymbol && tree.symbol.isPackage) print(tree.symbol.fullName) + if (tree.hasExistingSymbol && tree.symbol.hasPackageFlag) print(tree.symbol.fullName) else printThis(th, printedName(qual)) // remove this prefix from constructor invocation in typechecked trees: this.this -> this @@ -1023,7 +1023,7 @@ trait Printers extends api.Printers { self: SymbolTable => }) && (tr match { // check that Select contains package case Select(q, _) => checkRootPackage(q) case _: Ident | _: This => val sym = tr.symbol - tr.hasExistingSymbol && sym.isPackage && sym.name != nme.ROOTPKG + tr.hasExistingSymbol && sym.hasPackageFlag && sym.name != nme.ROOTPKG case _ => false }) diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index d23a102b28..5e47f127d4 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -155,11 +155,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => def toTypeConstructor: Type = typeConstructor def setAnnotations(annots: AnnotationInfo*): this.type = { setAnnotations(annots.toList); this } - def getter: Symbol = getter(owner) - def setter: Symbol = setter(owner) + def getter: Symbol = getterIn(owner) + def setter: Symbol = setterIn(owner) def companion: Symbol = { - if (isModule && !isPackage) companionSymbol + if (isModule && !hasPackageFlag) companionSymbol else if (isModuleClass && !isPackageClass) sourceModule.companionSymbol else if (isClass && !isModuleClass && !isPackageClass) companionSymbol else NoSymbol @@ -1047,7 +1047,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => final def isIncompleteIn(base: Symbol): Boolean = this.isDeferred || (this hasFlag ABSOVERRIDE) && { - val supersym = superSymbol(base) + val supersym = superSymbolIn(base) supersym == NoSymbol || supersym.isIncompleteIn(base) } @@ -2375,13 +2375,13 @@ trait Symbols extends api.Symbols { self: SymbolTable => Nil ) + @deprecated("Use `superSymbolIn` instead", "2.11.0") + final def superSymbol(base: Symbol): Symbol = superSymbolIn(base) + /** The symbol accessed by a super in the definition of this symbol when * seen from class `base`. This symbol is always concrete. * pre: `this.owner` is in the base class sequence of `base`. */ - @deprecated("Use `superSymbolIn` instead", "2.11.0") - final def superSymbol(base: Symbol): Symbol = superSymbolIn(base) - final def superSymbolIn(base: Symbol): Symbol = { var bcs = base.info.baseClasses dropWhile (owner != _) drop 1 var sym: Symbol = NoSymbol @@ -2393,12 +2393,10 @@ trait Symbols extends api.Symbols { self: SymbolTable => sym } - /** The getter of this value or setter definition in class `base`, or NoSymbol if - * none exists. - */ @deprecated("Use `getterIn` instead", "2.11.0") final def getter(base: Symbol): Symbol = getterIn(base) + /** The getter of this value or setter definition in class `base`, or NoSymbol if none exists. */ final def getterIn(base: Symbol): Symbol = base.info decl getterName filter (_.hasAccessorFlag) @@ -2406,11 +2404,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => def setterName: TermName = name.setterName def localName: TermName = name.localName - /** The setter of this value or getter definition, or NoSymbol if none exists */ @deprecated("Use `setterIn` instead", "2.11.0") final def setter(base: Symbol, hasExpandedName: Boolean = needsExpandedSetterName): Symbol = setterIn(base, hasExpandedName) + /** The setter of this value or getter definition, or NoSymbol if none exists. */ final def setterIn(base: Symbol, hasExpandedName: Boolean = needsExpandedSetterName): Symbol = base.info decl setterNameInBase(base, hasExpandedName) filter (_.hasAccessorFlag) @@ -2538,7 +2536,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => else if (isInstanceOf[FreeTermSymbol]) ("free term", "free term", "FTE") else if (isInstanceOf[FreeTypeSymbol]) ("free type", "free type", "FTY") else if (isPackageClass) ("package class", "package", "PKC") - else if (isPackage) ("package", "package", "PK") + else if (hasPackageFlag) ("package", "package", "PK") else if (isPackageObject) ("package object", "package", "PKO") else if (isPackageObjectClass) ("package object class", "package", "PKOC") else if (isAnonymousClass) ("anonymous class", "anonymous class", "AC") @@ -2852,8 +2850,8 @@ trait Symbols extends api.Symbols { self: SymbolTable => accessed.expandName(base) } else if (hasGetter) { - getter(owner).expandName(base) - setter(owner).expandName(base) + getterIn(owner).expandName(base) + setterIn(owner).expandName(base) } name = nme.expandedName(name.toTermName, base) } diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 8f114caac0..ad2a545587 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -247,7 +247,7 @@ trait Types def companion = { val sym = typeSymbolDirect - if (sym.isModule && !sym.isPackage) sym.companionSymbol.tpe + if (sym.isModule && !sym.hasPackageFlag) sym.companionSymbol.tpe else if (sym.isModuleClass && !sym.isPackageClass) sym.sourceModule.companionSymbol.tpe else if (sym.isClass && !sym.isModuleClass && !sym.isPackageClass) sym.companionSymbol.info else NoType -- cgit v1.2.3 From 86999ed76b5d9df51c337c5e33fe265e04d8f39c Mon Sep 17 00:00:00 2001 From: Simon Ochsenreither Date: Sat, 29 Nov 2014 04:05:10 +0100 Subject: new{Term,Type}Name→{Term,Type}Name, tpename/nme→{type,term}Names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/compiler/scala/reflect/quasiquotes/Holes.scala | 2 +- src/compiler/scala/reflect/quasiquotes/Reifiers.scala | 2 +- src/compiler/scala/reflect/reify/codegen/GenUtils.scala | 8 ++++---- src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala | 2 +- src/compiler/scala/tools/nsc/javac/JavaParsers.scala | 2 +- .../scala/tools/nsc/symtab/classfile/ClassfileParser.scala | 2 +- .../scala/tools/nsc/symtab/classfile/ICodeReader.scala | 2 +- src/compiler/scala/tools/nsc/typechecker/Implicits.scala | 2 +- .../scala/tools/nsc/typechecker/TypeDiagnostics.scala | 2 +- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 2 +- src/reflect/scala/reflect/api/Names.scala | 8 ++++---- src/reflect/scala/reflect/api/StandardLiftables.scala | 2 +- src/reflect/scala/reflect/api/Trees.scala | 4 ++-- src/reflect/scala/reflect/internal/Definitions.scala | 12 ++++++------ src/reflect/scala/reflect/internal/Symbols.scala | 3 ++- src/reflect/scala/reflect/runtime/JavaMirrors.scala | 8 ++++---- src/reflect/scala/reflect/runtime/package.scala | 6 +++--- src/repl/scala/tools/nsc/interpreter/IMain.scala | 6 +++--- test/files/neg/reflection-names-neg.check | 13 ------------- test/files/neg/reflection-names-neg.scala | 6 ------ test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala | 4 ++-- 21 files changed, 40 insertions(+), 58 deletions(-) delete mode 100644 test/files/neg/reflection-names-neg.check delete mode 100644 test/files/neg/reflection-names-neg.scala (limited to 'src') diff --git a/src/compiler/scala/reflect/quasiquotes/Holes.scala b/src/compiler/scala/reflect/quasiquotes/Holes.scala index 38b05f9d4b..2bbc753a81 100644 --- a/src/compiler/scala/reflect/quasiquotes/Holes.scala +++ b/src/compiler/scala/reflect/quasiquotes/Holes.scala @@ -132,7 +132,7 @@ trait Holes { self: Quasiquotes => private def mapF(tree: Tree, f: Tree => Tree): Tree = if (f(Ident(TermName("x"))) equalsStructure Ident(TermName("x"))) tree else { - val x: TermName = c.freshName() + val x = TermName(c.freshName()) // q"$tree.map { $x => ${f(Ident(x))} }" Apply(Select(tree, nme.map), Function(ValDef(Modifiers(PARAM), x, TypeTree(), EmptyTree) :: Nil, diff --git a/src/compiler/scala/reflect/quasiquotes/Reifiers.scala b/src/compiler/scala/reflect/quasiquotes/Reifiers.scala index cc98717c4e..7c0e7dfbb8 100644 --- a/src/compiler/scala/reflect/quasiquotes/Reifiers.scala +++ b/src/compiler/scala/reflect/quasiquotes/Reifiers.scala @@ -247,7 +247,7 @@ trait Reifiers { self: Quasiquotes => hole.tree case Placeholder(hole: UnapplyHole) => hole.treeNoUnlift case FreshName(prefix) if prefix != nme.QUASIQUOTE_NAME_PREFIX => - def fresh() = c.freshName[TermName](nme.QUASIQUOTE_NAME_PREFIX) + def fresh() = c.freshName(TermName(nme.QUASIQUOTE_NAME_PREFIX)) def introduceName() = { val n = fresh(); nameMap(name) += n; n} def result(n: Name) = if (isReifyingExpressions) Ident(n) else Bind(n, Ident(nme.WILDCARD)) if (isReifyingPatterns) result(introduceName()) diff --git a/src/compiler/scala/reflect/reify/codegen/GenUtils.scala b/src/compiler/scala/reflect/reify/codegen/GenUtils.scala index de9fec0df5..b5b0f93750 100644 --- a/src/compiler/scala/reflect/reify/codegen/GenUtils.scala +++ b/src/compiler/scala/reflect/reify/codegen/GenUtils.scala @@ -15,7 +15,7 @@ trait GenUtils { def reifyProduct(prefix: String, elements: List[Any]): Tree = { // reflection would be more robust, but, hey, this is a hot path if (prefix.startsWith("Tuple")) scalaFactoryCall(prefix, (elements map reify).toList: _*) - else mirrorCall(prefix, (elements map reify): _*) + else mirrorCall(TermName(prefix), (elements map reify): _*) } // helper functions @@ -49,16 +49,16 @@ trait GenUtils { call("" + nme.MIRROR_PREFIX + name, args: _*) def mirrorFactoryCall(value: Product, args: Tree*): Tree = - mirrorFactoryCall(value.productPrefix, args: _*) + mirrorFactoryCall(TermName(value.productPrefix), args: _*) def mirrorFactoryCall(prefix: TermName, args: Tree*): Tree = - mirrorCall("" + prefix, args: _*) + mirrorCall(TermName("" + prefix), args: _*) def scalaFactoryCall(name: TermName, args: Tree*): Tree = call(s"scala.$name.apply", args: _*) def scalaFactoryCall(name: String, args: Tree*): Tree = - scalaFactoryCall(name: TermName, args: _*) + scalaFactoryCall(TermName(name), args: _*) def mkList(args: List[Tree]): Tree = scalaFactoryCall("collection.immutable.List", args: _*) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index d7abe2be4b..7e79ff3e87 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -689,7 +689,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => null else { val outerName = javaName(innerSym.rawowner) - if (isTopLevelModule(innerSym.rawowner)) "" + nme.stripModuleSuffix(newTermName(outerName)) + if (isTopLevelModule(innerSym.rawowner)) "" + TermName(outerName).dropModule else outerName } } diff --git a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala index 9433ddcf31..4ad4c13c0c 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala @@ -125,7 +125,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { def makeSyntheticParam(count: Int, tpt: Tree): ValDef = makeParam(nme.syntheticParamName(count), tpt) def makeParam(name: String, tpt: Tree): ValDef = - makeParam(name: TermName, tpt) + makeParam(TermName(name), tpt) def makeParam(name: TermName, tpt: Tree): ValDef = ValDef(Modifiers(Flags.JAVA | Flags.PARAM), name, tpt, EmptyTree) diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index 4d08be3c24..96800175ee 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -1103,7 +1103,7 @@ abstract class ClassfileParser { def enclosing = if (jflags.isStatic) enclModule else enclClass // The name of the outer class, without its trailing $ if it has one. - private def strippedOuter = nme stripModuleSuffix outerName + private def strippedOuter = outerName.dropModule private def isInner = innerClasses contains strippedOuter private def enclClass = if (isInner) innerClasses innerSymbol strippedOuter else classNameToSymbol(strippedOuter) private def enclModule = enclClass.companionModule diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala index bd1fa4e707..ea46116976 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala @@ -74,7 +74,7 @@ abstract class ICodeReader extends ClassfileParser { first != CONSTANT_METHODREF && first != CONSTANT_INTFMETHODREF) errorBadTag(start) val ownerTpe = getClassOrArrayType(in.getChar(start + 1).toInt) - debuglog("getMemberSymbol(static: " + static + "): owner type: " + ownerTpe + " " + ownerTpe.typeSymbol.originalName) + debuglog("getMemberSymbol(static: " + static + "): owner type: " + ownerTpe + " " + ownerTpe.typeSymbol.unexpandedName) val (name0, tpe0) = getNameAndType(in.getChar(start + 3).toInt, ownerTpe) debuglog("getMemberSymbol: name and tpe: " + name0 + ": " + tpe0) diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index d3cd26f256..52132dd74a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -276,7 +276,7 @@ trait Implicits { /** An extractor for types of the form ? { name: (? >: argtpe <: Any*)restp } */ object HasMethodMatching { - val dummyMethod = NoSymbol.newTermSymbol("typer$dummy") setInfo NullaryMethodType(AnyTpe) + val dummyMethod = NoSymbol.newTermSymbol(TermName("typer$dummy")) setInfo NullaryMethodType(AnyTpe) def templateArgType(argtpe: Type) = new BoundedWildcardType(TypeBounds.lower(argtpe)) diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index 0c36d74847..059981aa37 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -439,7 +439,7 @@ trait TypeDiagnostics { context.warning(pos, "imported `%s' is permanently hidden by definition of %s".format(hidden, defn.fullLocationString)) object checkUnused { - val ignoreNames = Set[TermName]("readResolve", "readObject", "writeObject", "writeReplace") + val ignoreNames: Set[TermName] = Set(TermName("readResolve"), TermName("readObject"), TermName("writeObject"), TermName("writeReplace")) class UnusedPrivates extends Traverser { val defnTrees = ListBuffer[MemberDef]() diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 1afb45289f..46f2177035 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -5198,7 +5198,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def warn(message: String) = context.warning(lit.pos, s"possible missing interpolator: $message") def suspiciousSym(name: TermName) = context.lookupSymbol(name, _ => true).symbol def suspiciousExpr = InterpolatorCodeRegex findFirstIn s - def suspiciousIdents = InterpolatorIdentRegex findAllIn s map (s => suspiciousSym(s drop 1)) + def suspiciousIdents = InterpolatorIdentRegex findAllIn s map (s => suspiciousSym(TermName(s drop 1))) if (suspiciousExpr.nonEmpty) warn("detected an interpolated expression") // "${...}" diff --git a/src/reflect/scala/reflect/api/Names.scala b/src/reflect/scala/reflect/api/Names.scala index 472da60338..cc01225287 100644 --- a/src/reflect/scala/reflect/api/Names.scala +++ b/src/reflect/scala/reflect/api/Names.scala @@ -30,15 +30,15 @@ import scala.language.implicitConversions */ trait Names { /** An implicit conversion from String to TermName. - * Enables an alternative notation `"map": TermName` as opposed to `TermName("map")`. - * @group Names + * Enables an alternative notation `"map": TermName` as opposed to `TermName("map")`. + * @group Names */ @deprecated("Use explicit `TermName(s)` instead", "2.11.0") implicit def stringToTermName(s: String): TermName = TermName(s) /** An implicit conversion from String to TypeName. - * Enables an alternative notation `"List": TypeName` as opposed to `TypeName("List")`. - * @group Names + * Enables an alternative notation `"List": TypeName` as opposed to `TypeName("List")`. + * @group Names */ @deprecated("Use explicit `TypeName(s)` instead", "2.11.0") implicit def stringToTypeName(s: String): TypeName = TypeName(s) diff --git a/src/reflect/scala/reflect/api/StandardLiftables.scala b/src/reflect/scala/reflect/api/StandardLiftables.scala index 66ac62cc9e..ebf15e4f57 100644 --- a/src/reflect/scala/reflect/api/StandardLiftables.scala +++ b/src/reflect/scala/reflect/api/StandardLiftables.scala @@ -230,6 +230,6 @@ trait StandardLiftables { self: Universe => val Symbol = TermName("Symbol") val util = TermName("util") val Vector = TermName("Vector") - val WILDCARD = self.nme.WILDCARD + val WILDCARD = self.termNames.WILDCARD } } diff --git a/src/reflect/scala/reflect/api/Trees.scala b/src/reflect/scala/reflect/api/Trees.scala index 9ecd87c17e..2bf407ee19 100644 --- a/src/reflect/scala/reflect/api/Trees.scala +++ b/src/reflect/scala/reflect/api/Trees.scala @@ -2661,7 +2661,7 @@ trait Trees { self: Universe => * @group Traversal */ abstract class ModifiersExtractor { - def apply(): Modifiers = Modifiers(NoFlags, tpnme.EMPTY, List()) + def apply(): Modifiers = Modifiers(NoFlags, typeNames.EMPTY, List()) def apply(flags: FlagSet, privateWithin: Name, annotations: List[Tree]): Modifiers def unapply(mods: Modifiers): Option[(FlagSet, Name, List[Tree])] } @@ -2674,7 +2674,7 @@ trait Trees { self: Universe => /** The factory for `Modifiers` instances. * @group Traversal */ - def Modifiers(flags: FlagSet): Modifiers = Modifiers(flags, tpnme.EMPTY) + def Modifiers(flags: FlagSet): Modifiers = Modifiers(flags, typeNames.EMPTY) /** An empty `Modifiers` object: no flags, empty visibility annotation and no Scala annotations. * @group Traversal diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 9f4ec3e6d1..756ed870ca 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -156,11 +156,11 @@ trait Definitions extends api.StandardDefinitions { // It becomes tricky to create dedicated objects for other symbols because // of initialization order issues. - lazy val JavaLangPackage = getPackage("java.lang") + lazy val JavaLangPackage = getPackage(TermName("java.lang")) lazy val JavaLangPackageClass = JavaLangPackage.moduleClass.asClass - lazy val ScalaPackage = getPackage("scala") + lazy val ScalaPackage = getPackage(TermName("scala")) lazy val ScalaPackageClass = ScalaPackage.moduleClass.asClass - lazy val RuntimePackage = getPackage("scala.runtime") + lazy val RuntimePackage = getPackage(TermName("scala.runtime")) lazy val RuntimePackageClass = RuntimePackage.moduleClass.asClass def javaTypeToValueClass(jtype: Class[_]): Symbol = jtype match { @@ -453,7 +453,7 @@ trait Definitions extends api.StandardDefinitions { // XML lazy val ScalaXmlTopScope = getModuleIfDefined("scala.xml.TopScope") - lazy val ScalaXmlPackage = getPackageIfDefined("scala.xml") + lazy val ScalaXmlPackage = getPackageIfDefined(TermName("scala.xml")) // scala.reflect lazy val ReflectPackage = requiredModule[scala.reflect.`package`.type] @@ -1148,7 +1148,7 @@ trait Definitions extends api.StandardDefinitions { // Trying to allow for deprecated locations sym.isAliasType && isMetaAnnotation(sym.info.typeSymbol) ) - lazy val metaAnnotations: Set[Symbol] = getPackage("scala.annotation.meta").info.members filter (_ isSubClass StaticAnnotationClass) toSet + lazy val metaAnnotations: Set[Symbol] = getPackage(TermName("scala.annotation.meta")).info.members filter (_ isSubClass StaticAnnotationClass) toSet // According to the scala.annotation.meta package object: // * By default, annotations on (`val`-, `var`- or plain) constructor parameters @@ -1462,7 +1462,7 @@ trait Definitions extends api.StandardDefinitions { ) lazy val TagSymbols = TagMaterializers.keySet lazy val Predef_conforms = (getMemberIfDefined(PredefModule, nme.conforms) - orElse getMemberMethod(PredefModule, "conforms": TermName)) // TODO: predicate on -Xsource:2.10 (for now, needed for transition from M8 -> RC1) + orElse getMemberMethod(PredefModule, TermName("conforms"))) // TODO: predicate on -Xsource:2.10 (for now, needed for transition from M8 -> RC1) lazy val Predef_classOf = getMemberMethod(PredefModule, nme.classOf) lazy val Predef_implicitly = getMemberMethod(PredefModule, nme.implicitly) lazy val Predef_wrapRefArray = getMemberMethod(PredefModule, nme.wrapRefArray) diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 5e47f127d4..f4b741bd19 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -2820,7 +2820,8 @@ trait Symbols extends api.Symbols { self: SymbolTable => override def outerSource: Symbol = // SI-6888 Approximate the name to workaround the deficiencies in `nme.originalName` // in the face of classes named '$'. SI-2806 remains open to address the deeper problem. - if (originalName endsWith (nme.OUTER)) initialize.referenced + if (unexpandedName endsWith (nme.OUTER)) initialize.referenced + else NoSymbol def setModuleClass(clazz: Symbol): TermSymbol = { diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 3b497227e7..6efac6d873 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -142,7 +142,7 @@ private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUnive object ConstantArg { def enumToSymbol(enum: Enum[_]): Symbol = { val staticPartOfEnum = classToScala(enum.getClass).companionSymbol - staticPartOfEnum.info.declaration(enum.name: TermName) + staticPartOfEnum.info.declaration(TermName(enum.name)) } def unapply(schemaAndValue: (jClass[_], Any)): Option[Any] = schemaAndValue match { @@ -172,7 +172,7 @@ private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUnive // currently I'm simply sorting the methods to guarantee stability of the output override lazy val assocs: List[(Name, ClassfileAnnotArg)] = ( jann.annotationType.getDeclaredMethods.sortBy(_.getName).toList map (m => - (m.getName: TermName) -> toAnnotArg(m.getReturnType -> m.invoke(jann)) + TermName(m.getName) -> toAnnotArg(m.getReturnType -> m.invoke(jann)) ) ) } @@ -940,7 +940,7 @@ private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUnive val ownerModule: ModuleSymbol = if (split > 0) packageNameToScala(fullname take split) else this.RootPackage val owner = ownerModule.moduleClass - val name = (fullname: TermName) drop split + 1 + val name = TermName(fullname) drop split + 1 val opkg = owner.info decl name if (opkg.hasPackageFlag) opkg.asModule @@ -991,7 +991,7 @@ private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUnive if (name.startsWith(nme.NAME_JOIN_STRING)) coreLookup(name drop 1) else NoSymbol } if (nme.isModuleName(simpleName)) - coreLookup(nme.stripModuleSuffix(simpleName).toTermName) map (_.moduleClass) + coreLookup(simpleName.dropModule.toTermName) map (_.moduleClass) else coreLookup(simpleName) } diff --git a/src/reflect/scala/reflect/runtime/package.scala b/src/reflect/scala/reflect/runtime/package.scala index e240bed0a7..77eb610a84 100644 --- a/src/reflect/scala/reflect/runtime/package.scala +++ b/src/reflect/scala/reflect/runtime/package.scala @@ -30,9 +30,9 @@ package runtime { import c.universe._ val runtimeClass = c.reifyEnclosingRuntimeClass if (runtimeClass.isEmpty) c.abort(c.enclosingPosition, "call site does not have an enclosing class") - val scalaPackage = Select(Ident(newTermName("_root_")), newTermName("scala")) - val runtimeUniverse = Select(Select(Select(scalaPackage, newTermName("reflect")), newTermName("runtime")), newTermName("universe")) - val currentMirror = Apply(Select(runtimeUniverse, newTermName("runtimeMirror")), List(Select(runtimeClass, newTermName("getClassLoader")))) + val scalaPackage = Select(Ident(TermName("_root_")), TermName("scala")) + val runtimeUniverse = Select(Select(Select(scalaPackage, TermName("reflect")), TermName("runtime")), TermName("universe")) + val currentMirror = Apply(Select(runtimeUniverse, TermName("runtimeMirror")), List(Select(runtimeClass, TermName("getClassLoader")))) c.Expr[Nothing](currentMirror)(c.WeakTypeTag.Nothing) } } diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala index 0347622cf4..c281126d5f 100644 --- a/src/repl/scala/tools/nsc/interpreter/IMain.scala +++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala @@ -309,7 +309,7 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set def shift[T](op: => T): T = exitingFlatten(op) } - def originalPath(name: String): String = originalPath(name: TermName) + def originalPath(name: String): String = originalPath(TermName(name)) def originalPath(name: Name): String = typerOp path name def originalPath(sym: Symbol): String = typerOp path sym def flatPath(sym: Symbol): String = flatOp shift sym.javaClassName @@ -1106,8 +1106,8 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set def tryTwice(op: => Symbol): Symbol = exitingTyper(op) orElse exitingFlatten(op) def symbolOfIdent(id: String): Symbol = symbolOfType(id) orElse symbolOfTerm(id) - def symbolOfType(id: String): Symbol = tryTwice(replScope lookup (id: TypeName)) - def symbolOfTerm(id: String): Symbol = tryTwice(replScope lookup (id: TermName)) + def symbolOfType(id: String): Symbol = tryTwice(replScope lookup TypeName(id)) + def symbolOfTerm(id: String): Symbol = tryTwice(replScope lookup TermName(id)) def symbolOfName(id: Name): Symbol = replScope lookup id def runtimeClassAndTypeOfTerm(id: String): Option[(JClass, Type)] = { diff --git a/test/files/neg/reflection-names-neg.check b/test/files/neg/reflection-names-neg.check deleted file mode 100644 index f941ec8dc1..0000000000 --- a/test/files/neg/reflection-names-neg.check +++ /dev/null @@ -1,13 +0,0 @@ -reflection-names-neg.scala:5: error: type mismatch; - found : String("abc") - required: reflect.runtime.universe.Name -Note that implicit conversions are not applicable because they are ambiguous: - both method stringToTermName in trait Names of type (s: String)reflect.runtime.universe.TermName - and method stringToTypeName in trait Names of type (s: String)reflect.runtime.universe.TypeName - are possible conversion functions from String("abc") to reflect.runtime.universe.Name - val x2 = ("abc": Name) drop 1 // error - ^ -reflection-names-neg.scala:5: error: value drop is not a member of reflect.runtime.universe.Name - val x2 = ("abc": Name) drop 1 // error - ^ -two errors found diff --git a/test/files/neg/reflection-names-neg.scala b/test/files/neg/reflection-names-neg.scala deleted file mode 100644 index 7283d16db9..0000000000 --- a/test/files/neg/reflection-names-neg.scala +++ /dev/null @@ -1,6 +0,0 @@ -import scala.reflect.runtime.universe._ - -object Test { - val x1 = "abc" drop 1 // "bc": String - val x2 = ("abc": Name) drop 1 // error -} diff --git a/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala b/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala index 895ad9d683..5a921a5eda 100644 --- a/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala +++ b/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala @@ -33,10 +33,10 @@ class SymbolTableTest { import symbolTable._ symbolTable.definitions.init() val rootClass = symbolTable.rootMirror.RootClass - val fooSymbol = rootClass.newClassSymbol("Foo": TypeName, NoPosition, 0) + val fooSymbol = rootClass.newClassSymbol(TypeName("Foo"), NoPosition, 0) val fooType = new ClassInfoType(Nil, EmptyScope, fooSymbol) fooSymbol.info = fooType - val barSymbol = rootClass.newClassSymbol("Bar": TypeName, NoPosition, 0) + val barSymbol = rootClass.newClassSymbol(TypeName("Bar"), NoPosition, 0) val fooTypeRef = TypeRef(fooSymbol.owner.tpe, fooSymbol, Nil) val barType = new ClassInfoType(List(fooTypeRef), EmptyScope, barSymbol) barSymbol.info = barType -- cgit v1.2.3 From d4546fd74f94c653200200ef09cbbf2e1a808668 Mon Sep 17 00:00:00 2001 From: EECOLOR Date: Thu, 15 Jan 2015 22:43:04 +0100 Subject: Removed warnings - Added `since` to deprecation statement - Added unit to parameter list - Removed usage of deprecated method polyType - Replaced deprecated `debugwarn` with `devWarning` - Changed switch statement to if else in order to remove a warning - Switched implementation of `init` and `processOptions` to prevent warning - Replaced deprecated `Console.readLine` with `scala.io.StdIn.readLine` - Replaced deprecated `startOrPoint` with `start` - Replaced deprecated `tpe_=` with `setType` - Replaced deprecated `typeCheck` with `typecheck` - Replaced deprecated `CompilationUnit.warning` with `typer.context.warning` - Replaced deprecated `scala.tools.nsc.util.ScalaClassLoader` with `scala.reflect.internal.util.ScalaClassLoader` - Replaced deprecated `scala.tools.ListOfNil` with `scala.reflect.internal.util.ListOfNil` - Replaced deprecated `scala.tools.utils.ScalaClassLoader` with `scala.reflect.internal.util.ScalaClassLoader` - Replaced deprecated `emptyValDef` with `noSelfType` - In `BoxesRunTime` removed unused method and commented out unused values. Did not delete to keep a reference to the values. If they are deleted people might wonder why `1` and `2` are not used. - Replaced deprecated `scala.tools.nsc.util.AbstractFileClassLoader` with `scala.reflect.internal.util.AbstractFileClassLoader` --- src/compiler/scala/reflect/quasiquotes/Holes.scala | 2 +- src/compiler/scala/tools/ant/FastScalac.scala | 2 +- src/compiler/scala/tools/ant/sabbus/Compiler.scala | 2 +- src/compiler/scala/tools/ant/sabbus/ScalacFork.scala | 2 +- src/compiler/scala/tools/nsc/EvalLoop.scala | 3 ++- src/compiler/scala/tools/nsc/GenericRunnerCommand.scala | 3 ++- src/compiler/scala/tools/nsc/ObjectRunner.scala | 2 +- src/compiler/scala/tools/nsc/ast/NodePrinters.scala | 1 + src/compiler/scala/tools/nsc/ast/parser/Parsers.scala | 4 ++-- .../scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala | 1 + src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala | 8 ++++---- src/compiler/scala/tools/nsc/javac/JavaParsers.scala | 1 + src/compiler/scala/tools/nsc/plugins/Plugin.scala | 6 +++--- .../scala/tools/nsc/symtab/classfile/ClassfileParser.scala | 6 +++--- src/compiler/scala/tools/nsc/transform/Constructors.scala | 1 + src/compiler/scala/tools/nsc/transform/Erasure.scala | 2 +- src/compiler/scala/tools/nsc/transform/Mixin.scala | 2 +- src/compiler/scala/tools/nsc/transform/UnCurry.scala | 3 ++- src/compiler/scala/tools/nsc/typechecker/Implicits.scala | 2 +- src/compiler/scala/tools/nsc/typechecker/Macros.scala | 3 ++- .../scala/tools/nsc/typechecker/MethodSynthesis.scala | 1 + src/compiler/scala/tools/nsc/typechecker/Namers.scala | 3 ++- .../scala/tools/nsc/typechecker/PatternTypers.scala | 2 +- src/compiler/scala/tools/nsc/typechecker/RefChecks.scala | 4 ++-- .../scala/tools/nsc/typechecker/SyntheticMethods.scala | 2 +- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 13 +++++++------ src/compiler/scala/tools/nsc/typechecker/Unapplies.scala | 1 + src/compiler/scala/tools/reflect/ToolBoxFactory.scala | 2 +- src/library/scala/runtime/BoxesRunTime.java | 6 +----- src/reflect/scala/reflect/api/Types.scala | 2 +- src/reflect/scala/reflect/internal/Importers.scala | 2 +- src/reflect/scala/reflect/internal/Symbols.scala | 1 - src/reflect/scala/reflect/internal/TreeGen.scala | 4 ++-- src/reflect/scala/reflect/internal/Trees.scala | 2 +- src/reflect/scala/reflect/internal/Types.scala | 2 +- 35 files changed, 55 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/reflect/quasiquotes/Holes.scala b/src/compiler/scala/reflect/quasiquotes/Holes.scala index 2bbc753a81..6fa6b9b37a 100644 --- a/src/compiler/scala/reflect/quasiquotes/Holes.scala +++ b/src/compiler/scala/reflect/quasiquotes/Holes.scala @@ -187,7 +187,7 @@ trait Holes { self: Quasiquotes => lazy val tree = tptopt.map { tpt => val TypeDef(_, _, _, typedTpt) = - try c.typeCheck(TypeDef(NoMods, TypeName("T"), Nil, tpt)) + try c.typecheck(TypeDef(NoMods, TypeName("T"), Nil, tpt)) catch { case TypecheckException(pos, msg) => c.abort(pos.asInstanceOf[c.Position], msg) } val tpe = typedTpt.tpe val (iterableRank, _) = stripIterable(tpe) diff --git a/src/compiler/scala/tools/ant/FastScalac.scala b/src/compiler/scala/tools/ant/FastScalac.scala index c3eb9eef9c..6f0a30aa9d 100644 --- a/src/compiler/scala/tools/ant/FastScalac.scala +++ b/src/compiler/scala/tools/ant/FastScalac.scala @@ -15,7 +15,7 @@ import org.apache.tools.ant.types.Path import scala.tools.nsc.Settings import scala.tools.nsc.io.File import scala.tools.nsc.settings.FscSettings -import scala.tools.nsc.util.ScalaClassLoader +import scala.reflect.internal.util.ScalaClassLoader /** An Ant task to compile with the fast Scala compiler (`fsc`). * diff --git a/src/compiler/scala/tools/ant/sabbus/Compiler.scala b/src/compiler/scala/tools/ant/sabbus/Compiler.scala index 65cd9f41c2..81cd1f3196 100644 --- a/src/compiler/scala/tools/ant/sabbus/Compiler.scala +++ b/src/compiler/scala/tools/ant/sabbus/Compiler.scala @@ -12,7 +12,7 @@ package scala.tools.ant.sabbus import java.io.File import java.net.URL import java.lang.reflect.InvocationTargetException -import scala.tools.nsc.util.ScalaClassLoader +import scala.reflect.internal.util.ScalaClassLoader class Compiler(classpath: Array[URL], val settings: Settings) { diff --git a/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala b/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala index 595b45ae51..cde827ba54 100644 --- a/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala +++ b/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala @@ -16,7 +16,7 @@ import org.apache.tools.ant.taskdefs.Java import org.apache.tools.ant.util.{ GlobPatternMapper, SourceFileScanner } import org.apache.tools.ant.BuildException import scala.tools.nsc.io -import scala.tools.nsc.util.ScalaClassLoader +import scala.reflect.internal.util.ScalaClassLoader /** An Ant task to compile with the new Scala compiler (NSC). * diff --git a/src/compiler/scala/tools/nsc/EvalLoop.scala b/src/compiler/scala/tools/nsc/EvalLoop.scala index 15a296c836..73f4b9a119 100644 --- a/src/compiler/scala/tools/nsc/EvalLoop.scala +++ b/src/compiler/scala/tools/nsc/EvalLoop.scala @@ -6,6 +6,7 @@ package scala.tools.nsc import scala.annotation.tailrec +import scala.io.StdIn import java.io.EOFException trait EvalLoop { @@ -14,7 +15,7 @@ trait EvalLoop { def loop(action: (String) => Unit) { @tailrec def inner() { Console.print(prompt) - val line = try Console.readLine() catch { case _: EOFException => null } + val line = try StdIn.readLine() catch { case _: EOFException => null } if (line != null && line != "") { action(line) inner() diff --git a/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala b/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala index dbdeec809f..2584054686 100644 --- a/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala +++ b/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala @@ -6,6 +6,7 @@ package scala.tools.nsc import GenericRunnerCommand._ +import scala.reflect.internal.util.ScalaClassLoader /** A command for ScriptRunner */ class GenericRunnerCommand( @@ -32,7 +33,7 @@ extends CompilerCommand(args, settings) { private def guessHowToRun(target: String): GenericRunnerCommand.HowToRun = { if (!ok) Error else if (io.Jar.isJarOrZip(target)) AsJar - else if (util.ScalaClassLoader.classExists(settings.classpathURLs, target)) AsObject + else if (ScalaClassLoader.classExists(settings.classpathURLs, target)) AsObject else { val f = io.File(target) if (!f.hasExtension("class", "jar", "zip") && f.canRead) AsScript diff --git a/src/compiler/scala/tools/nsc/ObjectRunner.scala b/src/compiler/scala/tools/nsc/ObjectRunner.scala index 7c14f4943f..8e01418e8b 100644 --- a/src/compiler/scala/tools/nsc/ObjectRunner.scala +++ b/src/compiler/scala/tools/nsc/ObjectRunner.scala @@ -7,8 +7,8 @@ package scala.tools.nsc import java.net.URL -import util.ScalaClassLoader import util.Exceptional.unwrap +import scala.reflect.internal.util.ScalaClassLoader trait CommonRunner { /** Run a given object, specified by name, using a diff --git a/src/compiler/scala/tools/nsc/ast/NodePrinters.scala b/src/compiler/scala/tools/nsc/ast/NodePrinters.scala index 9c8e13a1a9..6fe85cde7a 100644 --- a/src/compiler/scala/tools/nsc/ast/NodePrinters.scala +++ b/src/compiler/scala/tools/nsc/ast/NodePrinters.scala @@ -9,6 +9,7 @@ package ast import scala.compat.Platform.EOL import symtab.Flags._ import scala.language.postfixOps +import scala.reflect.internal.util.ListOfNil /** The object `nodePrinter` converts the internal tree * representation to a string. diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 4663810003..67e91ae857 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -13,7 +13,7 @@ import scala.collection.{ mutable, immutable } import mutable.{ ListBuffer, StringBuilder } import scala.reflect.internal.{ Precedence, ModifierFlags => Flags } import scala.reflect.internal.Chars.{ isScalaLetter } -import scala.reflect.internal.util.{ SourceFile, Position, FreshNameCreator } +import scala.reflect.internal.util.{ SourceFile, Position, FreshNameCreator, ListOfNil } import Tokens._ /** Historical note: JavaParsers started life as a direct copy of Parsers @@ -2788,7 +2788,7 @@ self => */ def packageObjectDef(start: Offset): PackageDef = { val defn = objectDef(in.offset, NoMods) - val pidPos = o2p(defn.pos.startOrPoint) + val pidPos = o2p(defn.pos.start) val pkgPos = r2p(start, pidPos.point) gen.mkPackageObject(defn, pidPos, pkgPos) } diff --git a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala index 8cd915bf22..d2a999cdec 100755 --- a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala @@ -8,6 +8,7 @@ package ast.parser import scala.collection.{ mutable, immutable } import symtab.Flags.MUTABLE +import scala.reflect.internal.util.ListOfNil import scala.reflect.internal.util.StringOps.splitWhere /** This class builds instance of `Tree` that represent XML. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 7e79ff3e87..f866c0d038 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -2054,7 +2054,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => seen ::= LocVarEntry(lv, start, end) case _ => // TODO SI-6049 track down the cause for these. - debugwarn(s"$iPos: Visited SCOPE_EXIT before visiting corresponding SCOPE_ENTER. SI-6191") + devWarning(s"$iPos: Visited SCOPE_EXIT before visiting corresponding SCOPE_ENTER. SI-6191") } } @@ -2424,7 +2424,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => // SI-6102: Determine whether eliding this JUMP results in an empty range being covered by some EH. // If so, emit a NOP in place of the elided JUMP, to avoid "java.lang.ClassFormatError: Illegal exception table range" else if (newNormal.isJumpOnly(b) && m.exh.exists(eh => eh.covers(b))) { - debugwarn("Had a jump only block that wasn't collapsed") + devWarning("Had a jump only block that wasn't collapsed") emit(asm.Opcodes.NOP) } @@ -3136,13 +3136,13 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => val (remappings, cycles) = detour partition {case (source, target) => source != target} for ((source, target) <- remappings) { debuglog(s"Will elide jump only block $source because it can be jumped around to get to $target.") - if (m.startBlock == source) debugwarn("startBlock should have been re-wired by now") + if (m.startBlock == source) devWarning("startBlock should have been re-wired by now") } val sources = remappings.keySet val targets = remappings.values.toSet val intersection = sources intersect targets - if (intersection.nonEmpty) debugwarn(s"contradiction: we seem to have some source and target overlap in blocks ${intersection.mkString}. Map was ${detour.mkString}") + if (intersection.nonEmpty) devWarning(s"contradiction: we seem to have some source and target overlap in blocks ${intersection.mkString}. Map was ${detour.mkString}") for ((source, _) <- cycles) { debuglog(s"Block $source is in a do-nothing infinite loop. Did the user write 'while(true){}'?") diff --git a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala index 4ad4c13c0c..d34c14be0f 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala @@ -13,6 +13,7 @@ import symtab.Flags import JavaTokens._ import scala.language.implicitConversions import scala.reflect.internal.util.Position +import scala.reflect.internal.util.ListOfNil trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { val global : Global diff --git a/src/compiler/scala/tools/nsc/plugins/Plugin.scala b/src/compiler/scala/tools/nsc/plugins/Plugin.scala index 7837f9a11a..5a7a0df595 100644 --- a/src/compiler/scala/tools/nsc/plugins/Plugin.scala +++ b/src/compiler/scala/tools/nsc/plugins/Plugin.scala @@ -7,7 +7,7 @@ package scala.tools.nsc package plugins import scala.tools.nsc.io.{ Jar } -import scala.tools.nsc.util.ScalaClassLoader +import scala.reflect.internal.util.ScalaClassLoader import scala.reflect.io.{ Directory, File, Path } import java.io.InputStream import java.util.zip.ZipException @@ -60,13 +60,13 @@ abstract class Plugin { * @return true to continue, or false to opt out */ def init(options: List[String], error: String => Unit): Boolean = { - processOptions(options, error) + if (!options.isEmpty) error(s"Error: $name takes no options") true } @deprecated("use Plugin#init instead", since="2.11") def processOptions(options: List[String], error: String => Unit): Unit = { - if (!options.isEmpty) error(s"Error: $name takes no options") + init(options, error) } /** A description of this plugin's options, suitable as a response diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index 96800175ee..1ffa064b78 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -807,10 +807,10 @@ abstract class ClassfileParser { val c = pool.getConstant(u2) val c1 = convertTo(c, symtype) if (c1 ne null) sym.setInfo(ConstantType(c1)) - else debugwarn(s"failure to convert $c to $symtype") + else devWarning(s"failure to convert $c to $symtype") case tpnme.ScalaSignatureATTR => if (!isScalaAnnot) { - debugwarn(s"symbol ${sym.fullName} has pickled signature in attribute") + devWarning(s"symbol ${sym.fullName} has pickled signature in attribute") unpickler.unpickle(in.buf, in.bp, clazz, staticModule, in.file.name) } in.skip(attrLen) @@ -1123,7 +1123,7 @@ abstract class ClassfileParser { def add(entry: InnerClassEntry): Unit = { inners get entry.externalName foreach (existing => - debugwarn(s"Overwriting inner class entry! Was $existing, now $entry") + devWarning(s"Overwriting inner class entry! Was $existing, now $entry") ) inners(entry.externalName) = entry } diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 362cbde04f..d0fca12e6a 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -8,6 +8,7 @@ package transform import scala.collection.{ mutable, immutable } import scala.collection.mutable.ListBuffer +import scala.reflect.internal.util.ListOfNil import symtab.Flags._ /** This phase converts classes with parameters into Java-like classes with diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 5c72bb3258..facce9062b 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -1036,7 +1036,7 @@ abstract class Erasure extends AddInterfaces // See SI-5568. tree setSymbol Object_getClass } else { - debugwarn(s"The symbol '${fn.symbol}' was interecepted but didn't match any cases, that means the intercepted methods set doesn't match the code") + devWarning(s"The symbol '${fn.symbol}' was interecepted but didn't match any cases, that means the intercepted methods set doesn't match the code") tree } } else qual match { diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index b5ffc8bc58..408f4466e1 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -267,7 +267,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { /* Mix in members of implementation class mixinClass into class clazz */ def mixinImplClassMembers(mixinClass: Symbol, mixinInterface: Symbol) { - if (!mixinClass.isImplClass) debugwarn ("Impl class flag is not set " + + if (!mixinClass.isImplClass) devWarning ("Impl class flag is not set " + ((mixinClass.debugLocationString, mixinInterface.debugLocationString))) for (member <- mixinClass.info.decls ; if isForwarded(member)) { diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index e1cf53059a..3330fbcae2 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -10,6 +10,7 @@ package transform import symtab.Flags._ import scala.collection.{ mutable, immutable } import scala.language.postfixOps +import scala.reflect.internal.util.ListOfNil /* */ /** - uncurry all symbol and tree types (@see UnCurryPhase) -- this includes normalizing all proper types. @@ -206,7 +207,7 @@ abstract class UnCurry extends InfoTransform // (() => Int) { def apply(): Int @typeConstraint } case RefinedType(List(funTp), decls) => debuglog(s"eliminate refinement from function type ${fun.tpe}") - fun.tpe = funTp + fun.setType(funTp) case _ => () } diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 52132dd74a..5ecca5abce 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -893,7 +893,7 @@ trait Implicits { try improves(firstPending, alt) catch { case e: CyclicReference => - debugwarn(s"Discarding $firstPending during implicit search due to cyclic reference.") + devWarning(s"Discarding $firstPending during implicit search due to cyclic reference.") true } ) diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index da7b8b09aa..10aefae20b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -3,13 +3,14 @@ package typechecker import java.lang.Math.min import symtab.Flags._ -import scala.tools.nsc.util._ +import scala.reflect.internal.util.ScalaClassLoader import scala.reflect.runtime.ReflectionUtils import scala.collection.mutable.ListBuffer import scala.reflect.ClassTag import scala.reflect.internal.util.Statistics import scala.reflect.macros.util._ import scala.util.control.ControlThrowable +import scala.reflect.internal.util.ListOfNil import scala.reflect.macros.runtime.{AbortMacroException, MacroRuntimes} import scala.reflect.runtime.{universe => ru} import scala.reflect.macros.compiler.DefaultMacroCompiler diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index 1f00403f3a..f90e32ce8a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -8,6 +8,7 @@ package typechecker import symtab.Flags._ import scala.reflect.internal.util.StringOps.{ ojoin } import scala.reflect.ClassTag +import scala.reflect.internal.util.ListOfNil import scala.reflect.runtime.{ universe => ru } import scala.language.higherKinds diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 4b942e28a6..24238b8e41 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -10,6 +10,7 @@ import scala.collection.mutable import scala.annotation.tailrec import symtab.Flags._ import scala.language.postfixOps +import scala.reflect.internal.util.ListOfNil /** This trait declares methods to create symbols and to enter them into scopes. * @@ -1305,7 +1306,7 @@ trait Namers extends MethodSynthesis { // by martin: the null case can happen in IDE; this is really an ugly hack on top of an ugly hack but it seems to work case Some(cda) => if (cda.companionModuleClassNamer == null) { - debugwarn(s"SI-6576 The companion module namer for $meth was unexpectedly null") + devWarning(s"SI-6576 The companion module namer for $meth was unexpectedly null") return } val p = (cda.classWithDefault, cda.companionModuleClassNamer) diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala index fa4a764f1b..8a66c7d274 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala @@ -268,7 +268,7 @@ trait PatternTypers { def freshArgType(tp: Type): Type = tp match { case MethodType(param :: _, _) => param.tpe - case PolyType(tparams, restpe) => createFromClonedSymbols(tparams, freshArgType(restpe))(polyType) + case PolyType(tparams, restpe) => createFromClonedSymbols(tparams, freshArgType(restpe))(genPolyType) case OverloadedType(_, _) => OverloadedUnapplyError(fun) ; ErrorType case _ => UnapplyWithSingleArgError(fun) ; ErrorType } diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index dfc0a433a9..5abfbe850f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -1684,9 +1684,9 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans case _ => } if (skipBounds) { - tree.tpe = tree.tpe.map { + tree.setType(tree.tpe.map { _.filterAnnotations(_.symbol != UncheckedBoundsClass) - } + }) } tree diff --git a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala index d2046a158c..966e8f1abe 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala @@ -344,7 +344,7 @@ trait SyntheticMethods extends ast.TreeDSL { // Without a means to suppress this warning, I've thought better of it. if (settings.warnValueOverrides) { (clazz.info nonPrivateMember m.name) filter (m => (m.owner != AnyClass) && (m.owner != clazz) && !m.isDeferred) andAlso { m => - currentUnit.warning(clazz.pos, s"Implementation of ${m.name} inherited from ${m.owner} overridden in $clazz to enforce value class semantics") + typer.context.warning(clazz.pos, s"Implementation of ${m.name} inherited from ${m.owner} overridden in $clazz to enforce value class semantics") } } true diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 46f2177035..aef2e437b7 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -14,7 +14,7 @@ package tools.nsc package typechecker import scala.collection.{mutable, immutable} -import scala.reflect.internal.util.{ BatchSourceFile, Statistics, shortClassOfInstance } +import scala.reflect.internal.util.{ BatchSourceFile, Statistics, shortClassOfInstance, ListOfNil } import mutable.ListBuffer import symtab.Flags._ import Mode._ @@ -245,7 +245,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case TypeRef(_, sym, _) if sym.isAliasType => val tp0 = tp.dealias if (tp eq tp0) { - debugwarn(s"dropExistential did not progress dealiasing $tp, see SI-7126") + devWarning(s"dropExistential did not progress dealiasing $tp, see SI-7126") tp } else { val tp1 = dropExistential(tp0) @@ -2565,7 +2565,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val default = methodSym newValueParameter (newTermName("default"), tree.pos.focus, SYNTHETIC) setInfo functionType(List(A1.tpe), B1.tpe) val paramSyms = List(x, default) - methodSym setInfo polyType(List(A1, B1), MethodType(paramSyms, B1.tpe)) + methodSym setInfo genPolyType(List(A1, B1), MethodType(paramSyms, B1.tpe)) val methodBodyTyper = newTyper(context.makeNewScope(context.tree, methodSym)) if (!paramSynthetic) methodBodyTyper.context.scope enter x @@ -2865,7 +2865,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper ClassDef(Modifiers(FINAL), tpnme.ANON_FUN_NAME, tparams = Nil, gen.mkTemplate( parents = TypeTree(samClassTpFullyDefined) :: serializableParentAddendum, - self = emptyValDef, + self = noSelfType, constrMods = NoMods, vparamss = ListOfNil, body = List(samDef), @@ -2934,7 +2934,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper var issuedMissingParameterTypeError = false foreach2(fun.vparams, argpts) { (vparam, argpt) => if (vparam.tpt.isEmpty) { - vparam.tpt.tpe = + val vparamType = if (isFullyDefined(argpt)) argpt else { fun match { @@ -2953,6 +2953,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper issuedMissingParameterTypeError = true ErrorType } + vparam.tpt.setType(vparamType) if (!vparam.tpt.pos.isDefined) vparam.tpt setPos vparam.pos.focus } } @@ -5381,7 +5382,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper ) def runTyper(): Tree = { if (retypingOk) { - tree.tpe = null + tree.setType(null) if (tree.hasSymbolField) tree.symbol = NoSymbol } val alreadyTyped = tree.tpe ne null diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index fc1f45e358..22fb0728e6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -7,6 +7,7 @@ package scala.tools.nsc package typechecker import symtab.Flags._ +import scala.reflect.internal.util.ListOfNil /* * @author Martin Odersky diff --git a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala index 1643e0061f..47c88f2c00 100644 --- a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala +++ b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala @@ -7,7 +7,7 @@ import scala.tools.nsc.Global import scala.tools.nsc.reporters._ import scala.tools.nsc.CompilerCommand import scala.tools.nsc.io.{AbstractFile, VirtualDirectory} -import scala.tools.nsc.util.AbstractFileClassLoader +import scala.reflect.internal.util.AbstractFileClassLoader import scala.reflect.internal.Flags._ import scala.reflect.internal.util.{BatchSourceFile, NoSourceFile, NoFile} import java.lang.{Class => jClass} diff --git a/src/library/scala/runtime/BoxesRunTime.java b/src/library/scala/runtime/BoxesRunTime.java index 82a3b00ac4..9cb1dee41c 100644 --- a/src/library/scala/runtime/BoxesRunTime.java +++ b/src/library/scala/runtime/BoxesRunTime.java @@ -28,7 +28,7 @@ import scala.math.ScalaNumber; * @version 2.0 */ public final class BoxesRunTime { - private static final int CHAR = 0, BYTE = 1, SHORT = 2, INT = 3, LONG = 4, FLOAT = 5, DOUBLE = 6, OTHER = 7; + private static final int CHAR = 0, /* BYTE = 1, SHORT = 2, */ INT = 3, LONG = 4, FLOAT = 5, DOUBLE = 6, OTHER = 7; /** We don't need to return BYTE and SHORT, as everything which might * care widens to INT. @@ -43,10 +43,6 @@ public final class BoxesRunTime return OTHER; } - private static String boxDescription(Object a) { - return "" + a.getClass().getSimpleName() + "(" + a + ")"; - } - /* BOXING ... BOXING ... BOXING ... BOXING ... BOXING ... BOXING ... BOXING ... BOXING */ public static java.lang.Boolean boxToBoolean(boolean b) { diff --git a/src/reflect/scala/reflect/api/Types.scala b/src/reflect/scala/reflect/api/Types.scala index f6995dd5de..cd7648a44a 100644 --- a/src/reflect/scala/reflect/api/Types.scala +++ b/src/reflect/scala/reflect/api/Types.scala @@ -469,7 +469,7 @@ trait Types { def unapply(tpe: SingleType): Option[(Type, Symbol)] /** @see [[InternalApi.singleType]] */ - @deprecated("Use `ClassSymbol.thisPrefix` or `internal.singleType` instead") + @deprecated("Use `ClassSymbol.thisPrefix` or `internal.singleType` instead", "2.11.0") def apply(pre: Type, sym: Symbol)(implicit token: CompatToken): Type = internal.singleType(pre, sym) } diff --git a/src/reflect/scala/reflect/internal/Importers.scala b/src/reflect/scala/reflect/internal/Importers.scala index dc4ad25ef2..494f62af06 100644 --- a/src/reflect/scala/reflect/internal/Importers.scala +++ b/src/reflect/scala/reflect/internal/Importers.scala @@ -301,7 +301,7 @@ trait Importers { to: SymbolTable => case (their: from.TypeTree, my: to.TypeTree) => if (their.wasEmpty) my.defineType(importType(their.tpe)) else my.setType(importType(their.tpe)) case (_, _) => - my.tpe = importType(their.tpe) + my.setType(importType(their.tpe)) } } } diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index f4b741bd19..d85ec22a84 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -2821,7 +2821,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => // SI-6888 Approximate the name to workaround the deficiencies in `nme.originalName` // in the face of classes named '$'. SI-2806 remains open to address the deeper problem. if (unexpandedName endsWith (nme.OUTER)) initialize.referenced - else NoSymbol def setModuleClass(clazz: Symbol): TermSymbol = { diff --git a/src/reflect/scala/reflect/internal/TreeGen.scala b/src/reflect/scala/reflect/internal/TreeGen.scala index b3e11a826e..75a1969d22 100644 --- a/src/reflect/scala/reflect/internal/TreeGen.scala +++ b/src/reflect/scala/reflect/internal/TreeGen.scala @@ -362,7 +362,7 @@ abstract class TreeGen { if (body forall treeInfo.isInterfaceMember) None else Some( atPos(wrappingPos(superPos, lvdefs)) ( - DefDef(NoMods, nme.MIXIN_CONSTRUCTOR, Nil, ListOfNil, TypeTree(), Block(lvdefs, Literal(Constant()))))) + DefDef(NoMods, nme.MIXIN_CONSTRUCTOR, Nil, ListOfNil, TypeTree(), Block(lvdefs, Literal(Constant(())))))) } else { // convert (implicit ... ) to ()(implicit ... ) if its the only parameter section @@ -376,7 +376,7 @@ abstract class TreeGen { // therefore here we emit a dummy which gets populated when the template is named and typechecked Some( atPos(wrappingPos(superPos, lvdefs ::: vparamss1.flatten).makeTransparent) ( - DefDef(constrMods, nme.CONSTRUCTOR, List(), vparamss1, TypeTree(), Block(lvdefs ::: List(superCall), Literal(Constant()))))) + DefDef(constrMods, nme.CONSTRUCTOR, List(), vparamss1, TypeTree(), Block(lvdefs ::: List(superCall), Literal(Constant(())))))) } } constr foreach (ensureNonOverlapping(_, parents ::: gvdefs, focus = false)) diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index fd918b8595..e3f95f9fd8 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -87,7 +87,7 @@ trait Trees extends api.Trees { private[scala] def copyAttrs(tree: Tree): this.type = { rawatt = tree.rawatt - tpe = tree.tpe + setType(tree.tpe) if (hasSymbolField) symbol = tree.symbol this } diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index ad2a545587..86a53a1b02 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -209,7 +209,7 @@ trait Types case object UnmappableTree extends TermTree { override def toString = "" - super.tpe_=(NoType) + super.setType(NoType) override def tpe_=(t: Type) = if (t != NoType) { throw new UnsupportedOperationException("tpe_=("+t+") inapplicable for ") } -- cgit v1.2.3 From 6e6632e9739364e6daecba9efd6491a5766743f2 Mon Sep 17 00:00:00 2001 From: Antoine Gourlay Date: Sun, 21 Dec 2014 15:56:54 +0100 Subject: SI-9038 fix scaladoc syntax highlightning to leave unicode alone Syntax highlightning in code blocks used to manipulate the raw bytes of a String, converting them to chars when needed, which breaks Unicode surrogate pairs. Using a char array instead of a byte array will leave them alone. --- build.xml | 1 + .../scala/tools/nsc/doc/html/SyntaxHigh.scala | 34 +++++++++++----------- .../scala/tools/nsc/doc/html/HtmlDocletTest.scala | 22 ++++++++++++++ 3 files changed, 40 insertions(+), 17 deletions(-) create mode 100644 test/junit/scala/tools/nsc/doc/html/HtmlDocletTest.scala (limited to 'src') diff --git a/build.xml b/build.xml index ee6a045bda..4b79b68a02 100755 --- a/build.xml +++ b/build.xml @@ -978,6 +978,7 @@ TODO: + diff --git a/src/scaladoc/scala/tools/nsc/doc/html/SyntaxHigh.scala b/src/scaladoc/scala/tools/nsc/doc/html/SyntaxHigh.scala index 910148532d..9ab3999447 100644 --- a/src/scaladoc/scala/tools/nsc/doc/html/SyntaxHigh.scala +++ b/src/scaladoc/scala/tools/nsc/doc/html/SyntaxHigh.scala @@ -52,7 +52,7 @@ private[html] object SyntaxHigh { "Triple", "TypeTag", "Unit") def apply(data: String): NodeSeq = { - val buf = data.getBytes + val buf = data.toCharArray val out = new StringBuilder def compare(offset: Int, key: String): Int = { @@ -60,7 +60,7 @@ private[html] object SyntaxHigh { var j = 0 val l = key.length while (i < buf.length && j < l) { - val bch = buf(i).toChar + val bch = buf(i) val kch = key charAt j if (bch < kch) return -1 else if (bch > kch) return 1 @@ -94,13 +94,13 @@ private[html] object SyntaxHigh { def line(i: Int): Int = if (i == buf.length || buf(i) == '\n') i else { - out append buf(i).toChar + out append buf(i) line(i+1) } var level = 0 def multiline(i: Int, star: Boolean): Int = { if (i == buf.length) return i - val ch = buf(i).toChar + val ch = buf(i) out append ch ch match { case '*' => @@ -127,7 +127,7 @@ private[html] object SyntaxHigh { if (i == buf.length) i else if (i > j+6) { out setLength 0; j } else { - val ch = buf(i).toChar + val ch = buf(i) out append ch ch match { case '\\' => @@ -148,7 +148,7 @@ private[html] object SyntaxHigh { val out = new StringBuilder("\"") def strlit0(i: Int, bslash: Boolean): Int = { if (i == buf.length) return i - val ch = buf(i).toChar + val ch = buf(i) out append ch ch match { case '\\' => @@ -167,7 +167,7 @@ private[html] object SyntaxHigh { val out = new StringBuilder def intg(i: Int): Int = { if (i == buf.length) return i - val ch = buf(i).toChar + val ch = buf(i) ch match { case '.' => out append ch @@ -181,7 +181,7 @@ private[html] object SyntaxHigh { } def frac(i: Int): Int = { if (i == buf.length) return i - val ch = buf(i).toChar + val ch = buf(i) ch match { case 'e' | 'E' => out append ch @@ -195,7 +195,7 @@ private[html] object SyntaxHigh { } def expo(i: Int, signed: Boolean): Int = { if (i == buf.length) return i - val ch = buf(i).toChar + val ch = buf(i) ch match { case '+' | '-' if !signed => out append ch @@ -222,7 +222,7 @@ private[html] object SyntaxHigh { case '&' => parse("&", i+1) case '<' if i+1 < buf.length => - val ch = buf(i+1).toChar + val ch = buf(i+1) if (ch == '-' || ch == ':' || ch == '%') parse("<"+ch+"", i+2) else @@ -236,19 +236,19 @@ private[html] object SyntaxHigh { if (i+1 < buf.length && buf(i+1) == '>') parse("=>", i+2) else - parse(buf(i).toChar.toString, i+1) + parse(buf(i).toString, i+1) case '/' => if (i+1 < buf.length && (buf(i+1) == '/' || buf(i+1) == '*')) { val c = comment(i+1) parse(""+c+"", i+c.length) } else - parse(buf(i).toChar.toString, i+1) + parse(buf(i).toString, i+1) case '\'' => val s = charlit(i+1) if (s.length > 0) parse(""+s+"", i+s.length) else - parse(buf(i).toChar.toString, i+1) + parse(buf(i).toString, i+1) case '"' => val s = strlit(i+1) parse(""+s+"", i+s.length) @@ -257,9 +257,9 @@ private[html] object SyntaxHigh { if (k >= 0) parse("@"+annotations(k)+"", i+annotations(k).length+1) else - parse(buf(i).toChar.toString, i+1) + parse(buf(i).toString, i+1) case _ => - if (i == 0 || (i >= 1 && !Character.isJavaIdentifierPart(buf(i-1).toChar))) { + if (i == 0 || (i >= 1 && !Character.isJavaIdentifierPart(buf(i-1)))) { if (Character.isDigit(buf(i).toInt) || (buf(i) == '.' && i + 1 < buf.length && Character.isDigit(buf(i+1).toInt))) { val s = numlit(i) @@ -273,11 +273,11 @@ private[html] object SyntaxHigh { if (k >= 0) parse(""+standards(k)+"", i+standards(k).length) else - parse(buf(i).toChar.toString, i+1) + parse(buf(i).toString, i+1) } } } else - parse(buf(i).toChar.toString, i+1) + parse(buf(i).toString, i+1) } } diff --git a/test/junit/scala/tools/nsc/doc/html/HtmlDocletTest.scala b/test/junit/scala/tools/nsc/doc/html/HtmlDocletTest.scala new file mode 100644 index 0000000000..13a955b55d --- /dev/null +++ b/test/junit/scala/tools/nsc/doc/html/HtmlDocletTest.scala @@ -0,0 +1,22 @@ +package scala.tools.nsc.doc.html + +import org.junit.Test +import org.junit.Assert._ +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +import scala.tools.testing.AssertUtil._ + +@RunWith(classOf[JUnit4]) +class HtmlDocletTest { + @Test + def testSyntaxHighlightningUnicode() { + val in = "unicode: …" + + val out = SyntaxHigh(in).toString + + // SI-9038, this failed with + // "unicode: …" != "unicode: ¬タᆭ" + assertEquals(in, out) + } +} -- cgit v1.2.3 From bf6398820971fb0133c53a86e6835ee0f549c35b Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Tue, 31 Mar 2015 14:02:51 +0200 Subject: SI-8731 don't issue a @switch warning for two-case matches This allows annotating small pattern matches with `@switch` without getting any warnings. There's no reason to warn, the performance of the generated bytecode is good. --- src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala | 2 -- src/library/scala/annotation/switch.scala | 3 +++ test/files/neg/t8731.check | 5 +---- 3 files changed, 4 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala index b703b5bc6d..e1fe220556 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala @@ -577,8 +577,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { lengthMax3(casesNoSubstOnly) > 2 } val requireSwitch = hasSwitchAnnotation && exceedsTwoCasesOrAlts - if (hasSwitchAnnotation && !requireSwitch) - reporter.warning(scrut.pos, "matches with two cases or fewer are emitted using if-then-else instead of switch") (suppression, requireSwitch) case _ => (Suppression.NoSuppression, false) diff --git a/src/library/scala/annotation/switch.scala b/src/library/scala/annotation/switch.scala index 23e3923407..00124cf88b 100644 --- a/src/library/scala/annotation/switch.scala +++ b/src/library/scala/annotation/switch.scala @@ -21,6 +21,9 @@ package scala.annotation case _ => 4 } }}} + * + * Note: for pattern matches with one or two cases, the compiler generates jump instructions. + * Annotating such a match with `@switch` does not issue any warning. * * @author Paul Phillips * @since 2.8 diff --git a/test/files/neg/t8731.check b/test/files/neg/t8731.check index 2a9af475fc..d47bd55b45 100644 --- a/test/files/neg/t8731.check +++ b/test/files/neg/t8731.check @@ -1,9 +1,6 @@ -t8731.scala:5: warning: matches with two cases or fewer are emitted using if-then-else instead of switch - def f(x: Int) = (x: @annotation.switch) match { - ^ t8731.scala:10: warning: could not emit switch for @switch annotated match def g(x: Int) = (x: @annotation.switch) match { ^ error: No warnings can be incurred under -Xfatal-warnings. -two warnings found +one warning found one error found -- cgit v1.2.3