複数ラウンドの処理
KSPは「複数ラウンドの処理(multiple round processing)」、つまり複数回にわたるファイルの処理をサポートしています。これは、後続のラウンドが前のラウンドからの出力を追加の入力として使用することを意味します。
プロセッサの変更点
複数ラウンドの処理を使用するには、SymbolProcessor.process() 関数が、無効なシンボルのための遅延シンボルのリスト(List<KSAnnotated>)を返す必要があります。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
}複数ラウンドの動作
次のラウンドへのシンボルの遅延
プロセッサは、特定のシンボルの処理を次のラウンドに遅延させることができます。シンボルが遅延されると、プロセッサは他のプロセッサが追加情報を提供するのを待機します。必要に応じて、何ラウンドでもシンボルの遅延を継続できます。他のプロセッサが必要な情報を提供すると、プロセッサはその遅延されたシンボルを処理できるようになります。 プロセッサは、必要な情報が不足している無効なシンボルのみを遅延させるべきです。したがって、プロセッサはクラスパス(classpath)からのシンボルを遅延させるべきではありません。KSPは、ソースコード以外からの遅延シンボルもすべて除外します。
例として、アノテーションが付加されたクラスのビルダーを作成するプロセッサが、そのコンストラクタのすべてのパラメータ型が有効である(具体的な型に解決されている)ことを必要とする場合を考えます。第1ラウンドでは、パラメータ型の1つが解決できないとします。その後、第1ラウンドで生成されたファイルによって、第2ラウンドでその型が解決可能になります。
シンボルのバリデーション
シンボルを遅延させるべきかどうかを判断する便利な方法は、バリデーション(検証)を行うことです。プロセッサは、シンボルを適切に処理するためにどの情報が必要かを知っている必要があります。 バリデーションには通常、コストのかかる型解決(resolution)が必要になるため、必要な箇所のみをチェックすることをお勧めします。前述の例で言えば、ビルダープロセッサの理想的なバリデーションは、アノテーションが付加されたシンボルのコンストラクタの解決済みパラメータ型すべてに isError == false が含まれているかどうかのみをチェックすることです。
KSPはデフォルトのバリデーションユーティリティを提供しています。詳細については、高度なトピックセクションを参照してください。
終了条件
複数ラウンドの処理は、あるラウンドの処理全体で新しいファイルが生成されなかったときに終了します。終了条件が満たされたときに未処理の遅延シンボルが残っている場合、KSPは未処理の遅延シンボルを持つ各プロセッサに対してエラーメッセージをログに出力します。
各ラウンドでアクセス可能なファイル
新しく生成されたファイルと既存のファイルの両方に、Resolver を通じてアクセスできます。KSPはファイルにアクセスするための2つの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(述語)ラムダを提供することで、独自のバリデーションロジックを記述できます。このラムダは、チェックが必要なシンボルをフィルタリングするために KSValidateVisitor によって使用されます。
