diff --git a/core/src/main/scala/com/typesafe/tools/mima/core/ClassInfo.scala b/core/src/main/scala/com/typesafe/tools/mima/core/ClassInfo.scala index ba05eb4a..481248ae 100644 --- a/core/src/main/scala/com/typesafe/tools/mima/core/ClassInfo.scala +++ b/core/src/main/scala/com/typesafe/tools/mima/core/ClassInfo.scala @@ -56,6 +56,12 @@ abstract class ClassInfo(val owner: PackageInfo) extends HasDeclarationName with else owner.fullName + "." + bytecodeName } + var _innerClasses: Seq[String] = Seq.empty + def innerClasses = { ensureLoaded(); _innerClasses } + + var _isTopLevel = true + def isTopLevel = { ensureLoaded(); _isTopLevel } + final override def equals(other: Any): Boolean = other match { case that: ClassInfo => (that canEqual this) && this.fullName == that.fullName case _ => false @@ -65,10 +71,10 @@ abstract class ClassInfo(val owner: PackageInfo) extends HasDeclarationName with override def canEqual(other: Any) = other.isInstanceOf[ClassInfo] - def formattedFullName = formatClassName(if (isObject) fullName.init else fullName) + def formattedFullName = formatClassName(if (isModule) fullName.init else fullName) def declarationPrefix = { - if (isObject) "object" + if (isModule) "object" else if (isTrait) "trait" else if (loaded && isInterface) "interface" // java interfaces and traits with no implementation methods else "class" @@ -290,7 +296,7 @@ abstract class ClassInfo(val owner: PackageInfo) extends HasDeclarationName with ClassfileParser.isInterface(flags) } - def isObject: Boolean = bytecodeName.endsWith("$") + def isModule: Boolean = bytecodeName.endsWith("$") /** Is this class public? */ /* diff --git a/core/src/main/scala/com/typesafe/tools/mima/core/ClassfileParser.scala b/core/src/main/scala/com/typesafe/tools/mima/core/ClassfileParser.scala index a6319476..837f27a4 100644 --- a/core/src/main/scala/com/typesafe/tools/mima/core/ClassfileParser.scala +++ b/core/src/main/scala/com/typesafe/tools/mima/core/ClassfileParser.scala @@ -267,6 +267,17 @@ abstract class ClassfileParser(definitions: Definitions) { val attrNameIndex = in.nextChar c.sourceFileName = pool.getName(attrNameIndex) } + } else if (attrName == "InnerClasses") { + val entries = in.nextChar.toInt + c._innerClasses = (0 until entries).map { _ => + val innerIndex, outerIndex, innerNameIndex = in.nextChar.toInt + in.skip(2) + if (innerIndex != 0 && outerIndex != 0 && innerNameIndex != 0) { + val n = pool.getClassName(innerIndex) + if (n == c.bytecodeName) c._isTopLevel = false // an inner class lists itself in InnerClasses + if (pool.getClassName(outerIndex) == c.bytecodeName) n else "" + } else "" + }.filterNot(_.isEmpty) } else if (attrName == "Scala" || attrName == "ScalaSig") { this.parsedClass.isScala = true } diff --git a/core/src/main/scala/com/typesafe/tools/mima/core/PackageInfo.scala b/core/src/main/scala/com/typesafe/tools/mima/core/PackageInfo.scala index e55c444c..18e22b3c 100644 --- a/core/src/main/scala/com/typesafe/tools/mima/core/PackageInfo.scala +++ b/core/src/main/scala/com/typesafe/tools/mima/core/PackageInfo.scala @@ -1,5 +1,6 @@ package com.typesafe.tools.mima.core +import scala.annotation.tailrec import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.util.ClassPath import collection.mutable @@ -65,25 +66,23 @@ abstract class PackageInfo(val owner: PackageInfo) { lazy val accessibleClasses: Set[ClassInfo] = { /** Fixed point iteration for finding all accessible classes. */ - def accessibleClassesUnder(prefix: Set[ClassInfo]): Set[ClassInfo] = { - val vclasses = (classes.valuesIterator filter (isAccessible(_, prefix))).toSet - if (vclasses.isEmpty) vclasses - else vclasses union accessibleClassesUnder(vclasses) + @tailrec + def accessibleClassesUnder(prefix: Set[ClassInfo], found: Set[ClassInfo]): Set[ClassInfo] = { + val vclasses = (classes.valuesIterator.filter(isAccessible(_, prefix))).toSet + if (vclasses.isEmpty) found + else accessibleClassesUnder(vclasses, vclasses union found) } def isAccessible(clazz: ClassInfo, prefix: Set[ClassInfo]) = { def isReachable = { if (clazz.isSynthetic) false - else { - val idx = clazz.decodedName.lastIndexOf("$") - if (idx < 0) prefix.isEmpty // class name contains no $ - else prefix exists (_.decodedName == clazz.decodedName.substring(0, idx)) // prefix before dollar is an accessible class detected previously - } + else if (prefix.isEmpty) clazz.isTopLevel && !clazz.bytecodeName.contains("$$") + else prefix.exists(_.innerClasses contains clazz.bytecodeName) } clazz.isPublic && isReachable } - accessibleClassesUnder(Set.empty) + accessibleClassesUnder(Set.empty, Set.empty) } /** All implementation classes of traits (classes that end in "$" followed by "class"). */ diff --git a/reporter/functional-tests/src/test/class-removing-inner-object-nok/problems.txt b/reporter/functional-tests/src/test/class-removing-inner-object-nok/problems.txt new file mode 100644 index 00000000..57dea9bc --- /dev/null +++ b/reporter/functional-tests/src/test/class-removing-inner-object-nok/problems.txt @@ -0,0 +1,2 @@ +object A#B does not have a correspondent in new version +method B()A#B# in class A does not have a correspondent in new version \ No newline at end of file diff --git a/reporter/functional-tests/src/test/class-removing-inner-object-nok/v1/A.scala b/reporter/functional-tests/src/test/class-removing-inner-object-nok/v1/A.scala new file mode 100644 index 00000000..6e207331 --- /dev/null +++ b/reporter/functional-tests/src/test/class-removing-inner-object-nok/v1/A.scala @@ -0,0 +1,5 @@ +class A { + object B { + def foo: Int = 2 + } +} diff --git a/reporter/functional-tests/src/test/class-removing-inner-object-nok/v2/A.scala b/reporter/functional-tests/src/test/class-removing-inner-object-nok/v2/A.scala new file mode 100644 index 00000000..2e2439c3 --- /dev/null +++ b/reporter/functional-tests/src/test/class-removing-inner-object-nok/v2/A.scala @@ -0,0 +1,2 @@ +class A { +} diff --git a/reporter/functional-tests/src/test/object-removing-inner-object-nok/problems.txt b/reporter/functional-tests/src/test/object-removing-inner-object-nok/problems.txt new file mode 100644 index 00000000..f32b7751 --- /dev/null +++ b/reporter/functional-tests/src/test/object-removing-inner-object-nok/problems.txt @@ -0,0 +1,2 @@ +object A#B does not have a correspondent in new version +object A#B#C does not have a correspondent in new version diff --git a/reporter/functional-tests/src/test/object-removing-inner-object-nok/v1/A.scala b/reporter/functional-tests/src/test/object-removing-inner-object-nok/v1/A.scala new file mode 100644 index 00000000..1d74855d --- /dev/null +++ b/reporter/functional-tests/src/test/object-removing-inner-object-nok/v1/A.scala @@ -0,0 +1,6 @@ +object A { + object B { + def foo: Int = 2 + object C + } +} diff --git a/reporter/functional-tests/src/test/object-removing-inner-object-nok/v2/A.scala b/reporter/functional-tests/src/test/object-removing-inner-object-nok/v2/A.scala new file mode 100644 index 00000000..169ef293 --- /dev/null +++ b/reporter/functional-tests/src/test/object-removing-inner-object-nok/v2/A.scala @@ -0,0 +1,2 @@ +object A { +}