How KSP models Kotlin code
KSP represents source code as a hierarchy of symbols. Processors can navigate this hierarchy to inspect declarations, types, annotations, and other elements of the source code. Consider the following top-level function:
KSP represents this function with the following symbol hierarchy:
The resolve() calls in the hierarchy represent full type resolution. Unlike inspecting a type reference as written, resolving it requires KSP to analyze its context and determine the type it refers to. This additional analysis makes type resolution one of the most expensive operations in the KSP API. Read on to learn how type resolution works and when to use it.
Type resolution
Some properties in the symbol hierarchy, such as annotationType and returnType, are represented as KSTypeReference. Processors can inspect these references directly or resolve them to access more information about the underlying type.
Properties that refer to types, such as KSFunctionDeclaration.returnType and KSAnnotation.annotationType, return a KSTypeReference.
A KSTypeReference represents an unresolved type and preserves the syntactic representation of the type as it appears in the source code. Its KSReferenceElement models the corresponding type element in Kotlin's grammar, including its annotations and modifiers.
You can inspect a KSReferenceElement without resolving it. It can be one of the following:
KSClassifierReference, which provides information such asreferencedName.KSCallableReference, which provides information such asreceiverType,functionParameters, andreturnType.
If a processor generates code that references the same types as the source code, it doesn't need to resolve those types. Instead, it can use the type names available from KSTypeReference to generate the same syntactic type reference. KSP adds the generated source files to the compilation, and the Kotlin compiler later resolves and type-checks the type references together with the rest of the source code.
KSTypeReference.resolve() resolves the reference to a KSType, which provides access to the declaration that defines the type:
For function type references, most information is already available from KSCallableReference. Resolving a function type produces a type from the Function0, Function1, and related families, and is usually unnecessary. However, resolution can provide additional information, such as the identity of the function's prototype.
When to resolve types
Type resolution is one of the most expensive operations in the KSP API. To avoid unnecessary resolutions, KSP generally doesn't resolve type references implicitly. Instead, call KSTypeReference.resolve() explicitly when your processor needs the resolved type.
When possible, inspect the KSReferenceElement before resolving the type. For example, use KSClassifierReference.referencedName() to filter references that aren't relevant to your processor.
Whether a processor needs to resolve a type depends on the information it needs. The following example compares both approaches by inspecting the same property types with and without resolution. The processor provider uses the resolveTypes option to select which approach to use:
With resolveTypes = false, the relevant output is:
With resolveTypes = true, the output is:
Without resolution, the processor can inspect syntactic information such as the type name and type arguments. For example, it sees the imported alias SqlDate as written in the source code. After resolution, the processor can access semantic information about the type, such as the fully qualified declaration name and nullability.
KSP model reference
The following diagram illustrates the relationships between the main KSP API types. It was generated from the KSP API source using IntelliJ IDEA's class diagram feature.
For the complete API definition, see the KSP API source in the KSP GitHub repository.