KSP Extensions for KotlinPoet¶
interop:ksp
is an interop API for converting
Kotlin Symbol Processing (KSP) types to KotlinPoet types and
writing to KSP CodeGenerator
.
dependencies {
implementation("com.squareup:kotlinpoet-ksp:<version>")
}
Examples¶
Examples are based on reading the following property as a KSProperty
:
class Taco {
internal inline val seasoning: String get() = "spicy"
}
Convert a KSType
to a TypeName
// returns a `ClassName` of value `kotlin.String`
seasoningKsProperty.type.toTypeName()
Convert a Modifier
to a KModifier
// returns `[KModifier.INLINE]`
seasoningKsProperty.modifiers.mapNotNull { it.toKModifier() }
Convert a Visibility
to a KModifier
// 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.
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
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.
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.
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
.
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 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.
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.