Kotlin 元编程之 KSP 全面突破

Posted 川峰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin 元编程之 KSP 全面突破相关的知识,希望对你有一定的参考价值。

什么是元编程

没想到吧,这世上除了元宇宙,还有元编程,如果没有接触过,可能会有点懵,不过没关系,简单的说就是用代码来生成代码。实现元编程的传统常见手段主要是使用 APT注解处理器 + JavaPoet 组合拳,如果你是作为一名android 开发者,一定在曾经或者现在使用过很多知名的开源库,比如ButterKnifeARouter等,这些都是基于 注解处理器 + JavaPoet 的方式实现的元编程,是的,虽然元编程这个词很高大上,但是可能你已经默默的使用了很多年了。

元编程就是以源代码作为输入数据的程序,比如编译器、链接器、解释器、调试工具和程序分析工具等等,它们可以在编译时分析源码,对源码进行处理或修改,或者产生中间代码。当然主要的目的还是为了生成代码。

在什么场景下需要元编程呢?

  • 当我们需要生成某种模板代码、样板代码的时候
  • 当我们厌倦了写太多重复代码的时候
  • 当我们需要隐藏实现细节的时候
  • 当我们想要创建语法糖的时候

Kotlin 元编程的常见实现手段

  • Kotlin 反射 / Java 反射
  • Kotlin 注解处理器 (KAPT:Kotlin Annotation Processor Tool)
  • Kotlin 符号处理器 (KSP:Kotlin Symbol Processing)
  • Kotlin 编译器插件(KCP:Kotlin Compiler Plugin)

kotlin 元编程的几种方案对比

ReflectionKAPTKSPKCP
运行时---
编译时-解析metadata基于 Kotlin AST基于 Kotlin AST
复杂度较低较高
主要场景提供动态能力生成源码生成源码生成、修改IR
现状稳定稳定稳定实验
多平台JVM + JSJVM全部全部

KAPT 的工作机制

在进行Android应用开发时,不少人吐槽 Kotlin 的编译速度慢,而 KAPT 便是拖慢编译的元凶之一。我们知道,Android的很多库都会使用注解简化模板代码,例如 Room、Dagger、Retrofit 等,而默认情况下Kotlin 使用的是 KAPT 来处理注解的。KAPT没有专门的注解处理器,需要借助APT实现的,因此需要先生成 APT 可解析的 stub (Java代码),这拖慢了 Kotlin 的整体编译速度。



所以 KAPT 的本质还是基于 Java 注解处理器实现的一个Kotlin 编译器插件。

KAPT 处理 Kotlin 源码存在的问题:

  • 实现复杂,需要手动解析 Kotlin 类信息
  • 编译耗时,KAPT 需将 Kotlin 类转成 Java Stubs
  • 只支持Kotlin-JVM

KCP

KCP是在 kotlinc 过程中提供 Hook 时机,可以在期间解析 AST、修改字节码产物等,Kotlin 的不少语法糖都是 KCP 实现的。例如, data class、 @Parcelize、kotlin-android-extension 等,如今火爆的 Jetpack Compose也是借助 KCP 完成的。

理论上来说, KCP 的能力是 KAPT 的超集,完全可以替代 KAPT 以提升编译速度。但是 KCP 的开发成本太高,涉及 Gradle Plugin、Kotlin Plugin 等的使用,API 涉及一些编译器知识的了解,一般开发者很难掌握。

KSP 简化了KCP的整个流程,开发者无需了解编译器工作原理,处理注解等成本也变得像 KAPT 一样低。

什么是 KSP

KSP 的全称是 Kotlin Symbol Processing ,Kotlin符号处理器,由Google开发,它提供了一套API可以开发轻量级的编译器插件。KSP 官网:https://github.com/google/ksp

KSP本身也是一种KCP插件的实现。

KSP API 根据Kotlin语法在符号级对Kotlin程序结构进行建模。当基于KSP 的插件处理源代码时,可以访问类、类成员、函数和相关参数等结构,但是不能访问 if 块和 for 循环等。

从概念上讲,KSP类似于Kotlin反射中的KType。该API允许处理器从类声明导航到具有特定类型参数的对应类型,反之亦然。还可以替换类型参数、指定方差、应用星型投影和标记类型的可空性。

另一种理解KSP的方式是将其视为Kotlin程序的预处理器框架。编译中的数据流可以按照以下步骤描述:

  1. KSP读取并分析源代码。
  2. KSP生成代码或输出其他形式的产物。
  3. Kotlin编译器将源代码与KSP生成的代码一起编译。

与成熟的编译器插件不同,KSP不能修改代码。因为改变语言语义的编译器插件有时会让人非常困惑。KSP是以只读的方式来处理源代码,从而避免这种情况。

为什么更推荐使用 KSP

KSP 使得创建轻量级编译器插件更加容易

KSP被设计为隐藏编译器更改,最大限度地减少使用它的处理器的维护工作。KSP被设计成不与JVM绑定,因此将来可以更容易地适应其他平台。

KSP VS KCP

KCP相比于KSP的不足:

  • 技能过于复杂,凡人难以驾驭:KCP插件几乎可以访问编译器中的所有内容,具有最大的功能和灵活性,但这种强大的功能是有代价的。即使要编写最简单的插件,你也需要有一些编译器的背景知识,以及对特定编译器的实现细节有一定程度的熟悉。一般的开发者很难在短时间内通过学习成为编译器大师,并且这会花费很多的时间。如果你不需要修改源代码,那么KSP则是一个更好的选择。
  • 依赖项过多,凡人难以维护:由于KCP插件可能依赖于编译器中的任何东西,所以它们对编译器的更改很敏感,需要经常维护。在实际中,插件通常与特定的编译器版本紧密相关,这意味着每次你想要支持一个更新版本的编译器时,你可能需要更新你的插件。

KSP通过定义良好的API隐藏大多数编译器更改,尽管编译器甚至Kotlin语言的重大更改可能仍然需要向API用户公开。KSP试图通过提供一个API来实现常见的用例,该API以功能换取简单性。它的功能是一个通用kotlinc插件的严格子集。例如,kotlinc可以检查表达式和语句,甚至可以修改代码,而KSP不能。

KSP VS 反射

KSP的API看起来类似于kotlin.reflect。它们之间的主要区别是KSP中的类型引用需要显式地解析。这是不共享接口的原因之一。

KSP VS KAPT

KAPT使大量的Java注释处理器可以为Kotlin程序开箱即用。与KAPT相比,KSP的主要优点是改进了构建性能(不依赖于JVM)、更习惯的Kotlin API以及理解Kotlin专用符号的能力。

在性能方面,相比于 KAPT,使用KSP生成代码性能要快2倍以上,因为它省掉了生成 Java Stubs 的耗时过程。

为了不加修改的直接运行 Java 注解处理器,kapt 将 Kotlin 代码编译为 Java 桩代码(stub),其中保留了 Java 注解处理器关注的信息。为了创建这些桩代码, kapt 需要解析 Kotlin 程序中的所有符号。桩代码生成占据了 kotlinc 完整分析过程的大约 1/3kotlinc 的代码生成过程也是如此。 对于很多注解处理器,这个过程比处理器本身耗费的时间要长很多。比如, Glide 只会分析使用了预定义注解的非常少量的类,它的代码生成非常快速, 几乎所有的构建开销都发生在桩代码生成阶段,切换到 KSP 可以立即减少编译器消耗时间的 25%

kapt 不同, KSP 中的处理器不会以 Java 的方式看待输入程序。 APIKotlin 来说更加自然,尤其是对于 Kotlin 专有的功能,比如顶层函数。由于 KSP 不会象 kapt 那样将处理代理给 javac, 因此它不会依赖于 JVM 专有的行为,并且将来有可能用于其它平台。

KSP 的限制

虽然KSP试图成为大多数常见用例的简单解决方案,但与其他插件解决方案相比,它做了一些权衡。KSP目前存在以下几点限制:

  • 无法做到检查源代码的表达式级信息。
  • 无法修改源代码。
  • 无法 100% 的兼容Java注解处理API。
  • 目前IDEKSP生成的代码无法感知,必须手动为项目配置生成路径。

Kotlin Symbols

大多数处理器通过输入源代码的各种程序结构进行导航。在深入研究API的使用之前,让我们看看从 KSP 的视角来看Kotlin源文件是怎样的:

KSFile
  packageName: KSName
  fileName: String
  annotations: List<KSAnnotation>  (File annotations)
  declarations: List<KSDeclaration>
    KSClassDeclaration // class, interface, object
      simpleName: KSName
      qualifiedName: KSName
      containingFile: String
      typeParameters: KSTypeParameter
      parentDeclaration: KSDeclaration
      classKind: ClassKind
      primaryConstructor: KSFunctionDeclaration
      superTypes: List<KSTypeReference>
      // contains inner classes, member functions, properties, etc.
      declarations: List<KSDeclaration>
    KSFunctionDeclaration // top level function
      simpleName: KSName
      qualifiedName: KSName
      containingFile: String
      typeParameters: KSTypeParameter
      parentDeclaration: KSDeclaration
      functionKind: FunctionKind
      extensionReceiver: KSTypeReference?
      returnType: KSTypeReference
      parameters: List<KSValueParameter>
      // contains local classes, local functions, local variables, etc.
      declarations: List<KSDeclaration>
    KSPropertyDeclaration // global variable
      simpleName: KSName
      qualifiedName: KSName
      containingFile: String
      typeParameters: KSTypeParameter
      parentDeclaration: KSDeclaration
      extensionReceiver: KSTypeReference?
      type: KSTypeReference
      getter: KSPropertyGetter
        returnType: KSTypeReference
      setter: KSPropertySetter
        parameter: KSValueParameter

这里列出了一个Kotlin源文件中声明的常见内容如: 类、函数、属性等等。该结构也被称为AST(抽象语法树),类似的, APT/KAPT 则是对 Java AST 的抽象,我们可以找到一些对应关系,比如 Java 使用 Element 描述包、类、方法或者变量等, KSP 中使用 Declaration。

KSP 是如何组织 Kotlin 代码模型的

类型解析

在 KSP API 的底层实现中, 主要的资源消耗是类型解析。因此类型引用被设计为由处理器明确解析的类型(也有少数例外情况)。当一个类型(Type) (比如 KSFunctionDeclaration.returnTypeKSAnnotation.annotationType)被引用时,它永远是一个 KSTypeReference类型,这是一个带有注解和修饰符的 KSReferenceElement

interface KSFunctionDeclaration : ... 
  val returnType: KSTypeReference?
  // ...


interface KSTypeReference : KSAnnotated, KSModifierListOwner 
  val type: KSReferenceElement

一个 KSTypeReference 可以解析为一个 KSType, 它引用到 Kotlin 类型系统中的一个类型。

一个KSTypeReference 拥有一个 KSReferenceElement, 它是 Kotlin 程序结构的数据模型:也就是类型引用是如何编写的。它对应于 Kotlin 语法中的 type 元素。

一个 KSReferenceElement 可以是一个 KSClassifierReferenceKSCallableReference,其中包含很多不需要解析的有用信息。 比如 KSClassifierReference 拥有 referencedName,而 KSCallableReference 拥有 receiverType, functionArguments, 和 returnType

如果需要一个 KSTypeReference 引用的原始声明, 通常可以通过将其解析为 KSType, 并通过访问 KSType.declaration 得到。要从一个类型得到它的类声明, 代码如下:

val ksType: KSType = ksTypeReference.resolve()
val ksDeclaration: KSDeclaration = ksType.declaration

类型解析的代价很高,因此需要明确调用。通过解析得到的有些信息在 KSReferenceElement 中已经存在了。 比如, 通过 KSClassifierReference.referencedName 可以过滤掉很多不感兴趣的元素。你应该只有在需要从 KSDeclarationKSType 得到具体信息的时候才进行类型解析。

指向一个函数类型的 KSTypeReference 在它的元素中已经有了大部分信息。尽管可以解析到 Function0, Function1 等等的函数群, 但这些解析不会带来比 KSCallableReference 更多的任何信息。有一种情况需要解析函数类型引用,就是处理函数原型(Function Prototype)的 identity.

KSP 和 Java 中的程序元素对应关系

Java / APTKSP 中的类似功能注意事项
AnnotationMirrorKSAnnotation
AnnotationValueKSValueArguments
ElementKSDeclaration/KSDeclarationContainer
ExecutableElementKSFunctionDeclaration
PackageElementKSFileKSP不会将package建模为程序元素
ExecuteableElementKSFunctionDeclaration某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素
TypeElementKSClassDeclaration一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口
VariableElementKSVariableParameter / KSPropertyDeclaration一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
ParameterizableKSDeclaration
QualifiedNameableKSDeclaration
TypeElementKSClassDeclaration
TypeParameterElementKSTypeParameter
VariableElementKSValueParameter/KSPropertyDeclaration

类型

KSP 要求明确解析类型, 因此在解析之前, Java 中的有些功能只能通过 KSType 和对应的元素得到.

Java / APTKSP 中的类似功能注意事项
ArrayTypeKSBuiltIns.arrayType
DeclaredTypeKSType / KSClassifierReference
ErrorTypeKSType.isError
ExecutableTypeKSType / KSCallableReference
IntersectionTypeKSType / KSTypeParameter
NoTypeKSType.isErrorKSP 中没有这样的功能
NullTypeKSP 中没有这样的功能
PrimitiveTypeKSBuiltIns与 Java 中的基本类型不完全相同
ReferenceTypeKSTypeReference
TypeMirrorKSType
TypeVariableKSTypeParameter
UnionType没有这样的功能 Kotlin 的 每个 catch 代码段只有 1 个类型.
即使对 Java 注解处理器来说, UnionType 也是不可访问的
WildcardTypeKSType / KSTypeArgument

杂项

Java / APTKSP 中的类似功能注意事项
NameKSName
ElementKindClassKind / FunctionKind
ModifierModifier
NestingKindClassKind / FunctionKind
AnnotationValueVisitor
ElementVisitorKSVisitor
AnnotatedConstructKSAnnotated
TypeVisitor
TypeKindKSBuiltIns有些可以在 builtin 中得到, 其他通过 KSClassDeclaration 得到 DeclaredType
ElementFilterCollection.filterIsInstance
ElementKindVisitorKSVisitor
ElementScannerKSTopDownVisitor
SimpleAnnotationValueVisitorKSP 中不需要
SimpleElementVisitorKSVisitor
SimpleTypeVisitor
TypeKindVisitor
TypesResolver / utils有些 utils 也被集成在符号接口中
ElementsResolver / utils

细节

这部分介绍 KSP 怎样提供 Java 注解处理 API 的功能.

AnnotationMirror

JavaKSP 中的同等功能
getAnnotationTypeksAnnotation.annotationType
getElementValuesksAnnotation.arguments

AnnotationValue

JavaKSP 中的同等功能
getValueksValueArgument.value

Element

JavaKSP 中的同等功能
asTypeksClassDeclaration.asType(…)
getAnnotation未实现
getAnnotationMirrorsksDeclaration.annotations
getEnclosedElementsksDeclarationContainer.declarations
getEnclosingElementsksDeclaration.parentDeclaration
getKind通过 ClassKind 或 FunctionKind 进行类型检查和转换
getModifiersksDeclaration.modifiers
getSimpleNameksDeclaration.simpleName

ExecutableElement

JavaKSP 中的同等功能
getDefaultValue未实现
getParametersksFunctionDeclaration.parameters
getReceiverTypeksFunctionDeclaration.parentDeclaration
getReturnTypeksFunctionDeclaration.returnType
getSimpleNameksFunctionDeclaration.simpleName
getThrownTypesKotlin 中不需要
getTypeParametersksFunctionDeclaration.typeParameters
isDefault检查父类型是不是接口
isVarArgsksFunctionDeclaration.parameters.any it.isVarArg

Parameterizable

JavaKSP 中的同等功能
getTypeParametersksFunctionDeclaration.typeParameters

QualifiedNameable

JavaKSP 中的同等功能
getQualifiedNameksDeclaration.qualifiedName

TypeElement

JavaKSP 中的同等功能
getEnclosedElementsksClassDeclaration.declarations
getEnclosingElementksClassDeclaration.parentDeclaration
getInterfaces// 不需要类型解析也应该能够实现
ksClassDeclaration.superTypes
.map it.resolve()
.filter (it?.declaration as? KSClassDeclaration)?.classKind == ClassKind.INTERFACE
getNestingKindCheck KSClassDeclaration.parentDeclaration 和 inner 修饰符
getQualifiedNameksClassDeclaration.qualifiedName
getSimpleNameksClassDeclaration.simpleName
getSuperclass// 不需要类型解析也应该能够实现
ksClassDeclaration.superTypes
.map it.resolve()
.filter (it?.declaration as? KSClassDeclaration)?.classKind == ClassKind.CLASS
getTypeParametersksClassDeclaration.typeParameters

TypeParameterElement

JavaKSP 中的同等功能
getBoundsksTypeParameter.bounds
getEnclosingElementksTypeParameter.parentDeclaration
getGenericElementksTypeParameter.parentDeclaration

VariableElement

JavaKSP 中的同等功能
getConstantValue未实现
getEnclosingElementksValueParameter.parentDeclaration
getSimpleNameksValueParameter.simpleName

ArrayType

JavaKSP 中的同等功能
getComponentTypeksType.arguments.first()

DeclaredType

JavaKSP 中的同等功能
asElementksType.declaration
getEnclosingTypeksType.declaration.parentDeclaration
getTypeArgumentsksType.arguments

ExecutableType

函数的 KSType 只是一个签名, 由 FunctionN<R, T1, T2, ..., TN> 群表达.

JavaKSP 中的同等功能
getParameterTypesksType.declaration.typeParameters, ksFunctionDeclaration.parameters.map it.type
getReceiverTypeksFunctionDeclaration.parentDeclaration.asType(…)
getReturnTypeksType.declaration.typeParameters.last()
getThrownTypesKotlin 中不需要
getTypeVariablesksFunctionDeclaration.typeParameters

Elements

JavaKSP 中的同等功能
getAllAnnotationMirrorsKSDeclarations.annotations
getAllMembersgetAllFunctions, getAllProperties未实现
getBinaryName未决定, 参见 Java Specification
getConstantExpression常数值, 而不是表达式
getDocComment未实现
getElementValuesWithDefaults未实现
getNameresolver.getKSNameFromString
getPackageElement不支持包, 但可以取得包信息. KSP 中不能对包进行操作.
getPackageOf不支持包
getTypeElementResolver.getClassDeclarationByName
hides未实现
isDeprecatedKsDeclaration.annotations.any it.annotationType.resolve()!!.declaration.qualifiedName!!.asString()== Deprecated::class.qualifiedName
overridesKSFunctionDeclaration.overrides / KSPropertyDeclaration.overrides (各个类的成员函数)
printElementsKSP 对大多数类有基本的 toString() 实现

Types

JavaKSP 中的同等功能
asElementksType.declaration
asMemberOfresolver.asMemberOf
boxedClass不需要
capture未决定
containsKSType.isAssignableFrom
directSuperTypes(ksType.declaration as KSClassDeclaration).superTypes
erasureksType.starProjection()
getArrayTypeksBuiltIns.arrayType.replace(…)
getDeclaredTypeksClassDeclaration.asType
getNoTypeksBuiltIns.nothingType / null
getNullType根据上下文确定, 可能可以使用 KSType.markNullable
getPrimitiveType不需要, 检查 KSBuiltins
getWildcardType在需要 KSTypeArgument 的地方使用 Variance
isAssignableksType.isAssignableFrom
isSameTypeksType.equals
isSubsignaturefunctionTypeA == functionTypeB / functionTypeA == functionTypeB.starProjection()
isSubtypeksType.isAssignableFrom
unboxedType不需要

KSP 的使用

依赖配置

在 Android Studio 已有的kotlin项目中新建一个普通的 library 工程作为KSP处理模块(其他IDE配置请参考官网),在其build.gradle中添加如下配置:

plugins 
    id 'java-library'
    id 'org.jetbrains.kotlin.jvm'


java 
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8


java.sourceSets 
    main 
        java.srcDirs += "src/main/kotlin"
    


dependencies  
    implementation 'com.google.devtools.ksp:symbol-processing-api:1.7.21-1.0.8'

ksp依赖库的版本需要根据项目使用的kotlin版本来决定,最新版本的Android Studio一般默认是在根目录下的build.gradle中可以找到kotlin版本配置。然后到KSP的发布页找到对应的KSP版本即可:https://github.com/google/ksp/releases

SymbolProcessorProvider : KSP 的入口

在 library module 中需要新建一个 SymbolProcessorProvider 的实现类作为KSP的入口,SymbolProcessorProvider 接口的代码如下:

interface SymbolProcessorProvider 
    fun create(environment: SymbolProcessorEnvironment): SymbolProcessor

可以看到它只有一个 create 方法,该方法需要返回一个实现 SymbolProcessor 接口的对象,而 create 方法的入参SymbolProcessorEnvironment 主要就是用来给创建 SymbolProcessor 对象用的,通过environment参数可以获取到 KSP 运行时的相关依赖,我们只需将这些依赖注入到自定义的 Processor 对象即可。

class ProcessorProvider : SymbolProcessorProvider 
    override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor 
        return Processor(environment.codeGenerator, environment.logger,environment.options)
    

这里创建Processor使用了SymbolProcessorEnvironment 的三个字段:

  • codeGenerator:可以用来生成代码文件
  • logger:可以用来输出日志
  • options:可以用来接受命令行或Gradle插件中的配置参数

一般来说,codeGenerator 参数是一定要的,因为你总要生成代码吧,而其他的参数可以根据自己的需要选择。通过查看SymbolProcessorEnvironment的源码可以知道全部可用的字段:

class SymbolProcessorEnvironment(
    /**
     * passed from command line, Gradle, etc.
     */
    val options: Map<String, String>,

    /**
     * language version of compilation environment.
     */
    val kotlinVersion: KotlinVersion,

    /**
     * creates managed files.
     */
    val codeGenerator: CodeGenerator,

    /**
     * for logging to build output.
     */
    val logger: KSPLogger,

    /**
     * Kotlin API version of compilation environment.
     */
    val apiVersion: KotlinVersion,

    /**
     * Kotlin compiler version of compilation environment.
     */
    val compilerVersion: KotlinVersion,

    /**
     * Information of target platforms
     *
     * There can be multiple platforms in a metadata compilation.
     */
    val platforms: List<PlatformInfo>,
) ...

当我们创建好 SymbolProcessorProvider 对象后就可以先将其添加到src/main/resources/META-INF/services/路径下的一个名为com.google.devtools.ksp.processing.SymbolProcessorProvider的文件中:

在上面的文件中,输入ProcessorProvider对象的全类名,例如:

com.fly.ksp.processor.ProcessorProvider

SymbolProcessor

SymbolProcessor 接口类就是KSP开发时唯一需要重点关注的类

interface SymbolProcessor 
    fun process(resolver: Resolver): List<KSAnnotated> // 重点关注
    fun finish() 
    fun onError() 

它有三个方法,但唯一需要覆写的只有 process 这个方法,下面定义一个类来实现该接口:

class Processor(val codeGenerator: CodeGenerator, val logger: KSPLogger) : SymbolProcessor 

    val functions = mutableListOf<String>()
    val visitor = FindFunctionsVisitor()

    override fun process(resolver: Resolver) 
        resolver.getAllFiles().map  it.accept(visitor, Unit) 
    

KSVisitor

SymbolProcessor.process() 方法提供了一个 Resolver , 来解析源文件的 symbols,而Resolver 使用访问者模式去遍历 AST,需要一个KSVisitor参数。

下面代码定义了一个 FindFunctionsVisitorResolver 使用,在这个Visitor中负责找出当前 KSFile 中的 top-levelfunction 以及 Class 成员方法。

class FindFunctionsVisitor : KSVisitorVoid() 
     override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) 
         classDeclaration.getDeclaredFunctions().map  it.accept(this, Unit) 
     

     override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) 
         functions.add(function)
     

     override fun visitFile(file: KSFile, data: Unit) 
         file.declarations.map  it.accept(this, Unit) 
     

一些 KSP API 示例

得到所有成员函数
fun KSClassDeclaration.getDeclaredFunctions(): List<KSFunctionDeclaration> =
    declarations.filterIsInstance<KSFunctionDeclaration>()
检查一个类或函数是否为 local
fun KSDeclaration.isLocal(): Boolean =
    parentDeclaration != null && parentDeclaration !is KSClassDeclaration
查找类型别名指向的实际的类或接口声明
fun KSTypeAlias.findActualType(): KSClassDeclaration 
    val resolvedType = this.type.resolve().declaration
    return if (resolvedType is KSTypeAlias) 
        resolvedType.findActualType()
     else 
        resolvedType as KSClassDeclaration
    

在源代码文件的注解中查找被压制(Suppressed)的名称
// @file:kotlin.Suppress("Example1", "Example2")
fun KSFile.suppressedNames(): List<String> 
    val ignoredNames = mutableListOf<String>()
    annotations.filter 
        it.shortName.asString() == "Suppress" && it.annotationType.resolve()?.declaration?.qualifiedName?.asString() == "kotlin.Suppress"
    .forEach 
        val argValues: List<String> = it.arguments.flatMap  it.value 
        ignoredNames.addAll(argValues)
    
    return ignoredNames

一个简单的 demo

现在有一个类,代码如下,假如我们现在想要为其生成建造者模式的代码

class AClass(private val a: Int, val b: String, val c: Double) 
    val p = "$a, $b, $c"
    fun foo() = p

尽管 kotlin 中支持默认参数值和命名参数,基本上可以取代建造者模式的使用了,但是假如你更喜欢建造者模式的使用方式,你仍然可以通过代码来编写它,问题是这样的代码有大量重复的样板代码需要编写,十分的消耗体力,那么此时使用KSP就可以为我们节省劳动力。

假如我们期望在生成建造者模式的代码之后使用方式如下:

@Builder
class AClass(private val a: Int, val b: String, val c: Double) 
    val p = "$a, $b, $c"
    fun foo() = p


fun main()  
    val a = AClassBuilder()
        .withA(1)
        .withB("foo")
        .withC(2.3)
        .build()
    println(a.foo())

以上是关于Kotlin 元编程之 KSP 全面突破的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 元编程之 KSP 实战:通过自定义注解配置Compose导航路由

Kotlin 元编程之 KotlinPoet

KSP - 元编程编译提速的小助手

告别KAPT,使用KSP为Android编译提速

告别KAPT,使用KSP为Android编译提速

拥抱Kotlin Symbol Processing(KSP),手把手带你实现Kotlin的专有注解处理