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:
+96
-45
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user