diff --git a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt index bfe72055e89..46a5b0cafde 100644 --- a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt @@ -155,22 +155,15 @@ class ClassFileToSourceStubConverter( val isNested = (descriptor as? ClassDescriptor)?.isNested ?: false val isInner = isNested && (descriptor as? ClassDescriptor)?.isInner ?: false - val flags = when { - (descriptor.containingDeclaration as? ClassDescriptor)?.kind == ClassKind.INTERFACE -> { - // Classes inside interfaces should always be public and static. - // See com.sun.tools.javac.comp.Enter.visitClassDef for more information. - (clazz.access or Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC) and - Opcodes.ACC_PRIVATE.inv() and Opcodes.ACC_PROTECTED.inv() // Remove private and protected modifiers - } - !isInner && isNested -> clazz.access or Opcodes.ACC_STATIC - else -> clazz.access - } - - val modifiers = convertModifiers(flags, ElementKind.CLASS, packageFqName, clazz.visibleAnnotations, clazz.invisibleAnnotations) + val flags = getClassAccessFlags(clazz, descriptor, isInner, isNested) val isEnum = clazz.isEnum() val isAnnotation = clazz.isAnnotation() + val modifiers = convertModifiers(flags, + if (isEnum) ElementKind.ENUM else ElementKind.CLASS, + packageFqName, clazz.visibleAnnotations, clazz.invisibleAnnotations) + val isDefaultImpls = clazz.name.endsWith("${descriptor.name.asString()}/DefaultImpls") && isPublic(clazz.access) && isFinal(clazz.access) && descriptor is ClassDescriptor @@ -181,14 +174,7 @@ class ClassFileToSourceStubConverter( return null } - val simpleName = when (descriptor) { - is PackageFragmentDescriptor -> { - val className = if (packageFqName.isEmpty()) clazz.name else clazz.name.drop(packageFqName.length + 1) - if (className.isEmpty()) throw IllegalStateException("Invalid package facade class name: ${clazz.name}") - className - } - else -> if (isDefaultImpls) "DefaultImpls" else descriptor.name.asString() - } + val simpleName = getClassName(clazz, descriptor, isDefaultImpls, packageFqName) val interfaces = mapJList(clazz.interfaces) { if (isAnnotation && it == "java/lang/annotation/Annotation") return@mapJList null @@ -200,7 +186,49 @@ class ClassFileToSourceStubConverter( val hasSuperClass = clazz.superName != "java/lang/Object" && !isEnum val genericType = signatureParser.parseClassSignature(clazz.signature, superClass, interfaces) - val fields = mapJList(clazz.fields) { convertField(it, packageFqName) } + class EnumValueData(val field: FieldNode, val innerClass: InnerClassNode?, val correspondingClass: ClassNode?) + + val enumValuesData = clazz.fields.filter { it.isEnumValue() }.map { field -> + var foundInnerClass: InnerClassNode? = null + var correspondingClass: ClassNode? = null + + for (innerClass in clazz.innerClasses) { + // Class should have the same name as enum value + if (innerClass.innerName != field.name) continue + val classNode = kaptContext.compiledClasses.firstOrNull { it.name == innerClass.name } ?: continue + + // Super class name of the class should be our enum class + if (classNode.superName != clazz.name) continue + + correspondingClass = classNode + foundInnerClass = innerClass + break + } + + EnumValueData(field, foundInnerClass, correspondingClass) + } + + val enumValues: JavacList = mapJList(enumValuesData) { data -> + val constructorArguments = Type.getArgumentTypes(clazz.methods.firstOrNull { + it.name == "" && Type.getArgumentsAndReturnSizes(it.desc).shr(2) >= 2 + }?.desc ?: "()Z") + + val args = mapJList(constructorArguments.drop(2)) { convertLiteralExpression(getDefaultValue(it)) } + + val def = data.correspondingClass?.let { convertClass(it, packageFqName, false) } + + convertField(data.field, packageFqName, treeMaker.NewClass( + /* enclosing = */ null, + /* typeArgs = */ JavacList.nil(), + /* clazz = */ treeMaker.Ident(treeMaker.name(data.field.name)), + /* args = */ args, + /* def = */ def)) + } + + val fields = mapJList(clazz.fields) { + if (it.isEnumValue()) null else convertField(it, packageFqName) + } + val methods = mapJList(clazz.methods) { if (isEnum) { if (it.name == "values" && it.desc == "()[L${clazz.name};") return@mapJList null @@ -209,7 +237,9 @@ class ClassFileToSourceStubConverter( convertMethod(it, clazz, packageFqName) } + val nestedClasses = mapJList(clazz.innerClasses) { innerClass -> + if (enumValuesData.any { it.innerClass == innerClass }) return@mapJList null if (innerClass.outerName != clazz.name) return@mapJList null val innerClassNode = kaptContext.compiledClasses.firstOrNull { it.name == innerClass.name } ?: return@mapJList null convertClass(innerClassNode, packageFqName, false) @@ -221,10 +251,32 @@ class ClassFileToSourceStubConverter( genericType.typeParameters, if (hasSuperClass) genericType.superClass else null, genericType.interfaces, - fields + methods + nestedClasses) + enumValues + fields + methods + nestedClasses) } - private fun convertField(field: FieldNode, packageFqName: String): JCVariableDecl? { + private fun getClassAccessFlags(clazz: ClassNode, descriptor: DeclarationDescriptor, isInner: Boolean, isNested: Boolean) = when { + (descriptor.containingDeclaration as? ClassDescriptor)?.kind == ClassKind.INTERFACE -> { + // Classes inside interfaces should always be public and static. + // See com.sun.tools.javac.comp.Enter.visitClassDef for more information. + (clazz.access or Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC) and + Opcodes.ACC_PRIVATE.inv() and Opcodes.ACC_PROTECTED.inv() // Remove private and protected modifiers + } + !isInner && isNested -> clazz.access or Opcodes.ACC_STATIC + else -> clazz.access + } + + private fun getClassName(clazz: ClassNode, descriptor: DeclarationDescriptor, isDefaultImpls: Boolean, packageFqName: String): String { + return when (descriptor) { + is PackageFragmentDescriptor -> { + val className = if (packageFqName.isEmpty()) clazz.name else clazz.name.drop(packageFqName.length + 1) + if (className.isEmpty()) throw IllegalStateException("Invalid package facade class name: ${clazz.name}") + className + } + else -> if (isDefaultImpls) "DefaultImpls" else descriptor.name.asString() + } + } + + private fun convertField(field: FieldNode, packageFqName: String, explicitInitializer: JCExpression? = null): JCVariableDecl? { if (isSynthetic(field.access)) return null val descriptor = kaptContext.origins[field]?.descriptor @@ -244,7 +296,8 @@ class ClassFileToSourceStubConverter( val value = field.value - val initializer = convertValueOfPrimitiveTypeOrString(value) + val initializer = explicitInitializer + ?: convertValueOfPrimitiveTypeOrString(value) ?: if (isFinal(field.access)) convertLiteralExpression(getDefaultValue(type)) else null return treeMaker.VarDef(modifiers, name, typeExpression, initializer) @@ -460,6 +513,7 @@ class ClassFileToSourceStubConverter( } ?: annotations val flags = when (kind) { + ElementKind.ENUM -> access and CLASS_MODIFIERS and Opcodes.ACC_ABSTRACT.inv().toLong() ElementKind.CLASS -> access and CLASS_MODIFIERS ElementKind.METHOD -> access and METHOD_MODIFIERS ElementKind.FIELD -> access and FIELD_MODIFIERS diff --git a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/util/asmUtils.kt b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/util/asmUtils.kt index 71298820e19..1dc8d9bf5d6 100644 --- a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/util/asmUtils.kt +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/util/asmUtils.kt @@ -20,6 +20,7 @@ import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.tree.AnnotationNode import org.jetbrains.org.objectweb.asm.tree.ClassNode +import org.jetbrains.org.objectweb.asm.tree.FieldNode import org.jetbrains.org.objectweb.asm.tree.MethodNode 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 internal fun ClassNode.isAnnotation() = (access and Opcodes.ACC_ANNOTATION) != 0 internal fun MethodNode.isVarargs() = (access and Opcodes.ACC_VARARGS) != 0 +internal fun FieldNode.isEnumValue() = (access and Opcodes.ACC_ENUM) != 0 + internal fun List?.isNullOrEmpty() = this == null || this.isEmpty() internal fun MethodNode.isJvmOverloadsGenerated(): Boolean { diff --git a/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java b/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java index 5093ec4d662..8ef1a04f60d 100644 --- a/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java +++ b/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java @@ -32,6 +32,12 @@ import java.util.regex.Pattern; @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) public class ClassFileToSourceStubConverterTestGenerated extends AbstractClassFileToSourceStubConverterTest { + @TestMetadata("abstractEnum.kt") + public void testAbstractEnum() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/abstractEnum.kt"); + doTest(fileName); + } + @TestMetadata("abstractMethods.kt") public void testAbstractMethods() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/abstractMethods.kt"); diff --git a/plugins/kapt3/testData/converter/abstractEnum.kt b/plugins/kapt3/testData/converter/abstractEnum.kt new file mode 100644 index 00000000000..c004dc3ae7d --- /dev/null +++ b/plugins/kapt3/testData/converter/abstractEnum.kt @@ -0,0 +1,37 @@ +enum class E { + X { + override fun a() {} + }, + Y { + override fun a() {} + }; + + abstract fun a() + + fun b() {} + + object Obj + class NestedClass +} + +enum class E2 { + X("") { + override fun a() {} + }, + Y(5) { + override fun a() {} + }; + + constructor(n: Int) {} + constructor(s: String) {} + + abstract fun a() +} + +enum class E3(val a: String) { + X(""), Y("") +} + +enum class E4(val a: String, val b: Int, val c: Long, val d: Boolean) { + X("", 4, 2L, true) +} \ No newline at end of file diff --git a/plugins/kapt3/testData/converter/abstractEnum.txt b/plugins/kapt3/testData/converter/abstractEnum.txt new file mode 100644 index 00000000000..2a1a2d49bfb --- /dev/null +++ b/plugins/kapt3/testData/converter/abstractEnum.txt @@ -0,0 +1,125 @@ +public enum E { + /*public static final*/ X /* = new X(){ + + @java.lang.Override() + public void a() { + } + + X() { + super(); + } + } */, + /*public static final*/ Y /* = new Y(){ + + @java.lang.Override() + public void a() { + } + + Y() { + super(); + } + } */; + + public abstract void a(); + + public final void b() { + } + + E() { + } + + public static final class Obj { + public static final E.Obj INSTANCE = null; + + private Obj() { + super(); + } + } + + public static final class NestedClass { + + public NestedClass() { + super(); + } + } +} + +//////////////////// + + +public enum E2 { + /*public static final*/ X /* = new X(0){ + + @java.lang.Override() + public void a() { + } + + X() { + super(0); + } + } */, + /*public static final*/ Y /* = new Y(0){ + + @java.lang.Override() + public void a() { + } + + Y() { + super(0); + } + } */; + + public abstract void a(); + + E2(int n) { + } + + E2(java.lang.String s) { + } +} + +//////////////////// + + +public enum E3 { + /*public static final*/ X /* = new X(null) */, + /*public static final*/ Y /* = new Y(null) */; + private final java.lang.String a = null; + + public final java.lang.String getA() { + return null; + } + + E3(java.lang.String a) { + } +} + +//////////////////// + + +public enum E4 { + /*public static final*/ X /* = new X(null, 0, 0L, false) */; + private final java.lang.String a = null; + private final int b = 0; + private final long c = 0L; + private final boolean d = false; + + public final java.lang.String getA() { + return null; + } + + public final int getB() { + return 0; + } + + public final long getC() { + return 0L; + } + + public final boolean getD() { + return false; + } + + E4(java.lang.String a, int b, long c, boolean d) { + } +} diff --git a/plugins/kapt3/testData/converter/annotations.txt b/plugins/kapt3/testData/converter/annotations.txt index a4f0f43f1a7..c7eea6993da 100644 --- a/plugins/kapt3/testData/converter/annotations.txt +++ b/plugins/kapt3/testData/converter/annotations.txt @@ -37,8 +37,8 @@ public abstract @interface Anno3 { public enum Colors { - /*public static final*/ WHITE /* = null */, - /*public static final*/ BLACK /* = null */; + /*public static final*/ WHITE /* = new WHITE() */, + /*public static final*/ BLACK /* = new BLACK() */; Colors() { } diff --git a/plugins/kapt3/testData/converter/annotations2.txt b/plugins/kapt3/testData/converter/annotations2.txt index 6cd3079f44b..08d03d43fb0 100644 --- a/plugins/kapt3/testData/converter/annotations2.txt +++ b/plugins/kapt3/testData/converter/annotations2.txt @@ -37,8 +37,8 @@ package test; @Anno(value = "enum") public enum Enum { - /*public static final*/ WHITE /* = null */, - /*public static final*/ BLACK /* = null */; + /*public static final*/ WHITE /* = new WHITE(0) */, + /*public static final*/ BLACK /* = new BLACK(0) */; private final int x = 0; public final int getX() { diff --git a/plugins/kapt3/testData/converter/enums.txt b/plugins/kapt3/testData/converter/enums.txt index c90b8c22607..f73c938ac96 100644 --- a/plugins/kapt3/testData/converter/enums.txt +++ b/plugins/kapt3/testData/converter/enums.txt @@ -7,8 +7,8 @@ public abstract @interface Anno1 { public enum Enum1 { - /*public static final*/ BLACK /* = null */, - /*public static final*/ WHITE /* = null */; + /*public static final*/ BLACK /* = new BLACK() */, + /*public static final*/ WHITE /* = new WHITE() */; Enum1() { } @@ -18,8 +18,8 @@ public enum Enum1 { public enum Enum2 { - /*public static final*/ RED /* = null */, - /*public static final*/ WHITE /* = null */; + /*public static final*/ RED /* = new RED(null, 0) */, + /*public static final*/ WHITE /* = new WHITE(null, 0) */; private final java.lang.String col = null; private final int col2 = 0; diff --git a/plugins/kapt3/testData/converter/inheritanceSimple.txt b/plugins/kapt3/testData/converter/inheritanceSimple.txt index 41cfbec19c0..43679addb1b 100644 --- a/plugins/kapt3/testData/converter/inheritanceSimple.txt +++ b/plugins/kapt3/testData/converter/inheritanceSimple.txt @@ -32,8 +32,8 @@ public final class Inheritor extends BaseClass { public enum Result { - /*public static final*/ SUCCESS /* = null */, - /*public static final*/ ERROR /* = null */; + /*public static final*/ SUCCESS /* = new SUCCESS() */, + /*public static final*/ ERROR /* = new ERROR() */; Result() { } diff --git a/plugins/kapt3/testData/converter/nestedClasses.txt b/plugins/kapt3/testData/converter/nestedClasses.txt index 1980fb066c8..64c19c0178b 100644 --- a/plugins/kapt3/testData/converter/nestedClasses.txt +++ b/plugins/kapt3/testData/converter/nestedClasses.txt @@ -37,8 +37,8 @@ public final class Test { } public static enum NestedEnum { - /*public static final*/ BLACK /* = null */, - /*public static final*/ WHITE /* = null */; + /*public static final*/ BLACK /* = new BLACK() */, + /*public static final*/ WHITE /* = new WHITE() */; NestedEnum() { } diff --git a/plugins/kapt3/testData/kotlinRunner/Simple.it.txt b/plugins/kapt3/testData/kotlinRunner/Simple.it.txt index 5f9bc73e07e..9332d32eebb 100644 --- a/plugins/kapt3/testData/kotlinRunner/Simple.it.txt +++ b/plugins/kapt3/testData/kotlinRunner/Simple.it.txt @@ -1,8 +1,20 @@ package test; +public enum EnumClass { + /*public static final*/ BLACK /* = new BLACK() */, + /*public static final*/ WHITE /* = new WHITE() */; + + EnumClass() { + } +} + +//////////////////// + +package test; + public enum EnumClass2 { - /*public static final*/ WHITE /* = null */, - /*public static final*/ RED /* = null */; + /*public static final*/ WHITE /* = new WHITE(null) */, + /*public static final*/ RED /* = new RED(null) */; private final java.lang.String blah = null; EnumClass2(java.lang.String blah) { @@ -18,18 +30,6 @@ public abstract @interface MyAnnotation { //////////////////// -package test; - -public enum EnumClass { - /*public static final*/ BLACK /* = null */, - /*public static final*/ WHITE /* = null */; - - EnumClass() { - } -} - -//////////////////// - package error; public final class NonExistentClass { diff --git a/plugins/kapt3/testData/runner/Simple.java b/plugins/kapt3/testData/runner/Simple.java index c84f0363859..3b49e5635b2 100644 --- a/plugins/kapt3/testData/runner/Simple.java +++ b/plugins/kapt3/testData/runner/Simple.java @@ -24,4 +24,13 @@ enum EnumClass2 { EnumClass2(String blah) { this.blah = blah; } +} + +enum EnumClass3 { + A { + @Override + void a() {} + }; + + abstract void a(); } \ No newline at end of file