SuperAPI AI 工具集

jvmname-disambiguate-erased-overloads

编程与开发

查看项目

更新于 10-17 16:15

技能介绍

用途

本技能针对 Kotlin 的一个具体编译问题:两个扩展函数只在泛型接收者的类型参数上不同,经过类型擦除后编译成同一个 JVM 静态方法,导致出现 platform declaration clash(平台声明冲突)。例如 ArrayList<SongsResult>.toListTrack() 与 ArrayList<VideosResult>.toListTrack() 都会变成 toListTrack(ArrayList)。解决办法是在其中一个函数上添加 @JvmName,为 JVM 侧方法指定一个不同名称。

主要能力与要点

  • 在冲突函数对中的一方使用 @JvmName,即可消除擦除造成的签名冲突,Kotlin 调用点的解析行为保持不变。
  • 应重命名 JVM 名而非 Kotlin 名:Kotlin 名称是 API 的一部分,重载解析本身已经知道元素类型,改成 toListTrackFromSongs() 之类会把类型信息推到每个调用点。
  • 只有擦除后完全相同才会冲突:List<A> 与 ArrayList<B> 擦除后描述符不同,本可共存;把一方改为 List<…> 虽然能编译,却扩大了该函数接受的类型范围,属于改变 API 承诺。
  • 冲突范围限于声明所在的类(顶层函数落在以文件命名的文件类中),不在整个模块范围。不同文件中的同签名函数不会冲突,因此仓库级重名搜索多为误报,应先按文件分组。
  • JVM 名对 Kotlin 不可见,注解中的字符串写错也没有编译期症状,只会在 Java 调用、反射查找或堆栈帧中暴露。
  • 非 Kotlin 调用方(如 Java)只能使用 JVM 名,例:ModelToEntityKt.songsResultToListTrack(list)。这对 Kotlin 是源码兼容变更,对其他语言是破坏性变更,因此应在冲突首次出现时一次性定好 JVM 名。
  • @JvmName 不能用于 open、override、abstract 成员;属性上需标注在访问器上,如 @get:JvmName(…) / @set:JvmName(…)。
  • 在多平台模块中,@JvmName 可从 kotlin.jvm 导入并在 common 源集中编译,但该约束只存在于 JVM 目标,建议在注解旁留一行原因说明。

使用方式与验证

技能提供了一段 grep 脚本,用于查找真正可能冲突的候选:同一文件中、同一函数名、同一擦除后接收者。脚本对每个文件提取 fun <泛型>?.方法名( 模式并归一化后找出重复项。由于模式要求泛型接收者,它无法发现另一种冲突:两个同名扩展函数仅在可空性上不同(T 与 T? 擦除后描述符相同),这类只能靠注解排查发现。

随后用 grep -rn -A1 "@JvmName" --include="*.kt" . 检查每个注解是否必要、字符串是否是你希望 Java 看到的名称。未被检测器列出的注解可能是防御性的、遗留的,或用于处理可空性冲突,三者都值得补充注释或删除。

适用场景与限制

适用于 Kotlin/JVM 项目中因泛型扩展函数类型擦除而出现平台声明冲突,且需要保留 Kotlin 重载 API 与调用体验的场景。限制包括:仅适用于编译器确信无子类受重命名影响的成员;名称一致性无编译期保障;在跨平台项目中注解的动机对非 JVM 目标不明显,需额外注释说明。