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:
Alexander Udalov
2015-02-14 03:27:05 +03:00
parent 98b554d995
commit 6ce8d6bd2e
34 changed files with 567 additions and 127 deletions
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.lexer.JetTokens;
import org.jetbrains.kotlin.load.java.JvmAbi;
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.name.FqName;
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.binding.CodegenBinding.enumEntryNeedSubclass;
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.descriptorToDeclaration;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.*;
@@ -218,19 +218,20 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
@Override
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;
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.create(descriptor, new JvmSerializerExtension(v.getSerializationBindings(), typeMapper));
@@ -239,8 +240,12 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
ClassData data = new ClassData(createNameResolver(serializer.getStringTable()), classProto);
AnnotationVisitor av = v.getVisitor().visitAnnotation(asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_CLASS), true);
//noinspection ConstantConditions
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);
for (String string : BitEncoding.encodeBytes(data.toBytes())) {
array.visit(null, string);
@@ -260,10 +260,13 @@ public abstract class FileBasedKotlinClass implements KotlinJvmBinaryClass {
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)) {
// 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;
}
else if (name.equals(JvmAnnotationNames.KotlinClass.KIND_INTERNAL_NAME)) {
return JvmAnnotationNames.KotlinClass.KIND_CLASS_ID;
}
List<String> classes = new ArrayList<String>(1);
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`()
@@ -22,14 +22,20 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.load.java.AbiVersionUtil;
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.resolve.jvm.JvmClassName;
import org.jetbrains.kotlin.test.ConfigurationKind;
import java.lang.annotation.Annotation;
import java.util.Collection;
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.*;
public class KotlinSyntheticClassAnnotationTest extends CodegenTestCase {
@@ -42,81 +48,122 @@ public class KotlinSyntheticClassAnnotationTest extends CodegenTestCase {
}
public void testPackagePart() {
doTest("fun foo() = 42",
"$",
PACKAGE_PART);
doTestKotlinSyntheticClass(
"fun foo() = 42",
"$",
PACKAGE_PART
);
}
public void testTraitImpl() {
doTest("trait A { fun foo() = 42 }",
JvmAbi.TRAIT_IMPL_SUFFIX,
TRAIT_IMPL);
doTestKotlinSyntheticClass(
"trait A { fun foo() = 42 }",
JvmAbi.TRAIT_IMPL_SUFFIX,
TRAIT_IMPL
);
}
public void testSamWrapper() {
doTest("val f = {}\nval foo = Thread(f)",
"$sam",
SAM_WRAPPER);
doTestKotlinSyntheticClass(
"val f = {}\nval foo = Thread(f)",
"$sam",
SAM_WRAPPER
);
}
public void testSamLambda() {
doTest("val foo = Thread { }",
"$1",
SAM_LAMBDA);
doTestKotlinSyntheticClass(
"val foo = Thread { }",
"$1",
SAM_LAMBDA
);
}
public void testCallableReferenceWrapper() {
doTest("val f = String::get",
"$1",
CALLABLE_REFERENCE_WRAPPER);
doTestKotlinSyntheticClass(
"val f = String::get",
"$1",
CALLABLE_REFERENCE_WRAPPER
);
}
public void testLocalFunction() {
doTest("fun foo() { fun bar() {} }",
"$1",
LOCAL_FUNCTION);
doTestKotlinSyntheticClass(
"fun foo() { fun bar() {} }",
"$1",
LOCAL_FUNCTION
);
}
public void testAnonymousFunction() {
doTest("val f = {}",
"$1",
ANONYMOUS_FUNCTION);
doTestKotlinSyntheticClass(
"val f = {}",
"$1",
ANONYMOUS_FUNCTION
);
}
public void testLocalClass() {
doTest("fun foo() { class Local }",
"Local",
LOCAL_CLASS);
doTestKotlinClass(
"fun foo() { class Local }",
"Local",
LOCAL_CLASS
);
}
public void testLocalTraitImpl() {
doTest("fun foo() { trait Local { fun bar() = 42 } }",
"Local$$TImpl.class",
LOCAL_TRAIT_IMPL);
doTestKotlinSyntheticClass(
"fun foo() { trait Local { fun bar() = 42 } }",
"Local$$TImpl.class",
LOCAL_TRAIT_IMPL
);
}
public void testLocalTraitInterface() {
doTest("fun foo() { trait Local { fun bar() = 42 } }",
"Local.class",
LOCAL_CLASS);
doTestKotlinClass(
"fun foo() { trait Local { fun bar() = 42 } }",
"Local.class",
LOCAL_CLASS
);
}
public void testInnerClassOfLocalClass() {
doTest("fun foo() { class Local { inner class Inner } }",
"Inner",
LOCAL_CLASS);
doTestKotlinClass(
"fun foo() { class Local { inner class Inner } }",
"Inner",
LOCAL_CLASS
);
}
public void testAnonymousObject() {
doTest("val o = object {}",
"$1",
ANONYMOUS_OBJECT);
doTestKotlinClass(
"val o = 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(
@NotNull String code,
@NotNull final String classFilePart,
@NotNull KotlinSyntheticClass.Kind expectedKind
@NotNull JvmClassName annotationName,
@NotNull String expectedKind
) {
loadText("package " + PACKAGE_NAME + "\n\n" + code);
List<OutputFile> output = generateClassesInFile().asList();
@@ -132,22 +179,26 @@ public class KotlinSyntheticClassAnnotationTest extends CodegenTestCase {
String path = files.iterator().next().getRelativePath();
String fqName = path.substring(0, path.length() - ".class".length()).replace('/', '.');
Class<?> aClass = generateClass(fqName);
assertAnnotatedWithKind(aClass, expectedKind);
assertAnnotatedWithKind(aClass, annotationName.getFqNameForClassNameWithoutDollars().asString(), expectedKind);
}
private void assertAnnotatedWithKind(@NotNull Class<?> aClass, @NotNull KotlinSyntheticClass.Kind expectedKind) {
Class<? extends Annotation> annotationClass = loadAnnotationClassQuietly(
KotlinSyntheticClass.CLASS_NAME.getFqNameForClassNameWithoutDollars().asString());
assertTrue("No KotlinSyntheticClass annotation found", aClass.isAnnotationPresent(annotationClass));
private void assertAnnotatedWithKind(
@NotNull Class<?> aClass,
@NotNull String annotationFqName,
@NotNull String expectedKind
) {
Class<? extends Annotation> annotationClass = loadAnnotationClassQuietly(annotationFqName);
assertTrue("No annotation " + annotationFqName + " found in " + aClass, aClass.isAnnotationPresent(annotationClass));
Annotation annotation = aClass.getAnnotation(annotationClass);
Integer version = (Integer) CodegenTestUtil.getAnnotationAttribute(annotation, ABI_VERSION_FIELD_NAME);
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);
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) {
StringBuilder result = new StringBuilder();
appendDeclarationRecursively(declarationDescriptor, DescriptorUtils.getContainingModule(declarationDescriptor), new Printer(result, 1), true);
return result.toString();
appendDeclarationRecursively(declarationDescriptor, DescriptorUtils.getContainingModule(declarationDescriptor),
new Printer(result, 1), true);
return JetTestUtils.replaceHashWithStar(result.toString());
}
private void appendDeclarationRecursively(
@@ -45,6 +45,19 @@ public final class JvmAnnotationNames {
public static class 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 {
@@ -54,8 +67,7 @@ public final class JvmAnnotationNames {
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".
* Both places should be updated simultaneously
* This enum duplicates {@link kotlin.jvm.internal.KotlinSyntheticClass.Kind}. Both places should be updated simultaneously.
*/
public enum Kind {
PACKAGE_PART,
@@ -66,8 +78,6 @@ public final class JvmAnnotationNames {
CALLABLE_REFERENCE_WRAPPER,
LOCAL_FUNCTION,
ANONYMOUS_FUNCTION,
LOCAL_CLASS,
ANONYMOUS_OBJECT,
;
}
}
@@ -17,22 +17,29 @@
package org.jetbrains.kotlin.load.kotlin.header
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinSyntheticClass
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinClass
import org.jetbrains.kotlin.load.java.AbiVersionUtil
public class KotlinClassHeader(
public val kind: KotlinClassHeader.Kind,
public val version: Int,
public val annotationData: Array<String>?,
public val classKind: KotlinClass.Kind?,
public val syntheticClassKind: KotlinSyntheticClass.Kind?
) {
public val isCompatibleAbiVersion: Boolean get() = AbiVersionUtil.isAbiVersionCompatible(version);
{
assert(!isCompatibleAbiVersion || (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 + ")"
if (isCompatibleAbiVersion) {
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((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
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
@@ -59,6 +59,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
private String[] annotationData = null;
private KotlinClassHeader.Kind headerKind = null;
private KotlinClass.Kind classKind = null;
private KotlinSyntheticClass.Kind syntheticClassKind = null;
@Nullable
@@ -68,7 +69,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
}
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) {
@@ -77,7 +78,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
return null;
}
return new KotlinClassHeader(headerKind, version, annotationData, syntheticClassKind);
return new KotlinClassHeader(headerKind, version, annotationData, classKind, syntheticClassKind);
}
@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
@Nullable
public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) {
@@ -203,12 +199,26 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
public ClassHeaderReader() {
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 {
public PackageHeaderReader() {
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 {
@@ -218,7 +228,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
@Override
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());
if (syntheticClassKind != null) return;
}
@@ -23,5 +23,18 @@ import java.lang.annotation.RetentionPolicy;
public @interface KotlinClass {
int abiVersion();
Kind kind();
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();
// Inner classes of local classes have kind LOCAL_CLASS
public static enum Kind {
enum Kind {
PACKAGE_PART,
TRAIT_IMPL,
LOCAL_TRAIT_IMPL,
@@ -35,7 +34,5 @@ public @interface KotlinSyntheticClass {
CALLABLE_REFERENCE_WRAPPER,
LOCAL_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.completion.AbstractMultiFileSmartCompletionTest
import org.jetbrains.kotlin.completion.handlers.AbstractCompletionCharFilterTest
import org.jetbrains.kotlin.serialization.AbstractLocalClassProtoTest
import org.jetbrains.kotlin.idea.resolve.AbstractPartialBodyResolveTest
import org.jetbrains.kotlin.checkers.AbstractJetDiagnosticsTestWithJsStdLib
import org.jetbrains.kotlin.renderer.AbstractDescriptorRendererTest
@@ -309,6 +310,10 @@ fun main(args: Array<String>) {
model("lineNumber", recursive = false)
model("lineNumber/custom", testMethod = "doTestCustom")
}
testClass(javaClass<AbstractLocalClassProtoTest>()) {
model("serialization/local")
}
}
testGroup("idea/tests", "idea/testData") {
@@ -23,7 +23,6 @@ import com.intellij.psi.stubs.PsiFileStub
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.impl.compiled.ClassFileStubBuilder
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.psi.JetFile
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.isKotlinInternalCompiledFile
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() {
override fun getStubVersion() = ClassFileStubBuilder.STUB_VERSION + 1
@@ -62,19 +64,19 @@ public class KotlinClsStubBuilder : ClsStubBuilder() {
LOG.error("Corrupted kotlin header for file ${file.getName()}")
return null
}
return when (header.kind) {
KotlinClassHeader.Kind.PACKAGE_FACADE -> {
return when {
header.isCompatiblePackageFacadeKind() -> {
val packageData = JvmProtoBufUtil.readPackageDataFrom(annotationData)
val context = components.createContext(packageData.getNameResolver(), packageFqName)
createPackageFacadeFileStub(packageData.getPackageProto(), packageFqName, context)
}
KotlinClassHeader.Kind.CLASS -> {
header.isCompatibleClassKind() -> {
if (header.classKind != JvmAnnotationNames.KotlinClass.Kind.CLASS) return null
val classData = JvmProtoBufUtil.readClassDataFrom(annotationData)
val context = components.createContext(classData.getNameResolver(), packageFqName)
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 testLocalClassIsInvisible() = doTestNoPsiFilesAreBuiltForSyntheticClass(LOCAL_CLASS)
fun testAnonymousObjectIsInvisible() = doTestNoPsiFilesAreBuiltForSyntheticClass(ANONYMOUS_OBJECT)
fun testInnerClassIsInvisible() = doTestNoPsiFilesAreBuiltFor("inner or nested class") {
ClassFileViewProvider.isInnerClass(this)
}
@@ -30,11 +30,9 @@ import org.jetbrains.org.objectweb.asm.*
import com.intellij.util.io.EnumeratorStringDescriptor
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import java.util.HashSet
import org.jetbrains.kotlin.load.kotlin.incremental.cache.IncrementalCache
import java.util.HashMap
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils
import com.intellij.openapi.util.io.FileUtil
import java.security.MessageDigest
import org.jetbrains.jps.incremental.storage.StorageOwner
import org.jetbrains.jps.builders.storage.StorageProvider
@@ -137,41 +135,34 @@ public class IncrementalCacheImpl(targetDataRoot: File) : StorageOwner, Incremen
dirtyOutputClassesMap.notDirty(className.getInternalName())
sourceFiles.forEach { sourceToClassesMap.addSourceToClass(it, className) }
val annotationDataEncoded = header.annotationData
if (annotationDataEncoded != null) {
val data = BitEncoding.decodeBytes(annotationDataEncoded)
return when {
header.isCompatiblePackageFacadeKind() ->
getRecompilationDecision(
protoChanged = protoMap.put(className, data),
constantsChanged = false,
inlinesChanged = false
)
header.isCompatibleClassKind() ->
getRecompilationDecision(
protoChanged = protoMap.put(className, data),
constantsChanged = constantsMap.process(className, fileBytes),
inlinesChanged = inlineFunctionsMap.process(className, fileBytes)
)
else -> {
throw IllegalStateException("Unexpected kind with annotationData: ${header.kind}, isCompatible: ${header.isCompatibleAbiVersion}")
}
return when {
header.isCompatiblePackageFacadeKind() ->
getRecompilationDecision(
protoChanged = protoMap.put(className, BitEncoding.decodeBytes(header.annotationData)),
constantsChanged = false,
inlinesChanged = false
)
header.isCompatibleClassKind() ->
getRecompilationDecision(
protoChanged = protoMap.put(className, BitEncoding.decodeBytes(header.annotationData)),
constantsChanged = constantsMap.process(className, fileBytes),
inlinesChanged = inlineFunctionsMap.process(className, fileBytes)
)
header.syntheticClassKind == JvmAnnotationNames.KotlinSyntheticClass.Kind.PACKAGE_PART -> {
assert(sourceFiles.size() == 1) { "Package part from several source files: $sourceFiles" }
packagePartMap.addPackagePart(className)
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 {
@@ -135,6 +135,12 @@ public class IncrementalJpsTestGenerated extends AbstractIncrementalJpsTest {
doTest(fileName);
}
@TestMetadata("anonymousObjectChanged")
public void testAnonymousObjectChanged() throws Exception {
String fileName = JetTestUtils.navigationMetadata("jps-plugin/testData/incremental/pureKotlin/anonymousObjectChanged/");
doTest(fileName);
}
@TestMetadata("classInlineFunctionChanged")
public void testClassInlineFunctionChanged() throws Exception {
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()