Serialize descriptors for local/anonymous classes on JVM
Reflection needs this information to work for local classes and anonymous objects
This commit is contained in:
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.descriptors.*;
|
|||||||
import org.jetbrains.kotlin.lexer.JetTokens;
|
import org.jetbrains.kotlin.lexer.JetTokens;
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi;
|
import org.jetbrains.kotlin.load.java.JvmAbi;
|
||||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames;
|
import org.jetbrains.kotlin.load.java.JvmAnnotationNames;
|
||||||
|
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinClass;
|
||||||
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor;
|
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor;
|
||||||
import org.jetbrains.kotlin.name.FqName;
|
import org.jetbrains.kotlin.name.FqName;
|
||||||
import org.jetbrains.kotlin.name.Name;
|
import org.jetbrains.kotlin.name.Name;
|
||||||
@@ -72,7 +73,6 @@ import static org.jetbrains.kotlin.codegen.AsmUtil.*;
|
|||||||
import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.*;
|
import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.*;
|
||||||
import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.enumEntryNeedSubclass;
|
import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.enumEntryNeedSubclass;
|
||||||
import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.isLocalNamedFun;
|
import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.isLocalNamedFun;
|
||||||
import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinSyntheticClass;
|
|
||||||
import static org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.classDescriptorToDeclaration;
|
import static org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.classDescriptorToDeclaration;
|
||||||
import static org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration;
|
import static org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration;
|
||||||
import static org.jetbrains.kotlin.resolve.DescriptorUtils.*;
|
import static org.jetbrains.kotlin.resolve.DescriptorUtils.*;
|
||||||
@@ -218,19 +218,20 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void generateKotlinAnnotation() {
|
protected void generateKotlinAnnotation() {
|
||||||
if (isAnonymousObject(descriptor)) {
|
|
||||||
writeKotlinSyntheticClassAnnotation(v, KotlinSyntheticClass.Kind.ANONYMOUS_OBJECT);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isTopLevelOrInnerClass(descriptor)) {
|
|
||||||
// LOCAL_CLASS is also written to inner classes of local classes
|
|
||||||
writeKotlinSyntheticClassAnnotation(v, KotlinSyntheticClass.Kind.LOCAL_CLASS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.getClassBuilderMode() != ClassBuilderMode.FULL) return;
|
if (state.getClassBuilderMode() != ClassBuilderMode.FULL) return;
|
||||||
|
|
||||||
|
KotlinClass.Kind kind;
|
||||||
|
if (isAnonymousObject(descriptor)) {
|
||||||
|
kind = KotlinClass.Kind.ANONYMOUS_OBJECT;
|
||||||
|
}
|
||||||
|
else if (isTopLevelOrInnerClass(descriptor)) {
|
||||||
|
kind = KotlinClass.Kind.CLASS;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// LOCAL_CLASS is also written to inner classes of local classes
|
||||||
|
kind = KotlinClass.Kind.LOCAL_CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
DescriptorSerializer serializer =
|
DescriptorSerializer serializer =
|
||||||
DescriptorSerializer.create(descriptor, new JvmSerializerExtension(v.getSerializationBindings(), typeMapper));
|
DescriptorSerializer.create(descriptor, new JvmSerializerExtension(v.getSerializationBindings(), typeMapper));
|
||||||
|
|
||||||
@@ -239,8 +240,12 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
|
|||||||
ClassData data = new ClassData(createNameResolver(serializer.getStringTable()), classProto);
|
ClassData data = new ClassData(createNameResolver(serializer.getStringTable()), classProto);
|
||||||
|
|
||||||
AnnotationVisitor av = v.getVisitor().visitAnnotation(asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_CLASS), true);
|
AnnotationVisitor av = v.getVisitor().visitAnnotation(asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_CLASS), true);
|
||||||
//noinspection ConstantConditions
|
|
||||||
av.visit(JvmAnnotationNames.ABI_VERSION_FIELD_NAME, JvmAbi.VERSION);
|
av.visit(JvmAnnotationNames.ABI_VERSION_FIELD_NAME, JvmAbi.VERSION);
|
||||||
|
av.visitEnum(
|
||||||
|
JvmAnnotationNames.KIND_FIELD_NAME,
|
||||||
|
Type.getObjectType(KotlinClass.KIND_INTERNAL_NAME).getDescriptor(),
|
||||||
|
kind.toString()
|
||||||
|
);
|
||||||
AnnotationVisitor array = av.visitArray(JvmAnnotationNames.DATA_FIELD_NAME);
|
AnnotationVisitor array = av.visitArray(JvmAnnotationNames.DATA_FIELD_NAME);
|
||||||
for (String string : BitEncoding.encodeBytes(data.toBytes())) {
|
for (String string : BitEncoding.encodeBytes(data.toBytes())) {
|
||||||
array.visit(null, string);
|
array.visit(null, string);
|
||||||
|
|||||||
+4
-1
@@ -260,10 +260,13 @@ public abstract class FileBasedKotlinClass implements KotlinJvmBinaryClass {
|
|||||||
return ClassId.topLevel(new FqName(name.replace('/', '.')));
|
return ClassId.topLevel(new FqName(name.replace('/', '.')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this is a hack which can be dropped once JVM back-end begins to write InnerClasses attribute for all referenced classes
|
||||||
if (name.equals(JvmAnnotationNames.KotlinSyntheticClass.KIND_INTERNAL_NAME)) {
|
if (name.equals(JvmAnnotationNames.KotlinSyntheticClass.KIND_INTERNAL_NAME)) {
|
||||||
// TODO: this is a hack which can be dropped once JVM back-end begins to write InnerClasses attribute for all referenced classes
|
|
||||||
return JvmAnnotationNames.KotlinSyntheticClass.KIND_CLASS_ID;
|
return JvmAnnotationNames.KotlinSyntheticClass.KIND_CLASS_ID;
|
||||||
}
|
}
|
||||||
|
else if (name.equals(JvmAnnotationNames.KotlinClass.KIND_INTERNAL_NAME)) {
|
||||||
|
return JvmAnnotationNames.KotlinClass.KIND_CLASS_ID;
|
||||||
|
}
|
||||||
|
|
||||||
List<String> classes = new ArrayList<String>(1);
|
List<String> classes = new ArrayList<String>(1);
|
||||||
boolean local = false;
|
boolean local = false;
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// CLASS_NAME_SUFFIX: A$foo$Local
|
||||||
|
|
||||||
|
class A {
|
||||||
|
annotation class Ann(val info: String)
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
[Ann("class")] class Local {
|
||||||
|
Ann("fun") fun foo(): Local = this
|
||||||
|
Ann("val") val x = foo()
|
||||||
|
|
||||||
|
Ann("inner") inner class Inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
A.Ann(info = "class": kotlin.String) local final class `A$foo$Local`
|
||||||
|
|
||||||
|
public constructor `A$foo$Local`()
|
||||||
|
A.Ann(info = "val": kotlin.String) internal final val x: `A$foo$Local`
|
||||||
|
A.Ann(info = "fun": kotlin.String) internal final fun foo(): `A$foo$Local`
|
||||||
|
|
||||||
|
A.Ann(info = "inner": kotlin.String) local final inner class Inner {
|
||||||
|
public constructor Inner()
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// CLASS_NAME_SUFFIX: $main$1
|
||||||
|
|
||||||
|
fun main() = object : Runnable {
|
||||||
|
override fun run() { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
local final class `_DefaultPackage$anonymousObject$*$main$1` : java.lang.Runnable
|
||||||
|
|
||||||
|
public constructor `_DefaultPackage$anonymousObject$*$main$1`()
|
||||||
|
public open override /*1*/ fun run(): kotlin.Unit
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// CLASS_NAME_SUFFIX: Deepest
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
class Local {
|
||||||
|
inner class Inner {
|
||||||
|
val prop = object {
|
||||||
|
fun foo() {
|
||||||
|
fun bar() {
|
||||||
|
class DeepLocal {
|
||||||
|
inner class Deepest {
|
||||||
|
fun local(): Local = Local()
|
||||||
|
fun inner(): Inner = Inner()
|
||||||
|
fun deep(): DeepLocal = DeepLocal()
|
||||||
|
fun deepest(): Deepest? = Deepest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
local final inner class Deepest
|
||||||
|
|
||||||
|
public constructor Deepest()
|
||||||
|
internal final fun deep(): `_DefaultPackage$deepInnerChain$*$main$Local$Inner$prop$1$foo$1$DeepLocal`
|
||||||
|
internal final fun deepest(): `_DefaultPackage$deepInnerChain$*$main$Local$Inner$prop$1$foo$1$DeepLocal`.Deepest?
|
||||||
|
internal final fun inner(): `_DefaultPackage$deepInnerChain$*$main$Local`.Inner
|
||||||
|
internal final fun local(): `_DefaultPackage$deepInnerChain$*$main$Local`
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// CLASS_NAME_SUFFIX: main$Local$Inner
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
class Local {
|
||||||
|
inner class Inner {
|
||||||
|
fun local(l: Local) {}
|
||||||
|
fun inner(i: Inner) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
local final inner class Inner
|
||||||
|
|
||||||
|
public constructor Inner()
|
||||||
|
internal final fun inner(/*0*/ i: `_DefaultPackage$innerOfLocal$*$main$Local`.Inner): kotlin.Unit
|
||||||
|
internal final fun local(/*0*/ l: `_DefaultPackage$innerOfLocal$*$main$Local`): kotlin.Unit
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// CLASS_NAME_SUFFIX: $main$Local
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
open class Local {
|
||||||
|
fun param(l: Local) {}
|
||||||
|
|
||||||
|
val returnType: Local = this
|
||||||
|
|
||||||
|
fun Local.receiver() = this
|
||||||
|
|
||||||
|
fun <T : Local, U : T> generic(t: T): U = null!!
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
local open class `_DefaultPackage$localClassInSignature$*$main$Local`
|
||||||
|
|
||||||
|
public constructor `_DefaultPackage$localClassInSignature$*$main$Local`()
|
||||||
|
internal final val returnType: `_DefaultPackage$localClassInSignature$*$main$Local`
|
||||||
|
internal final fun </*0*/ T : `_DefaultPackage$localClassInSignature$*$main$Local`, /*1*/ U : T> generic(/*0*/ t: T): U
|
||||||
|
internal final fun param(/*0*/ l: `_DefaultPackage$localClassInSignature$*$main$Local`): kotlin.Unit
|
||||||
|
internal final fun `_DefaultPackage$localClassInSignature$*$main$Local`.receiver(): `_DefaultPackage$localClassInSignature$*$main$Local`
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// CLASS_NAME_SUFFIX: A$foo$Local
|
||||||
|
|
||||||
|
class A {
|
||||||
|
fun foo() {
|
||||||
|
class Local
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
local final class `A$foo$Local`
|
||||||
|
|
||||||
|
public constructor `A$foo$Local`()
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// CLASS_NAME_SUFFIX: $main$Local
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
class Local
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
local final class `_DefaultPackage$simpleInTopLevelFunction$*$main$Local`
|
||||||
|
|
||||||
|
public constructor `_DefaultPackage$simpleInTopLevelFunction$*$main$Local`()
|
||||||
+96
-45
@@ -22,14 +22,20 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.kotlin.backend.common.output.OutputFile;
|
import org.jetbrains.kotlin.backend.common.output.OutputFile;
|
||||||
import org.jetbrains.kotlin.load.java.AbiVersionUtil;
|
import org.jetbrains.kotlin.load.java.AbiVersionUtil;
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi;
|
import org.jetbrains.kotlin.load.java.JvmAbi;
|
||||||
|
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinClass;
|
||||||
|
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinSyntheticClass;
|
||||||
import org.jetbrains.kotlin.name.FqName;
|
import org.jetbrains.kotlin.name.FqName;
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
|
||||||
import org.jetbrains.kotlin.test.ConfigurationKind;
|
import org.jetbrains.kotlin.test.ConfigurationKind;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.*;
|
import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.ABI_VERSION_FIELD_NAME;
|
||||||
|
import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.KIND_FIELD_NAME;
|
||||||
|
import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinClass.Kind.ANONYMOUS_OBJECT;
|
||||||
|
import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinClass.Kind.LOCAL_CLASS;
|
||||||
import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinSyntheticClass.Kind.*;
|
import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinSyntheticClass.Kind.*;
|
||||||
|
|
||||||
public class KotlinSyntheticClassAnnotationTest extends CodegenTestCase {
|
public class KotlinSyntheticClassAnnotationTest extends CodegenTestCase {
|
||||||
@@ -42,81 +48,122 @@ public class KotlinSyntheticClassAnnotationTest extends CodegenTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testPackagePart() {
|
public void testPackagePart() {
|
||||||
doTest("fun foo() = 42",
|
doTestKotlinSyntheticClass(
|
||||||
"$",
|
"fun foo() = 42",
|
||||||
PACKAGE_PART);
|
"$",
|
||||||
|
PACKAGE_PART
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testTraitImpl() {
|
public void testTraitImpl() {
|
||||||
doTest("trait A { fun foo() = 42 }",
|
doTestKotlinSyntheticClass(
|
||||||
JvmAbi.TRAIT_IMPL_SUFFIX,
|
"trait A { fun foo() = 42 }",
|
||||||
TRAIT_IMPL);
|
JvmAbi.TRAIT_IMPL_SUFFIX,
|
||||||
|
TRAIT_IMPL
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSamWrapper() {
|
public void testSamWrapper() {
|
||||||
doTest("val f = {}\nval foo = Thread(f)",
|
doTestKotlinSyntheticClass(
|
||||||
"$sam",
|
"val f = {}\nval foo = Thread(f)",
|
||||||
SAM_WRAPPER);
|
"$sam",
|
||||||
|
SAM_WRAPPER
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSamLambda() {
|
public void testSamLambda() {
|
||||||
doTest("val foo = Thread { }",
|
doTestKotlinSyntheticClass(
|
||||||
"$1",
|
"val foo = Thread { }",
|
||||||
SAM_LAMBDA);
|
"$1",
|
||||||
|
SAM_LAMBDA
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testCallableReferenceWrapper() {
|
public void testCallableReferenceWrapper() {
|
||||||
doTest("val f = String::get",
|
doTestKotlinSyntheticClass(
|
||||||
"$1",
|
"val f = String::get",
|
||||||
CALLABLE_REFERENCE_WRAPPER);
|
"$1",
|
||||||
|
CALLABLE_REFERENCE_WRAPPER
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testLocalFunction() {
|
public void testLocalFunction() {
|
||||||
doTest("fun foo() { fun bar() {} }",
|
doTestKotlinSyntheticClass(
|
||||||
"$1",
|
"fun foo() { fun bar() {} }",
|
||||||
LOCAL_FUNCTION);
|
"$1",
|
||||||
|
LOCAL_FUNCTION
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAnonymousFunction() {
|
public void testAnonymousFunction() {
|
||||||
doTest("val f = {}",
|
doTestKotlinSyntheticClass(
|
||||||
"$1",
|
"val f = {}",
|
||||||
ANONYMOUS_FUNCTION);
|
"$1",
|
||||||
|
ANONYMOUS_FUNCTION
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testLocalClass() {
|
public void testLocalClass() {
|
||||||
doTest("fun foo() { class Local }",
|
doTestKotlinClass(
|
||||||
"Local",
|
"fun foo() { class Local }",
|
||||||
LOCAL_CLASS);
|
"Local",
|
||||||
|
LOCAL_CLASS
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testLocalTraitImpl() {
|
public void testLocalTraitImpl() {
|
||||||
doTest("fun foo() { trait Local { fun bar() = 42 } }",
|
doTestKotlinSyntheticClass(
|
||||||
"Local$$TImpl.class",
|
"fun foo() { trait Local { fun bar() = 42 } }",
|
||||||
LOCAL_TRAIT_IMPL);
|
"Local$$TImpl.class",
|
||||||
|
LOCAL_TRAIT_IMPL
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testLocalTraitInterface() {
|
public void testLocalTraitInterface() {
|
||||||
doTest("fun foo() { trait Local { fun bar() = 42 } }",
|
doTestKotlinClass(
|
||||||
"Local.class",
|
"fun foo() { trait Local { fun bar() = 42 } }",
|
||||||
LOCAL_CLASS);
|
"Local.class",
|
||||||
|
LOCAL_CLASS
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testInnerClassOfLocalClass() {
|
public void testInnerClassOfLocalClass() {
|
||||||
doTest("fun foo() { class Local { inner class Inner } }",
|
doTestKotlinClass(
|
||||||
"Inner",
|
"fun foo() { class Local { inner class Inner } }",
|
||||||
LOCAL_CLASS);
|
"Inner",
|
||||||
|
LOCAL_CLASS
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAnonymousObject() {
|
public void testAnonymousObject() {
|
||||||
doTest("val o = object {}",
|
doTestKotlinClass(
|
||||||
"$1",
|
"val o = object {}",
|
||||||
ANONYMOUS_OBJECT);
|
"$1",
|
||||||
|
ANONYMOUS_OBJECT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doTestKotlinSyntheticClass(
|
||||||
|
@NotNull String code,
|
||||||
|
@NotNull String classFilePart,
|
||||||
|
@NotNull KotlinSyntheticClass.Kind expectedKind
|
||||||
|
) {
|
||||||
|
doTest(code, classFilePart, KotlinSyntheticClass.CLASS_NAME, expectedKind.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doTestKotlinClass(
|
||||||
|
@NotNull String code,
|
||||||
|
@NotNull String classFilePart,
|
||||||
|
@NotNull KotlinClass.Kind expectedKind
|
||||||
|
) {
|
||||||
|
doTest(code, classFilePart, KotlinClass.CLASS_NAME, expectedKind.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doTest(
|
private void doTest(
|
||||||
@NotNull String code,
|
@NotNull String code,
|
||||||
@NotNull final String classFilePart,
|
@NotNull final String classFilePart,
|
||||||
@NotNull KotlinSyntheticClass.Kind expectedKind
|
@NotNull JvmClassName annotationName,
|
||||||
|
@NotNull String expectedKind
|
||||||
) {
|
) {
|
||||||
loadText("package " + PACKAGE_NAME + "\n\n" + code);
|
loadText("package " + PACKAGE_NAME + "\n\n" + code);
|
||||||
List<OutputFile> output = generateClassesInFile().asList();
|
List<OutputFile> output = generateClassesInFile().asList();
|
||||||
@@ -132,22 +179,26 @@ public class KotlinSyntheticClassAnnotationTest extends CodegenTestCase {
|
|||||||
String path = files.iterator().next().getRelativePath();
|
String path = files.iterator().next().getRelativePath();
|
||||||
String fqName = path.substring(0, path.length() - ".class".length()).replace('/', '.');
|
String fqName = path.substring(0, path.length() - ".class".length()).replace('/', '.');
|
||||||
Class<?> aClass = generateClass(fqName);
|
Class<?> aClass = generateClass(fqName);
|
||||||
assertAnnotatedWithKind(aClass, expectedKind);
|
assertAnnotatedWithKind(aClass, annotationName.getFqNameForClassNameWithoutDollars().asString(), expectedKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertAnnotatedWithKind(@NotNull Class<?> aClass, @NotNull KotlinSyntheticClass.Kind expectedKind) {
|
private void assertAnnotatedWithKind(
|
||||||
Class<? extends Annotation> annotationClass = loadAnnotationClassQuietly(
|
@NotNull Class<?> aClass,
|
||||||
KotlinSyntheticClass.CLASS_NAME.getFqNameForClassNameWithoutDollars().asString());
|
@NotNull String annotationFqName,
|
||||||
assertTrue("No KotlinSyntheticClass annotation found", aClass.isAnnotationPresent(annotationClass));
|
@NotNull String expectedKind
|
||||||
|
) {
|
||||||
|
Class<? extends Annotation> annotationClass = loadAnnotationClassQuietly(annotationFqName);
|
||||||
|
assertTrue("No annotation " + annotationFqName + " found in " + aClass, aClass.isAnnotationPresent(annotationClass));
|
||||||
|
|
||||||
Annotation annotation = aClass.getAnnotation(annotationClass);
|
Annotation annotation = aClass.getAnnotation(annotationClass);
|
||||||
|
|
||||||
Integer version = (Integer) CodegenTestUtil.getAnnotationAttribute(annotation, ABI_VERSION_FIELD_NAME);
|
Integer version = (Integer) CodegenTestUtil.getAnnotationAttribute(annotation, ABI_VERSION_FIELD_NAME);
|
||||||
assertNotNull(version);
|
assertNotNull(version);
|
||||||
assertTrue("KotlinSyntheticClass annotation is written with an unsupported format", AbiVersionUtil.isAbiVersionCompatible(version));
|
assertTrue("Annotation " + annotationFqName + " is written with an unsupported format",
|
||||||
|
AbiVersionUtil.isAbiVersionCompatible(version));
|
||||||
|
|
||||||
Object actualKind = CodegenTestUtil.getAnnotationAttribute(annotation, KIND_FIELD_NAME);
|
Object actualKind = CodegenTestUtil.getAnnotationAttribute(annotation, KIND_FIELD_NAME);
|
||||||
assertNotNull(actualKind);
|
assertNotNull(actualKind);
|
||||||
assertEquals("KotlinSyntheticClass annotation has the wrong kind", expectedKind.toString(), actualKind.toString());
|
assertEquals("Annotation " + annotationFqName + " has the wrong kind", expectedKind, actualKind.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2015 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.serialization
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.jvm.compiler.LoadDescriptorUtil
|
||||||
|
import org.jetbrains.kotlin.test.TestCaseWithTmpdir
|
||||||
|
import java.io.File
|
||||||
|
import org.jetbrains.kotlin.test.ConfigurationKind
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
|
||||||
|
import org.jetbrains.kotlin.test.JetTestUtils
|
||||||
|
import org.jetbrains.kotlin.cli.jvm.compiler.JetCoreEnvironment
|
||||||
|
import org.jetbrains.kotlin.test.TestJdkKind
|
||||||
|
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||||
|
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.di.InjectorForTopDownAnalyzerForJvm
|
||||||
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM
|
||||||
|
import org.jetbrains.kotlin.context.GlobalContext
|
||||||
|
import org.jetbrains.kotlin.resolve.TopDownAnalysisParameters
|
||||||
|
import com.google.common.base.Predicates
|
||||||
|
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
|
||||||
|
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator
|
||||||
|
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||||
|
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
|
||||||
|
|
||||||
|
public abstract class AbstractLocalClassProtoTest : TestCaseWithTmpdir() {
|
||||||
|
protected fun doTest(filename: String) {
|
||||||
|
val source = File(filename)
|
||||||
|
LoadDescriptorUtil.compileKotlinToDirAndGetAnalysisResult(listOf(source), tmpdir, getTestRootDisposable(), ConfigurationKind.ALL)
|
||||||
|
|
||||||
|
val classNameSuffix = InTextDirectivesUtils.findStringWithPrefixes(source.readText(), "// CLASS_NAME_SUFFIX: ")
|
||||||
|
?: error("CLASS_NAME_SUFFIX directive not found in test data")
|
||||||
|
|
||||||
|
val classLoader = URLClassLoader(array(tmpdir.toURI().toURL(), ForTestCompileRuntime.runtimeJarForTests().toURI().toURL()), null)
|
||||||
|
|
||||||
|
val classFile = tmpdir.listFiles().singleOrNull { it.getPath().endsWith("$classNameSuffix.class") }
|
||||||
|
?: error("Local class with suffix `$classNameSuffix` is not found in: ${tmpdir.listFiles().toList()}")
|
||||||
|
val clazz = classLoader.loadClass(classFile.name.substringBeforeLast(".class"))
|
||||||
|
assertHasAnnotationData(clazz)
|
||||||
|
|
||||||
|
val environment = JetCoreEnvironment.createForTests(
|
||||||
|
getTestRootDisposable(),
|
||||||
|
JetTestUtils.compilerConfigurationForTests(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK, tmpdir),
|
||||||
|
EnvironmentConfigFiles.JVM_CONFIG_FILES
|
||||||
|
)
|
||||||
|
val module = TopDownAnalyzerFacadeForJVM.createSealedJavaModule()
|
||||||
|
val globalContext = GlobalContext()
|
||||||
|
val params = TopDownAnalysisParameters.create(
|
||||||
|
globalContext.storageManager, globalContext.exceptionTracker, Predicates.alwaysTrue(), false, false
|
||||||
|
)
|
||||||
|
val providerFactory = FileBasedDeclarationProviderFactory(globalContext.storageManager, emptyList())
|
||||||
|
|
||||||
|
val injector = InjectorForTopDownAnalyzerForJvm(
|
||||||
|
environment.getProject(), params, CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(), module,
|
||||||
|
providerFactory, GlobalSearchScope.allScope(environment.getProject())
|
||||||
|
)
|
||||||
|
module.initialize(injector.getJavaDescriptorResolver().packageFragmentProvider)
|
||||||
|
|
||||||
|
val components = injector.getDeserializationComponentsForJava().components
|
||||||
|
|
||||||
|
val classDescriptor = components.classDeserializer.deserializeClass(clazz.classId)
|
||||||
|
?: error("Class is not resolved: $clazz (classId = ${clazz.classId})")
|
||||||
|
|
||||||
|
RecursiveDescriptorComparator.validateAndCompareDescriptorWithFile(
|
||||||
|
classDescriptor,
|
||||||
|
RecursiveDescriptorComparator.DONT_INCLUDE_METHODS_OF_OBJECT,
|
||||||
|
JetTestUtils.replaceExtension(source, "txt")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val Class<*>.classId: ClassId
|
||||||
|
get() {
|
||||||
|
if (getEnclosingMethod() != null || getEnclosingConstructor() != null) {
|
||||||
|
return ClassId(FqName.ROOT, FqNameUnsafe(getName()), /* local = */ true)
|
||||||
|
}
|
||||||
|
return getDeclaringClass()?.classId?.createNestedClassId(Name.identifier(getSimpleName()))
|
||||||
|
?: ClassId.topLevel(FqName(getName()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertHasAnnotationData(clazz: Class<*>) {
|
||||||
|
[suppress("UNCHECKED_CAST")]
|
||||||
|
val annotation = clazz.getAnnotation(
|
||||||
|
clazz.getClassLoader().loadClass(JvmAnnotationNames.KOTLIN_CLASS.asString()) as Class<Annotation>
|
||||||
|
)
|
||||||
|
assert(annotation != null) { "KotlinClass annotation is not found for class $clazz" }
|
||||||
|
|
||||||
|
val kindMethod = annotation.annotationType().getDeclaredMethod("kind")
|
||||||
|
val kind = kindMethod(annotation)
|
||||||
|
assert(kind.toString() != JvmAnnotationNames.KotlinClass.Kind.CLASS.toString()) {
|
||||||
|
"'kind' should not be CLASS: $clazz (was $kind)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2015 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.serialization;
|
||||||
|
|
||||||
|
import com.intellij.testFramework.TestDataPath;
|
||||||
|
import org.jetbrains.kotlin.test.InnerTestClasses;
|
||||||
|
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
|
||||||
|
import org.jetbrains.kotlin.test.JetTestUtils;
|
||||||
|
import org.jetbrains.kotlin.test.TestMetadata;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
@TestMetadata("compiler/testData/serialization/local")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public class LocalClassProtoTestGenerated extends AbstractLocalClassProtoTest {
|
||||||
|
public void testAllFilesPresentInLocal() throws Exception {
|
||||||
|
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/serialization/local"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("annotationsInLocalClass.kt")
|
||||||
|
public void testAnnotationsInLocalClass() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/serialization/local/annotationsInLocalClass.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("anonymousObject.kt")
|
||||||
|
public void testAnonymousObject() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/serialization/local/anonymousObject.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("deepInnerChain.kt")
|
||||||
|
public void testDeepInnerChain() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/serialization/local/deepInnerChain.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("innerOfLocal.kt")
|
||||||
|
public void testInnerOfLocal() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/serialization/local/innerOfLocal.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("localClassInSignature.kt")
|
||||||
|
public void testLocalClassInSignature() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/serialization/local/localClassInSignature.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("simpleInMemberFunction.kt")
|
||||||
|
public void testSimpleInMemberFunction() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/serialization/local/simpleInMemberFunction.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("simpleInTopLevelFunction.kt")
|
||||||
|
public void testSimpleInTopLevelFunction() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/serialization/local/simpleInTopLevelFunction.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,8 +85,9 @@ public class RecursiveDescriptorComparator {
|
|||||||
|
|
||||||
public String serializeRecursively(@NotNull DeclarationDescriptor declarationDescriptor) {
|
public String serializeRecursively(@NotNull DeclarationDescriptor declarationDescriptor) {
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
appendDeclarationRecursively(declarationDescriptor, DescriptorUtils.getContainingModule(declarationDescriptor), new Printer(result, 1), true);
|
appendDeclarationRecursively(declarationDescriptor, DescriptorUtils.getContainingModule(declarationDescriptor),
|
||||||
return result.toString();
|
new Printer(result, 1), true);
|
||||||
|
return JetTestUtils.replaceHashWithStar(result.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void appendDeclarationRecursively(
|
private void appendDeclarationRecursively(
|
||||||
|
|||||||
+14
-4
@@ -45,6 +45,19 @@ public final class JvmAnnotationNames {
|
|||||||
|
|
||||||
public static class KotlinClass {
|
public static class KotlinClass {
|
||||||
public static final JvmClassName CLASS_NAME = JvmClassName.byInternalName("kotlin/jvm/internal/KotlinClass");
|
public static final JvmClassName CLASS_NAME = JvmClassName.byInternalName("kotlin/jvm/internal/KotlinClass");
|
||||||
|
public static final ClassId KIND_CLASS_ID =
|
||||||
|
ClassId.topLevel(CLASS_NAME.getFqNameForClassNameWithoutDollars()).createNestedClassId(Name.identifier("Kind"));
|
||||||
|
public static final String KIND_INTERNAL_NAME = JvmClassName.byClassId(KIND_CLASS_ID).getInternalName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This enum duplicates {@link kotlin.jvm.internal.KotlinClass.Kind}. Both places should be updated simultaneously.
|
||||||
|
*/
|
||||||
|
public enum Kind {
|
||||||
|
CLASS,
|
||||||
|
LOCAL_CLASS,
|
||||||
|
ANONYMOUS_OBJECT,
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class KotlinSyntheticClass {
|
public static class KotlinSyntheticClass {
|
||||||
@@ -54,8 +67,7 @@ public final class JvmAnnotationNames {
|
|||||||
public static final String KIND_INTERNAL_NAME = JvmClassName.byClassId(KIND_CLASS_ID).getInternalName();
|
public static final String KIND_INTERNAL_NAME = JvmClassName.byClassId(KIND_CLASS_ID).getInternalName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This enum duplicates {@link kotlin.jvm.internal.KotlinSyntheticClass.Kind}, because this code can't depend on "runtime.jvm".
|
* This enum duplicates {@link kotlin.jvm.internal.KotlinSyntheticClass.Kind}. Both places should be updated simultaneously.
|
||||||
* Both places should be updated simultaneously
|
|
||||||
*/
|
*/
|
||||||
public enum Kind {
|
public enum Kind {
|
||||||
PACKAGE_PART,
|
PACKAGE_PART,
|
||||||
@@ -66,8 +78,6 @@ public final class JvmAnnotationNames {
|
|||||||
CALLABLE_REFERENCE_WRAPPER,
|
CALLABLE_REFERENCE_WRAPPER,
|
||||||
LOCAL_FUNCTION,
|
LOCAL_FUNCTION,
|
||||||
ANONYMOUS_FUNCTION,
|
ANONYMOUS_FUNCTION,
|
||||||
LOCAL_CLASS,
|
|
||||||
ANONYMOUS_OBJECT,
|
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-5
@@ -17,22 +17,29 @@
|
|||||||
package org.jetbrains.kotlin.load.kotlin.header
|
package org.jetbrains.kotlin.load.kotlin.header
|
||||||
|
|
||||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinSyntheticClass
|
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinSyntheticClass
|
||||||
|
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinClass
|
||||||
import org.jetbrains.kotlin.load.java.AbiVersionUtil
|
import org.jetbrains.kotlin.load.java.AbiVersionUtil
|
||||||
|
|
||||||
public class KotlinClassHeader(
|
public class KotlinClassHeader(
|
||||||
public val kind: KotlinClassHeader.Kind,
|
public val kind: KotlinClassHeader.Kind,
|
||||||
public val version: Int,
|
public val version: Int,
|
||||||
public val annotationData: Array<String>?,
|
public val annotationData: Array<String>?,
|
||||||
|
public val classKind: KotlinClass.Kind?,
|
||||||
public val syntheticClassKind: KotlinSyntheticClass.Kind?
|
public val syntheticClassKind: KotlinSyntheticClass.Kind?
|
||||||
) {
|
) {
|
||||||
public val isCompatibleAbiVersion: Boolean get() = AbiVersionUtil.isAbiVersionCompatible(version);
|
public val isCompatibleAbiVersion: Boolean get() = AbiVersionUtil.isAbiVersionCompatible(version);
|
||||||
|
|
||||||
{
|
{
|
||||||
assert(!isCompatibleAbiVersion || (annotationData == null) == (kind != Kind.CLASS && kind != Kind.PACKAGE_FACADE)) {
|
if (isCompatibleAbiVersion) {
|
||||||
"Annotation data should be not null only for CLASS and PACKAGE_FACADE (kind=" + kind + ")"
|
assert((annotationData == null) == (kind != Kind.CLASS && kind != Kind.PACKAGE_FACADE)) {
|
||||||
}
|
"Annotation data should be not null only for CLASS and PACKAGE_FACADE (kind=$kind)"
|
||||||
assert(!isCompatibleAbiVersion || (syntheticClassKind == null) == (kind != Kind.SYNTHETIC_CLASS)) {
|
}
|
||||||
"Synthetic class kind should be present for SYNTHETIC_CLASS (kind=" + kind + ")"
|
assert((syntheticClassKind == null) == (kind != Kind.SYNTHETIC_CLASS)) {
|
||||||
|
"Synthetic class kind should be present for SYNTHETIC_CLASS (kind=$kind)"
|
||||||
|
}
|
||||||
|
assert((classKind == null) == (kind != Kind.CLASS)) {
|
||||||
|
"Class kind should be present for CLASS (kind=$kind)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +48,12 @@ public class KotlinClassHeader(
|
|||||||
PACKAGE_FACADE
|
PACKAGE_FACADE
|
||||||
SYNTHETIC_CLASS
|
SYNTHETIC_CLASS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString() =
|
||||||
|
"$kind " +
|
||||||
|
(if (classKind != null) "$classKind " else "") +
|
||||||
|
(if (syntheticClassKind != null) "$syntheticClassKind " else "") +
|
||||||
|
"version=$version"
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun KotlinClassHeader.isCompatibleClassKind(): Boolean = isCompatibleAbiVersion && kind == KotlinClassHeader.Kind.CLASS
|
public fun KotlinClassHeader.isCompatibleClassKind(): Boolean = isCompatibleAbiVersion && kind == KotlinClassHeader.Kind.CLASS
|
||||||
|
|||||||
+18
-8
@@ -59,6 +59,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
|
|||||||
|
|
||||||
private String[] annotationData = null;
|
private String[] annotationData = null;
|
||||||
private KotlinClassHeader.Kind headerKind = null;
|
private KotlinClassHeader.Kind headerKind = null;
|
||||||
|
private KotlinClass.Kind classKind = null;
|
||||||
private KotlinSyntheticClass.Kind syntheticClassKind = null;
|
private KotlinSyntheticClass.Kind syntheticClassKind = null;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -68,7 +69,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!AbiVersionUtil.isAbiVersionCompatible(version)) {
|
if (!AbiVersionUtil.isAbiVersionCompatible(version)) {
|
||||||
return new KotlinClassHeader(headerKind, version, null, syntheticClassKind);
|
return new KotlinClassHeader(headerKind, version, null, classKind, syntheticClassKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((headerKind == CLASS || headerKind == PACKAGE_FACADE) && annotationData == null) {
|
if ((headerKind == CLASS || headerKind == PACKAGE_FACADE) && annotationData == null) {
|
||||||
@@ -77,7 +78,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new KotlinClassHeader(headerKind, version, annotationData, syntheticClassKind);
|
return new KotlinClassHeader(headerKind, version, annotationData, classKind, syntheticClassKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -135,11 +136,6 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
|
|
||||||
unexpectedEnumArgument(name, enumClassId, enumEntryName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) {
|
public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) {
|
||||||
@@ -203,12 +199,26 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
|
|||||||
public ClassHeaderReader() {
|
public ClassHeaderReader() {
|
||||||
super(KotlinClass.CLASS_NAME);
|
super(KotlinClass.CLASS_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
|
||||||
|
if (KotlinClass.KIND_CLASS_ID.equals(enumClassId) && KIND_FIELD_NAME.equals(name.asString())) {
|
||||||
|
classKind = valueOfOrNull(KotlinClass.Kind.class, enumEntryName.asString());
|
||||||
|
if (classKind != null) return;
|
||||||
|
}
|
||||||
|
unexpectedEnumArgument(name, enumClassId, enumEntryName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PackageHeaderReader extends HeaderAnnotationArgumentVisitor {
|
private class PackageHeaderReader extends HeaderAnnotationArgumentVisitor {
|
||||||
public PackageHeaderReader() {
|
public PackageHeaderReader() {
|
||||||
super(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_PACKAGE));
|
super(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_PACKAGE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
|
||||||
|
unexpectedEnumArgument(name, enumClassId, enumEntryName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SyntheticClassHeaderReader extends HeaderAnnotationArgumentVisitor {
|
private class SyntheticClassHeaderReader extends HeaderAnnotationArgumentVisitor {
|
||||||
@@ -218,7 +228,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
|
public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
|
||||||
if (enumClassId.equals(KotlinSyntheticClass.KIND_CLASS_ID) && name.asString().equals(KIND_FIELD_NAME)) {
|
if (KotlinSyntheticClass.KIND_CLASS_ID.equals(enumClassId) && KIND_FIELD_NAME.equals(name.asString())) {
|
||||||
syntheticClassKind = valueOfOrNull(KotlinSyntheticClass.Kind.class, enumEntryName.asString());
|
syntheticClassKind = valueOfOrNull(KotlinSyntheticClass.Kind.class, enumEntryName.asString());
|
||||||
if (syntheticClassKind != null) return;
|
if (syntheticClassKind != null) return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,5 +23,18 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
public @interface KotlinClass {
|
public @interface KotlinClass {
|
||||||
int abiVersion();
|
int abiVersion();
|
||||||
|
|
||||||
|
Kind kind();
|
||||||
|
|
||||||
String[] data();
|
String[] data();
|
||||||
|
|
||||||
|
enum Kind {
|
||||||
|
CLASS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class has this kind if and only if its first non-class container is not a package.
|
||||||
|
*/
|
||||||
|
LOCAL_CLASS,
|
||||||
|
|
||||||
|
ANONYMOUS_OBJECT,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ public @interface KotlinSyntheticClass {
|
|||||||
|
|
||||||
Kind kind();
|
Kind kind();
|
||||||
|
|
||||||
// Inner classes of local classes have kind LOCAL_CLASS
|
enum Kind {
|
||||||
public static enum Kind {
|
|
||||||
PACKAGE_PART,
|
PACKAGE_PART,
|
||||||
TRAIT_IMPL,
|
TRAIT_IMPL,
|
||||||
LOCAL_TRAIT_IMPL,
|
LOCAL_TRAIT_IMPL,
|
||||||
@@ -35,7 +34,5 @@ public @interface KotlinSyntheticClass {
|
|||||||
CALLABLE_REFERENCE_WRAPPER,
|
CALLABLE_REFERENCE_WRAPPER,
|
||||||
LOCAL_FUNCTION,
|
LOCAL_FUNCTION,
|
||||||
ANONYMOUS_FUNCTION,
|
ANONYMOUS_FUNCTION,
|
||||||
LOCAL_CLASS,
|
|
||||||
ANONYMOUS_OBJECT,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ import org.jetbrains.kotlin.j2k.AbstractJavaToKotlinConverterForWebDemoTest
|
|||||||
import org.jetbrains.kotlin.idea.decompiler.textBuilder.AbstractDecompiledTextTest
|
import org.jetbrains.kotlin.idea.decompiler.textBuilder.AbstractDecompiledTextTest
|
||||||
import org.jetbrains.kotlin.completion.AbstractMultiFileSmartCompletionTest
|
import org.jetbrains.kotlin.completion.AbstractMultiFileSmartCompletionTest
|
||||||
import org.jetbrains.kotlin.completion.handlers.AbstractCompletionCharFilterTest
|
import org.jetbrains.kotlin.completion.handlers.AbstractCompletionCharFilterTest
|
||||||
|
import org.jetbrains.kotlin.serialization.AbstractLocalClassProtoTest
|
||||||
import org.jetbrains.kotlin.idea.resolve.AbstractPartialBodyResolveTest
|
import org.jetbrains.kotlin.idea.resolve.AbstractPartialBodyResolveTest
|
||||||
import org.jetbrains.kotlin.checkers.AbstractJetDiagnosticsTestWithJsStdLib
|
import org.jetbrains.kotlin.checkers.AbstractJetDiagnosticsTestWithJsStdLib
|
||||||
import org.jetbrains.kotlin.renderer.AbstractDescriptorRendererTest
|
import org.jetbrains.kotlin.renderer.AbstractDescriptorRendererTest
|
||||||
@@ -309,6 +310,10 @@ fun main(args: Array<String>) {
|
|||||||
model("lineNumber", recursive = false)
|
model("lineNumber", recursive = false)
|
||||||
model("lineNumber/custom", testMethod = "doTestCustom")
|
model("lineNumber/custom", testMethod = "doTestCustom")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testClass(javaClass<AbstractLocalClassProtoTest>()) {
|
||||||
|
model("serialization/local")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testGroup("idea/tests", "idea/testData") {
|
testGroup("idea/tests", "idea/testData") {
|
||||||
|
|||||||
+8
-6
@@ -23,7 +23,6 @@ import com.intellij.psi.stubs.PsiFileStub
|
|||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.psi.impl.compiled.ClassFileStubBuilder
|
import com.intellij.psi.impl.compiled.ClassFileStubBuilder
|
||||||
import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache
|
import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache
|
||||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
|
||||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil
|
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil
|
||||||
import org.jetbrains.kotlin.psi.JetFile
|
import org.jetbrains.kotlin.psi.JetFile
|
||||||
import org.jetbrains.kotlin.idea.decompiler.textBuilder.LocalClassFinder
|
import org.jetbrains.kotlin.idea.decompiler.textBuilder.LocalClassFinder
|
||||||
@@ -32,6 +31,9 @@ import com.intellij.openapi.diagnostic.Logger
|
|||||||
import org.jetbrains.kotlin.idea.decompiler.textBuilder.LoggingErrorReporter
|
import org.jetbrains.kotlin.idea.decompiler.textBuilder.LoggingErrorReporter
|
||||||
import org.jetbrains.kotlin.idea.decompiler.isKotlinInternalCompiledFile
|
import org.jetbrains.kotlin.idea.decompiler.isKotlinInternalCompiledFile
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.load.kotlin.header.isCompatiblePackageFacadeKind
|
||||||
|
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleClassKind
|
||||||
|
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
|
||||||
|
|
||||||
public class KotlinClsStubBuilder : ClsStubBuilder() {
|
public class KotlinClsStubBuilder : ClsStubBuilder() {
|
||||||
override fun getStubVersion() = ClassFileStubBuilder.STUB_VERSION + 1
|
override fun getStubVersion() = ClassFileStubBuilder.STUB_VERSION + 1
|
||||||
@@ -62,19 +64,19 @@ public class KotlinClsStubBuilder : ClsStubBuilder() {
|
|||||||
LOG.error("Corrupted kotlin header for file ${file.getName()}")
|
LOG.error("Corrupted kotlin header for file ${file.getName()}")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return when (header.kind) {
|
return when {
|
||||||
KotlinClassHeader.Kind.PACKAGE_FACADE -> {
|
header.isCompatiblePackageFacadeKind() -> {
|
||||||
val packageData = JvmProtoBufUtil.readPackageDataFrom(annotationData)
|
val packageData = JvmProtoBufUtil.readPackageDataFrom(annotationData)
|
||||||
val context = components.createContext(packageData.getNameResolver(), packageFqName)
|
val context = components.createContext(packageData.getNameResolver(), packageFqName)
|
||||||
createPackageFacadeFileStub(packageData.getPackageProto(), packageFqName, context)
|
createPackageFacadeFileStub(packageData.getPackageProto(), packageFqName, context)
|
||||||
}
|
}
|
||||||
|
header.isCompatibleClassKind() -> {
|
||||||
KotlinClassHeader.Kind.CLASS -> {
|
if (header.classKind != JvmAnnotationNames.KotlinClass.Kind.CLASS) return null
|
||||||
val classData = JvmProtoBufUtil.readClassDataFrom(annotationData)
|
val classData = JvmProtoBufUtil.readClassDataFrom(annotationData)
|
||||||
val context = components.createContext(classData.getNameResolver(), packageFqName)
|
val context = components.createContext(classData.getNameResolver(), packageFqName)
|
||||||
createTopLevelClassStub(classId, classData.getClassProto(), context)
|
createTopLevelClassStub(classId, classData.getClassProto(), context)
|
||||||
}
|
}
|
||||||
else -> throw IllegalStateException("Should have processed " + file.getPath() + " with ${header.kind}")
|
else -> throw IllegalStateException("Should have processed " + file.getPath() + " with header $header")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,10 +37,6 @@ public class InternalCompiledClassesTest : AbstractInternalCompiledClassesTest()
|
|||||||
|
|
||||||
fun testAnonymousFunctionIsInvisible() = doTestNoPsiFilesAreBuiltForSyntheticClass(ANONYMOUS_FUNCTION)
|
fun testAnonymousFunctionIsInvisible() = doTestNoPsiFilesAreBuiltForSyntheticClass(ANONYMOUS_FUNCTION)
|
||||||
|
|
||||||
fun testLocalClassIsInvisible() = doTestNoPsiFilesAreBuiltForSyntheticClass(LOCAL_CLASS)
|
|
||||||
|
|
||||||
fun testAnonymousObjectIsInvisible() = doTestNoPsiFilesAreBuiltForSyntheticClass(ANONYMOUS_OBJECT)
|
|
||||||
|
|
||||||
fun testInnerClassIsInvisible() = doTestNoPsiFilesAreBuiltFor("inner or nested class") {
|
fun testInnerClassIsInvisible() = doTestNoPsiFilesAreBuiltFor("inner or nested class") {
|
||||||
ClassFileViewProvider.isInnerClass(this)
|
ClassFileViewProvider.isInnerClass(this)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,11 +30,9 @@ import org.jetbrains.org.objectweb.asm.*
|
|||||||
import com.intellij.util.io.EnumeratorStringDescriptor
|
import com.intellij.util.io.EnumeratorStringDescriptor
|
||||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
|
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
import java.util.HashSet
|
|
||||||
import org.jetbrains.kotlin.load.kotlin.incremental.cache.IncrementalCache
|
import org.jetbrains.kotlin.load.kotlin.incremental.cache.IncrementalCache
|
||||||
import java.util.HashMap
|
import java.util.HashMap
|
||||||
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils
|
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils
|
||||||
import com.intellij.openapi.util.io.FileUtil
|
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import org.jetbrains.jps.incremental.storage.StorageOwner
|
import org.jetbrains.jps.incremental.storage.StorageOwner
|
||||||
import org.jetbrains.jps.builders.storage.StorageProvider
|
import org.jetbrains.jps.builders.storage.StorageProvider
|
||||||
@@ -137,41 +135,34 @@ public class IncrementalCacheImpl(targetDataRoot: File) : StorageOwner, Incremen
|
|||||||
dirtyOutputClassesMap.notDirty(className.getInternalName())
|
dirtyOutputClassesMap.notDirty(className.getInternalName())
|
||||||
sourceFiles.forEach { sourceToClassesMap.addSourceToClass(it, className) }
|
sourceFiles.forEach { sourceToClassesMap.addSourceToClass(it, className) }
|
||||||
|
|
||||||
val annotationDataEncoded = header.annotationData
|
return when {
|
||||||
if (annotationDataEncoded != null) {
|
header.isCompatiblePackageFacadeKind() ->
|
||||||
val data = BitEncoding.decodeBytes(annotationDataEncoded)
|
getRecompilationDecision(
|
||||||
return when {
|
protoChanged = protoMap.put(className, BitEncoding.decodeBytes(header.annotationData)),
|
||||||
header.isCompatiblePackageFacadeKind() ->
|
constantsChanged = false,
|
||||||
getRecompilationDecision(
|
inlinesChanged = false
|
||||||
protoChanged = protoMap.put(className, data),
|
)
|
||||||
constantsChanged = false,
|
header.isCompatibleClassKind() ->
|
||||||
inlinesChanged = false
|
getRecompilationDecision(
|
||||||
)
|
protoChanged = protoMap.put(className, BitEncoding.decodeBytes(header.annotationData)),
|
||||||
header.isCompatibleClassKind() ->
|
constantsChanged = constantsMap.process(className, fileBytes),
|
||||||
getRecompilationDecision(
|
inlinesChanged = inlineFunctionsMap.process(className, fileBytes)
|
||||||
protoChanged = protoMap.put(className, data),
|
)
|
||||||
constantsChanged = constantsMap.process(className, fileBytes),
|
header.syntheticClassKind == JvmAnnotationNames.KotlinSyntheticClass.Kind.PACKAGE_PART -> {
|
||||||
inlinesChanged = inlineFunctionsMap.process(className, fileBytes)
|
assert(sourceFiles.size() == 1) { "Package part from several source files: $sourceFiles" }
|
||||||
)
|
|
||||||
else -> {
|
packagePartMap.addPackagePart(className)
|
||||||
throw IllegalStateException("Unexpected kind with annotationData: ${header.kind}, isCompatible: ${header.isCompatibleAbiVersion}")
|
|
||||||
}
|
getRecompilationDecision(
|
||||||
|
protoChanged = false,
|
||||||
|
constantsChanged = constantsMap.process(className, fileBytes),
|
||||||
|
inlinesChanged = inlineFunctionsMap.process(className, fileBytes)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
DO_NOTHING
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.syntheticClassKind == JvmAnnotationNames.KotlinSyntheticClass.Kind.PACKAGE_PART) {
|
|
||||||
assert(sourceFiles.size() == 1) { "Package part from several source files: $sourceFiles" }
|
|
||||||
|
|
||||||
packagePartMap.addPackagePart(className)
|
|
||||||
|
|
||||||
return getRecompilationDecision(
|
|
||||||
protoChanged = false,
|
|
||||||
constantsChanged = constantsMap.process(className, fileBytes),
|
|
||||||
inlinesChanged = inlineFunctionsMap.process(className, fileBytes)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return DO_NOTHING
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun clearCacheForRemovedClasses(): RecompilationDecision {
|
public fun clearCacheForRemovedClasses(): RecompilationDecision {
|
||||||
|
|||||||
@@ -135,6 +135,12 @@ public class IncrementalJpsTestGenerated extends AbstractIncrementalJpsTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("anonymousObjectChanged")
|
||||||
|
public void testAnonymousObjectChanged() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("jps-plugin/testData/incremental/pureKotlin/anonymousObjectChanged/");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("classInlineFunctionChanged")
|
@TestMetadata("classInlineFunctionChanged")
|
||||||
public void testClassInlineFunctionChanged() throws Exception {
|
public void testClassInlineFunctionChanged() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("jps-plugin/testData/incremental/pureKotlin/classInlineFunctionChanged/");
|
String fileName = JetTestUtils.navigationMetadata("jps-plugin/testData/incremental/pureKotlin/classInlineFunctionChanged/");
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package a
|
||||||
|
|
||||||
|
fun foo() = object {
|
||||||
|
fun bar() = ":("
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package a
|
||||||
|
|
||||||
|
// Signatures in the class "...$main$1" have changed, thus we need to recompile the whole module
|
||||||
|
fun foo() = object {
|
||||||
|
fun baz() = ":)"
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
Cleaning output files:
|
||||||
|
out/production/module/a/APackage$a$*$foo$1.class
|
||||||
|
out/production/module/a/APackage$a$*.class
|
||||||
|
out/production/module/a/APackage.class
|
||||||
|
End of files
|
||||||
|
Compiling files:
|
||||||
|
src/a.kt
|
||||||
|
End of files
|
||||||
|
Cleaning output files:
|
||||||
|
out/production/module/usage/UsagePackage$usage$*.class
|
||||||
|
out/production/module/usage/UsagePackage.class
|
||||||
|
End of files
|
||||||
|
Compiling files:
|
||||||
|
src/usage.kt
|
||||||
|
End of files
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package usage
|
||||||
|
|
||||||
|
import a.foo
|
||||||
|
|
||||||
|
fun bar() = foo()
|
||||||
Reference in New Issue
Block a user