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:
+2
-2
@@ -15,9 +15,9 @@ import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
|||||||
/**
|
/**
|
||||||
* This pass removes all declarations with `isExpect == true`.
|
* 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) {
|
override fun lower(irFile: IrFile) {
|
||||||
irFile.acceptVoid(visitor)
|
irFile.acceptVoid(visitor)
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ private val arrayConstructorPhase = makeIrFilePhase(
|
|||||||
description = "Transform `Array(size) { index -> value }` into a loop"
|
description = "Transform `Array(size) { index -> value }` into a loop"
|
||||||
)
|
)
|
||||||
|
|
||||||
private val expectDeclarationsRemovingPhase = makeIrModulePhase(
|
private val expectDeclarationsRemovingPhase = makeIrModulePhase<JvmBackendContext>(
|
||||||
::ExpectDeclarationsRemoveLowering,
|
{ context -> ExpectDeclarationsRemoveLowering(context, keepOptionalAnnotations = true) },
|
||||||
name = "ExpectDeclarationsRemoving",
|
name = "ExpectDeclarationsRemoving",
|
||||||
description = "Remove expect declaration from module fragment"
|
description = "Remove expect declaration from module fragment"
|
||||||
)
|
)
|
||||||
|
|||||||
+8
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.ir.types.isNullable
|
|||||||
import org.jetbrains.kotlin.ir.util.*
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker
|
||||||
import org.jetbrains.kotlin.synthetic.isVisibleOutside
|
import org.jetbrains.kotlin.synthetic.isVisibleOutside
|
||||||
import org.jetbrains.org.objectweb.asm.AnnotationVisitor
|
import org.jetbrains.org.objectweb.asm.AnnotationVisitor
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
@@ -156,6 +157,13 @@ class AnnotationCodegen(
|
|||||||
val retentionPolicy = getRetentionPolicy(annotationClass)
|
val retentionPolicy = getRetentionPolicy(annotationClass)
|
||||||
if (retentionPolicy == RetentionPolicy.SOURCE) return null
|
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)
|
innerClassConsumer.addInnerClassInfoFromAnnotation(annotationClass)
|
||||||
|
|
||||||
val asmTypeDescriptor = typeMapper.mapType(annotation.type).descriptor
|
val asmTypeDescriptor = typeMapper.mapType(annotation.type).descriptor
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.ir.util
|
package org.jetbrains.kotlin.ir.util
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.MemberDescriptor
|
import org.jetbrains.kotlin.descriptors.MemberDescriptor
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
@@ -22,7 +23,11 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
|||||||
import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver
|
import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver
|
||||||
|
|
||||||
// `doRemove` means should expect-declaration be removed from IR
|
// `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) {
|
override fun visitElement(element: IrElement) {
|
||||||
element.acceptChildrenVoid(this)
|
element.acceptChildrenVoid(this)
|
||||||
}
|
}
|
||||||
@@ -30,12 +35,16 @@ class ExpectDeclarationRemover(val symbolTable: ReferenceSymbolTable, private va
|
|||||||
override fun visitFile(declaration: IrFile) {
|
override fun visitFile(declaration: IrFile) {
|
||||||
// All declarations with `isExpect == true` are nested into a top-level declaration with `isExpect == true`.
|
// All declarations with `isExpect == true` are nested into a top-level declaration with `isExpect == true`.
|
||||||
declaration.declarations.removeAll {
|
declaration.declarations.removeAll {
|
||||||
|
// TODO: rewrite findCompatibleActualForExpected using IR structures instead of descriptors
|
||||||
val descriptor = it.descriptor
|
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)
|
copyDefaultArgumentsFromExpectToActual(it)
|
||||||
doRemove
|
doRemove
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,9 +65,7 @@ class ExpectDeclarationRemover(val symbolTable: ReferenceSymbolTable, private va
|
|||||||
assert(function.valueParameters[index] == declaration)
|
assert(function.valueParameters[index] == declaration)
|
||||||
|
|
||||||
if (function is IrConstructor &&
|
if (function is IrConstructor &&
|
||||||
ExpectedActualDeclarationChecker.isOptionalAnnotationClass(
|
ExpectedActualDeclarationChecker.isOptionalAnnotationClass(function.descriptor.constructedClass)
|
||||||
function.descriptor.constructedClass
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -145,4 +152,4 @@ class ExpectDeclarationRemover(val symbolTable: ReferenceSymbolTable, private va
|
|||||||
else -> error(parent)
|
else -> error(parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ fun generateKLib(
|
|||||||
val moduleName = configuration[CommonConfigurationKeys.MODULE_NAME]!!
|
val moduleName = configuration[CommonConfigurationKeys.MODULE_NAME]!!
|
||||||
|
|
||||||
if (!configuration.klibMpp) {
|
if (!configuration.klibMpp) {
|
||||||
moduleFragment.acceptVoid(ExpectDeclarationRemover(psi2IrContext.symbolTable, false))
|
moduleFragment.acceptVoid(ExpectDeclarationRemover(psi2IrContext.symbolTable, doRemove = false, keepOptionalAnnotations = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeModuleIntoKlib(
|
serializeModuleIntoKlib(
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// !LANGUAGE: +MultiPlatformProjects
|
// !LANGUAGE: +MultiPlatformProjects
|
||||||
// !USE_EXPERIMENTAL: kotlin.ExperimentalMultiplatform
|
// !USE_EXPERIMENTAL: kotlin.ExperimentalMultiplatform
|
||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
// FULL_JDK
|
// FULL_JDK
|
||||||
// FILE: A.kt
|
// 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
|
||||||
+18
@@ -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")
|
@TestMetadata("compiler/testData/writeFlags/property")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
+18
@@ -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")
|
@TestMetadata("compiler/testData/writeFlags/property")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user