Skip to content

多段処理

KSPは、多段処理 (multiple round processing)、つまり複数ラウンドにわたるファイルの処理をサポートしています。これは、後続のラウンドが以前のラウンドからの出力を追加の入力として使用することを意味します。

プロセッサの変更点

多段処理を使用するには、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も、ソースコード由来ではない遅延シンボルをフィルタリングします。

例として、アノテーションが付与されたクラスのビルダーを作成するプロセッサは、そのコンストラクタのすべてのパラメータ型が有効であること(具体的な型に解決されること)を要求する場合があります。最初のラウンドでは、パラメータ型の一つが解決できないとします。その後、2番目のラウンドでは、最初のラウンドで生成されたファイルによって解決可能になります。

シンボルの検証

シンボルを遅延させるべきかどうかを判断する便利な方法は、検証によるものです。プロセッサは、シンボルを適切に処理するためにどの情報が必要かを知っているべきです。 検証は通常、コストのかかる解決を必要とするため、必要なものだけをチェックすることを推奨します。 前述の例に続けて、ビルダープロセッサにとって理想的な検証は、アノテーションが付与されたシンボルのコンストラクタのすべての解決されたパラメータ型がisError == falseを含むかどうかだけをチェックすることです。

KSPはデフォルトの検証ユーティリティを提供します。詳細については、「Advanced」セクションを参照してください。

終了条件

多段処理は、処理のフルラウンドで新しいファイルが生成されなくなったときに終了します。終了条件が満たされたときに、未処理の遅延シンボルがまだ存在する場合、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 implementation)を提供します。 このメソッドをオーバーライドして、独自のエラー処理ロジックを提供できます。

Advanced

検証のデフォルトの動作

KSPが提供するデフォルトの検証ロジックは、検証されるシンボルの囲んでいるスコープ内で直接到達可能なすべてのシンボルを検証します。 デフォルトの検証は、囲んでいるスコープ内の参照が具体的な型に解決可能であるかをチェックしますが、参照された型に再帰的に深く入り込んで検証を実行することはありません。

独自の検証ロジックを記述する

デフォルトの検証動作がすべてのケースに適しているとは限りません。KSValidateVisitorを参照し、カスタムのpredicateラムダを提供することで独自の検証ロジックを記述できます。このラムダはKSValidateVisitorによって、チェックする必要があるシンボルをフィルタリングするために使用されます。