diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala b/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala index a7c0a71155ab..5fe63a8cfa61 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala @@ -26,8 +26,8 @@ final class BCodeAsmCommon[I <: DottyBackendInterface](val interface: I) { // 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 || { - val originalOwnerLexicallyEnclosingClass = classSym.originalOwner.originalLexicallyEnclosingClass - originalOwnerLexicallyEnclosingClass != NoSymbol && !originalOwnerLexicallyEnclosingClass.isClass + val originalOwner = classSym.originalOwner + originalOwner != NoSymbol && !originalOwner.isClass } } @@ -59,9 +59,9 @@ final class BCodeAsmCommon[I <: DottyBackendInterface](val interface: I) { def enclosingMethod(sym: Symbol): Option[Symbol] = { if (sym.isClass || sym == NoSymbol) None else if (sym.is(Method)) Some(sym) - else enclosingMethod(sym.originalOwner.originalLexicallyEnclosingClass) + else enclosingMethod(sym.originalOwner) } - enclosingMethod(classSym.originalOwner.originalLexicallyEnclosingClass) + enclosingMethod(classSym.originalOwner) } /** diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 253dc495d9c0..43a926001456 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -221,6 +221,11 @@ trait BCodeSkelBuilder extends BCodeHelpers { addClassFields() innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.memberClasses + + val companion = claszSymbol.companionClass + if companion.isTopLevelModuleClass then + innerClassBufferASM ++= classBTypeFromSymbol(companion).info.memberClasses + gen(cd.rhs) addInnerClassesASM(cnode, innerClassBufferASM.toList) diff --git a/tests/run/i4192/Checks.scala b/tests/run/i4192/Checks.scala new file mode 100644 index 000000000000..cb910d81d9ec --- /dev/null +++ b/tests/run/i4192/Checks.scala @@ -0,0 +1,84 @@ +package checks + +import reflect.ClassTag + + +trait Checks: + val expectedTopLevelChecksCount: Int + val expectedMemberChecksCount: Int + val expectedLocalChecksCount: Int + + var topLevelChecksCount = 0 + var memberChecksCount = 0 + var localChecksCount = 0 + + def verifyChecksCounts() = + assert(topLevelChecksCount == expectedTopLevelChecksCount, + s"top level checks: expected $expectedTopLevelChecksCount but was $topLevelChecksCount") + assert(memberChecksCount == expectedMemberChecksCount, + s"member checks: expected $expectedMemberChecksCount but was $memberChecksCount") + assert(localChecksCount == expectedLocalChecksCount, + s"local checks: expected $expectedLocalChecksCount but was $localChecksCount") + + // The methods below rely on the naming convention described in TestCases.scala + + /** Check JVM class properties of a top level class */ + def checkTopLevel(self: AnyRef) = + val cls = self.getClass + assert(cls.getEnclosingClass == null, s"Top level class $cls should have no enclosing class") + assert(cls.getDeclaringClass == null, s"Top level class $cls should have no declaring class") + assert(cls.getEnclosingMethod == null, s"Top level class $cls should have no enclosing method") + topLevelChecksCount += 1 + + /** Check JVM class properties of a member class (defined directly inside another class) */ + def checkMember(self: AnyRef, outer: AnyRef) = + val cls = self.getClass + def className = cls.simpleName + def enclosingClassName = cls.getEnclosingClass.simpleName + def declaringClassName = cls.getDeclaringClass.simpleName + // Classes defined directly in top level objects should be moved to their companion/mirror classes + val expectedEnclosingClassName = outer.getClass.simpleName match + case "B$" => "B" + case "C$" => "C" + case name => name + assert(cls.getEnclosingClass != null, + s"Member class $className should have an enclosing class") + assert(enclosingClassName == expectedEnclosingClassName, + s"The enclosing class of class $className should be $expectedEnclosingClassName but was $enclosingClassName") + assert(cls.getDeclaringClass == cls.getEnclosingClass, + s"The declaring class of class $className should be the same as its enclosing class but was $declaringClassName") + assert(cls.getEnclosingMethod == null, + s"Member class $className should have no enclosing method") + memberChecksCount += 1 + + /** Check JVM class properties of a local class (defined directly inside a method) */ + def checkLocal(self: AnyRef, outer: AnyRef) = + val cls = self.getClass + def className = cls.simpleName + def enclosingClassName = cls.getEnclosingClass.simpleName + def method = cls.getEnclosingMethod + val expectedEnclosingClassName = outer.getClass.simpleName + // extracting method name basing on the described naming convention + // $1 gets added during lambdaLift in case of a method defined inside another method + val expectedEnclosingMethodName = + val prefix = className.init + val suffix = if prefix.filter(_.isLetter).endsWith("DD") then "$1" else "" + prefix ++ suffix + assert(cls.getEnclosingClass != null, + s"Local class $className should have an enclosing class") + assert(enclosingClassName == expectedEnclosingClassName, + s"The enclosing class of class $className should be $expectedEnclosingClassName but was $enclosingClassName") + assert(cls.getDeclaringClass == null, + s"Local class $className should have no declaring class") + assert(method != null, + s"Local class $className should have an enclosing method") + assert(method.getName == expectedEnclosingMethodName, + s"The enclosing method of class $className should be $expectedEnclosingMethodName but was ${method.getName}") + localChecksCount += 1 + + extension (cls: Class[?]) + // java 8 implementation of cls.getSimpleName() is buggy - https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8057919 + def simpleName = cls.getName + .stripSuffix("$1").stripSuffix("$2") + .split("\\.").last + .split("\\$").last diff --git a/tests/run/i4192/Test.scala b/tests/run/i4192/Test.scala new file mode 100644 index 000000000000..1e2cb0b83700 --- /dev/null +++ b/tests/run/i4192/Test.scala @@ -0,0 +1,5 @@ +object Test { + def main(args: Array[String]): Unit = { + foo.bar.Checks.run() + } +} diff --git a/tests/run/i4192/TestCases.scala b/tests/run/i4192/TestCases.scala new file mode 100644 index 000000000000..33b4e458ebe7 --- /dev/null +++ b/tests/run/i4192/TestCases.scala @@ -0,0 +1,360 @@ +package foo.bar + +/* This test checks whether InnerClasses and EnclosingMethod sections in generated class files are correct + * for different possibilities of nesting of classes in other classes, objects and methods (the attributes are accessed via java reflection). + * The checked cases are: + * 1) a top level class/object + * 2) a class/object in a top level class/object + * 3) a class in another class/object in a top level class/object + * 4) a class in a method in another class/object in top level class/object + * 5) a class/object in a method in a top level class/object + * 6) a class in a method in another method in a top level class/object + * 7) a class in another class in a method in a top level class/object + * Objects with and without a companion class need to be treated as separate cases + * to ensure this works uniformly whether a mirror class is generated or not. + * For completeness classes without a companion object were added as well. + * + * Names of nested definitions are derived from the name of their enclosing definition by appending a letter following the scheme below: + * A - a class without a companion object + * B - an object without a companion class + * C - a class with its companion object + * D - a method + * Additionally a number may be added to avoid clashes between definitions from classes and their companion objects + * (1 - defined in the companion class; 2 - defined in the companion object), + * e.g. ACD2 - a method inside the companion object of a class inside a top level class + * + * Self-standing references to objects and constructor calls serve the purpose + * of enforcing initialization and running the checks. + */ + +object Checks extends checks.Checks { + val expectedTopLevelChecksCount = 4 + val expectedMemberChecksCount = 4 * 8 + val expectedLocalChecksCount = 4 * 6 + + def run() = + new A + B + new C + C + + verifyChecksCounts() +} + +import Checks._ + + +class A { topLevel => + checkTopLevel(this) + + class AA { nestedOnce => + checkMember(this, topLevel) + + class AAA { checkMember(this, nestedOnce) } + new AAA + + def AAD(): Unit = { + class AADA { checkLocal(this, nestedOnce) } + new AADA + } + AAD() + } + new AA + + object AB { + val nestedOnce = this // self alias cannot be used uniformly here: https://github.com/lampepfl/dotty/issues/11648 + + checkMember(this, topLevel) + + class ABA { checkMember(this, nestedOnce) } + new ABA + + def ABD(): Unit = { + class ABDA { checkLocal(this, nestedOnce) } + new ABDA + } + ABD() + } + AB + + class AC { nestedOnce => + checkMember(this, topLevel) + + class ACA1 { checkMember(this, nestedOnce) } + new ACA1 + + def ACD(): Unit = { + class ACDA { checkLocal(this, nestedOnce) } + new ACDA + } + ACD() + } + new AC + + object AC { + val nestedOnce = this + + checkMember(this, topLevel) + + class ACA2 { checkMember(this, nestedOnce) } + new ACA2 + + def ACD(): Unit = { + class ACDA { checkLocal(this, nestedOnce) } + new ACDA + } + ACD() + } + AC + + def AD(): Unit = { + class ADA { nestedTwice => + checkLocal(this, topLevel) + + class ADAA { checkMember(this, nestedTwice) } + } + new ADA + + def ADD() = { + class ADDA { checkLocal(this, topLevel) } + new ADDA + } + ADD() + } + AD() +} + + +object B { topLevel => + checkTopLevel(this) + + class BA { nestedOnce => + checkMember(this, topLevel) + + class BAA { checkMember(this, nestedOnce) } + new BAA + + def BAD(): Unit = { + class BADA { checkLocal(this, nestedOnce) } + new BADA + } + BAD() + } + new BA + + object BB { nestedOnce => + checkMember(this, topLevel) + + class BBA { checkMember(this, nestedOnce) } + new BBA + + def BBD(): Unit = { + class BBDA { checkLocal(this, nestedOnce) } + new BBDA + } + BBD() + } + BB + + class BC { nestedOnce => + checkMember(this, topLevel) + + class BCA1 { checkMember(this, nestedOnce) } + new BCA1 + + def BCD(): Unit = { + class BCDA { checkLocal(this, nestedOnce) } + new BCDA + } + BCD() + } + new BC + + object BC { nestedOnce => + checkMember(this, topLevel) + + class BCA2 { checkMember(this, nestedOnce) } + new BCA2 + + def BCD(): Unit = { + class BCDA { checkLocal(this, nestedOnce) } + new BCDA + } + BCD() + } + BC + + def BD(): Unit = { + class BDA { nestedTwice => + checkLocal(this, topLevel) + + class BDAA { checkMember(this, nestedTwice) } + } + new BDA + + def BDD() = { + class BDDA { checkLocal(this, topLevel) } + new BDDA + } + BDD() + } + BD() +} + + +class C { topLevel => + checkTopLevel(this) + + class CA1 { nestedOnce => + checkMember(this, topLevel) + + class CA1A { checkMember(this, nestedOnce) } + new CA1A + + def CA1D(): Unit = { + class CA1DA { checkLocal(this, nestedOnce) } + new CA1DA + } + CA1D() + } + new CA1 + + object CB1 { + val nestedOnce = this + + checkMember(this, topLevel) + + class CB1A { checkMember(this, nestedOnce) } + new CB1A + + def CB1D(): Unit = { + class CB1DA { checkLocal(this, nestedOnce) } + new CB1DA + } + CB1D() + } + CB1 + + class CC1 { nestedOnce => + checkMember(this, topLevel) + + class CC1A1 { checkMember(this, nestedOnce) } + new CC1A1 + + def CC1D(): Unit = { + class CC1DA { checkLocal(this, nestedOnce) } + new CC1DA + } + CC1D() + } + new CC1 + + object CC1 { + val nestedOnce = this + + checkMember(this, topLevel) + + class CC1A2 { checkMember(this, nestedOnce) } + new CC1A2 + + def CC1D(): Unit = { + class CC1DA { checkLocal(this, nestedOnce) } + new CC1DA + } + CC1D() + } + CC1 + + def CD1(): Unit = { + class CD1A { nestedTwice => + checkLocal(this, topLevel) + + class CD1AA { checkMember(this, nestedTwice) } + } + new CD1A + + def CD1D() = { + class CD1DA { checkLocal(this, topLevel) } + new CD1DA + } + CD1D() + } + CD1() +} + + +object C { topLevel => + checkTopLevel(this) + + class CA2 { nestedOnce => + checkMember(this, topLevel) + + class CA2A { checkMember(this, nestedOnce) } + new CA2A + + def CA2D(): Unit = { + class CA2DA { checkLocal(this, nestedOnce) } + new CA2DA + } + CA2D() + } + new CA2 + + object CB2 { nestedOnce => + checkMember(this, topLevel) + + class CB2A { checkMember(this, nestedOnce) } + new CB2A + + def CB2D(): Unit = { + class CB2DA { checkLocal(this, nestedOnce) } + new CB2DA + } + CB2D() + } + CB2 + + class CC2 { nestedOnce => + checkMember(this, topLevel) + + class CC2A1 { checkMember(this, nestedOnce) } + new CC2A1 + + def CC2D(): Unit = { + class CC2DA { checkLocal(this, nestedOnce) } + new CC2DA + } + CC2D() + } + new CC2 + + object CC2 { nestedOnce => + checkMember(this, topLevel) + + class CC2A2 { checkMember(this, nestedOnce) } + new CC2A2 + + def CC2D(): Unit = { + class CC2DA { checkLocal(this, nestedOnce) } + new CC2DA + } + CC2D() + } + CC2 + + def CD2(): Unit = { + class CD2A { nestedTwice => + checkLocal(this, topLevel) + + class CD2AA { checkMember(this, nestedTwice) } + } + new CD2A + + def CD2D() = { + class CD2DA { checkLocal(this, topLevel) } + new CD2DA + } + CD2D() + } + CD2() +}