Skip to content

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.

Note that this API is currently in preview and subject to API changes. Usage of it requires opting in to the @KotlinPoetKspPreview annotation.

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.