Custom Transformers
This guide covers how to create custom transformers when the default transformers don't meet your specific needs.
Overviewβ
You can create fully customized transformers if the default ones don't meet your requirements.
For example, if you want to implement blocking logic without using kotlinx.coroutines.runBlocking
, or if you need platform-specific behavior.
A fully customized implementation of JVM Blocking/Async Transformers can be found at: simbot's BlockingRunner
Custom transformer functions cannot be placed in the same module as the one using the compiler plugin. They need to be created in a separate module.
For more information: #100
Basic Setupβ
When using custom transformers, you typically won't need the default annotation and runtime dependencies:
suspendTransformPlugin {
// If customized, then you may not use the annotation and runtime we provide.
includeAnnotation = false
includeRuntime = false
transformers {
// Your custom transformer configuration
}
}
Creating a Custom Transformerβ
Let's create a custom transformer step by step. We'll create a @JBlock
annotation that uses a custom inBlock
function.
Step 1: Define Your Custom Componentsβ
First, define your custom annotation and transform function:
// Your custom annotation
annotation class JBlock(
val baseName: String = "",
val suffix: String = "Blocking",
val asProperty: Boolean = false
)
// Your custom transform function
fun <T> inBlock(block: suspend () -> T): T {
// Your custom implementation
TODO("Your impl")
}
According to the agreement: the first parameter of a transform function
should be a lambda of type suspend () -> T
.
Step 2: Configure the Annotationβ
Configure the annotation properties in your transformer:
suspendTransformPlugin {
includeAnnotation = false
includeRuntime = false
transformers {
addJvm {
markAnnotation {
// Your annotation class information
classInfo {
packageName = "com.example"
className = "JBlock"
}
// Property names in your annotation
baseNameProperty = "baseName" // Default is `baseName`
suffixProperty = "suffix" // Default is `suffix`
asPropertyProperty = "asProperty" // Default is `asProperty`
// Default values (compiler plugin can't get annotation defaults)
defaultSuffix = "Blocking"
defaultAsProperty = false
}
}
}
}
Step 3: Configure the Transform Functionβ
Configure your custom transform function:
suspendTransformPlugin {
transformers {
addJvm {
markAnnotation {
// ... annotation configuration
}
// Transform function information
transformFunctionInfo {
packageName = "com.example"
functionName = "inBlock"
}
// Return type configuration
transformReturnType = null // null means same type as original function
transformReturnTypeGeneric = false // true if return type has generics
}
}
}
Advanced Configurationβ
Custom Property Namesβ
You can use different property names in your annotation:
annotation class JBlock(
val myBaseName: String = "",
val mySuffix: String = "Blocking",
val myAsProperty: Boolean = false
)
Configure the mapping:
suspendTransformPlugin {
transformers {
addJvm {
markAnnotation {
classInfo {
packageName = "com.example"
className = "JBlock"
}
// Map to your custom property names
baseNameProperty = "myBaseName"
suffixProperty = "mySuffix"
asPropertyProperty = "myAsProperty"
defaultSuffix = "Blocking"
defaultAsProperty = false
}
}
}
}
Return Type Configurationβ
Same Return Typeβ
For functions that return the same type as the original:
transformReturnType = null
transformReturnTypeGeneric = false
Generic Return Typeβ
For functions that return a generic type containing the original type (e.g., CompletableFuture<T>
):
transformReturnType = "java.util.concurrent.CompletableFuture"
transformReturnTypeGeneric = true
Specific Return Typeβ
For functions that return a specific type without generics (e.g., Job
):
transformReturnType = "kotlinx.coroutines.Job"
transformReturnTypeGeneric = false
Annotation Managementβ
Adding Annotations to Original Functionβ
Add annotations to the original suspend function:
suspendTransformPlugin {
transformers {
addJvm {
// ... other configuration
// Add @JvmSynthetic to original function
addOriginFunctionIncludeAnnotation {
classInfo {
packageName = "kotlin.jvm"
className = "JvmSynthetic"
}
repeatable = false // Default is false
}
}
}
}
Adding Annotations to Generated Functionβ
Add annotations to the generated synthetic function:
suspendTransformPlugin {
transformers {
addJvm {
// ... other configuration
// Add custom @JApi annotation to generated function
addSyntheticFunctionIncludeAnnotation {
classInfo {
packageName = "com.example"
className = "JApi"
}
includeProperty = true // Can be added to properties
}
}
}
}
Copying Annotationsβ
Enable copying annotations from original to synthetic function:
suspendTransformPlugin {
transformers {
addJvm {
// ... other configuration
// Enable annotation copying
copyAnnotationsToSyntheticFunction = true
copyAnnotationsToSyntheticProperty = true // For properties
// Exclude specific annotations from copying
addCopyAnnotationExclude {
classInfo {
packageName = "kotlin.jvm"
className = "JvmSynthetic"
}
}
}
}
}
Complete Exampleβ
Here's a complete example of a custom transformer:
Custom Componentsβ
// Custom annotation
annotation class JBlock(
val myBaseName: String = "",
val mySuffix: String = "Blocking",
val myAsProperty: Boolean = false
)
// Custom warning annotation
@RequiresOptIn(message = "Api for Java", level = RequiresOptIn.Level.WARNING)
@Retention(AnnotationRetention.BINARY)
annotation class JApi
// Custom transform function
fun <T> inBlock(block: suspend () -> T): T {
// Your custom blocking implementation
TODO("Your impl")
}
Configurationβ
suspendTransformPlugin {
includeAnnotation = false
includeRuntime = false
transformers {
addJvm {
// Annotation configuration
markAnnotation {
classInfo {
packageName = "com.example"
className = "JBlock"
}
baseNameProperty = "myBaseName"
suffixProperty = "mySuffix"
asPropertyProperty = "myAsProperty"
defaultSuffix = "Blocking"
defaultAsProperty = false
}
// Transform function configuration
transformFunctionInfo {
packageName = "com.example"
functionName = "inBlock"
}
// Annotation management
copyAnnotationsToSyntheticFunction = true
copyAnnotationsToSyntheticProperty = true
// Add @JvmSynthetic to original function
addOriginFunctionIncludeAnnotation {
classInfo {
from(SuspendTransformConfigurations.jvmSyntheticClassInfo)
}
repeatable = false
}
// Add @JApi to generated function
addSyntheticFunctionIncludeAnnotation {
classInfo {
packageName = "com.example"
className = "JApi"
}
includeProperty = true
}
// Exclude @JvmSynthetic from copying
addCopyAnnotationExclude {
from(SuspendTransformConfigurations.jvmSyntheticClassInfo)
}
}
}
}
Multi-Purpose Annotationsβ
You can create annotations that work with multiple transformers by using different property names:
Annotation Definitionβ
annotation class JTrans(
val blockingBaseName: String = "",
val blockingSuffix: String = "Blocking",
val blockingAsProperty: Boolean = false,
val asyncBaseName: String = "",
val asyncSuffix: String = "Async",
val asyncAsProperty: Boolean = false
)
Configurationβ
suspendTransformPlugin {
includeAnnotation = false
includeRuntime = false
transformers {
// Blocking transformer
addJvm {
markAnnotation {
classInfo {
packageName = "com.example"
className = "JTrans"
}
baseNameProperty = "blockingBaseName"
suffixProperty = "blockingSuffix"
asPropertyProperty = "blockingAsProperty"
defaultSuffix = "Blocking"
defaultAsProperty = false
}
transformFunctionInfo {
packageName = "com.example"
functionName = "inBlock"
}
// ... other config
}
// Async transformer
addJvm {
markAnnotation {
classInfo {
packageName = "com.example"
className = "JTrans"
}
baseNameProperty = "asyncBaseName"
suffixProperty = "asyncSuffix"
asPropertyProperty = "asyncAsProperty"
defaultSuffix = "Async"
defaultAsProperty = false
}
transformFunctionInfo {
packageName = "com.example"
functionName = "inAsync"
}
// ... other config
}
}
}
Built-in Configurationsβ
The plugin provides some common configurations in
love.forte.plugin.suspendtrans.configuration.SuspendTransformConfigurations
:
// Common annotation class infos
SuspendTransformConfigurations.jvmSyntheticClassInfo
SuspendTransformConfigurations.kotlinJsExportIgnoreClassInfo
// Use them in your configuration
addCopyAnnotationExclude {
from(SuspendTransformConfigurations.jvmSyntheticClassInfo)
}
Othersβ
MarkNameβ
Version 0.13.0For configuration of markName
in custom annotations,
refer to Mark Name.