JVM IR: retain optional annotations as package private classes

Similarly to how it's done in the old backend (see
PackageCodegenImpl.generateClassesAndObjectsInFile and
AnnotationCodegen.genAnnotation)
This commit is contained in:
Alexander Udalov
2019-12-17 19:25:37 +01:00
parent 5d3ef4c632
commit 0cb48999ff
9 changed files with 76 additions and 14 deletions
@@ -15,9 +15,9 @@ import org.jetbrains.kotlin.ir.visitors.acceptVoid
/**
* This pass removes all declarations with `isExpect == true`.
*/
class ExpectDeclarationsRemoveLowering(val context: BackendContext) : FileLoweringPass {
class ExpectDeclarationsRemoveLowering(val context: BackendContext, keepOptionalAnnotations: Boolean = false) : FileLoweringPass {
val visitor = ExpectDeclarationRemover(context.ir.symbols.externalSymbolTable, true)
val visitor = ExpectDeclarationRemover(context.ir.symbols.externalSymbolTable, doRemove = true, keepOptionalAnnotations)
override fun lower(irFile: IrFile) {
irFile.acceptVoid(visitor)
@@ -74,8 +74,8 @@ private val arrayConstructorPhase = makeIrFilePhase(
description = "Transform `Array(size) { index -> value }` into a loop"
)
private val expectDeclarationsRemovingPhase = makeIrModulePhase(
::ExpectDeclarationsRemoveLowering,
private val expectDeclarationsRemovingPhase = makeIrModulePhase<JvmBackendContext>(
{ context -> ExpectDeclarationsRemoveLowering(context, keepOptionalAnnotations = true) },
name = "ExpectDeclarationsRemoving",
description = "Remove expect declaration from module fragment"
)
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.ir.types.isNullable
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker
import org.jetbrains.kotlin.synthetic.isVisibleOutside
import org.jetbrains.org.objectweb.asm.AnnotationVisitor
import org.jetbrains.org.objectweb.asm.Type
@@ -156,6 +157,13 @@ class AnnotationCodegen(
val retentionPolicy = getRetentionPolicy(annotationClass)
if (retentionPolicy == RetentionPolicy.SOURCE) return null
// We do not generate annotations whose classes are optional (annotated with `@OptionalExpectation`) because if an annotation entry
// is resolved to the expected declaration, this means that annotation has no actual class, and thus should not be generated.
// (Otherwise we would've resolved the entry to the actual annotation class.)
if (ExpectedActualDeclarationChecker.isOptionalAnnotationClass(annotationClass.descriptor)) {
return null
}
innerClassConsumer.addInnerClassInfoFromAnnotation(annotationClass)
val asmTypeDescriptor = typeMapper.mapType(annotation.type).descriptor
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.ir.util
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.MemberDescriptor
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.*
@@ -22,7 +23,11 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver
// `doRemove` means should expect-declaration be removed from IR
class ExpectDeclarationRemover(val symbolTable: ReferenceSymbolTable, private val doRemove: Boolean) : IrElementVisitorVoid {
class ExpectDeclarationRemover(
val symbolTable: ReferenceSymbolTable,
private val doRemove: Boolean,
private val keepOptionalAnnotations: Boolean
) : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
@@ -30,12 +35,16 @@ class ExpectDeclarationRemover(val symbolTable: ReferenceSymbolTable, private va
override fun visitFile(declaration: IrFile) {
// All declarations with `isExpect == true` are nested into a top-level declaration with `isExpect == true`.
declaration.declarations.removeAll {
// TODO: rewrite findCompatibleActualForExpected using IR structures instead of descriptors
val descriptor = it.descriptor
if (descriptor is MemberDescriptor && descriptor.isExpect) {
if (descriptor !is MemberDescriptor || !descriptor.isExpect ||
(keepOptionalAnnotations && descriptor is ClassDescriptor &&
ExpectedActualDeclarationChecker.shouldGenerateExpectClass(descriptor))
) {
false
} else {
copyDefaultArgumentsFromExpectToActual(it)
doRemove
} else {
false
}
}
}
@@ -56,9 +65,7 @@ class ExpectDeclarationRemover(val symbolTable: ReferenceSymbolTable, private va
assert(function.valueParameters[index] == declaration)
if (function is IrConstructor &&
ExpectedActualDeclarationChecker.isOptionalAnnotationClass(
function.descriptor.constructedClass
)
ExpectedActualDeclarationChecker.isOptionalAnnotationClass(function.descriptor.constructedClass)
) {
return
}
@@ -145,4 +152,4 @@ class ExpectDeclarationRemover(val symbolTable: ReferenceSymbolTable, private va
else -> error(parent)
}
}
}
}
@@ -136,7 +136,7 @@ fun generateKLib(
val moduleName = configuration[CommonConfigurationKeys.MODULE_NAME]!!
if (!configuration.klibMpp) {
moduleFragment.acceptVoid(ExpectDeclarationRemover(psi2IrContext.symbolTable, false))
moduleFragment.acceptVoid(ExpectDeclarationRemover(psi2IrContext.symbolTable, doRemove = false, keepOptionalAnnotations = false))
}
serializeModuleIntoKlib(
@@ -1,6 +1,5 @@
// !LANGUAGE: +MultiPlatformProjects
// !USE_EXPERIMENTAL: kotlin.ExperimentalMultiplatform
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// FULL_JDK
// FILE: A.kt
@@ -0,0 +1,12 @@
// !LANGUAGE: +MultiPlatformProjects
// !USE_EXPERIMENTAL: kotlin.ExperimentalMultiplatform
// WITH_RUNTIME
@file:Suppress("OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE") // TODO: support common sources in the test infrastructure
@OptionalExpectation
expect annotation class Anno(val s: String)
// TESTED_OBJECT_KIND: class
// TESTED_OBJECTS: Anno
// FLAGS: ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
@@ -872,6 +872,24 @@ public class WriteFlagsTestGenerated extends AbstractWriteFlagsTest {
}
}
@TestMetadata("compiler/testData/writeFlags/multiplatform")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Multiplatform extends AbstractWriteFlagsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
public void testAllFilesPresentInMultiplatform() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/writeFlags/multiplatform"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
}
@TestMetadata("optionalExpectation.kt")
public void testOptionalExpectation() throws Exception {
runTest("compiler/testData/writeFlags/multiplatform/optionalExpectation.kt");
}
}
@TestMetadata("compiler/testData/writeFlags/property")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -872,6 +872,24 @@ public class IrWriteFlagsTestGenerated extends AbstractIrWriteFlagsTest {
}
}
@TestMetadata("compiler/testData/writeFlags/multiplatform")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Multiplatform extends AbstractIrWriteFlagsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
}
public void testAllFilesPresentInMultiplatform() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/writeFlags/multiplatform"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@TestMetadata("optionalExpectation.kt")
public void testOptionalExpectation() throws Exception {
runTest("compiler/testData/writeFlags/multiplatform/optionalExpectation.kt");
}
}
@TestMetadata("compiler/testData/writeFlags/property")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)