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
}

多轮行为

将符号延迟到下一轮

处理器可以将某些符号的处理延迟到下一轮。当符号被延迟时,处理器会等待其他处理器提供额外信息。它可以根据需要持续延迟该符号任意轮次。一旦其他处理器提供了所需信息,该处理器随后即可处理该延迟符号。处理器应仅延迟缺乏必要信息的无效符号。因此,处理器应延迟来自类路径的符号,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 在 SymbolProcessor 接口中为 onError() 提供了默认的无操作 (no-op) 实现。您可以重写此方法以提供您自己的错误处理逻辑。

高级

验证的默认行为

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

编写您自己的验证逻辑

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