Skip to content

Commit cfe6538

Browse files
committed
Generate enum values with bodies properly (KT-15803)
1 parent beec138 commit cfe6538

File tree

13 files changed

+292
-58
lines changed

13 files changed

+292
-58
lines changed

plugins/kapt3/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -155,22 +155,15 @@ class ClassFileToSourceStubConverter(
155155
val isNested = (descriptor as? ClassDescriptor)?.isNested ?: false
156156
val isInner = isNested && (descriptor as? ClassDescriptor)?.isInner ?: false
157157

158-
val flags = when {
159-
(descriptor.containingDeclaration as? ClassDescriptor)?.kind == ClassKind.INTERFACE -> {
160-
// Classes inside interfaces should always be public and static.
161-
// See com.sun.tools.javac.comp.Enter.visitClassDef for more information.
162-
(clazz.access or Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC) and
163-
Opcodes.ACC_PRIVATE.inv() and Opcodes.ACC_PROTECTED.inv() // Remove private and protected modifiers
164-
}
165-
!isInner && isNested -> clazz.access or Opcodes.ACC_STATIC
166-
else -> clazz.access
167-
}
168-
169-
val modifiers = convertModifiers(flags, ElementKind.CLASS, packageFqName, clazz.visibleAnnotations, clazz.invisibleAnnotations)
158+
val flags = getClassAccessFlags(clazz, descriptor, isInner, isNested)
170159

171160
val isEnum = clazz.isEnum()
172161
val isAnnotation = clazz.isAnnotation()
173162

163+
val modifiers = convertModifiers(flags,
164+
if (isEnum) ElementKind.ENUM else ElementKind.CLASS,
165+
packageFqName, clazz.visibleAnnotations, clazz.invisibleAnnotations)
166+
174167
val isDefaultImpls = clazz.name.endsWith("${descriptor.name.asString()}/DefaultImpls")
175168
&& isPublic(clazz.access) && isFinal(clazz.access)
176169
&& descriptor is ClassDescriptor
@@ -181,14 +174,7 @@ class ClassFileToSourceStubConverter(
181174
return null
182175
}
183176

184-
val simpleName = when (descriptor) {
185-
is PackageFragmentDescriptor -> {
186-
val className = if (packageFqName.isEmpty()) clazz.name else clazz.name.drop(packageFqName.length + 1)
187-
if (className.isEmpty()) throw IllegalStateException("Invalid package facade class name: ${clazz.name}")
188-
className
189-
}
190-
else -> if (isDefaultImpls) "DefaultImpls" else descriptor.name.asString()
191-
}
177+
val simpleName = getClassName(clazz, descriptor, isDefaultImpls, packageFqName)
192178

193179
val interfaces = mapJList(clazz.interfaces) {
194180
if (isAnnotation && it == "java/lang/annotation/Annotation") return@mapJList null
@@ -200,7 +186,49 @@ class ClassFileToSourceStubConverter(
200186
val hasSuperClass = clazz.superName != "java/lang/Object" && !isEnum
201187
val genericType = signatureParser.parseClassSignature(clazz.signature, superClass, interfaces)
202188

203-
val fields = mapJList<FieldNode, JCTree>(clazz.fields) { convertField(it, packageFqName) }
189+
class EnumValueData(val field: FieldNode, val innerClass: InnerClassNode?, val correspondingClass: ClassNode?)
190+
191+
val enumValuesData = clazz.fields.filter { it.isEnumValue() }.map { field ->
192+
var foundInnerClass: InnerClassNode? = null
193+
var correspondingClass: ClassNode? = null
194+
195+
for (innerClass in clazz.innerClasses) {
196+
// Class should have the same name as enum value
197+
if (innerClass.innerName != field.name) continue
198+
val classNode = kaptContext.compiledClasses.firstOrNull { it.name == innerClass.name } ?: continue
199+
200+
// Super class name of the class should be our enum class
201+
if (classNode.superName != clazz.name) continue
202+
203+
correspondingClass = classNode
204+
foundInnerClass = innerClass
205+
break
206+
}
207+
208+
EnumValueData(field, foundInnerClass, correspondingClass)
209+
}
210+
211+
val enumValues: JavacList<JCTree> = mapJList(enumValuesData) { data ->
212+
val constructorArguments = Type.getArgumentTypes(clazz.methods.firstOrNull {
213+
it.name == "<init>" && Type.getArgumentsAndReturnSizes(it.desc).shr(2) >= 2
214+
}?.desc ?: "()Z")
215+
216+
val args = mapJList(constructorArguments.drop(2)) { convertLiteralExpression(getDefaultValue(it)) }
217+
218+
val def = data.correspondingClass?.let { convertClass(it, packageFqName, false) }
219+
220+
convertField(data.field, packageFqName, treeMaker.NewClass(
221+
/* enclosing = */ null,
222+
/* typeArgs = */ JavacList.nil(),
223+
/* clazz = */ treeMaker.Ident(treeMaker.name(data.field.name)),
224+
/* args = */ args,
225+
/* def = */ def))
226+
}
227+
228+
val fields = mapJList<FieldNode, JCTree>(clazz.fields) {
229+
if (it.isEnumValue()) null else convertField(it, packageFqName)
230+
}
231+
204232
val methods = mapJList<MethodNode, JCTree>(clazz.methods) {
205233
if (isEnum) {
206234
if (it.name == "values" && it.desc == "()[L${clazz.name};") return@mapJList null
@@ -209,7 +237,9 @@ class ClassFileToSourceStubConverter(
209237

210238
convertMethod(it, clazz, packageFqName)
211239
}
240+
212241
val nestedClasses = mapJList<InnerClassNode, JCTree>(clazz.innerClasses) { innerClass ->
242+
if (enumValuesData.any { it.innerClass == innerClass }) return@mapJList null
213243
if (innerClass.outerName != clazz.name) return@mapJList null
214244
val innerClassNode = kaptContext.compiledClasses.firstOrNull { it.name == innerClass.name } ?: return@mapJList null
215245
convertClass(innerClassNode, packageFqName, false)
@@ -221,10 +251,32 @@ class ClassFileToSourceStubConverter(
221251
genericType.typeParameters,
222252
if (hasSuperClass) genericType.superClass else null,
223253
genericType.interfaces,
224-
fields + methods + nestedClasses)
254+
enumValues + fields + methods + nestedClasses)
255+
}
256+
257+
private fun getClassAccessFlags(clazz: ClassNode, descriptor: DeclarationDescriptor, isInner: Boolean, isNested: Boolean) = when {
258+
(descriptor.containingDeclaration as? ClassDescriptor)?.kind == ClassKind.INTERFACE -> {
259+
// Classes inside interfaces should always be public and static.
260+
// See com.sun.tools.javac.comp.Enter.visitClassDef for more information.
261+
(clazz.access or Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC) and
262+
Opcodes.ACC_PRIVATE.inv() and Opcodes.ACC_PROTECTED.inv() // Remove private and protected modifiers
263+
}
264+
!isInner && isNested -> clazz.access or Opcodes.ACC_STATIC
265+
else -> clazz.access
266+
}
267+
268+
private fun getClassName(clazz: ClassNode, descriptor: DeclarationDescriptor, isDefaultImpls: Boolean, packageFqName: String): String {
269+
return when (descriptor) {
270+
is PackageFragmentDescriptor -> {
271+
val className = if (packageFqName.isEmpty()) clazz.name else clazz.name.drop(packageFqName.length + 1)
272+
if (className.isEmpty()) throw IllegalStateException("Invalid package facade class name: ${clazz.name}")
273+
className
274+
}
275+
else -> if (isDefaultImpls) "DefaultImpls" else descriptor.name.asString()
276+
}
225277
}
226278

227-
private fun convertField(field: FieldNode, packageFqName: String): JCVariableDecl? {
279+
private fun convertField(field: FieldNode, packageFqName: String, explicitInitializer: JCExpression? = null): JCVariableDecl? {
228280
if (isSynthetic(field.access)) return null
229281
val descriptor = kaptContext.origins[field]?.descriptor
230282

@@ -244,7 +296,8 @@ class ClassFileToSourceStubConverter(
244296

245297
val value = field.value
246298

247-
val initializer = convertValueOfPrimitiveTypeOrString(value)
299+
val initializer = explicitInitializer
300+
?: convertValueOfPrimitiveTypeOrString(value)
248301
?: if (isFinal(field.access)) convertLiteralExpression(getDefaultValue(type)) else null
249302

250303
return treeMaker.VarDef(modifiers, name, typeExpression, initializer)
@@ -460,6 +513,7 @@ class ClassFileToSourceStubConverter(
460513
} ?: annotations
461514

462515
val flags = when (kind) {
516+
ElementKind.ENUM -> access and CLASS_MODIFIERS and Opcodes.ACC_ABSTRACT.inv().toLong()
463517
ElementKind.CLASS -> access and CLASS_MODIFIERS
464518
ElementKind.METHOD -> access and METHOD_MODIFIERS
465519
ElementKind.FIELD -> access and FIELD_MODIFIERS

plugins/kapt3/src/org/jetbrains/kotlin/kapt3/util/asmUtils.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import org.jetbrains.org.objectweb.asm.Opcodes
2020
import org.jetbrains.org.objectweb.asm.Type
2121
import org.jetbrains.org.objectweb.asm.tree.AnnotationNode
2222
import org.jetbrains.org.objectweb.asm.tree.ClassNode
23+
import org.jetbrains.org.objectweb.asm.tree.FieldNode
2324
import org.jetbrains.org.objectweb.asm.tree.MethodNode
2425

2526
internal fun isEnum(access: Int) = (access and Opcodes.ACC_ENUM) != 0
@@ -33,6 +34,8 @@ internal fun ClassNode.isEnum() = (access and Opcodes.ACC_ENUM) != 0
3334
internal fun ClassNode.isAnnotation() = (access and Opcodes.ACC_ANNOTATION) != 0
3435
internal fun MethodNode.isVarargs() = (access and Opcodes.ACC_VARARGS) != 0
3536

37+
internal fun FieldNode.isEnumValue() = (access and Opcodes.ACC_ENUM) != 0
38+
3639
internal fun <T> List<T>?.isNullOrEmpty() = this == null || this.isEmpty()
3740

3841
internal fun MethodNode.isJvmOverloadsGenerated(): Boolean {

plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@
3232
@TestDataPath("$PROJECT_ROOT")
3333
@RunWith(JUnit3RunnerWithInners.class)
3434
public class ClassFileToSourceStubConverterTestGenerated extends AbstractClassFileToSourceStubConverterTest {
35+
@TestMetadata("abstractEnum.kt")
36+
public void testAbstractEnum() throws Exception {
37+
String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/abstractEnum.kt");
38+
doTest(fileName);
39+
}
40+
3541
@TestMetadata("abstractMethods.kt")
3642
public void testAbstractMethods() throws Exception {
3743
String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/abstractMethods.kt");
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
enum class E {
2+
X {
3+
override fun a() {}
4+
},
5+
Y {
6+
override fun a() {}
7+
};
8+
9+
abstract fun a()
10+
11+
fun b() {}
12+
13+
object Obj
14+
class NestedClass
15+
}
16+
17+
enum class E2 {
18+
X("") {
19+
override fun a() {}
20+
},
21+
Y(5) {
22+
override fun a() {}
23+
};
24+
25+
constructor(n: Int) {}
26+
constructor(s: String) {}
27+
28+
abstract fun a()
29+
}
30+
31+
enum class E3(val a: String) {
32+
X(""), Y("")
33+
}
34+
35+
enum class E4(val a: String, val b: Int, val c: Long, val d: Boolean) {
36+
X("", 4, 2L, true)
37+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
public enum E {
2+
/*public static final*/ X /* = new X(){
3+
4+
@java.lang.Override()
5+
public void a() {
6+
}
7+
8+
X() {
9+
super();
10+
}
11+
} */,
12+
/*public static final*/ Y /* = new Y(){
13+
14+
@java.lang.Override()
15+
public void a() {
16+
}
17+
18+
Y() {
19+
super();
20+
}
21+
} */;
22+
23+
public abstract void a();
24+
25+
public final void b() {
26+
}
27+
28+
E() {
29+
}
30+
31+
public static final class Obj {
32+
public static final E.Obj INSTANCE = null;
33+
34+
private Obj() {
35+
super();
36+
}
37+
}
38+
39+
public static final class NestedClass {
40+
41+
public NestedClass() {
42+
super();
43+
}
44+
}
45+
}
46+
47+
////////////////////
48+
49+
50+
public enum E2 {
51+
/*public static final*/ X /* = new X(0){
52+
53+
@java.lang.Override()
54+
public void a() {
55+
}
56+
57+
X() {
58+
super(0);
59+
}
60+
} */,
61+
/*public static final*/ Y /* = new Y(0){
62+
63+
@java.lang.Override()
64+
public void a() {
65+
}
66+
67+
Y() {
68+
super(0);
69+
}
70+
} */;
71+
72+
public abstract void a();
73+
74+
E2(int n) {
75+
}
76+
77+
E2(java.lang.String s) {
78+
}
79+
}
80+
81+
////////////////////
82+
83+
84+
public enum E3 {
85+
/*public static final*/ X /* = new X(null) */,
86+
/*public static final*/ Y /* = new Y(null) */;
87+
private final java.lang.String a = null;
88+
89+
public final java.lang.String getA() {
90+
return null;
91+
}
92+
93+
E3(java.lang.String a) {
94+
}
95+
}
96+
97+
////////////////////
98+
99+
100+
public enum E4 {
101+
/*public static final*/ X /* = new X(null, 0, 0L, false) */;
102+
private final java.lang.String a = null;
103+
private final int b = 0;
104+
private final long c = 0L;
105+
private final boolean d = false;
106+
107+
public final java.lang.String getA() {
108+
return null;
109+
}
110+
111+
public final int getB() {
112+
return 0;
113+
}
114+
115+
public final long getC() {
116+
return 0L;
117+
}
118+
119+
public final boolean getD() {
120+
return false;
121+
}
122+
123+
E4(java.lang.String a, int b, long c, boolean d) {
124+
}
125+
}

plugins/kapt3/testData/converter/annotations.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ public abstract @interface Anno1 {
55

66

77
public enum Colors {
8-
/*public static final*/ WHITE /* = null */,
9-
/*public static final*/ BLACK /* = null */;
8+
/*public static final*/ WHITE /* = new WHITE() */,
9+
/*public static final*/ BLACK /* = new BLACK() */;
1010

1111
Colors() {
1212
}

plugins/kapt3/testData/converter/annotations2.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ package test;
5050

5151
@Anno(value = "enum")
5252
public enum Enum {
53-
/*public static final*/ WHITE /* = null */,
54-
/*public static final*/ BLACK /* = null */;
53+
/*public static final*/ WHITE /* = new WHITE(0) */,
54+
/*public static final*/ BLACK /* = new BLACK(0) */;
5555
private final int x = 0;
5656

5757
public final int getX() {

plugins/kapt3/testData/converter/enums.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
public enum Enum1 {
2-
/*public static final*/ BLACK /* = null */,
3-
/*public static final*/ WHITE /* = null */;
2+
/*public static final*/ BLACK /* = new BLACK() */,
3+
/*public static final*/ WHITE /* = new WHITE() */;
44

55
Enum1() {
66
}
@@ -18,8 +18,8 @@ public abstract @interface Anno1 {
1818

1919

2020
public enum Enum2 {
21-
/*public static final*/ RED /* = null */,
22-
/*public static final*/ WHITE /* = null */;
21+
/*public static final*/ RED /* = new RED(null, 0) */,
22+
/*public static final*/ WHITE /* = new WHITE(null, 0) */;
2323
private final java.lang.String col = null;
2424
private final int col2 = 0;
2525

0 commit comments

Comments
 (0)