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.