-
Notifications
You must be signed in to change notification settings - Fork 4
Support override return type generic #102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
…rt for generic-based return type overrides, and update related configurations and annotations.
…urations, and introduce `test-runner` module with generic return type support.
…t usages across the plugin, and improve transformer initialization with type parameter caching.
…etter organization and maintainability within the FIR transformer module.
But then a second problem arose: it seemed that there was a problem with using inline value classes when using generics.
…lasses when using generics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds support for overriding the generated function's return type via a generic parameter on the mark annotation. Key changes include
- Introducing
hasReturnTypeOverrideGeneric
throughout the plugin configuration, DSL, and codegen - Enhancing the FIR transformer and runtime helpers to respect the annotation’s generic when determining return types
- Adding a
Res<T>
wrapper, test-runner helpers, and extensive JVM/JS tests to validate the new behavior
Reviewed Changes
Copilot reviewed 49 out of 51 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
plugins/suspend-transform-plugin-gradle/.../MarkAnnotationSpec.kt | Added hasReturnTypeOverrideGeneric property to the annotation DSL |
compiler/.../SuspendTransformFirTransformer.kt | Updated FIR transformer to apply generic return-type overrides |
runtime/suspend-transform-runtime/src/jvmMain/.../RunInSuspendJvm.kt | Clarified scope comments in the async bridge runtime |
tests/test-runner/src/commonMain/kotlin/.../Res.kt | Introduced Res<T> data class for testing overridden return types |
tests/test-runner/src/jvmMain/kotlin/.../JvmRunner.kt | Added helper functions including jvmResultToAsync and jvmResToBlock |
tests/test-jvm/src/.../ReturnTypeOverrideTests.kt | Extended JVM tests for return override scenarios |
tests/test-js/src/.../JsReturnTypeOverrideTests.kt | Extended JS tests for return override scenarios |
.run/PublishAllToLocal.run.xml | Updated IDE run config key to IS_LOCAL |
Comments suppressed due to low confidence (2)
tests/test-runner/src/jvmMain/kotlin/love/forte/suspendtrans/test/runner/JvmRunner.kt:23
- [nitpick] The function name
jvmResToBlock
abbreviates “Result” inconsistently compared tojvmResultToAsync
andjvmResultToBlock
. Consider renaming tojvmResultToBlock
for consistency.
fun <T> jvmResToBlock(block: suspend () -> Res<T>): T {
.run/PublishAllToLocal.run.xml:6
- [nitpick] Committing IDE-specific run configurations can clutter the repository. Consider adding the
.run/
directory to.gitignore
and removing this file from version control.
<entry key="IS_LOCAL" value="true" />
// import org.gradle.api.Action | ||
// import org.gradle.api.Project | ||
// import org.gradle.plugins.signing.SigningExtension | ||
// | ||
// internal fun Project.`signing`(configure: Action<SigningExtension>): Unit = | ||
// (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("signing", configure) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] This file contains large sections of commented-out code. Consider removing unused comments or refactoring to keep the codebase clean and maintainable.
// import org.gradle.api.Action | |
// import org.gradle.api.Project | |
// import org.gradle.plugins.signing.SigningExtension | |
// | |
// internal fun Project.`signing`(configure: Action<SigningExtension>): Unit = | |
// (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("signing", configure) |
Copilot uses AI. Check for mistakes.
I encountered an issue. Previously, I found that when overriding // 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:
Specifically: 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 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 @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 |
see: KT-79267 |
See also: #99