kotlin-suspend-transform-compiler-plugin
kotlin-suspend-transform-compiler-plugin copied to clipboard
Support override return type generic
/**
* Indicates whether there is a generic type used to override the function return type
* on the current mark annotation.
*
* If `true`, when determining the return type of the generated function,
* the content of this generic type will be directly regarded as the return type of the origin function,
* rather than the actual type of the origin function.
*
* For example, Normally, the return type of the generated function
* depends on the return type of the origin function:
*
* ```Kotlin
* @JvmBlocking
* suspend fun run(): Result<String>
*
* // Generated
*
* @Api4J
* fun runBlocking(): Result<String>
* ```
*
* However, if the annotation has a generic type and `hasReturnTypeOverrideGeneric` is true:
*
* ```Kotlin
* @JvmBlockingWithType<String?>
* suspend fun run(): Result<String>
*
* // Generated
*
* @Api4J
* fun runBlocking(): String?
* ```
*
* As can be seen, the generated function ignores the actual return type of the origin function
* and instead treats the generic type specified in the annotation as the return type
* of the origin function.
*
* Note: If you want to determine the return type through type overloading,
* you must ensure that the transformer function you use correctly matches the input and output parameter types.
* Otherwise, it may result in compilation errors or runtime exceptions.
*
* @since 0.14.0
*/
@ExperimentalReturnTypeOverrideGenericApi
val hasReturnTypeOverrideGeneric: Property<Boolean>
See also: #99
I encountered an issue. Previously, I found that when overriding override fun FirDeclarationPredicateRegistrar.registerPredicates() in SuspendTransformFirTransformer to provide a Predicate based on transformer.markAnnotation, if the annotation involves a generic type T, like this:
// The annotation:
@OptIn(ExperimentalMultiplatform::class)
@OptionalExpectation
@Retention(AnnotationRetention.BINARY)
expect annotation class JvmResultBlock<T>(
val baseName: String = "",
val suffix: String = "Blocking",
val asProperty: Boolean = false
)
// The code:
interface Foo {
@JvmResultBlock<T>
suspend fun <T> foo(value: T): Result<T>
}
The compilation fails with this error:
e: file:///suspend-transform-kotlin-compile-plugin/tests/test-js/src/commonMain/kotlin/returntypeoverride/ReturnTypeOverrides.kt:21:22 Unresolved reference 'T'.
e: file:///suspend-transform-kotlin-compile-plugin/tests/test-js/src/commonMain/kotlin/returntypeoverride/ReturnTypeOverrides.kt:25:22 Unresolved reference 'T'.
Finished executing kotlin compiler using DAEMON strategy
Specifically: Unresolved reference 'T'.
This issue only occurs with real generics. If an explicit type is used instead, there’s no problem:
// The code:
interface Foo {
@JvmResultBlock<String>
suspend fun <T> foo(value: T): Result<T>
}
However, if I remove override fun FirDeclarationPredicateRegistrar.registerPredicates() or make it register predicates that exclude generic annotations, compilation succeeds and achieves the desired behavior in this project’s test module.
The problem lies in "this project’s test module". When I test with an external project, the code generation fails. For example, given this source:
@ST
public interface SendSupport {
public suspend fun send(text: String): MessageReceipt
public suspend fun send(message: Message): MessageReceipt
public suspend fun send(messageContent: MessageContent): MessageReceipt
}
Viewing the generated SendSupport.class directly in the IDE shows:
@love.forte.simbot.suspendrunner.SuspendTrans public interface SendSupport {
public abstract suspend fun send(text: kotlin.String): love.forte.simbot.message.MessageReceipt
public abstract suspend fun send(message: love.forte.simbot.message.Message): love.forte.simbot.message.MessageReceipt
public abstract suspend fun send(messageContent: love.forte.simbot.message.MessageContent): love.forte.simbot.message.MessageReceipt
public open fun sendAsync(message: love.forte.simbot.message.Message): java.util.concurrent.CompletableFuture<out love.forte.simbot.message.MessageReceipt> { /* compiled code */ }
public open fun sendAsync(messageContent: love.forte.simbot.message.MessageContent): java.util.concurrent.CompletableFuture<out love.forte.simbot.message.MessageReceipt> { /* compiled code */ }
public open fun sendAsync(text: kotlin.String): java.util.concurrent.CompletableFuture<out love.forte.simbot.message.MessageReceipt> { /* compiled code */ }
public open fun sendBlocking(message: love.forte.simbot.message.Message): love.forte.simbot.message.MessageReceipt { /* compiled code */ }
public open fun sendBlocking(messageContent: love.forte.simbot.message.MessageContent): love.forte.simbot.message.MessageReceipt { /* compiled code */ }
public open fun sendBlocking(text: kotlin.String): love.forte.simbot.message.MessageReceipt { /* compiled code */ }
public open fun sendReserve(message: love.forte.simbot.message.Message): love.forte.simbot.suspendrunner.reserve.SuspendReserve<out love.forte.simbot.message.MessageReceipt> { /* compiled code */ }
public open fun sendReserve(messageContent: love.forte.simbot.message.MessageContent): love.forte.simbot.suspendrunner.reserve.SuspendReserve<out love.forte.simbot.message.MessageReceipt> { /* compiled code */ }
public open fun sendReserve(text: kotlin.String): love.forte.simbot.suspendrunner.reserve.SuspendReserve<out love.forte.simbot.message.MessageReceipt> { /* compiled code */ }
}
Everything appears normal. But decompiling it via IDEA reveals the problem:
@Metadata(
mv = {2, 2, 0},
k = 1,
xi = 48,
d1 = {"\u00006\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u000e\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0000\bg\u0018\u00002\u00020\u0001J\u0016\u0010\u0002\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H¦@¢\u0006\u0002\u0010\u0006J\u0016\u0010\u0002\u001a\u00020\u00032\u0006\u0010\u0007\u001a\u00020\bH¦@¢\u0006\u0002\u0010\tJ\u0016\u0010\u0002\u001a\u00020\u00032\u0006\u0010\n\u001a\u00020\u000bH¦@¢\u0006\u0002\u0010\cJ\u0018\u0010\r\u001a\n\u0012\u0006\b\u0001\u0012\u00020\u00030\u000e2\u0006\u0010\u0007\u001a\u00020\bH\u0017J\u0018\u0010\r\u001a\n\u0012\u0006\b\u0001\u0012\u00020\u00030\u000e2\u0006\u0010\n\u001a\u00020\u000bH\u0017J\u0018\u0010\r\u001a\n\u0012\u0006\b\u0001\u0012\u00020\u00030\u000e2\u0006\u0010\u0004\u001a\u00020\u0005H\u0017J\u0010\u0010\u000f\u001a\u00020\u00032\u0006\u0010\u0007\u001a\u00020\bH\u0017J\u0010\u0010\u000f\u001a\u00020\u00032\u0006\u0010\n\u001a\u00020\u000bH\u0017J\u0010\u0010\u000f\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H\u0017J\u0018\u0010\u0010\u001a\n\u0012\u0006\b\u0001\u0012\u00020\u00030\u00112\u0006\u0010\u0007\u001a\u00020\bH\u0017J\u0018\u0010\u0010\u001a\n\u0012\u0006\b\u0001\u0012\u00020\u00030\u00112\u0006\u0010\n\u001a\u00020\u000bH\u0017J\u0018\u0010\u0010\u001a\n\u0012\u0006\b\u0001\u0012\u00020\u00030\u00112\u0006\u0010\u0004\u001a\u00020\u0005H\u0017ø\u0001\u0000\u0082\u0002\u0006\n\u0004\b!0\u0001¨\u0006\u0012À\u0006\u0001"},
d2 = {"Llove/forte/simbot/ability/SendSupport;", "", "send", "Llove/forte/simbot/message/MessageReceipt;", "text", "", "(Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "message", "Llove/forte/simbot/message/Message;", "(Llove/forte/simbot/message/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "messageContent", "Llove/forte/simbot/message/MessageContent;", "(Llove/forte/simbot/message/MessageContent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "sendAsync", "Ljava/util/concurrent/CompletableFuture;", "sendBlocking", "sendReserve", "Llove/forte/simbot/suspendrunner/reserve/SuspendReserve;", "simbot-api"}
)
@SuspendTrans
public interface SendSupport {
// $FF: synthetic method
Object send(String var1, Continuation var2);
// $FF: synthetic method
Object send(Message var1, Continuation var2);
// $FF: synthetic method
Object send(MessageContent var1, Continuation var2);
}
As seen, no expected synthetic functions are actually generated in the class—they only appear in @Metadata (visible via IDE). Restoring override fun FirDeclarationPredicateRegistrar.registerPredicates() fixes this external project issue, but reintroduces the Unresolved reference 'T' error.
see: KT-79267