Skip to content

Commit 49b6274

Browse files
committed
Fix #4192: Properly set InnerClasses end EnclosingMethod in class files
1 parent 4ed026c commit 49b6274

File tree

6 files changed

+446
-4
lines changed

6 files changed

+446
-4
lines changed

compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ final class BCodeAsmCommon[I <: DottyBackendInterface](val interface: I) {
2626
// always top-level. However, SI-8900 shows an example where the weak name-based implementation
2727
// of isDelambdafyFunction failed (for a function declared in a package named "lambda").
2828
classSym.isAnonymousClass || {
29-
val originalOwnerLexicallyEnclosingClass = classSym.originalOwner.originalLexicallyEnclosingClass
30-
originalOwnerLexicallyEnclosingClass != NoSymbol && !originalOwnerLexicallyEnclosingClass.isClass
29+
val originalOwner = classSym.originalOwner
30+
originalOwner != NoSymbol && !originalOwner.isClass
3131
}
3232
}
3333

@@ -59,9 +59,9 @@ final class BCodeAsmCommon[I <: DottyBackendInterface](val interface: I) {
5959
def enclosingMethod(sym: Symbol): Option[Symbol] = {
6060
if (sym.isClass || sym == NoSymbol) None
6161
else if (sym.is(Method)) Some(sym)
62-
else enclosingMethod(sym.originalOwner.originalLexicallyEnclosingClass)
62+
else enclosingMethod(sym.originalOwner)
6363
}
64-
enclosingMethod(classSym.originalOwner.originalLexicallyEnclosingClass)
64+
enclosingMethod(classSym.originalOwner)
6565
}
6666

6767
/**

compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,11 @@ trait BCodeSkelBuilder extends BCodeHelpers {
221221
addClassFields()
222222

223223
innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.memberClasses
224+
225+
val companion = claszSymbol.companionClass
226+
if companion.isTopLevelModuleClass then
227+
innerClassBufferASM ++= classBTypeFromSymbol(companion).info.memberClasses
228+
224229
gen(cd.rhs)
225230
addInnerClassesASM(cnode, innerClassBufferASM.toList)
226231

compiler/test/dotc/run-test-pickling.blacklist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
derive-generic.scala
22
eff-dependent.scala
33
enum-java
4+
i4192
45
i5257.scala
56
i7212
67
i7868.scala

tests/run/i4192/Checks.scala

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package checks
2+
3+
import reflect.ClassTag
4+
5+
/* This test checks whether InnerClasses and EnclosingMethod sections in generated class files are correct
6+
* for different possibilities of nesting of classes in other classes, objects and methods (the attributes are accessed via java reflection).
7+
* Names of nested definitions are derived from the name of their enclosing definition by appending a letter following the scheme below:
8+
* A - a class without a companion object
9+
* B - an object without a companion class
10+
* C - a class with its companion object
11+
* D - a method
12+
* Additionally a number may be added to avoid clashes between definitions from classes and their companion objects
13+
* (1 - defined in the companion class; 2 - defined in the companion object),
14+
* e.g. ACD2 - a method inside the companion object of a class inside a top level class
15+
*/
16+
trait Checks:
17+
val expectedTopLevelChecksCount: Int
18+
val expectedMemberChecksCount: Int
19+
val expectedLocalChecksCount: Int
20+
21+
var topLevelChecksCount = 0
22+
var memberChecksCount = 0
23+
var localChecksCount = 0
24+
25+
def verifyChecksCounts() =
26+
assert(topLevelChecksCount == expectedTopLevelChecksCount,
27+
s"top level checks: expected $expectedTopLevelChecksCount but was $topLevelChecksCount")
28+
assert(memberChecksCount == expectedMemberChecksCount,
29+
s"member checks: expected $expectedMemberChecksCount but was $memberChecksCount")
30+
assert(localChecksCount == expectedLocalChecksCount,
31+
s"local checks: expected $expectedLocalChecksCount but was $localChecksCount")
32+
33+
/** Check JVM class properties of a top level class */
34+
def checkTopLevel[ThisClass](using thisTag: ClassTag[ThisClass]) =
35+
val cls = thisTag.runtimeClass
36+
assert(cls.getEnclosingClass == null, s"Top level class $cls should have no enclosing class")
37+
assert(cls.getDeclaringClass == null, s"Top level class $cls should have no declaring class")
38+
assert(cls.getEnclosingMethod == null, s"Top level class $cls should have no enclosing method")
39+
topLevelChecksCount += 1
40+
41+
/** Check JVM class properties of a member class (defined directly inside another class) */
42+
def checkMember[ThisClass, EnclosingClass](using thisTag: ClassTag[ThisClass], enclosingTag: ClassTag[EnclosingClass]) =
43+
val cls = thisTag.runtimeClass
44+
def className = cls.simpleName
45+
def enclosingClassName = cls.getEnclosingClass.simpleName
46+
def declaringClassName = cls.getDeclaringClass.simpleName
47+
val expectedEnclosingClassName = enclosingTag.runtimeClass.simpleName match
48+
case "B$" => "B" // classes defined directly in top level objects should be moved to their companion/mirror classes
49+
case "C$" => "C"
50+
case name => name
51+
assert(cls.getEnclosingClass != null,
52+
s"Member class $className should have an enclosing class")
53+
assert(enclosingClassName == expectedEnclosingClassName,
54+
s"The enclosing class of class $className should be $expectedEnclosingClassName but was $enclosingClassName")
55+
assert(cls.getDeclaringClass == cls.getEnclosingClass,
56+
s"The declaring class of class $className should be the same as its enclosing class but was $declaringClassName")
57+
assert(cls.getEnclosingMethod == null,
58+
s"Member class $className should have no enclosing method")
59+
memberChecksCount += 1
60+
61+
/** Check JVM class properties of a local class (defined directly inside a method) */
62+
def checkLocal[ThisClass, EnclosingClass](using thisTag: ClassTag[ThisClass], enclosingTag: ClassTag[EnclosingClass]) =
63+
val cls = thisTag.runtimeClass
64+
def className = cls.simpleName
65+
def enclosingClassName = cls.getEnclosingClass.simpleName
66+
def meth = cls.getEnclosingMethod
67+
val expectedEnclosingClassName = enclosingTag.runtimeClass.simpleName
68+
// extracting method name basing on the described naming convention
69+
// $1 gets added during lambdaLift in case of a method defined inside another method
70+
val expectedEnclosingMethodName =
71+
val prefix = className.init
72+
val suffix = if prefix.filter(_.isLetter).endsWith("DD") then "$1" else ""
73+
prefix ++ suffix
74+
assert(cls.getEnclosingClass != null,
75+
s"Local class $className should have an enclosing class")
76+
assert(enclosingClassName == expectedEnclosingClassName,
77+
s"The enclosing class of class $className should be $expectedEnclosingClassName but was $enclosingClassName")
78+
assert(cls.getDeclaringClass == null,
79+
s"Local class $className should have no declaring class")
80+
assert(meth != null,
81+
s"Local class $className should have an enclosing method")
82+
assert(meth.getName == expectedEnclosingMethodName,
83+
s"The enclosing method of class $className should be $expectedEnclosingMethodName but was ${meth.getName}")
84+
localChecksCount += 1
85+
86+
extension (cls: Class[?])
87+
// java 8 implementation of cls.getSimpleName() is buggy - https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8057919
88+
def simpleName = cls.getName
89+
.stripSuffix("$1").stripSuffix("$2")
90+
.split("\\.").last
91+
.split("\\$").last

tests/run/i4192/Test.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object Test {
2+
def main(args: Array[String]): Unit = {
3+
foo.bar.Checks.run()
4+
}
5+
}

0 commit comments

Comments
 (0)