Generate enum values with bodies properly (KT-15803)

This commit is contained in:
Yan Zhulanow
2017-01-30 18:48:55 +03:00
parent 18e0baa13e
commit 1b337e1a48
12 changed files with 284 additions and 50 deletions
@@ -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<FieldNode, JCTree>(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<JCTree> = mapJList(enumValuesData) { data ->
val constructorArguments = Type.getArgumentTypes(clazz.methods.firstOrNull {
it.name == "<init>" && 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<FieldNode, JCTree>(clazz.fields) {
if (it.isEnumValue()) null else convertField(it, packageFqName)
}
val methods = mapJList<MethodNode, JCTree>(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<InnerClassNode, JCTree>(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
@@ -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 <T> List<T>?.isNullOrEmpty() = this == null || this.isEmpty()
internal fun MethodNode.isJvmOverloadsGenerated(): Boolean {
@@ -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");
+37
View File
@@ -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)
}
+125
View File
@@ -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) {
}
}
+2 -2
View File
@@ -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() {
}
+2 -2
View File
@@ -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() {
+4 -4
View File
@@ -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;
+2 -2
View File
@@ -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() {
}
+2 -2
View File
@@ -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() {
}
+14 -14
View File
@@ -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 {
+9
View File
@@ -24,4 +24,13 @@ enum EnumClass2 {
EnumClass2(String blah) {
this.blah = blah;
}
}
enum EnumClass3 {
A {
@Override
void a() {}
};
abstract void a();
}