跳到主要内容

从 Kotlin 编译器 API 的变化开始: 2.4.0

· 阅读需 6 分钟
法欧特斯卡雷特
可爱小猫咪一枚呀

大家好!众所周知,我有在平时维护一个简单的Kotlin编译器插件项目: Kotlin Suspend Transform Compiler Plugin。 想必经常维护编译器插件的小伙伴们也清楚,每次 Kotlin 的主要版本递进,编译器的API都会或多或少的发生变化, 也给编译器插件的更新维护带来不小的挑战。那么借此机会,我会在每次发生API变化的更新出现后, 藉由此系列记录一下能有哪些编译器API的变化可以被我发现。

不算是一种技术分享文,而是一种记录文,所以不保证有什么技术含金量喔~

今天要记录的版本变化是:v2.3.20 -> 2.4.0

不同于以往,这次在 Gradle sync 阶段 buildSrc 就开始出错了:

> Task :buildSrc:compileKotlin FAILED
e: file:///suspend-transform-kotlin-compile-plugin/buildSrc/src/main/kotlin/GradleSupportHelper.kt:29:70 Using 'KOTLIN_1_9' is an error. Unsupported
e: file:///suspend-transform-kotlin-compile-plugin/buildSrc/src/main/kotlin/GradleSupportHelper.kt:30:75 Using 'KOTLIN_1_9' is an error. Unsupported

不过当然,这在看到 2.4.0 的更新日志的时候我就已经有预期了。从 Kotlin 2.4.0 开始,-language-version=1.9 就不再被支持了, 也就是说 K1 编译器从现在开始正式地不再支持了。

出错的这部分代码在 buildSrc 中是一个辅助用的扩展函数:

fun org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension.configGradleBuildSrcFriendly() {
coreLibrariesVersion = "1.9.0"
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
}
}

因为在之前我注意到 Gradle 内置的 Kotlin 版本通常会略低,当我 K2 编译器发布了好几个版本了,但是在实际应用的 Kotlin 项目中, Gradle 内置的 Kotlin 版本仍然是 1.9.x,这就导致如果我打算通过 buildSrc 这类方式配置编译器插件的话,会导致低版本的 Kotlin 无法使用高版本产物的尴尬局面。 因此,为了能更高地支持较大范围内的 Gradle 版本,我为 gradle plugin 模块手动降低了编译产物的 Kotlin API 版本。

不过现在,1.9 是不能用了,所以让我们把它更新到 K2,然后再继续观察后续错误吧。


好了,回到我们的主线上来,来列举一下这次更新出现的所有错误和警告:

w: -Xjvm-default is deprecated. Use -jvm-default instead.
w: The argument '-Xcontext-parameters' is redundant for the current language version 2.4.
e: file:///suspend-transform-kotlin-compile-plugin/compiler/suspend-transform-plugin/src/main/kotlin/love/forte/plugin/suspendtrans/ir/SuspendTransformTransformerPostProcessing.kt:54:17 Argument type mismatch: actual type is 'IrConstructorCall', but 'IrAnnotation' was expected.
e: file:///suspend-transform-kotlin-compile-plugin/compiler/suspend-transform-plugin/src/main/kotlin/love/forte/plugin/suspendtrans/symbol/SuspendTransformSyntheticResolveExtension.kt:32:56 Unresolved reference 'allParameters'.
e: file:///suspend-transform-kotlin-compile-plugin/compiler/suspend-transform-plugin/src/main/kotlin/love/forte/plugin/suspendtrans/symbol/SuspendTransformSyntheticResolveExtension.kt:361:11 Unresolved reference 'allParameters'.
e: file:///suspend-transform-kotlin-compile-plugin/compiler/suspend-transform-plugin/src/main/kotlin/love/forte/plugin/suspendtrans/symbol/SuspendTransformSyntheticResolveExtension.kt:362:20 Unresolved reference 'name'.
e: file:///suspend-transform-kotlin-compile-plugin/compiler/suspend-transform-plugin/src/main/kotlin/love/forte/plugin/suspendtrans/symbol/SuspendTransformSyntheticResolveExtension.kt:364:20 Unresolved reference 'type'.
e: file:///suspend-transform-kotlin-compile-plugin/compiler/suspend-transform-plugin/src/main/kotlin/love/forte/plugin/suspendtrans/utils/AnnotationDescriptorUtils.kt:169:39 Too many arguments for 'fun FirAnnotation.getStringArgument(name: Name): String?'.
e: file:///suspend-transform-kotlin-compile-plugin/compiler/suspend-transform-plugin/src/main/kotlin/love/forte/plugin/suspendtrans/utils/AnnotationDescriptorUtils.kt:183:40 Too many arguments for 'fun FirAnnotation.getBooleanArgument(name: Name): Boolean?'.

这次的变更大概就是分为了两类:签名(参数)不匹配的(Argument type mismatchToo many arguments),和符号变化的(Unresolved reference

让我们挨个来看看。

签名变化

IrFunction.annotations

internal fun SuspendTransformTransformer.postProcessGenerateOriginFunction(
function: IrFunction,
originFunctionIncludeAnnotations: List<IncludeAnnotation>
) {
function.annotations = buildList {
val currentAnnotations = function.annotations
fun hasAnnotation(name: org.jetbrains.kotlin.name.FqName): Boolean =
currentAnnotations.any { a -> a.hasEqualAnnotationFqName(name) }
addAll(currentAnnotations)

originFunctionIncludeAnnotations.forEach { include ->
val classId = include.classInfo.toClassId()
val annotationClass = pluginContext.referenceClass(classId) ?: return@forEach
if (!include.repeatable && hasAnnotation(classId.asSingleFqName())) {
return@forEach
}

// ❌ Argument type mismatch: actual type is 'IrConstructorCall', but 'IrAnnotation' was expected.
add(pluginContext.createIrBuilder(function.symbol).irAnnotationConstructor(annotationClass))
}
}
}

从原本将一个 irAnnotationConstructor(annotationClass) 添加到一个 List 中来看,这里发生变化的应该是 IrFunction.annotations 这个列表的元素类型,从 IrConstructorCall 变为了 IrAnnotation

语义上 annotations 从‘一些构造器调用’变成了‘一些注解’,似乎也更符合其本意。

那么我们稍作调整即可。

internal fun SuspendTransformTransformer.postProcessGenerateOriginFunction(
function: IrFunction,
originFunctionIncludeAnnotations: List<IncludeAnnotation>
) {
function.annotations = buildList {
val currentAnnotations = function.annotations
fun hasAnnotation(name: org.jetbrains.kotlin.name.FqName): Boolean =
currentAnnotations.any { a -> a.hasEqualAnnotationFqName(name) }
addAll(currentAnnotations)

originFunctionIncludeAnnotations.forEach { include ->
val classId = include.classInfo.toClassId()
val annotationClass = pluginContext.referenceClass(classId) ?: return@forEach
if (!include.repeatable && hasAnnotation(classId.asSingleFqName())) {
return@forEach
}

val firstAnnotationConstructor = annotationClass.constructors.first()
add(pluginContext.createIrBuilder(function.symbol).irAnnotation(firstAnnotationConstructor))
}
}
}

// AnnotationDescriptorUtils.kt

fun IrBuilderWithScope.irAnnotation(symbol: IrConstructorSymbol): IrAnnotation {
return irAnnotation(symbol)
}

FirAnnotation.getStringArgument

上面提到的那个工具类 AnnotationDescriptorUtils.kt 中正好也包含了剩下的一个错误: org.jetbrains.kotlin.fir.declarations.FirAnnotationUtils.kt 中提供的辅助扩展函数 fun FirAnnotation.getStringArgument(name: Name, session: FirSession) 不再需要第二个参数了。

它们现在的定义如下:

@Deprecated(
message = "Use getStringArgument overload without session parameter",
replaceWith = ReplaceWith("getStringArgument(name)"),
level = DeprecationLevel.HIDDEN
)
fun FirAnnotation.getStringArgument(name: Name, session: FirSession): String? = getStringArgument(name)
fun FirAnnotation.getStringArgument(name: Name): String? = getPrimitiveArgumentValue(name)

倒是也没怎么解释,反正就是告诉你:获取注解上的参数不需要 session 了。那还说什么了兄弟,换了。

符号变化

接下来是我们的老朋友:Unresolved reference xxx。现在的我还没开始看,但是我猜八九不离十是这些函数挪了挪地方。

org.jetbrains.kotlin.backend.common.descriptors.allParameters

果不其然。那四个 Unresolved reference xxx 的本质原因是 org.jetbrains.kotlin.backend.common.descriptors.allParameters 被移动到了 import org.jetbrains.kotlin.descriptors.konan.allParameters。 修改为更正确的 import 即可。

尾声

这次编译器的变化对我来说竟然意外地仁慈,那些新产生的特性也没有对 API 造成大范围的损坏。 或许这也是 K2 编译器正在逐步走向更加健壮文档与成熟的另一种佐证?