[FIR plugin] Add test for plugin which generates annotations on IR

^KT-58638
This commit is contained in:
Dmitriy Novozhilov
2023-05-31 16:19:28 +03:00
committed by Space Team
parent fd670d33cb
commit 24e07fdfe0
13 changed files with 300 additions and 25 deletions
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.assignment.plugin.AbstractFirLightTreeBlackBoxCodege
import org.jetbrains.kotlin.assignment.plugin.AbstractFirPsiAssignmentPluginDiagnosticTest
import org.jetbrains.kotlin.assignment.plugin.AbstractIrBlackBoxCodegenTestAssignmentPlugin
import org.jetbrains.kotlin.fir.plugin.runners.AbstractFirLightTreePluginBlackBoxCodegenTest
import org.jetbrains.kotlin.fir.plugin.runners.AbstractFirLoadK2CompiledWithPluginJsKotlinTest
import org.jetbrains.kotlin.fir.plugin.runners.AbstractFirLoadK2CompiledWithPluginJvmKotlinTest
import org.jetbrains.kotlin.fir.plugin.runners.AbstractFirPsiPluginDiagnosticTest
import org.jetbrains.kotlin.generators.generateTestGroupSuiteWithJUnit5
@@ -265,6 +266,10 @@ fun main(args: Array<String>) {
testClass<AbstractFirLoadK2CompiledWithPluginJvmKotlinTest> {
model("firLoadK2Compiled")
}
testClass<AbstractFirLoadK2CompiledWithPluginJsKotlinTest> {
model("firLoadK2Compiled")
}
}
testGroup(
@@ -23,6 +23,7 @@ dependencies {
testApi(projectTests(":compiler:test-infrastructure"))
testApi(projectTests(":compiler:test-infrastructure-utils"))
testApi(projectTests(":compiler:fir:analysis-tests"))
testApi(projectTests(":js:js.tests"))
testApi(project(":compiler:fir:checkers"))
testApi(project(":compiler:fir:checkers:checkers.jvm"))
testApi(project(":compiler:fir:checkers:checkers.js"))
@@ -39,3 +39,7 @@ annotation class MetaSupertype
annotation class MyComposable
annotation class AllPropertiesConstructor
annotation class AddAnnotations
annotation class AnnotationToAdd
@@ -17,7 +17,8 @@ class GeneratedDeclarationsIrBodyFiller : IrGenerationExtension {
TransformerForCompanionGenerator(pluginContext),
TransformerForAdditionalMembersGenerator(pluginContext),
TransformerForTopLevelDeclarationsGenerator(pluginContext),
AllPropertiesConstructorIrGenerator(pluginContext)
AllPropertiesConstructorIrGenerator(pluginContext),
TransformerForAddingAnnotations(pluginContext),
)
for (transformer in transformers) {
@@ -0,0 +1,73 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.plugin
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.isAnnotationClass
import org.jetbrains.kotlin.ir.util.isFakeOverride
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
class TransformerForAddingAnnotations(val context: IrPluginContext) : IrElementVisitorVoid {
companion object {
private val markerAnnotationFqName = FqName("org.jetbrains.kotlin.fir.plugin.AddAnnotations")
private val annotationToAddId = ClassId(FqName("org.jetbrains.kotlin.fir.plugin"), Name.identifier("AnnotationToAdd"))
private val annotationToAddFqName = annotationToAddId.asSingleFqName()
}
private val annotationsAdder = AnnotationsAdder()
override fun visitElement(element: IrElement) {
when (element) {
is IrFile,
is IrModuleFragment -> element.acceptChildrenVoid(this)
else -> {}
}
}
override fun visitClass(declaration: IrClass) {
if (declaration.hasAnnotation(markerAnnotationFqName)) {
declaration.acceptVoid(annotationsAdder)
}
}
private inner class AnnotationsAdder : IrElementVisitorVoid {
val annotationClass = context.referenceClass(annotationToAddId)?.takeIf { it.owner.isAnnotationClass }
override fun visitElement(element: IrElement, data: Nothing?) {}
override fun visitDeclaration(declaration: IrDeclarationBase) {
addAnnotation(declaration)
declaration.acceptChildrenVoid(this)
}
override fun visitFunction(declaration: IrFunction) {
if (declaration.isFakeOverride) return
visitDeclaration(declaration)
}
private fun addAnnotation(declaration: IrDeclarationBase) {
if (declaration.hasAnnotation(annotationToAddFqName)) return
val annotationClass = annotationClass ?: return
val annotationConstructor = annotationClass.owner.constructors.first()
val annotationCall = IrConstructorCallImpl.fromSymbolOwner(
type = annotationClass.defaultType,
constructorSymbol = annotationConstructor.symbol
)
declaration.annotations += annotationCall
}
}
}
@@ -0,0 +1,14 @@
@R|org/jetbrains/kotlin/fir/plugin/AddAnnotations|() @R|org/jetbrains/kotlin/fir/plugin/AnnotationToAdd|() public final class Some : R|kotlin/Any| {
@R|org/jetbrains/kotlin/fir/plugin/AnnotationToAdd|() public final fun foo(): R|kotlin/Unit|
@PROPERTY:R|org/jetbrains/kotlin/fir/plugin/AnnotationToAdd|() field:@FIELD:R|org/jetbrains/kotlin/fir/plugin/AnnotationToAdd|() public final val x: R|kotlin/Int|
@R|org/jetbrains/kotlin/fir/plugin/AnnotationToAdd|() public get(): R|kotlin/Int|
@R|org/jetbrains/kotlin/fir/plugin/AnnotationToAdd|() public constructor(@R|org/jetbrains/kotlin/fir/plugin/AnnotationToAdd|() x: R|kotlin/Int|): R|test/Some|
@R|org/jetbrains/kotlin/fir/plugin/AnnotationToAdd|() public final class Derived : R|kotlin/Any| {
@R|org/jetbrains/kotlin/fir/plugin/AnnotationToAdd|() public constructor(): R|test/Some.Derived|
}
}
@@ -0,0 +1,14 @@
@R|org/jetbrains/kotlin/fir/plugin/AddAnnotations|() public final class Some : R|kotlin/Any| {
public final fun foo(): R|kotlin/Unit|
public final val x: R|kotlin/Int|
public get(): R|kotlin/Int|
public constructor(x: R|kotlin/Int|): R|test/Some|
public final class Derived : R|kotlin/Any| {
public constructor(): R|test/Some.Derived|
}
}
@@ -0,0 +1,35 @@
package test
@AddAnnotations
@AnnotationToAdd
class Some {
@AnnotationToAdd
constructor(@AnnotationToAdd x: Int) /* primary */ {
super/*Any*/()
/* <init>() */
}
@AnnotationToAdd
val x: Int
field = x
@AnnotationToAdd
get
@AnnotationToAdd
fun foo() {
}
@AnnotationToAdd
class Derived {
@AnnotationToAdd
constructor() /* primary */ {
super/*Any*/()
/* <init>() */
}
}
}
@@ -0,0 +1,12 @@
// PLATFORM_DEPENDANT_METADATA
// DUMP_KT_IR
package test
import org.jetbrains.kotlin.fir.plugin.AddAnnotations
@AddAnnotations
class Some(val x: Int) {
fun foo() {}
class Derived
}
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.plugin.runners;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import org.jetbrains.kotlin.test.TargetBackend;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.GenerateTestsKt}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("plugins/fir-plugin-prototype/testData/firLoadK2Compiled")
@TestDataPath("$PROJECT_ROOT")
public class FirLoadK2CompiledWithPluginJsKotlinTestGenerated extends AbstractFirLoadK2CompiledWithPluginJsKotlinTest {
@Test
public void testAllFilesPresentInFirLoadK2Compiled() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/fir-plugin-prototype/testData/firLoadK2Compiled"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true);
}
@Test
@TestMetadata("annotationsGeneratedInBackend.kt")
public void testAnnotationsGeneratedInBackend() throws Exception {
runTest("plugins/fir-plugin-prototype/testData/firLoadK2Compiled/annotationsGeneratedInBackend.kt");
}
@Test
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("plugins/fir-plugin-prototype/testData/firLoadK2Compiled/simple.kt");
}
@Test
@TestMetadata("simple-lang-ver-2.1.kt")
public void testSimple_lang_ver_2_1() throws Exception {
runTest("plugins/fir-plugin-prototype/testData/firLoadK2Compiled/simple-lang-ver-2.1.kt");
}
}
@@ -25,6 +25,12 @@ public class FirLoadK2CompiledWithPluginJvmKotlinTestGenerated extends AbstractF
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/fir-plugin-prototype/testData/firLoadK2Compiled"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@Test
@TestMetadata("annotationsGeneratedInBackend.kt")
public void testAnnotationsGeneratedInBackend() throws Exception {
runTest("plugins/fir-plugin-prototype/testData/firLoadK2Compiled/annotationsGeneratedInBackend.kt");
}
@Test
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
@@ -7,7 +7,11 @@ package org.jetbrains.kotlin.fir.plugin.runners
import org.jetbrains.kotlin.fir.plugin.services.ExtensionRegistrarConfigurator
import org.jetbrains.kotlin.fir.plugin.services.PluginAnnotationsProvider
import org.jetbrains.kotlin.fir.plugin.services.PluginRuntimeAnnotationsProvider
import org.jetbrains.kotlin.js.test.fir.AbstractFirLoadK2CompiledJsKotlinTest
import org.jetbrains.kotlin.test.backend.handlers.IrPrettyKotlinDumpHandler
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.builders.configureIrHandlersStep
import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives.ENABLE_PLUGIN_PHASES
import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives.FIR_DUMP
import org.jetbrains.kotlin.test.runners.AbstractFirLoadK2CompiledJvmKotlinTest
@@ -30,6 +34,18 @@ abstract class AbstractFirPsiPluginDiagnosticTest : AbstractFirPsiDiagnosticTest
}
open class AbstractFirLoadK2CompiledWithPluginJvmKotlinTest : AbstractFirLoadK2CompiledJvmKotlinTest() {
override fun configure(builder: TestConfigurationBuilder) {
super.configure(builder)
with(builder) {
commonFirWithPluginFrontendConfiguration()
configureIrHandlersStep {
useHandlers(::IrPrettyKotlinDumpHandler)
}
}
}
}
open class AbstractFirLoadK2CompiledWithPluginJsKotlinTest : AbstractFirLoadK2CompiledJsKotlinTest() {
override fun configure(builder: TestConfigurationBuilder) {
super.configure(builder)
builder.commonFirWithPluginFrontendConfiguration()
@@ -48,4 +64,9 @@ fun TestConfigurationBuilder.commonFirWithPluginFrontendConfiguration() {
::PluginAnnotationsProvider,
::ExtensionRegistrarConfigurator
)
useCustomRuntimeClasspathProviders(
::PluginRuntimeAnnotationsProvider
)
}
@@ -7,38 +7,82 @@ package org.jetbrains.kotlin.fir.plugin.services
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoot
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.platform.isJs
import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.EnvironmentConfigurator
import org.jetbrains.kotlin.test.services.RuntimeClasspathProvider
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.assertions
import java.io.File
import java.io.FilenameFilter
class PluginAnnotationsProvider(testServices: TestServices) : EnvironmentConfigurator(testServices) {
companion object {
private const val ANNOTATIONS_JAR_DIR = "plugins/fir-plugin-prototype/plugin-annotations/build/libs/"
private val ANNOTATIONS_JAR_FILTER = FilenameFilter { _, name -> name.startsWith("plugin-annotations") && name.endsWith(".jar") }
}
override fun configureCompilerConfiguration(configuration: CompilerConfiguration, module: TestModule) {
val pluginAnnotationsJar = findJarFromProperty()
?: findJarByPath()
?: error("Jar with annotations does not exist. Please run :plugins:fir-plugin-prototype:plugin-annotations:jar or specify firPluginAnnotations.path system property")
configuration.addJvmClasspathRoot(pluginAnnotationsJar)
}
private fun findJarByPath(): File? {
val libDir = File(ANNOTATIONS_JAR_DIR)
if (!libDir.exists() || !libDir.isDirectory) return null
return libDir.listFiles(ANNOTATIONS_JAR_FILTER)?.firstOrNull()
}
private fun findJarFromProperty(): File? {
val firPluginAnnotationsPath = System.getProperty("firPluginAnnotations.path") ?: return null
return File(firPluginAnnotationsPath).takeIf {
it.isFile &&
it.name.startsWith("plugin-annotations") &&
it.name.endsWith(".jar")
// TODO: handle property
val platform = module.targetPlatform
when {
platform.isJvm() -> {
val jar = findJvmLib()
configuration.addJvmClasspathRoot(jar)
}
platform.isJs() -> {
val jar = findJsLib()
val libraries = configuration.getList(JSConfigurationKeys.LIBRARIES)
configuration.put(JSConfigurationKeys.LIBRARIES, libraries + jar.absolutePath)
}
}
}
}
class PluginRuntimeAnnotationsProvider(testServices: TestServices) : RuntimeClasspathProvider(testServices) {
override fun runtimeClassPaths(module: TestModule): List<File> {
if (!module.targetPlatform.isJs()) return emptyList()
val jar = findJsLib()
return listOf(jar)
}
}
private const val ANNOTATIONS_JAR_DIR = "plugins/fir-plugin-prototype/plugin-annotations/build/libs/"
private val JVM_ANNOTATIONS_JAR_FILTER = createFilter("plugin-annotations-jvm", ".jar")
private val JS_ANNOTATIONS_KLIB_FILTER = createFilter("plugin-annotations-js", ".klib")
private fun findJvmLib(): File {
return findLib("jvm", ".jar", JVM_ANNOTATIONS_JAR_FILTER)
}
private fun findJsLib(): File {
return findLib("js", ".klib", JS_ANNOTATIONS_KLIB_FILTER)
}
@Suppress("warnings") // TODO
private fun findLib(platform: String, extension: String, filter: FilenameFilter): File {
return findLibFromProperty(platform, extension)
?: findLibByPath(filter)
?: error("Lib with annotations does not exist. Please run :plugins:fir-plugin-prototype:plugin-annotations:distAnnotations or specify firPluginAnnotations.path system property")
}
private fun createFilter(pattern: String, extension: String): FilenameFilter {
return FilenameFilter { _, name -> name.startsWith(pattern) && name.endsWith(extension) }
}
private fun findLibByPath(filter: FilenameFilter): File? {
val libDir = File(ANNOTATIONS_JAR_DIR)
if (!libDir.exists() || !libDir.isDirectory) return null
return libDir.listFiles(filter)?.firstOrNull()
}
/*
* Possible properties:
* - firPluginAnnotations.jvm.path
* - firPluginAnnotations.js.path
*/
private fun findLibFromProperty(platform: String, extension: String): File? {
val firPluginAnnotationsPath = System.getProperty("firPluginAnnotations.${platform}.path") ?: return null
return File(firPluginAnnotationsPath).takeIf {
it.isFile &&
it.name.startsWith("plugin-annotations") &&
it.name.endsWith(extension)
}
}