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.