继续创造,加速成长!这是我参与「日新方案 10 月更文应战」的第 3 天,点击检查活动详情

简介

Sword:一个能够给 Kotlin 函数添加署理的第三方库,依据 KCP 完成。

  1. Sword – 为 Kotlin 函数添加署理功用(一)
  2. Sword – 为 Kotlin 函数添加署理功用(二)
  3. Sword – 为 Kotlin 函数添加署理功用(三)

前面三篇文章笔者记录了 Sword 的完成进程,怎么运用 Sword 以及怎么经过 KSPInvocationHandler 生成 FqName 索引类 HandlerFqName

在第三篇文章的最终笔者有一个新的主意:经过 Kotlin IR 重新完成 Sword 的功用。经过最近几天晚上和早晨的努力,笔者开始完成了 Sword 的功用,或许还有一些问题,可是效果现已到达了笔者的预期,遂本篇文章记录下笔者的完成进程。

Kotlin IR 是什么以及能够做什么,本文不再赘述,网上有不少资料,读者能够自行参阅。

预期效果

假定有以下类(GetTextNoArgInvocationHandler)和函数(getTextNoArg()):

@ProxyHandler("GET_TEXT_NO_ARG")
class GetTextNoArgInvocationHandler : InvocationHandler {
​
  private val TAG = GetTextNoArgInvocationHandler::class.java.simpleName
​
  override fun invoke(className: String, methodName: String, args: Array<Any?>): Any? {
    Log.e(TAG, "invoke: className = $className, methodName = $methodName, args(${args.size}) = ${args.joinToString()}")
    return "guodongAndroid-Debug"
   }
}
​
// -----------------------------------------------------------------------------------------------------------------------@Proxy(
  enable = true,
  handler = HandlerFqName.GET_TEXT_NO_ARG
)
fun getTextNoArg() = "guodongAndroid"

经过 Kotlin IR 编译和 Sword 署理后,笔者期望 getTextNoArg 函数转化成类似下面的伪代码:

@Proxy(
  enable = true,
  handler = HandlerFqName.GET_TEXT_NO_ARG
)
fun getTextNoArg(): String {
  return GetTextNoArgInvocationHandler().invoke("Test", "getTextNoArg", emptyArray()) as String
}

SwordComponentRegistrar

要运用 IR 首要需求注册 IrGenerationExtension 扩展,修正之前 SwordComponentRegistrar 中的代码:

class SwordComponentRegistrar : ComponentRegistrar {
  override fun registerProjectComponents(
    project: MockProject,
    configuration: CompilerConfiguration
   ) {
    val messageCollector =
        configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
​
    /*ClassBuilderInterceptorExtension.registerExtension(
      project,
      SwordClassGenerationInterceptor(messageCollector)
    )*/
​
    IrGenerationExtension.registerExtension(
      project,
      SwordIrGenerationExtension(messageCollector)
     )
   }
}

在上面的代码中:

  1. 注释掉之前经过 ASM 修正字节码的 ClassBuilderInterceptorExtension 扩展,
  2. 新增 IrGenerationExtension 扩展。

Dump

IR 语法树中,所有的节点都完成了 IrElement 接口,这些节点能够是模块,包,文件,类,特点,函数,参数,表达式,函数调用、函数体等等。

那么这些节点是什么样子的呢?完成咱们的 SwordIrGenerationExtension

class SwordIrGenerationExtension(
  private val messageCollector: MessageCollector,
) : IrGenerationExtension {
  override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
    messageCollector.report(
      CompilerMessageSeverity.WARNING,
      moduleFragment.dump()
     )
   }
}

运用 IrElement 的扩展函数 dump 能够输出这些节点的语法树信息。

在上面的代码中,咱们输出了整个模块节点的语法树信息,假如模块中有许多文件,类,那么这些信息是适当巨大的,Sword 的方针是 Kotlin 函数,所以此处笔者不再贴出模块节点的语法树信息,比及咱们转化函数时,再看看函数节点的语法树信息。

假定咱们不知道怎么编写 IR 编译器插件的代码,咱们能够先写出要完成效果的 Kotlin 代码,再凭借 dump 函数输出 IR 语法树信息,参阅且对比语法树信息进行开发 IR 编译器插件,所以 dump 在开发 IR 编译器插件时十分有用。

IrElement

前面说了许多 IrElement 接口,现在咱们还不知道它的真面目,接下来让咱们看看它吧:

interface IrElement {
   ...
​
  fun <R, D> accept(visitor: IrElementVisitor<R, D>, data: D): R
  fun <D> acceptChildren(visitor: IrElementVisitor<Unit, D>, data: D)fun <D> transform(transformer: IrElementTransformer<D>, data: D): IrElement
  fun <D> transformChildren(transformer: IrElementTransformer<D>, data: D)
}

IrElement 中有四个接口函数,其间 accept 函数依据访问者方式访问各个节点,transform 函数又是依据 accept 函数供给修正节点语法树的才能。

accept 函数中的参数 IrElementVisitor 接口供给了访问各个节点的函数:

interface IrElementVisitor<out R, in D> {
  
  fun visitElement(element: IrElement, data: D): R
  fun visitModuleFragment(declaration: IrModuleFragment, data: D) = visitElement(declaration, data)
  fun visitPackageFragment(declaration: IrPackageFragment, data: D) = visitElement(declaration, data)
  fun visitFile(declaration: IrFile, data: D) = visitPackageFragment(declaration, data)
  
  fun visitDeclaration(declaration: IrDeclarationBase, data: D) = visitElement(declaration, data)
  fun visitClass(declaration: IrClass, data: D) = visitDeclaration(declaration, data)
  fun visitFunction(declaration: IrFunction, data: D) = visitDeclaration(declaration, data)
   ...
}

IrElementVisitor 中有十分多接口函数,上面代码片段列举了一些接口函数,这些接口函数大多数都有默认完成,细心观察这些函数的默认完成,最终都直接或间接的调用到 visitElement 函数。

transform 函数中的参数 IrElementTransformer 接口继承自 IrElementVisitor 接口,IrElementTransformer 接口首要是完成了 IrElementVisitor 的接口函数并调用 IrElement.transformChildren 函数遍历节点修正节点的语法树:

interface IrElementTransformer<in D> : IrElementVisitor<IrElement, D> {
  override fun visitElement(element: IrElement, data: D): IrElement {
    element.transformChildren(this, data)
    return element
   }
​
  override fun visitModuleFragment(declaration: IrModuleFragment, data: D): IrModuleFragment {
    declaration.transformChildren(this, data)
    return declaration
   }
​
  override fun visitFile(declaration: IrFile, data: D): IrFile {
    declaration.transformChildren(this, data)
    return declaration
   }
}

Sword 中,咱们首要利用 transform 函数修正节点语法树的才能来完成为 Kotlin 函数添加署理功用。

IrType & IrSymbol

IR 中不仅有 IrElement 还有 IrTypeIrSymbol 。那么这两个有什么作用呢?

IrType

IrType 能够说是 KotlinTypeIR 中的另一种表现方式,表明 Kotlin 中的各种类型,比方 AnyBooleanIntString 等等。IrType 常用在比较函数参数类型,调用函数时传入参数类型等,举个 Sword 中的栗子:

private val anyNType = pluginContext.irBuiltIns.anyNType
private val stringType = pluginContext.irBuiltIns.stringType
private val arrayAnyNType = pluginContext.irBuiltIns.arrayClass.typeWith(anyNType)
​
// `InvocationHandler.invoke(className: String, methodName: String, args: Array<Any?>): Any?`
val invokeSymbol =
    pluginContext.referenceFunctions(FqName("${param.handler}.$INVOKE_METHOD_NAME"))
        .single {
           val valueParameters = it.owner.valueParameters
           valueParameters.size == 3 &&
           valueParameters[0].type == stringType &&
           valueParameters[1].type == stringType &&
           valueParameters[2].type == arrayAnyNType
        }

在上面的代码片段中 pluginContextIrGenerationExtension.generate 函数中的第二个参数,经过它的 irBuiltIns 字段咱们获取一些 Kotlin 内置的 IrType

  • anyNType 表明 Kotlin 中的 Any? 类型,
  • stringType 表明 String 类型,假如想获取 String? 类型,则需求调用 stringType.makeNullable()
  • arrayAnyNType 表明 Array<Any?> 类型,Array 没有对应的 IrType 表明,咱们需求先获取 Array 对应的 IrSymbol,再调用 typeWith 扩展函数传入所需泛型IrType 即可获取 Array<Any?>IrType

在上面代码片段的最终,笔者的方针是获取 InvocationHandler.invoke 函数的 IrSymbol,考虑到开发者或许会重载 invoke 函数,所以笔者添加了以下判别逻辑:

  1. 函数中有且仅有三个参数,
  2. 第一个参数的类型有必要是 String 类型,
  3. 第二个参数的类型有必要是 String 类型,
  4. 第三个参数的类型有必要是 Array<Any?> 类型。

满足以上几个条件的函数笔者才以为是 InvocationHandler.invoke 函数。

IrSymbol

IrSymbolKotlin IR 中能够算是比较重要的一个接口了。它以「符号」的方式描绘了 Kotlin 的包、文件、类、函数、特点、字段等,笔者把它理解为 Java 字节码中的描绘符,所以 IrSymbol 常用在创建类、函数、特点,函数调用等,举个 Sword 中的栗子:

private val anyNType = pluginContext.irBuiltIns.anyNType
private val emptyArraySymbol = pluginContext.referenceFunctions(FqName("kotlin.emptyArray")).first()
​
irCall(emptyArraySymbol).also {
  it.putTypeArgument(0, anyNType)
}
  • anyNType 在上节中呈现过,它表明 Any? 类型,
  • emptyArraySymbolemptyArray() 函数在 IR 中的符号,咱们同样能够经过 irBuiltIns 获取一些 Kotlin 内置的 IrSymbol其他的符号能够经过 pluginContext.referenceXXX() 的一系列函数查找
  • 接下来调用 irCall 函数并传入 emptyArraySymbol,最终调用 putTypeArgument() 函数设置 emptyArray() 函数的泛型。

所以上面的代码片段其实是调用 Kotlin 中的 emptyArray<Any?>() 函数。

Sword

SwordIrGenerationExtension

class SwordIrGenerationExtension(
  private val messageCollector: MessageCollector,
) : IrGenerationExtension {
​
  private val proxyAnnotationFqName = FqName("com.guodong.android.sword.api.kt.Proxy")
​
  override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
    // 输出日志
    messageCollector.report(
      CompilerMessageSeverity.WARNING,
      "Welcome to guodongAndroid sword kcp kotlin ir plugin"
     )
​
    // 判别当时模块是否存在 `Proxy` 注解
    val proxyAnnotation = pluginContext.referenceClass(proxyAnnotationFqName)
    if (proxyAnnotation == null) {
      messageCollector.report(
        CompilerMessageSeverity.ERROR,
        "Not found `Proxy` annotation, make sure to add the "sword-api-kt" library to your dependencies"
       )
      return
     }
​
    // 开始转化
    moduleFragment.transform(
      SwordTransformer(pluginContext, proxyAnnotation, messageCollector),
      null
     )
   }
}

经过前面的常识衬托, SwordIrGenerationExtension 中的代码逻辑信任读者应该能理解了,笔者这里就不再赘述了,没有理解的读者能够再回忆下前面的内容。

接下来咱们首要看看 SwordTransformer 中的逻辑。

SwordTransformer

由于 Sword 的功用是为 Kotlin 函数添加署理功用,所以在 SwordTransformer 中咱们仅重视与函数相关的转化函数,即:visitFunctionNew(declaration: IrFunction) 函数。

首要声明一些变量和常量,其间一些变量前面笔者现已介绍过:

companion object {
  // `InvocationHandler` 中的 `invoke` 函数称号
  private const val INVOKE_METHOD_NAME = "invoke"
}
​
private val anyNType = pluginContext.irBuiltIns.anyNType
private val stringType = pluginContext.irBuiltIns.stringType
private val arrayAnyNType = pluginContext.irBuiltIns.arrayClass.typeWith(anyNType)
private val emptyArraySymbol = pluginContext.referenceFunctions(FqName("kotlin.emptyArray")).first()
private val arrayOfSymbol = pluginContext.irBuiltIns.arrayOf
​
// @JvmName 注解彻底限定名
private val jvmNameAnnotationFqName = FqName("kotlin.jvm.JvmName")
​
// @Proxy 注解彻底限定名
private val proxyAnnotationFqName = FqName("com.guodong.android.sword.api.kt.Proxy")

下面咱们就覆写 visitFunctionNew() 函数,在覆写的 visitFunctionNew() 函数中有较多的代码逻辑,仍是老习气,笔者先描绘下自己的完成思路,然后再依据完成思路顺次进行代码完成:

  1. 过滤一些笔者以为不需求处理的函数,读者能够自行斟酌;过滤不包括 Proxy 注解的函数,
  2. 获取 Proxy 注解里的数据存储于第二篇文章中的 SwordParam 中,
  3. 获取当时类名和函数名,判别当时函数是否启用署理,假如启用了署理可是 handler 为空则抛出反常,
  4. 启用署理后,咱们直接抛弃原函数体,生成新的署理函数体。

1.过滤

override fun visitFunctionNew(declaration: IrFunction): IrStatement {
  // 过滤挂起函数,内联函数,多渠道声明函数,外部函数(JNI)
  if (declaration.isSuspend || declaration.isInline || declaration.isExpect || declaration.isExternal) {
    return super.visitFunctionNew(declaration)
   }
​
  if (declaration is IrSimpleFunction) {
    // 过滤中缀函数,尾递归函数,操作符函数
    if (declaration.isInfix || declaration.isTailrec || declaration.isOperator) {
      return super.visitFunctionNew(declaration)
     }
   }
​
  // 过滤函数体为空的函数,不包括 `Proxy` 注解的函数
  if (declaration.body == null || !declaration.hasAnnotation(annotationClass)) {
    return super.visitFunctionNew(declaration)
   }
  
   ......
}
  1. 过滤挂起函数,内联函数,多渠道声明函数,外部函数(JNI),
  2. 过滤中缀函数,尾递归函数,操作符函数,
  3. 过滤函数体为空的函数,不包括 Proxy 注解的函数。

2.获取

override fun visitFunctionNew(declaration: IrFunction): IrStatement {
   ......
​
  val param = SwordParam()
  param.hasProxyAnnotation = true// 获取函数上 `Proxy` 注解的 `IrElement`, 回来 `IrConstructorCall`
  val irProxyConstructorCall = declaration.annotations.filter {
    it.isAnnotation(proxyAnnotationFqName)
   }.toList().single()
​
  // 获取 `Proxy.enable` 特点的值并存储
  val enableParam = irProxyConstructorCall.getValueArgument(0)
  enableParam?.let {
    if (it is IrConst<*>) {
      param.enable = it.value as Boolean
     }
   }
​
  // 获取 `Proxy.handler` 特点的值并存储
  val handlerParam = irProxyConstructorCall.getValueArgument(1)
  handlerParam?.let {
    if (it is IrConst<*>) {
      param.handler = it.value as String
     }
   }
​
   ......
}

获取 Proxy 注解中的数据,比较费事一些,一开始笔者以为获取到的注解或许类似于 IrAnnotation,可是发现却是 IrConstructorCall,后边细心想来注解不便是经过结构函数构建一个注解实例么?咱们在注解中传入的参数都是赋值给了其结构函数的特点。

经过 getValueArgument() 函数依据注解中声明特点的次序获取其对应的特点,由于特点或许有默认值,咱们在运用时能够不传入某个特点,比方:

@Proxy(
  // enable = true,
  handler = HandlerFqName.GET_TEXT_NO_ARG
)
fun getTextNoArg() = "guodongAndroid"

Proxy 注解中 enable 默以为 True,所以咱们在运用时能够不传入 enable 的值,使其运用默认值,可是对于 kotlin IR 来说,没有清晰运用的特点,经过 getValueArgument() 函数获取到的为 null,由于在 Kotlin IR 语法树中找不到这个特点。

由于注解中的特点值有必要是编译期常量,所以咱们能够把 handlerParam 转化为 IrConst 并获取它的值。

3.校验

override fun visitFunctionNew(declaration: IrFunction): IrStatement {
   ......
​
  // 获取 ClassName
  val className: String = getClassName(declaration)
  
  // 获取 MethodName
  val methodName = declaration.name.asString()
​
  // 校验 敞开署理后是否注入了 `handler`
  if (param.enable && (param.handler.isEmpty() || param.handler.isBlank())) {
    messageCollector.report(
      CompilerMessageSeverity.ERROR,
      "[$className.$methodName]启用署理后请注入`handler`",
     )
   }
​
   ......
}
​
private fun getClassName(
  declaration: IrFunction,
): String {
  val parentClassOrNull = declaration.parentClassOrNull
  val fileOrNull = declaration.fileOrNull
​
  return when {
    declaration.isLocal -> {
      // 本地办法: 类名.函数名.<anonymous>
      // 源码中有此逻辑, 逻辑较为繁琐,且不是 `Sword` 的中心逻辑,本文就不记录了
     }
    parentClassOrNull != null -> {
      // 获取类名
      parentClassOrNull.name.asString()
     }
    fileOrNull != null -> {
      // 假如是顶级函数,获取文件名或`JvmName`注解指定的姓名
      val annotations = fileOrNull.annotations
      if (annotations.hasAnnotation(jvmNameAnnotationFqName)) {
        val annotation = annotations.findAnnotation(jvmNameAnnotationFqName)!!
        val expression = annotation.getValueArgument(0)
        if (expression != null && expression is IrConst<*>) {
          expression.value as String
         } else {
          fileOrNull.name
         }
       } else {
        fileOrNull.name
       }
     }
    else -> "Unknown"
   }
}

获取 ClassName 时有以下几点考虑:

  1. 首要判别是否是本地办法,假如是本地办规律获取 类名.办法名.[<anonymous>]
  2. 其次获取当时函数地点的父级 IrClass,假如不为 null,则运用类名,
  3. 最终获取函数地点的 IrFile,假如不为 null,再判别文件上是否有 JvmName 注解,有的话运用 JvmName 注解指定的姓名,不然运用文件名。

获取 MethodName 时直接运用了函数称号,此处没有判别函数上是否有 JvmName 注解逻辑,信任读者能够自行扩展。

下面便是一个敞开署理后有必要注入 handler 的校验逻辑。

4.转化

在前面介绍 dump 函数时咱们并没有实际上看看 dump 函数的输出内容,接下来让咱们看看它输出的语法树信息。

以下面的函数为例:

@Proxy(
  enable = true,
  handler = HandlerFqName.GetTextArgInvocationHandler
)
fun testHandler(): User {
  return GetTextArgInvocationHandler().invoke("Test", "testHandler", emptyArray()) as User
}

dump 函数的输出结果如下:

FUN name:testHandler visibility:public modality:FINAL <> ($this:com.guodong.android.sword.app.Test) returnType:com.guodong.android.sword.app.User
  annotations:
   Proxy(enable = 'true', handler = 'com.guodong.android.sword.app.GetTextArgInvocationHandler')
  $this: VALUE_PARAMETER name:<this> type:com.guodong.android.sword.app.Test
  BLOCK_BODY
   RETURN type=kotlin.Nothing from='public final fun testHandler (): com.guodong.android.sword.app.User declared in com.guodong.android.sword.app.Test'
    TYPE_OP type=com.guodong.android.sword.app.User origin=CAST typeOperand=com.guodong.android.sword.app.User
     CALL 'public open fun invoke (className: kotlin.String, methodName: kotlin.String, args: kotlin.Array<kotlin.Any?>): kotlin.Any? declared in com.guodong.android.sword.app.GetTextArgInvocationHandler' type=kotlin.Any? origin=null
      $this: CONSTRUCTOR_CALL 'public constructor <init> () [primary] declared in com.guodong.android.sword.app.GetTextArgInvocationHandler' type=com.guodong.android.sword.app.GetTextArgInvocationHandler origin=null
      className: CONST String type=kotlin.String value="Test"
      methodName: CONST String type=kotlin.String value="testHandler"
      args: CALL 'public final fun emptyArray <T> (): kotlin.Array<T of kotlin.ArrayIntrinsicsKt.emptyArray> [inline] declared in kotlin.ArrayIntrinsicsKt' type=kotlin.Array<kotlin.Any?> origin=null
       <T>: kotlin.Any?

fun testHandler(): User

FUN name:testHandler visibility:public modality:FINAL <> ($this:com.guodong.android.sword.app.Test) returnType:com.guodong.android.sword.app.User

这是 testHandler 函数的界说。它界说了函数的称号,可见性,模态以及类型签名。咱们能够清楚的看到它是一个 publicfinal 名为 testHandler 的函数,而且它有一个隐含的参数 this()),可是没有类型参数(<>),最终回来值为 com.guodong.android.sword.app.User

类中的非静态函数(结构函数除外)都有一个隐含的 this 参数:

$this: VALUE_PARAMETER name:<this> type:com.guodong.android.sword.app.Test

GetTextArgInvocationHandler()

$this: CONSTRUCTOR_CALL 'public constructor <init> () [primary] declared in com.guodong.android.sword.app.GetTextArgInvocationHandler' type=com.guodong.android.sword.app.GetTextArgInvocationHandler origin=null

调用 GetTextArgInvocationHandler 的无参结构办法。

Test & testHandler

className: CONST String type=kotlin.String value="Test"
methodName: CONST String type=kotlin.String value="testHandler"

TesttestHandler 作为一个常量字符串。

emptyArray()

args: CALL 'public final fun emptyArray <T> (): kotlin.Array<T of kotlin.ArrayIntrinsicsKt.emptyArray> [inline] declared in kotlin.ArrayIntrinsicsKt' type=kotlin.Array<kotlin.Any?> origin=null
       <T>: kotlin.Any?

emptyArray() 函数是有类型参数的,因编译器推导咱们在编写代码时能够省略,可是在 Kotlin IR 中清晰显示了它的类型参数(<T>: kotlin.Any?),所以咱们在转化代码时需求留意此处细节,不然转化代码在编译期不会报错,在运行时会抛出反常

invoke("Test", "testHandler", emptyArray())

CALL 'public open fun invoke (className: kotlin.String, methodName: kotlin.String, args: kotlin.Array<kotlin.Any?>): kotlin.Any? declared in com.guodong.android.sword.app.GetTextArgInvocationHandler' type=kotlin.Any? origin=null

这是 invoke 函数的调用,清楚表明调用 invoke 函数需求三个参数以及参数的类型,一起函数回来值为 kotlin.Any?

as String

TYPE_OP type=com.guodong.android.sword.app.User origin=CAST typeOperand=com.guodong.android.sword.app.User

这是强转操作,TYPE_OP 表明一种类型操作,origin=CAST 表明类型操作符。

return

RETURN type=kotlin.Nothing from='public final fun testHandler (): com.guodong.android.sword.app.User declared in com.guodong.android.sword.app.Test'

Kotlinreturn 其实是一个表达式,所以此处 returntypekotlin.Nothing

经过上面 dump 函数输出语法树的剖析,咱们现已知道转化后函数体的语法信息,接下来让咱们依据上面的剖析顺次来完成吧。

val handlerConstructorSymbol =
    pluginContext.referenceConstructors(FqName(param.handler)).single {
      it.owner.valueParameters.isEmpty()
    }

首要经过 pluginContext.referenceConstructors() 查找 Handler 类的无参结构函数的符号。

val invokeSymbol =
    pluginContext.referenceFunctions(FqName("${param.handler}.$INVOKE_METHOD_NAME"))
        .single {
            val valueParameters = it.owner.valueParameters
            valueParameters.size == 3 &&
                    valueParameters[0].type == stringType &&
                    valueParameters[1].type == stringType &&
                    valueParameters[2].type == arrayAnyNType
        }

接下来经过 pluginContext.referenceFunctions() 查找 Handler 类的 invoke 函数,或许存在函数重载,需求经过 single 函数确定咱们需求的函数,这个在前面 IrType 举过比如。

private fun IrBuilderWithScope.irSwordArrayParams(function: IrFunction): IrCall {
  val parameters = function.valueParameters
  return if (parameters.isEmpty()) {
    irCall(emptyArraySymbol).also {
      it.putTypeArgument(0, anyNType)
     }
   } else {
    irCall(arrayOfSymbol).also {
      val expressions = parameters.map { parameter -> irGet(parameter) }
      it.putValueArgument(0, irVararg(anyNType, expressions))
     }
   }
}

上面代码是组装 invoke 函数的第三个参数 args: Array<Any?>,当时函数没有参数时运用 emptyArray<Any?>(),有参数时运用 arrayOf(vararg elements: T)

val invokeCall = irCall(invokeSymbol).apply {
  dispatchReceiver = irCallConstructor(handlerConstructorSymbol, emptyList())
  putValueArgument(0, irString(className))
  putValueArgument(1, irString(methodName))
  putValueArgument(2, irSwordArrayParams(function))
}

结构 Handler 实例并调用它的 invoke 函数。

val irReturn = irReturn(
  typeOperator(
    resultType = function.returnType,
    argument = invokeCall,
    typeOperator = IrTypeOperator.CAST,
    typeOperand = function.returnType
   )
)

typeOperator 是语法树中的 TYPE_OP,便是强转操作,irReturn 表明 Kotlin 中的 return 表达式。

private fun irSword(
  function: IrFunction,
  param: SwordParam,
  className: String,
  methodName: String,
): IrBlockBody {
  return DeclarationIrBuilder(pluginContext, function.symbol).irBlockBody {
     ......
    
    +irReturn
   }
}

经过 + 操作符(unaryPlus)链接整个回来表达式到函数体,最终用新的函数体替换原函数体到达署理的功用:

override fun visitFunctionNew(declaration: IrFunction): IrStatement {
   ......
​
  if (param.enable) {
    declaration.body = irSword(declaration, param, className, methodName)
   }
​
  return super.visitFunctionNew(declaration)
}

至此,运用 Kotlin IRKotlin 函数添加署理功用完成。

总结

本文简略记录了经过 Kotlin IR 完成 Sword 署理功用的进程,一起简略介绍了一些 Kotlin IR 的 API 以及笔者对这些 API 的个人理解。

期望能够帮您开发自己的 Kotlin 编译器插件,happy~

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。