diff --git a/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/SimplePluginRegistrar.kt b/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/SimplePluginRegistrar.kt deleted file mode 100644 index 2451839f..00000000 --- a/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/SimplePluginRegistrar.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.jetbrains.kotlinx.spark.api.compilerPlugin - -import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar - -// Potential future K2 FIR hook -class SimplePluginRegistrar(private val sparkifyAnnotationFqNames: List) : FirExtensionRegistrar() { - override fun ExtensionRegistrarContext.configurePlugin() {} -} diff --git a/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/SparkifyCompilerPluginRegistrar.kt b/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/SparkifyCompilerPluginRegistrar.kt index 6390c45f..f2456d50 100644 --- a/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/SparkifyCompilerPluginRegistrar.kt +++ b/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/SparkifyCompilerPluginRegistrar.kt @@ -3,10 +3,12 @@ package org.jetbrains.kotlinx.spark.api.compilerPlugin import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar +import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter import org.jetbrains.kotlinx.spark.api.Artifacts import org.jetbrains.kotlinx.spark.api.compilerPlugin.ir.SparkifyIrGenerationExtension -open class SparkifyCompilerPluginRegistrar: CompilerPluginRegistrar() { +open class SparkifyCompilerPluginRegistrar : CompilerPluginRegistrar() { init { println("SparkifyCompilerPluginRegistrar loaded") } @@ -26,6 +28,15 @@ open class SparkifyCompilerPluginRegistrar: CompilerPluginRegistrar() { val productFqNames = // TODO: get from configuration listOf("scala.Product") + // Front end (FIR) +// FirExtensionRegistrarAdapter.registerExtension( +// SparkifyFirPluginRegistrar( +// sparkifyAnnotationFqNames = sparkifyAnnotationFqNames, +// productFqNames = productFqNames, +// ) +// ) + + // Intermediate Representation IR IrGenerationExtension.registerExtension( SparkifyIrGenerationExtension( sparkifyAnnotationFqNames = sparkifyAnnotationFqNames, diff --git a/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/SparkifyFirPluginRegistrar.kt b/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/SparkifyFirPluginRegistrar.kt new file mode 100644 index 00000000..aca02af7 --- /dev/null +++ b/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/SparkifyFirPluginRegistrar.kt @@ -0,0 +1,23 @@ +package org.jetbrains.kotlinx.spark.api.compilerPlugin + +import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar +import org.jetbrains.kotlinx.spark.api.compilerPlugin.fir.DataClassSparkifyFunctionsGenerator +import org.jetbrains.kotlinx.spark.api.compilerPlugin.fir.DataClassSparkifySuperTypeGenerator + +// Potential future K2 FIR hook +// TODO +class SparkifyFirPluginRegistrar( + private val sparkifyAnnotationFqNames: List, + private val productFqNames: List +) : FirExtensionRegistrar() { + override fun ExtensionRegistrarContext.configurePlugin() { + +DataClassSparkifySuperTypeGenerator.builder( + sparkifyAnnotationFqNames = sparkifyAnnotationFqNames, + productFqNames = productFqNames, + ) + +DataClassSparkifyFunctionsGenerator.builder( + sparkifyAnnotationFqNames = sparkifyAnnotationFqNames, + productFqNames = productFqNames, + ) + } +} diff --git a/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/fir/DataClassSparkifyFunctionsGenerator.kt b/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/fir/DataClassSparkifyFunctionsGenerator.kt new file mode 100644 index 00000000..007c6e2c --- /dev/null +++ b/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/fir/DataClassSparkifyFunctionsGenerator.kt @@ -0,0 +1,115 @@ +package org.jetbrains.kotlinx.spark.api.compilerPlugin.fir + +import org.jetbrains.kotlin.GeneratedDeclarationKey +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.declarations.utils.isData +import org.jetbrains.kotlin.fir.extensions.FirDeclarationGenerationExtension +import org.jetbrains.kotlin.fir.extensions.MemberGenerationContext +import org.jetbrains.kotlin.fir.plugin.createMemberFunction +import org.jetbrains.kotlin.fir.render +import org.jetbrains.kotlin.fir.resolve.getSuperTypes +import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol +import org.jetbrains.kotlin.fir.types.toClassSymbol +import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.Name + +class DataClassSparkifyFunctionsGenerator( + session: FirSession, + private val sparkifyAnnotationFqNames: List, + private val productFqNames: List, +) : FirDeclarationGenerationExtension(session) { + + companion object { + fun builder( + sparkifyAnnotationFqNames: List, + productFqNames: List + ): (FirSession) -> FirDeclarationGenerationExtension = { + DataClassSparkifyFunctionsGenerator( + session = it, + sparkifyAnnotationFqNames = sparkifyAnnotationFqNames, + productFqNames = productFqNames, + ) + } + + // functions to generate + val canEqual = Name.identifier("canEqual") + val productElement = Name.identifier("productElement") + val productArity = Name.identifier("productArity") + } + + override fun generateFunctions( + callableId: CallableId, + context: MemberGenerationContext? + ): List { + val owner = context?.owner ?: return emptyList() + + val functionName = callableId.callableName + val superTypes = owner.getSuperTypes(session) + val superProduct = superTypes.first { + it.toString().endsWith("Product") + }.toClassSymbol(session)!! + val superEquals = superTypes.first { + it.toString().endsWith("Equals") + }.toClassSymbol(session)!! + + val function = when (functionName) { + canEqual -> { + val func = createMemberFunction( + owner = owner, + key = Key, + name = functionName, + returnType = session.builtinTypes.booleanType.type, + ) { + valueParameter( + name = Name.identifier("that"), + type = session.builtinTypes.nullableAnyType.type, + ) + } +// val superFunction = superEquals.declarationSymbols.first { +// it is FirNamedFunctionSymbol && it.name == functionName +// } as FirNamedFunctionSymbol +// overrides(func, superFunction) + func + } + + productElement -> { + createMemberFunction( + owner = owner, + key = Key, + name = functionName, + returnType = session.builtinTypes.nullableAnyType.type, + ) { + valueParameter( + name = Name.identifier("n"), + type = session.builtinTypes.intType.type, + ) + } + } + + productArity -> { + createMemberFunction( + owner = owner, + key = Key, + name = functionName, + returnType = session.builtinTypes.intType.type, + ) + } + + else -> { + return emptyList() + } + } + + return listOf(function.symbol) + } + + override fun getCallableNamesForClass(classSymbol: FirClassSymbol<*>, context: MemberGenerationContext): Set = + if (classSymbol.isData && classSymbol.annotations.any { "Sparkify" in it.render() }) { + setOf(canEqual, productElement, productArity) + } else { + emptySet() + } + + object Key : GeneratedDeclarationKey() +} \ No newline at end of file diff --git a/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/fir/DataClassSparkifySuperTypeGenerator.kt b/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/fir/DataClassSparkifySuperTypeGenerator.kt new file mode 100644 index 00000000..339b3269 --- /dev/null +++ b/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/fir/DataClassSparkifySuperTypeGenerator.kt @@ -0,0 +1,59 @@ +package org.jetbrains.kotlinx.spark.api.compilerPlugin.fir + +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration +import org.jetbrains.kotlin.fir.declarations.utils.isData +import org.jetbrains.kotlin.fir.extensions.FirSupertypeGenerationExtension +import org.jetbrains.kotlin.fir.render +import org.jetbrains.kotlin.fir.resolve.fqName +import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl +import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef +import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef +import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName + +/** + * This class tells the FIR that all @Sparkify annotated data classes + * get [scala.Product] as their super type. + */ +class DataClassSparkifySuperTypeGenerator( + session: FirSession, + private val sparkifyAnnotationFqNames: List, + private val productFqNames: List, +) : FirSupertypeGenerationExtension(session) { + + companion object { + fun builder(sparkifyAnnotationFqNames: List, productFqNames: List): (FirSession) -> FirSupertypeGenerationExtension = { + DataClassSparkifySuperTypeGenerator( + session = it, + sparkifyAnnotationFqNames = sparkifyAnnotationFqNames, + productFqNames = productFqNames, + ) + } + } + + context(TypeResolveServiceContainer) + override fun computeAdditionalSupertypes( + classLikeDeclaration: FirClassLikeDeclaration, + resolvedSupertypes: List + ): List = listOf( + buildResolvedTypeRef { + val scalaProduct = productFqNames.first().let { + ClassId.topLevel(FqName(it)) + } + type = ConeClassLikeTypeImpl( + lookupTag = ConeClassLikeLookupTagImpl(scalaProduct), + typeArguments = emptyArray(), + isNullable = false, + ) + } + + ) + + override fun needTransformSupertypes(declaration: FirClassLikeDeclaration): Boolean = + declaration.symbol.isData && + declaration.annotations.any { + "Sparkify" in it.render() + } +} \ No newline at end of file diff --git a/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/ir/DataClassPropertyAnnotationGenerator.kt b/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/ir/DataClassSparkifyGenerator.kt similarity index 99% rename from compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/ir/DataClassPropertyAnnotationGenerator.kt rename to compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/ir/DataClassSparkifyGenerator.kt index 30df9799..0438b42b 100644 --- a/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/ir/DataClassPropertyAnnotationGenerator.kt +++ b/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/ir/DataClassSparkifyGenerator.kt @@ -46,7 +46,7 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.SpecialNames -class DataClassPropertyAnnotationGenerator( +class DataClassSparkifyGenerator( private val pluginContext: IrPluginContext, private val sparkifyAnnotationFqNames: List, private val columnNameAnnotationFqNames: List, diff --git a/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/ir/SparkifyIrGenerationExtension.kt b/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/ir/SparkifyIrGenerationExtension.kt index a22cc6f8..d17da71f 100644 --- a/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/ir/SparkifyIrGenerationExtension.kt +++ b/compiler-plugin/src/main/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/ir/SparkifyIrGenerationExtension.kt @@ -4,7 +4,6 @@ import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid -import org.jetbrains.kotlinx.spark.api.compilerPlugin.ir.DataClassPropertyAnnotationGenerator class SparkifyIrGenerationExtension( private val sparkifyAnnotationFqNames: List, @@ -13,7 +12,7 @@ class SparkifyIrGenerationExtension( ) : IrGenerationExtension { override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { val visitors = listOf( - DataClassPropertyAnnotationGenerator( + DataClassSparkifyGenerator( pluginContext = pluginContext, sparkifyAnnotationFqNames = sparkifyAnnotationFqNames, columnNameAnnotationFqNames = columnNameAnnotationFqNames, diff --git a/compiler-plugin/src/test-gen/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/runners/DiagnosticTestGenerated.java b/compiler-plugin/src/test-gen/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/runners/DiagnosticTestGenerated.java new file mode 100644 index 00000000..566db49b --- /dev/null +++ b/compiler-plugin/src/test-gen/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/runners/DiagnosticTestGenerated.java @@ -0,0 +1,28 @@ + + +package org.jetbrains.kotlinx.spark.api.compilerPlugin.runners; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlinx.spark.api.compilerPlugin.GenerateTestsKt}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("/mnt/data/Projects/kotlin-spark-api/compiler-plugin/src/test/resources/testData/diagnostics") +@TestDataPath("$PROJECT_ROOT") +public class DiagnosticTestGenerated extends AbstractDiagnosticTest { + @Test + public void testAllFilesPresentInDiagnostics() { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("/mnt/data/Projects/kotlin-spark-api/compiler-plugin/src/test/resources/testData/diagnostics"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @Test + @TestMetadata("dataClassTest.kt") + public void testDataClassTest() { + runTest("/mnt/data/Projects/kotlin-spark-api/compiler-plugin/src/test/resources/testData/diagnostics/dataClassTest.kt"); + } +} diff --git a/compiler-plugin/src/test/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/GenerateTests.kt b/compiler-plugin/src/test/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/GenerateTests.kt index a85d0a1b..35eda3b4 100644 --- a/compiler-plugin/src/test/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/GenerateTests.kt +++ b/compiler-plugin/src/test/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/GenerateTests.kt @@ -3,6 +3,7 @@ package org.jetbrains.kotlinx.spark.api.compilerPlugin import org.jetbrains.kotlin.generators.generateTestGroupSuiteWithJUnit5 import org.jetbrains.kotlinx.spark.api.Artifacts import org.jetbrains.kotlinx.spark.api.compilerPlugin.runners.AbstractBoxTest +import org.jetbrains.kotlinx.spark.api.compilerPlugin.runners.AbstractDiagnosticTest fun main() { generateTestGroupSuiteWithJUnit5 { @@ -10,9 +11,9 @@ fun main() { testDataRoot = "${Artifacts.projectRoot}/${Artifacts.compilerPluginArtifactId}/src/test/resources/testData", testsRoot = "${Artifacts.projectRoot}/${Artifacts.compilerPluginArtifactId}/src/test-gen/kotlin", ) { -// testClass { -// model("diagnostics") -// } + testClass { + model("diagnostics") + } testClass { model("box") diff --git a/compiler-plugin/src/test/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/services/ExtensionRegistrarConfigurator.kt b/compiler-plugin/src/test/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/services/ExtensionRegistrarConfigurator.kt index b7942a4e..a48fa81b 100644 --- a/compiler-plugin/src/test/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/services/ExtensionRegistrarConfigurator.kt +++ b/compiler-plugin/src/test/kotlin/org/jetbrains/kotlinx/spark/api/compilerPlugin/services/ExtensionRegistrarConfigurator.kt @@ -3,9 +3,11 @@ package org.jetbrains.kotlinx.spark.api.compilerPlugin.services import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.services.EnvironmentConfigurator import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlinx.spark.api.compilerPlugin.SparkifyFirPluginRegistrar import org.jetbrains.kotlinx.spark.api.compilerPlugin.ir.SparkifyIrGenerationExtension class ExtensionRegistrarConfigurator(testServices: TestServices) : EnvironmentConfigurator(testServices) { @@ -16,6 +18,16 @@ class ExtensionRegistrarConfigurator(testServices: TestServices) : EnvironmentCo val sparkifyAnnotationFqNames = listOf("foo.bar.Sparkify") val columnNameAnnotationFqNames = listOf("foo.bar.ColumnName") val productFqNames = listOf("foo.bar.Product") + + // Front end (FIR) +// FirExtensionRegistrarAdapter.registerExtension( +// SparkifyFirPluginRegistrar( +// sparkifyAnnotationFqNames = sparkifyAnnotationFqNames, +// productFqNames = productFqNames, +// ) +// ) + + // Intermediate Representation IR IrGenerationExtension.registerExtension( SparkifyIrGenerationExtension( sparkifyAnnotationFqNames = sparkifyAnnotationFqNames, diff --git a/compiler-plugin/src/test/resources/testData/diagnostics/dataClassTest.kt b/compiler-plugin/src/test/resources/testData/diagnostics/dataClassTest.kt new file mode 100644 index 00000000..4b86f4c1 --- /dev/null +++ b/compiler-plugin/src/test/resources/testData/diagnostics/dataClassTest.kt @@ -0,0 +1,28 @@ +package foo.bar + +annotation class Sparkify +annotation class ColumnName(val name: String) + +// Fake Equals +interface Equals { + fun canEqual(that: Any?): Boolean +} + +// Fake Product +interface Product: Equals { + fun productElement(n: Int): Any + fun productArity(): Int +} + +fun test() { + val user = User() + user.productArity() // should not be an error +} + +@Sparkify +data class User( + val name: String = "John Doe", + val age: Int = 25, + @ColumnName("a") val test: Double = 1.0, + @get:ColumnName("b") val test2: Double = 2.0, +)