diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java index 904f631ba19..629f5748625 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java @@ -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); diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/FileBasedKotlinClass.java b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/FileBasedKotlinClass.java index a3ca6f30259..b856fe42849 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/FileBasedKotlinClass.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/FileBasedKotlinClass.java @@ -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 classes = new ArrayList(1); boolean local = false; diff --git a/compiler/testData/serialization/local/annotationsInLocalClass.kt b/compiler/testData/serialization/local/annotationsInLocalClass.kt new file mode 100644 index 00000000000..18883f85d7c --- /dev/null +++ b/compiler/testData/serialization/local/annotationsInLocalClass.kt @@ -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 + } + } +} diff --git a/compiler/testData/serialization/local/annotationsInLocalClass.txt b/compiler/testData/serialization/local/annotationsInLocalClass.txt new file mode 100644 index 00000000000..5050b729245 --- /dev/null +++ b/compiler/testData/serialization/local/annotationsInLocalClass.txt @@ -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() +} diff --git a/compiler/testData/serialization/local/anonymousObject.kt b/compiler/testData/serialization/local/anonymousObject.kt new file mode 100644 index 00000000000..57abb278522 --- /dev/null +++ b/compiler/testData/serialization/local/anonymousObject.kt @@ -0,0 +1,5 @@ +// CLASS_NAME_SUFFIX: $main$1 + +fun main() = object : Runnable { + override fun run() { } +} diff --git a/compiler/testData/serialization/local/anonymousObject.txt b/compiler/testData/serialization/local/anonymousObject.txt new file mode 100644 index 00000000000..8a05a35200c --- /dev/null +++ b/compiler/testData/serialization/local/anonymousObject.txt @@ -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 diff --git a/compiler/testData/serialization/local/deepInnerChain.kt b/compiler/testData/serialization/local/deepInnerChain.kt new file mode 100644 index 00000000000..5d059d89214 --- /dev/null +++ b/compiler/testData/serialization/local/deepInnerChain.kt @@ -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() + } + } + } + } + } + } + } +} diff --git a/compiler/testData/serialization/local/deepInnerChain.txt b/compiler/testData/serialization/local/deepInnerChain.txt new file mode 100644 index 00000000000..2c3ba294350 --- /dev/null +++ b/compiler/testData/serialization/local/deepInnerChain.txt @@ -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` diff --git a/compiler/testData/serialization/local/innerOfLocal.kt b/compiler/testData/serialization/local/innerOfLocal.kt new file mode 100644 index 00000000000..3215f89d4e8 --- /dev/null +++ b/compiler/testData/serialization/local/innerOfLocal.kt @@ -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) {} + } + } +} diff --git a/compiler/testData/serialization/local/innerOfLocal.txt b/compiler/testData/serialization/local/innerOfLocal.txt new file mode 100644 index 00000000000..55de5348986 --- /dev/null +++ b/compiler/testData/serialization/local/innerOfLocal.txt @@ -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 diff --git a/compiler/testData/serialization/local/localClassInSignature.kt b/compiler/testData/serialization/local/localClassInSignature.kt new file mode 100644 index 00000000000..f9a4902c3b4 --- /dev/null +++ b/compiler/testData/serialization/local/localClassInSignature.kt @@ -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 generic(t: T): U = null!! + } +} diff --git a/compiler/testData/serialization/local/localClassInSignature.txt b/compiler/testData/serialization/local/localClassInSignature.txt new file mode 100644 index 00000000000..fbfc1a90a3b --- /dev/null +++ b/compiler/testData/serialization/local/localClassInSignature.txt @@ -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 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` diff --git a/compiler/testData/serialization/local/simpleInMemberFunction.kt b/compiler/testData/serialization/local/simpleInMemberFunction.kt new file mode 100644 index 00000000000..42f37490fc9 --- /dev/null +++ b/compiler/testData/serialization/local/simpleInMemberFunction.kt @@ -0,0 +1,7 @@ +// CLASS_NAME_SUFFIX: A$foo$Local + +class A { + fun foo() { + class Local + } +} diff --git a/compiler/testData/serialization/local/simpleInMemberFunction.txt b/compiler/testData/serialization/local/simpleInMemberFunction.txt new file mode 100644 index 00000000000..e81f5e2ab94 --- /dev/null +++ b/compiler/testData/serialization/local/simpleInMemberFunction.txt @@ -0,0 +1,3 @@ +local final class `A$foo$Local` + +public constructor `A$foo$Local`() diff --git a/compiler/testData/serialization/local/simpleInTopLevelFunction.kt b/compiler/testData/serialization/local/simpleInTopLevelFunction.kt new file mode 100644 index 00000000000..bf8b5cb4339 --- /dev/null +++ b/compiler/testData/serialization/local/simpleInTopLevelFunction.kt @@ -0,0 +1,5 @@ +// CLASS_NAME_SUFFIX: $main$Local + +fun main() { + class Local +} diff --git a/compiler/testData/serialization/local/simpleInTopLevelFunction.txt b/compiler/testData/serialization/local/simpleInTopLevelFunction.txt new file mode 100644 index 00000000000..4d093909b6b --- /dev/null +++ b/compiler/testData/serialization/local/simpleInTopLevelFunction.txt @@ -0,0 +1,3 @@ +local final class `_DefaultPackage$simpleInTopLevelFunction$*$main$Local` + +public constructor `_DefaultPackage$simpleInTopLevelFunction$*$main$Local`() diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/KotlinSyntheticClassAnnotationTest.java b/compiler/tests/org/jetbrains/kotlin/codegen/KotlinSyntheticClassAnnotationTest.java index c9c94811458..115e93f9530 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/KotlinSyntheticClassAnnotationTest.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/KotlinSyntheticClassAnnotationTest.java @@ -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 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 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 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()); } } diff --git a/compiler/tests/org/jetbrains/kotlin/serialization/AbstractLocalClassProtoTest.kt b/compiler/tests/org/jetbrains/kotlin/serialization/AbstractLocalClassProtoTest.kt new file mode 100644 index 00000000000..c58543f4c30 --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/serialization/AbstractLocalClassProtoTest.kt @@ -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 + ) + 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)" + } + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/serialization/LocalClassProtoTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/serialization/LocalClassProtoTestGenerated.java new file mode 100644 index 00000000000..4dffacfb95c --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/serialization/LocalClassProtoTestGenerated.java @@ -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); + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/test/util/RecursiveDescriptorComparator.java b/compiler/tests/org/jetbrains/kotlin/test/util/RecursiveDescriptorComparator.java index 9e453d93464..06c52b32b86 100644 --- a/compiler/tests/org/jetbrains/kotlin/test/util/RecursiveDescriptorComparator.java +++ b/compiler/tests/org/jetbrains/kotlin/test/util/RecursiveDescriptorComparator.java @@ -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( diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java index 623d4a13534..ee2901f4b6b 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java @@ -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, ; } } diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/KotlinClassHeader.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/KotlinClassHeader.kt index ce76d70dc80..9f8f5942b85 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/KotlinClassHeader.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/KotlinClassHeader.kt @@ -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?, + 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 diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/ReadKotlinClassHeaderAnnotationVisitor.java b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/ReadKotlinClassHeaderAnnotationVisitor.java index a25a862e25d..2ef14721a66 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/ReadKotlinClassHeaderAnnotationVisitor.java +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/ReadKotlinClassHeaderAnnotationVisitor.java @@ -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; } diff --git a/core/runtime.jvm/src/kotlin/jvm/internal/KotlinClass.java b/core/runtime.jvm/src/kotlin/jvm/internal/KotlinClass.java index 619c47c0c62..8efd073d8f3 100644 --- a/core/runtime.jvm/src/kotlin/jvm/internal/KotlinClass.java +++ b/core/runtime.jvm/src/kotlin/jvm/internal/KotlinClass.java @@ -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, + } } diff --git a/core/runtime.jvm/src/kotlin/jvm/internal/KotlinSyntheticClass.java b/core/runtime.jvm/src/kotlin/jvm/internal/KotlinSyntheticClass.java index ff8a0eabbae..987f9d1a870 100644 --- a/core/runtime.jvm/src/kotlin/jvm/internal/KotlinSyntheticClass.java +++ b/core/runtime.jvm/src/kotlin/jvm/internal/KotlinSyntheticClass.java @@ -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, } } diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 3e799806013..0de0e8edfbe 100644 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -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) { model("lineNumber", recursive = false) model("lineNumber/custom", testMethod = "doTestCustom") } + + testClass(javaClass()) { + model("serialization/local") + } } testGroup("idea/tests", "idea/testData") { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/stubBuilder/KotlinClsStubBuilder.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/stubBuilder/KotlinClsStubBuilder.kt index afae5e356cb..83ac5eaa7dc 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/stubBuilder/KotlinClsStubBuilder.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/stubBuilder/KotlinClsStubBuilder.kt @@ -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") } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/decompiler/InternalCompiledClassesTest.kt b/idea/tests/org/jetbrains/kotlin/idea/decompiler/InternalCompiledClassesTest.kt index ea3b245e621..562c3c7b56d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/decompiler/InternalCompiledClassesTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/decompiler/InternalCompiledClassesTest.kt @@ -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) } diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/incremental/IncrementalCacheImpl.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/incremental/IncrementalCacheImpl.kt index 273806777cf..a45bc64f8ad 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/jps/incremental/IncrementalCacheImpl.kt +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/incremental/IncrementalCacheImpl.kt @@ -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 { diff --git a/jps-plugin/test/org/jetbrains/kotlin/jps/build/IncrementalJpsTestGenerated.java b/jps-plugin/test/org/jetbrains/kotlin/jps/build/IncrementalJpsTestGenerated.java index 061b7694af7..a8e6b201fb0 100644 --- a/jps-plugin/test/org/jetbrains/kotlin/jps/build/IncrementalJpsTestGenerated.java +++ b/jps-plugin/test/org/jetbrains/kotlin/jps/build/IncrementalJpsTestGenerated.java @@ -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/"); diff --git a/jps-plugin/testData/incremental/pureKotlin/anonymousObjectChanged/a.kt b/jps-plugin/testData/incremental/pureKotlin/anonymousObjectChanged/a.kt new file mode 100644 index 00000000000..842232877de --- /dev/null +++ b/jps-plugin/testData/incremental/pureKotlin/anonymousObjectChanged/a.kt @@ -0,0 +1,5 @@ +package a + +fun foo() = object { + fun bar() = ":(" +} diff --git a/jps-plugin/testData/incremental/pureKotlin/anonymousObjectChanged/a.kt.new b/jps-plugin/testData/incremental/pureKotlin/anonymousObjectChanged/a.kt.new new file mode 100644 index 00000000000..6255f915194 --- /dev/null +++ b/jps-plugin/testData/incremental/pureKotlin/anonymousObjectChanged/a.kt.new @@ -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() = ":)" +} diff --git a/jps-plugin/testData/incremental/pureKotlin/anonymousObjectChanged/build.log b/jps-plugin/testData/incremental/pureKotlin/anonymousObjectChanged/build.log new file mode 100644 index 00000000000..d2598525c01 --- /dev/null +++ b/jps-plugin/testData/incremental/pureKotlin/anonymousObjectChanged/build.log @@ -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 \ No newline at end of file diff --git a/jps-plugin/testData/incremental/pureKotlin/anonymousObjectChanged/usage.kt b/jps-plugin/testData/incremental/pureKotlin/anonymousObjectChanged/usage.kt new file mode 100644 index 00000000000..acc1e2b0f5a --- /dev/null +++ b/jps-plugin/testData/incremental/pureKotlin/anonymousObjectChanged/usage.kt @@ -0,0 +1,5 @@ +package usage + +import a.foo + +fun bar() = foo()