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 b48ca233..c280415d 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 @@ -65,10 +65,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 +290,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/PackageInfo.scala b/core/src/main/scala/com/typesafe/tools/mima/core/PackageInfo.scala index e55c444c..a3530c19 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 @@ -64,8 +64,9 @@ abstract class PackageInfo(val owner: PackageInfo) { def classes: mutable.Map[String, ClassInfo] lazy val accessibleClasses: Set[ClassInfo] = { + // FIXME: Make this a tailrecursive implementation /** Fixed point iteration for finding all accessible classes. */ - def accessibleClassesUnder(prefix: Set[ClassInfo]): Set[ClassInfo] = { + def accessibleClassesUnder(prefix: Set[ClassInfo]): Set[ClassInfo] = { val vclasses = (classes.valuesIterator filter (isAccessible(_, prefix))).toSet if (vclasses.isEmpty) vclasses else vclasses union accessibleClassesUnder(vclasses) @@ -77,7 +78,28 @@ abstract class PackageInfo(val owner: PackageInfo) { 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 { + // A top-level module is compiled into a class plus a module class (for instance, + // for a top-level `object A`, scalac creates two classfiles `A.class` and `A$.class`.). + // While, for an inner module, scalac creates only one classfile, i.e., the module class + // classfile. + // + // `clazz` is an inner module if: + // 1) `clazz` is a module, and + // 2) `clazz` decoded name starts with one of the passed `prefix`. + def isInnerModule: Boolean = { + clazz.isModule && + prefix.exists { outer => + // this condition is to avoid looping indefinitely + clazz.decodedName != outer.decodedName && + // checks if `outer` is a prefix of `clazz` name + clazz.decodedName.startsWith(outer.decodedName) + } + } + // class, trait, and top-level module go through this condition + (prefix exists (_.decodedName == clazz.decodedName.substring(0, idx))) || // prefix before dollar is an accessible class detected previously + isInnerModule + } } } clazz.isPublic && isReachable 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..e4037ccc --- /dev/null +++ b/reporter/functional-tests/src/test/object-removing-inner-object-nok/problems.txt @@ -0,0 +1 @@ +object A#B does not have a correspondent in new version \ No newline at end of file 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..598070d7 --- /dev/null +++ b/reporter/functional-tests/src/test/object-removing-inner-object-nok/v1/A.scala @@ -0,0 +1,5 @@ +object A { + object B { + def foo: Int = 2 + } +} 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 { +}