Skip to content

Commit

Permalink
Add testing framework and expose the true error behind IR:
Browse files Browse the repository at this point in the history
ava.lang.AssertionError: Declarations with wrong parent: 1
declaration: FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> (:kotlinx.coroutines.CoroutineScope) returnType:kotlin.Int [suspend]
org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl@3ed3e10f
expectedParent: FUN GENERATED_AS_FUTURE_CLASS_MEMBER name:myFunctionFuture visibility:public modality:FINAL <> (:com.xebia.test.Example) returnType:java.util.concurrent.CompletableFuture<kotlin.Int>
actualParent: FUN name:myFunction visibility:public modality:FINAL <> (:com.xebia.test.Example) returnType:kotlin.Int [suspend]
  • Loading branch information
raulraja authored and nomisRev committed Jun 13, 2023
1 parent 9f4d2ea commit 6a1d401
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 47 deletions.
65 changes: 64 additions & 1 deletion compiler-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,40 @@ group = "com.xebia"
version = "0.0.1"
val autoService = "1.1.1"

sourceSets {
test {
java.srcDirs("src/test-gen")
}
}

dependencies {
compileOnly("com.google.auto.service:auto-service:$autoService")
compileOnly("org.jetbrains.kotlin:kotlin-compiler:1.8.22")
kapt("com.google.auto.service:auto-service:$autoService")
compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.8.20")
compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.8.22")
testImplementation("dev.zacsweers.kctfork:core:0.2.1")
testImplementation("junit:junit:4.13.2")
testImplementation("com.google.truth:truth:1.1.3")
testImplementation(kotlin("reflect"))
testRuntimeOnly("org.jetbrains.kotlin:kotlin-test:1.8.22")
testRuntimeOnly("org.jetbrains.kotlin:kotlin-script-runtime:1.8.22")
testRuntimeOnly("org.jetbrains.kotlin:kotlin-annotations-jvm:1.8.22")

testCompileOnly("org.jetbrains.kotlin:kotlin-compiler:1.8.22")
testImplementation("org.jetbrains.kotlin:kotlin-compiler:1.8.22")
testImplementation("org.jetbrains.kotlin:kotlin-compiler-internal-test-framework:1.8.22")
testImplementation(platform("org.junit:junit-bom:5.9.1"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.junit.platform:junit-platform-commons")
testImplementation("org.junit.platform:junit-platform-launcher")
testImplementation("org.junit.platform:junit-platform-runner")
testImplementation("org.junit.platform:junit-platform-suite-api")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
}

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
}
Expand All @@ -40,3 +63,43 @@ tasks.withType<KotlinCompile> {
}

//./gradlew clean :lib:compileKotlinJvm --no-daemon -Dorg.gradle.debug=true -Dkotlin.compiler.execution.strategy="in-process" -Dkotlin.daemon.jvm.options="-Xdebug,-Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=n"

tasks.create<JavaExec>("generateTests") {
classpath = sourceSets.test.get().runtimeClasspath
mainClass.set("com.xebia.GenerateKotlinCompilerTestKt")
}


tasks.test {
testLogging { showStandardStreams = true }

useJUnitPlatform()
doFirst {
setLibraryProperty("org.jetbrains.kotlin.test.kotlin-stdlib", "kotlin-stdlib")
setLibraryProperty("org.jetbrains.kotlin.test.kotlin-stdlib-jdk8", "kotlin-stdlib-jdk8")
setLibraryProperty("org.jetbrains.kotlin.test.kotlin-reflect", "kotlin-reflect")
setLibraryProperty("org.jetbrains.kotlin.test.kotlin-test", "kotlin-test")
setLibraryProperty("org.jetbrains.kotlin.test.kotlin-script-runtime", "kotlin-script-runtime")
setLibraryProperty("org.jetbrains.kotlin.test.kotlin-annotations-jvm", "kotlin-annotations-jvm")
setLibraryProperty("coroutines.lib", "kotlinx-coroutines-core-jvm")
}

dependsOn("generateTests")
}

fun Test.setLibraryProperty(propName: String, jarName: String) {
//error(project.configurations.testRuntimeClasspath
// .get()
// .files.joinToString("\n") { it.absolutePath })
val path =
project.configurations.testRuntimeClasspath
.get()
.files
.find {
val matches = """$jarName-\d.*jar""".toRegex().matches(it.name)
matches
}
?.absolutePath
?: return
systemProperty(propName, path)
}
26 changes: 11 additions & 15 deletions compiler-plugin/src/main/java/com/xebia/CommonComponentRegistrar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,19 @@ class CommonComponentRegistrar : CompilerPluginRegistrar() {
get() = true

override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
registerExtensionsCommon(configuration)
}
}
if (configuration[KEY_ENABLED] == false) return

@OptIn(ExperimentalCompilerApi::class)
fun CompilerPluginRegistrar.ExtensionStorage.registerExtensionsCommon(configuration: CompilerConfiguration) {
if (configuration[KEY_ENABLED] == false) return
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
val asFutureAnnotation = ClassId.fromString("sample/AsFuture")
val coroutineScope = ClassId.fromString("kotlinx/coroutines/CoroutineScope")

val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
val asFutureAnnotation = ClassId.fromString("sample/AsFuture")
val coroutineScope = ClassId.fromString("kotlinx/coroutines/CoroutineScope")
// TODO implement Fir Checker for annotation
// FirExtensionRegistrarAdapter.registerExtension(FirRedactedExtensionRegistrar(messageCollector))

// TODO implement Fir Checker for annotation
// FirExtensionRegistrarAdapter.registerExtension(FirRedactedExtensionRegistrar(messageCollector))
IrGenerationExtension.registerExtension(
IrGenerationExtension(
messageCollector, asFutureAnnotation, coroutineScope
IrGenerationExtension.registerExtension(
IrGenerationExtension(
messageCollector, asFutureAnnotation, coroutineScope
)
)
)
}
}
50 changes: 23 additions & 27 deletions compiler-plugin/src/main/java/com/xebia/IrGenerationExtension.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.xebia

import java.util.concurrent.CompletableFuture
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
Expand Down Expand Up @@ -51,6 +52,7 @@ import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.types.SimpleTypeNullability
import org.jetbrains.kotlin.ir.types.SimpleTypeNullability.DEFINITELY_NOT_NULL
import org.jetbrains.kotlin.ir.types.addAnnotations
import org.jetbrains.kotlin.ir.types.typeWith
Expand Down Expand Up @@ -80,14 +82,11 @@ private class IrVisitor(
?: error("Internal error: Function $coroutineScope not found. Please include org.jetbrains.kotlinx:kotlinx-coroutines-core.")

val extensionFnConstructor: IrConstructorSymbol =
pluginContext.referenceClass(ClassId.fromString("kotlin/ExtensionFunctionType"))?.constructors?.firstOrNull()
pluginContext.referenceClass(classId<ExtensionFunctionType>())?.constructors?.firstOrNull()
?: error("Internal error: Class ExtensionFunctionType not found.")

val scopeSimpleType = IrSimpleTypeImpl(
coroutineScopeType,
DEFINITELY_NOT_NULL,
emptyList(),
emptyList()
coroutineScopeType, DEFINITELY_NOT_NULL, emptyList(), emptyList()
)

override fun visitFunctionNew(declaration: IrFunction): IrStatement {
Expand All @@ -106,15 +105,16 @@ private class IrVisitor(

if (pluginContext.platform?.isJvm() == true) {
// This declaration is only available on JVM
val futureFn: IrSimpleFunctionSymbol = pluginContext.referenceFunctions(futureCallableId).singleOrNull()
?: error("Internal error: Function $futureCallableId not found. Please include org.jetbrains.kotlinx:kotlinx-coroutines-jdk8.")
val futureFn: IrSimpleFunctionSymbol = pluginContext.referenceFunctions(futureCallableId).singleOrNull() ?: error(
"Internal error: Function $futureCallableId not found. Please include org.jetbrains.kotlinx:kotlinx-coroutines-jdk8."
)

val futureClass: IrClassSymbol =
pluginContext.referenceClass(ClassId.fromString("java.util.concurrent.CompletableFuture"))
?: error("Internal error: Function $coroutineScope not found. Please include org.jetbrains.kotlinx:kotlinx-coroutines-core.")
val futureClass: IrClassSymbol = pluginContext.referenceClass(classId<CompletableFuture<*>>())
?: error("Internal error: Function \"java/util/concurrent/CompletableFuture\" not found. Please include the jdk8 module.")

val futureSimpleType = IrSimpleTypeImpl(
futureClass, DEFINITELY_NOT_NULL,
futureClass,
DEFINITELY_NOT_NULL,
listOf(makeTypeProjection(declaration.returnType, Variance.INVARIANT)),
emptyList()
)
Expand All @@ -139,11 +139,9 @@ private class IrVisitor(
putTypeArgument(0, declaration.returnType)
// value argument 0, 1 have default values we want to maintain.
putValueArgument(
2,
lambdaArgument(
2, lambdaArgument(
lambda,
pluginContext.irBuiltIns.suspendFunctionN(1)
.typeWith(scopeSimpleType, declaration.returnType)
pluginContext.irBuiltIns.suspendFunctionN(1).typeWith(scopeSimpleType, declaration.returnType)
.addAnnotations(listOf(irCall(extensionFnConstructor)))
)
)
Expand All @@ -163,11 +161,9 @@ private class IrVisitor(

/** Finds the line and column of [irElement] within this file. */
private fun IrFile.locationOf(irElement: IrElement?): CompilerMessageSourceLocation {
val sourceRangeInfo =
fileEntry.getSourceRangeInfo(
beginOffset = irElement?.startOffset ?: SYNTHETIC_OFFSET,
endOffset = irElement?.endOffset ?: SYNTHETIC_OFFSET
)
val sourceRangeInfo = fileEntry.getSourceRangeInfo(
beginOffset = irElement?.startOffset ?: SYNTHETIC_OFFSET, endOffset = irElement?.endOffset ?: SYNTHETIC_OFFSET
)
return CompilerMessageLocationWithRange.create(
path = sourceRangeInfo.filePath,
lineStart = sourceRangeInfo.startLineNumber + 1,
Expand All @@ -179,8 +175,7 @@ private class IrVisitor(
}

fun IrBuilderWithScope.buildSuspendLambda(
returnType: IrType,
funApply: IrSimpleFunction.() -> Unit
returnType: IrType, funApply: IrSimpleFunction.() -> Unit
): IrSimpleFunction = pluginContext.irFactory.buildFun {
name = Name.special("<anonymous>")
this.returnType = returnType
Expand All @@ -201,10 +196,11 @@ private class IrVisitor(
}

fun lambdaArgument(lambda: IrSimpleFunction, type: IrType): IrFunctionExpression = IrFunctionExpressionImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
type,
lambda,
IrStatementOrigin.LAMBDA
UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, lambda, IrStatementOrigin.LAMBDA
)
}

private inline fun <reified T> classId(): ClassId {
val fqName = FqName(T::class.java.canonicalName)
return ClassId.topLevel(fqName)
}
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
2 changes: 1 addition & 1 deletion lib/src/commonMain/kotlin/sample/Example.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ class Example @JvmOverloads constructor(
delay(1000)
return 1 //argument.toInt()
}
}
}
8 changes: 7 additions & 1 deletion lib/src/jvmMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
class Main {
package test

import sample.Example
import kotlin.coroutines.EmptyCoroutineContext

suspend fun main() {
Example(EmptyCoroutineContext).myFunction()
}
1 change: 0 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

includeBuild("gradle-plugin") {
dependencySubstitution {
substitute(module("com.xebia:gradle-plugin:1.0.0")).using(project(":"))
Expand Down

0 comments on commit 6a1d401

Please sign in to comment.