137 lines
4.4 KiB
Markdown
137 lines
4.4 KiB
Markdown
|
|
KSP Extensions for KotlinPoet
|
||
|
|
==============
|
||
|
|
|
||
|
|
`interop:ksp` is an interop API for converting
|
||
|
|
[Kotlin Symbol Processing][ksp] (KSP) types to KotlinPoet types and
|
||
|
|
writing to KSP `CodeGenerator`.
|
||
|
|
|
||
|
|
```kotlin
|
||
|
|
dependencies {
|
||
|
|
implementation("com.squareup:kotlinpoet-ksp:<version>")
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Examples
|
||
|
|
|
||
|
|
Examples are based on reading the following property as a `KSProperty`:
|
||
|
|
|
||
|
|
```kotlin
|
||
|
|
class Taco {
|
||
|
|
internal inline val seasoning: String get() = "spicy"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Convert a `KSType` to a `TypeName`**
|
||
|
|
|
||
|
|
```kotlin
|
||
|
|
// returns a `ClassName` of value `kotlin.String`
|
||
|
|
seasoningKsProperty.type.toTypeName()
|
||
|
|
```
|
||
|
|
|
||
|
|
**Convert a `Modifier` to a `KModifier`**
|
||
|
|
|
||
|
|
```kotlin
|
||
|
|
// returns `[KModifier.INLINE]`
|
||
|
|
seasoningKsProperty.modifiers.mapNotNull { it.toKModifier() }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Convert a `Visibility` to a `KModifier`**
|
||
|
|
|
||
|
|
```kotlin
|
||
|
|
// returns `KModifier.INTERNAL`
|
||
|
|
seasoningKsProperty.getVisibility().toKModifier()
|
||
|
|
```
|
||
|
|
|
||
|
|
**Write to `CodeGenerator`**
|
||
|
|
|
||
|
|
To write a `FileSpec` to a KSP `CodeGenerator`, simply call the `FileSpec.writeTo(CodeGenerator, ...)`
|
||
|
|
extension function.
|
||
|
|
|
||
|
|
```kotlin
|
||
|
|
fileSpec.writeTo(codeGenerator)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Type Parameters
|
||
|
|
|
||
|
|
Type parameters can be declared on classes, functions, and typealiases. These parameters are then
|
||
|
|
available to all of its enclosed elements. In order for these elements to resolve these in KSP, you
|
||
|
|
must be able to reference these type parameters by their _index_.
|
||
|
|
|
||
|
|
In `kotlinpoet-ksp` this is orchestrated by the `TypeParameterResolver` API, which can be passed
|
||
|
|
into most `toTypeName()` (or similar) functions to give them access to enclosing type parameters
|
||
|
|
that they may reference.
|
||
|
|
|
||
|
|
The canonical way to create an instance of this is to call `toTypeParameterResolver()` on a
|
||
|
|
`List<KSTypeParameter>`.
|
||
|
|
|
||
|
|
Consider the following class and function
|
||
|
|
|
||
|
|
```kotlin
|
||
|
|
abstract class Taco<T> {
|
||
|
|
abstract val seasoning: T
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
To properly resolve the type of `seasoning`, we need to pass the class `TypeParameterResolver` to
|
||
|
|
`toTypeName()` so that it can properly resolve it.
|
||
|
|
|
||
|
|
```kotlin
|
||
|
|
val classTypeParams = ksClassDeclaration.typeParameters.toTypeParameterResolver()
|
||
|
|
// returns `T`
|
||
|
|
val seasoningType = seasoningKsProperty.type.toTypeName(classTypeParams)
|
||
|
|
```
|
||
|
|
|
||
|
|
`TypeParameterResolver` is also composable to allow for multi-level nesting. `toTypeParameterResolver()`
|
||
|
|
has an optional `parent` parameter to provide a parent instance.
|
||
|
|
|
||
|
|
Consider our previous example again, but this time with a function that defines its own type parameters.
|
||
|
|
|
||
|
|
```kotlin
|
||
|
|
class Taco<T> {
|
||
|
|
fun <E> getShellOfType(param1: E, param2: T) {
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
To resolve its parameters, we need to create a `TypeParameterResolver` from the function's
|
||
|
|
`typeParameters` and _compose_ it with the enclosing class's type parameters as a `parent`.
|
||
|
|
|
||
|
|
```kotlin
|
||
|
|
val classTypeParams = ksClassDeclaration.typeParameters.toTypeParameterResolver()
|
||
|
|
val functionTypeParams = ksFunction.typeParameters.toTypeParameterResolver(parent = classTypeParams)
|
||
|
|
// returns `[E, T]`
|
||
|
|
val seasoningType = ksFunction.parameterTypes.map { it.toTypeName(functionTypeParams) }
|
||
|
|
```
|
||
|
|
|
||
|
|
### Incremental Processing
|
||
|
|
|
||
|
|
KSP supports [incremental processing][incremental] as
|
||
|
|
long as symbol processors properly indicate originating files in generated new files and whether or
|
||
|
|
not they are `aggregating`. `kotlinpoet-ksp` supports this via `OriginatingKSFiles`, which is a simple
|
||
|
|
API that sits atop KotlinPoet's `Taggable` API. To use this, simply add relevant originating files to
|
||
|
|
any `TypeSpec`, `TypeAliasSpec`, `PropertySpec`, or `FunSpec` builders.
|
||
|
|
|
||
|
|
```kotlin
|
||
|
|
val functionBuilder = FunSpec.builder("sayHello")
|
||
|
|
.addOriginatingKSFile(sourceKsFile)
|
||
|
|
.build()
|
||
|
|
```
|
||
|
|
|
||
|
|
Like KotlinPoet's _originating elements_ support for javac annotation processors, calling the
|
||
|
|
`FileSpec.writeTo(CodeGenerator, ...)` function will automatically collect and de-dupe these originating
|
||
|
|
`KSFile` references and automatically assemble them in the underlying `Dependencies` for KSP's reference.
|
||
|
|
|
||
|
|
Optionally you can define your own collection of files and pass them to the `writeTo` function, but usually
|
||
|
|
you don't need to do this manually.
|
||
|
|
|
||
|
|
Lastly - `FileSpec.writeTo(CodeGenerator, ...)` also requires you to specify if your processor is
|
||
|
|
_aggregating_ or not via required parameter by the same name.
|
||
|
|
|
||
|
|
### TypeAlias Handling
|
||
|
|
|
||
|
|
For `typealias` types, KSP interop will store a `TypeAliasTag` in the `TypeName`'s tags with a reference to the abbreviated type. This can be useful for APIs that want to resolve all un-aliased types.
|
||
|
|
|
||
|
|
[ksp]: https://github.com/google/ksp
|
||
|
|
[incremental]: https://github.com/google/ksp/blob/main/docs/incremental.md
|