Skip to content

多轮处理

KSP 支持 多轮处理,即文件可以经过多轮处理。这意味着后续轮次会使用前一轮次的输出来作为额外的输入。

更改你的处理器

要使用多轮处理,SymbolProcessor.process() 函数需要为无效符号返回一个延迟符号列表(List<KSAnnotated>)。使用 KSAnnotated.validate() 来过滤要延迟到下一轮的无效符号。

以下示例代码展示了如何通过验证检测来延迟无效符号:

kotlin
override fun process(resolver: Resolver): List<KSAnnotated> {
    val symbols = resolver.getSymbolsWithAnnotation("com.example.annotation.Builder")
    val result = symbols.filter { !it.validate() }
    symbols
        .filter { it is KSClassDeclaration && it.validate() }
        .map { it.accept(BuilderVisitor(), Unit) }
    return result
}

多轮行为

将符号延迟到下一轮

处理器可以将某些符号的处理延迟到下一轮。当一个符号被延迟时,处理器会等待其他处理器提供额外信息。它可以根据需要持续延迟该符号,直到所需信息被其他处理器提供后,该处理器才能处理被延迟的符号。处理器应仅延迟那些缺少必要信息的无效符号。因此,处理器不应延迟来自 classpath 的符号,KSP 也会过滤掉任何非源代码的延迟符号。

例如,一个为注解类创建构建器的处理器可能要求其构造函数的所有形参类型都有效(即解析为具体类型)。在第一轮中,其中一个形参类型无法解析。然后,在第二轮中,由于第一轮生成的文件,它变得可解析了。

验证符号

判断一个符号是否应该被延迟的一个便捷方法是通过验证。处理器应该知道处理符号所必需的信息。请注意,验证通常需要解析,这可能代价昂贵,因此我们建议只检测所需内容。继续前例,对于构建器处理器而言,理想的验证是仅检测注解符号构造函数的所有已解析形参类型是否包含 isError == false

KSP 提供了一个默认的验证工具。更多信息,请参阅 高级 部分。

终止条件

当一整轮处理没有生成任何新文件时,多轮处理就会终止。如果在满足终止条件时仍存在未处理的延迟符号,KSP 会为每个带有未处理延迟符号的处理器记录一条错误消息。

每轮可访问的文件

新生成的文件和现有文件都可以通过 Resolver 访问。KSP 提供了两个用于访问文件的 API:Resolver.getAllFiles()Resolver.getNewFiles()getAllFiles() 返回现有文件和新生成文件的组合列表,而 getNewFiles() 仅返回新生成的文件。

getSymbolsAnnotatedWith() 的变化

为了避免不必要的符号重复处理,getSymbolsAnnotatedWith() 仅返回在新生成的文件中找到的符号,以及上一轮中延迟的符号。

处理器实例化

处理器实例只创建一次,这意味着你可以在处理器对象中存储信息,以便在后续轮次中使用。

跨轮次信息一致性

所有 KSP 符号都不能跨多轮重用,因为解析结果可能会根据前一轮中生成的内容而改变。然而,由于 KSP 不允许修改现有代码,因此某些信息,例如符号名称的字符串值,仍应可重用。总而言之,处理器可以存储前几轮的信息,但需要记住这些信息在后续轮次中可能会失效。

错误和异常处理

当发生错误(由处理器调用 KSPLogger.error() 定义)或异常时,处理会在当前轮次完成后停止。所有处理器都将调用 onError() 方法,并且不会调用 finish() 方法。

请注意,即使发生了错误,其他处理器在该轮次中仍会正常继续处理。这意味着错误处理发生在当前轮次处理完成后。

发生异常时,KSP 将尝试区分来自 KSP 的异常和来自处理器的异常。异常将导致处理立即终止,并作为错误记录在 KSPLogger 中。来自 KSP 的异常应报告给 KSP 开发者以进行进一步调查。在发生异常或错误的轮次结束时,所有处理器都将调用 onError() 函数来执行其自己的错误处理。

KSP 为 onError() 提供了一个默认的无操作(no-op)实现,作为 SymbolProcessor 接口的一部分。你可以覆盖此方法来提供自己的错误处理逻辑。

高级

验证的默认行为

KSP 提供的默认验证逻辑会验证被验证符号封闭作用域内所有直接可达的符号。默认验证检测封闭作用域中的引用是否可解析为具体类型,但不会递归地深入到引用类型中执行验证。

编写你自己的验证逻辑

默认的验证行为可能不适用于所有情况。你可以参考 KSValidateVisitor 并通过提供一个自定义的 predicate lambda 表达式来编写自己的验证逻辑,该lambda 表达式随后由 KSValidateVisitor 用来过滤需要检测的符号。