複数ラウンドの処理
KSPは、複数ラウンドの処理(multiple-round processing)、つまり複数回にわたるファイルの処理をサポートしています。各処理ラウンドからの出力は、続く各ラウンドの追加入力として使用されます。
複数ラウンドの処理を使用するには、SymbolProcessor.process() から遅延シンボルを List<KSAnnotated> として返します。KSPは、次のラウンドでこれらのシンボルを処理します。
無効なシンボルを遅延させるには、例えば KSAnnotated.validate() を使用してフィルタリングします。
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はそれらの遅延シンボルを保持している各プロセッサに対してエラーをログに出力します。
次のラウンドへのシンボルの遅延
プロセッサは、他のプロセッサからの追加情報が必要な場合に、シンボルを後のラウンドに遅延させることができます。プロセッサは、必要な情報が利用可能になるまで、複数ラウンドにわたってシンボルの遅延を継続できます。情報が利用可能になると、プロセッサはそのシンボルを処理できるようになります。
以下の場合にのみ、シンボルを遅延させてください:
シンボルを処理する前に追加情報が必要な場合。
シンボルがソースコード由来である場合。
クラスパス(classpath)からのシンボルは決して遅延させないでください。KSPはクラスパスのシンボルを自動的に除外します。
例えば、アノテーションが付加されたクラスのビルダーを生成するプロセッサが、すべてのコンストラクタのパラメータ型が具体的な型に解決されることを必要とする場合を考えます。第1ラウンドでは、パラメータ型の1つが解決できない可能性があります。その後のラウンドでは、その間に生成されたファイルによって解決可能になるかもしれません。その時点で、プロセッサはそのクラスを処理できます。
シンボルのバリデーション
バリデーション(検証)は、シンボルを後のラウンドに遅延させるべきかどうかを判断する便利な方法です。プロセッサは、シンボルを正しく処理するために必要な情報を定義する必要があります。
バリデーションには多くの場合、コストのかかる型解決(resolution)が必要になります。シンボルの処理に必要な情報のみをチェックしてください。
デフォルトのバリデーション動作がすべてのユースケースに適しているとは限りません。バリデーションをカスタマイズするには、KSValidateVisitor を使用し、バリデーション対象のシンボルを選択する predicate ラムダを提供します。
カスタムバリデーションを実装する際は、KSType.isError を使用して型が有効かどうかを判断してください。isError が true の場合、KSPはその型を解決できませんでした。この情報を使用して、処理を後のラウンドに遅延させるかどうかを決定します。
ファイルとシンボルへのアクセス
新しく生成されたファイルと既存のファイルの両方に、Resolver を通じてアクセスできます。
KSPはファイルにアクセスするための2つのAPIを提供しています:
Resolver.getAllFiles()は、以前から存在するファイルと新しく生成されたファイルの両方のリストを返します。Resolver.getNewFiles()は、前のラウンドで生成されたファイルのみを返します。
関連するシンボルを取得するための主要なエントリポイントとして Resolver.getSymbolsWithAnnotation() を使用してください。
各ラウンドにおいて、Resolver.getSymbolsWithAnnotation() は新しく生成されたファイルで見つかったシンボルと、前のラウンドからの遅延シンボルのみを返します。これにより、不要な再処理を避けることができます。
プロセッサのインスタンス化
KSPはプロセッサのインスタンスを一度だけ作成します。プロセッサインスタンスに情報を保存し、複数のラウンドにわたって再利用できます。
ただし、すべてのKSPシンボルがラウンド間で再利用できるわけではありません。プロセッサが新しいファイルを生成するとシンボルの解決結果が変わる可能性があり、以前に解決されたシンボルの妥当性に影響を与えることがあります。
現在のラウンドでプロセッサに渡された
Resolverインスタンスのみを使用してください。Resolverを保存してラウンド間で再利用しないでください。
エラーと例外の処理
エラー
プロセッサは KSPLogger.error() を呼び出すことでエラーを報告します。
プロセッサがエラーを報告すると、KSPは SymbolProcessor.finish() の代わりに SymbolProcessor.onError() を呼び出します。現在のラウンドが完了した後、処理は停止します。
そのラウンドの間、他のプロセッサは通常通り処理を継続します。KSPは、すべてのプロセッサが現在のラウンドを終了した後にのみエラーを処理します。
例外
KSPは、KSP自体によってスローされた例外と、プロセッサによってスローされた例外を区別します。どちらのタイプも直ちに処理を終了させ、KSPLogger を通じてエラーとして記録されます。
KSPによってスローされた例外については、調査のためにKSPの開発者に報告してください。KSP issue tracker で案件(issue)を作成してください。
エラーまたは例外が発生したラウンドの最後に、KSPはすべてのプロセッサに対して SymbolProcessor.onError() を呼び出します。 SymbolProcessor は onError() のデフォルトの何もしない(no-op)実装を提供しています。このメソッドをオーバーライドして、独自の例外処理ロジックを実装してください。
