diff --git a/pkgs/jni/lib/_internal.dart b/pkgs/jni/lib/_internal.dart index 9c0db34f76..2e6fcb4a06 100644 --- a/pkgs/jni/lib/_internal.dart +++ b/pkgs/jni/lib/_internal.dart @@ -52,7 +52,13 @@ export 'src/jni.dart' show ProtectedJniExtensions; export 'src/jobject.dart' show $JObject$NullableType$, $JObject$Type$; export 'src/jreference.dart'; export 'src/kotlin.dart' - show coroutineSingletonsClass, failureExceptionField, result$FailureClass; + show + KotlinContinuation, + coroutineSingletonsClass, + failureExceptionField, + result$Class, + result$FailureClass, + resultValueField; export 'src/lang/jboolean.dart' show $JBoolean$NullableType$, $JBoolean$Type$; export 'src/lang/jbyte.dart' show $JByte$NullableType$, $JByte$Type$; export 'src/lang/jcharacter.dart' diff --git a/pkgs/jni/lib/src/kotlin.dart b/pkgs/jni/lib/src/kotlin.dart index eb0e1d11a8..3c4534e7d8 100644 --- a/pkgs/jni/lib/src/kotlin.dart +++ b/pkgs/jni/lib/src/kotlin.dart @@ -4,6 +4,8 @@ import 'package:meta/meta.dart' show internal; +import 'jni.dart'; +import 'jobject.dart'; import 'types.dart'; @internal @@ -16,3 +18,44 @@ final result$FailureClass = JClass.forName(r'kotlin/Result$Failure'); @internal final failureExceptionField = result$FailureClass.instanceFieldId('exception', 'Ljava/lang/Throwable;'); + +@internal +final result$Class = JClass.forName(r'kotlin/Result'); + +@internal +final resultValueField = + result$Class.instanceFieldId('value', 'Ljava/lang/Object;'); + +final _coroutineIntrinsicsClass = + JClass.forName('kotlin/coroutines/intrinsics/IntrinsicsKt'); +final _coroutineSuspended = _coroutineIntrinsicsClass.staticMethodId( + 'getCOROUTINE_SUSPENDED', '()Ljava/lang/Object;')( + _coroutineIntrinsicsClass, const $JObject$Type$(), []); + +@internal +class KotlinContinuation extends JObject { + KotlinContinuation.fromReference( + super.reference, + ) : super.fromReference(); + + static final _class = JClass.forName(r'kotlin/coroutines/Continuation'); + + static final _resumeWithId = + _class.instanceMethodId(r'resumeWith', r'(Ljava/lang/Object;)V'); + void resumeWith(JObject? result) { + _resumeWithId(this, const jvoidType(), [result]); + } + + static final _result$FailureConstructor = + result$FailureClass.constructorId(r'(Ljava/lang/Throwable;)V'); + void resumeWithException(Object dartException, StackTrace stackTrace) { + resumeWith(_result$FailureConstructor(result$FailureClass, JObject.type, [ + ProtectedJniExtensions.newDartException('$dartException\n$stackTrace'), + ])); + } + + JObject resumeWithFuture(Future future) { + future.then(resumeWith, onError: resumeWithException); + return _coroutineSuspended; + } +} diff --git a/pkgs/jni/lib/src/plugin/generated_plugin.dart b/pkgs/jni/lib/src/plugin/generated_plugin.dart index 1a843d6266..c88e49b089 100644 --- a/pkgs/jni/lib/src/plugin/generated_plugin.dart +++ b/pkgs/jni/lib/src/plugin/generated_plugin.dart @@ -1,4 +1,4 @@ -// AUTO GENERATED BY JNIGEN 0.15.0. DO NOT EDIT! +// AUTO GENERATED BY JNIGEN 0.15.1. DO NOT EDIT! // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a diff --git a/pkgs/jnigen/CHANGELOG.md b/pkgs/jnigen/CHANGELOG.md index 7aeb7b3735..02846012e4 100644 --- a/pkgs/jnigen/CHANGELOG.md +++ b/pkgs/jnigen/CHANGELOG.md @@ -1,6 +1,8 @@ ## 0.15.1-wip - Add docs about debugging. +- Add support for Kotlin interfaces with suspend functions. These can now be + implemented using Dart functions that return a `Future`. ## 0.15.0 diff --git a/pkgs/jnigen/lib/src/bindings/dart_generator.dart b/pkgs/jnigen/lib/src/bindings/dart_generator.dart index effbbf49f8..8604dc91a4 100644 --- a/pkgs/jnigen/lib/src/bindings/dart_generator.dart +++ b/pkgs/jnigen/lib/src/bindings/dart_generator.dart @@ -81,6 +81,22 @@ extension on DeclaredType { } } +extension on Method { + bool get isSuspendFun => asyncReturnType != null; + + String returnTypeMaybeAsync(_TypeGenerator generator) => isSuspendFun + ? '$_core.Future<${asyncReturnType!.accept(generator)}>' + : returnType.accept(generator); + + List get paramsMaybeAsync { + final p = params.toList(); + if (isSuspendFun) { + p.removeLast(); + } + return p; + } +} + String _newLine({int depth = 0}) { return '\n${' ' * depth}'; } @@ -1337,10 +1353,6 @@ ${modifier}final _$name = $_protectedExtension '''); } - bool isSuspendFun(Method node) { - return node.asyncReturnType != null; - } - String constructor(Method node) { final name = node.finalName; final params = [ @@ -1447,10 +1459,7 @@ ${modifier}final _$name = $_protectedExtension } final name = node.finalName; - final returnType = isSuspendFun(node) - ? '$_core.Future<' - '${node.asyncReturnType!.accept(_TypeGenerator(resolver))}>' - : node.returnType.accept(_TypeGenerator(resolver)); + final returnType = node.returnTypeMaybeAsync(_TypeGenerator(resolver)); final ifStatic = node.isStatic && !isTopLevel ? 'static ' : ''; final defArgs = node.params.accept(_ParamDef(resolver)).toList(); final typeClassDef = node.typeParams @@ -1465,14 +1474,14 @@ ${modifier}final _$name = $_protectedExtension .accept(const _TypeParamDef()) .join(', ') .encloseIfNotEmpty('<', '>'); - if (isSuspendFun(node)) { + if (node.isSuspendFun) { defArgs.removeLast(); localReferences.removeLast(); } final params = defArgs.delimited(', '); s.write(' $ifStatic$returnType $name$typeParamsDef($params$typeClassDef)'); final callExpr = methodCall(node); - if (isSuspendFun(node)) { + if (node.isSuspendFun) { final returningType = node.asyncReturnType!.accept(_TypeGenerator(resolver)); final returningTypeClass = @@ -1486,13 +1495,15 @@ ${modifier}final _$name = $_protectedExtension ${localReferences.join(_newLine(depth: 2))} final \$r = $callExpr; _\$$continuation.release(); - final $_jObject${isNullable ? '?' : ''} \$o; + $_jObject${isNullable ? '?' : ''} \$o; if (${isNullable ? '\$r != null && ' : ''}\$r.isInstanceOf($_jni.coroutineSingletonsClass)) { \$r.release(); final \$a = await \$p.first; \$o = ${isNullable ? '\$a == 0 ? null :' : ''}$_jObject.fromReference( $_jGlobalReference($_jPointer.fromAddress(\$a))); - if (${isNullable ? '\$o != null && ' : ''}\$o.isInstanceOf($_jni.result\$FailureClass)) { + if (${isNullable ? '\$o != null && ' : ''}\$o.isInstanceOf($_jni.result\$Class)) { + \$o = $_jni.resultValueField.get(\$o, const ${_jObjectTypePrefix}Type\$()); + } else if (${isNullable ? '\$o != null && ' : ''}\$o.isInstanceOf($_jni.result\$FailureClass)) { final \$e = $_jni.failureExceptionField.get(\$o, const ${_jObjectTypePrefix}Type\$()); \$o.release(); @@ -1862,11 +1873,10 @@ class _ConcreteImplClosureDef extends Visitor { @override void visit(Method node) { - final returnType = node.returnType.accept( - _TypeGenerator(resolver, forInterfaceImplementation: true), - ); + final returnType = node.returnTypeMaybeAsync( + _TypeGenerator(resolver, forInterfaceImplementation: true)); final name = node.finalName; - final args = node.params + final args = node.paramsMaybeAsync .accept(_ParamDef(resolver, methodGenericErasure: true)) .join(', '); s.writeln(' final $returnType Function($args) _$name;'); @@ -1885,11 +1895,11 @@ class _AbstractImplFactoryArg extends Visitor { @override String visit(Method node) { - final returnType = node.returnType.accept( + final returnType = node.returnTypeMaybeAsync( _TypeGenerator(resolver, forInterfaceImplementation: true), ); final name = node.finalName; - final args = node.params + final args = node.paramsMaybeAsync .accept(_ParamDef(resolver, methodGenericErasure: true)) .join(', '); final functionArg = 'required $returnType Function($args) $name,'; @@ -1909,11 +1919,11 @@ class _ConcreteImplClosureCtorArg extends Visitor { @override String visit(Method node) { - final returnType = node.returnType.accept( + final returnType = node.returnTypeMaybeAsync( _TypeGenerator(resolver, forInterfaceImplementation: true), ); final name = node.finalName; - final args = node.params + final args = node.paramsMaybeAsync .accept(_ParamDef(resolver, methodGenericErasure: true)) .join(', '); final functionArg = 'required $returnType Function($args) $name,'; @@ -1940,11 +1950,21 @@ class _ConcreteImplMethod extends Visitor { final argsDef = node.params .accept(_ParamDef(resolver, methodGenericErasure: true)) .join(', '); - final argsCall = node.params.map((param) => param.finalName).join(', '); - s.write(''' + final argsCall = + node.paramsMaybeAsync.map((param) => param.finalName).join(', '); + if (node.isSuspendFun) { + final contArg = node.params.last.finalName; + s.write(''' + $returnType $name($argsDef) { + return $_jni.KotlinContinuation.fromReference($contArg.reference) + .resumeWithFuture(_$name($argsCall)); + }'''); + } else { + s.write(''' $returnType $name($argsDef) { return _$name($argsCall); }'''); + } } } diff --git a/pkgs/jnigen/test/kotlin_test/bindings/kotlin.dart b/pkgs/jnigen/test/kotlin_test/bindings/kotlin.dart index b3fd2e9967..9293b57928 100644 --- a/pkgs/jnigen/test/kotlin_test/bindings/kotlin.dart +++ b/pkgs/jnigen/test/kotlin_test/bindings/kotlin.dart @@ -3162,13 +3162,15 @@ class SuspendFun extends jni$_.JObject { _$continuation.pointer) .object(const jni$_.$JObject$Type$()); _$continuation.release(); - final jni$_.JObject $o; + jni$_.JObject $o; if ($r.isInstanceOf(jni$_.coroutineSingletonsClass)) { $r.release(); final $a = await $p.first; $o = jni$_.JObject.fromReference( jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); - if ($o.isInstanceOf(jni$_.result$FailureClass)) { + if ($o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o.isInstanceOf(jni$_.result$FailureClass)) { final $e = jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); $o.release(); @@ -3209,13 +3211,15 @@ class SuspendFun extends jni$_.JObject { _id_failWithoutDelay as jni$_.JMethodIDPtr, _$continuation.pointer) .object(const jni$_.$JObject$Type$()); _$continuation.release(); - final jni$_.JObject $o; + jni$_.JObject $o; if ($r.isInstanceOf(jni$_.coroutineSingletonsClass)) { $r.release(); final $a = await $p.first; $o = jni$_.JObject.fromReference( jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); - if ($o.isInstanceOf(jni$_.result$FailureClass)) { + if ($o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o.isInstanceOf(jni$_.result$FailureClass)) { final $e = jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); $o.release(); @@ -3256,13 +3260,15 @@ class SuspendFun extends jni$_.JObject { _$continuation.pointer) .object(const jni$_.$JObject$Type$()); _$continuation.release(); - final jni$_.JObject $o; + jni$_.JObject $o; if ($r.isInstanceOf(jni$_.coroutineSingletonsClass)) { $r.release(); final $a = await $p.first; $o = jni$_.JObject.fromReference( jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); - if ($o.isInstanceOf(jni$_.result$FailureClass)) { + if ($o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o.isInstanceOf(jni$_.result$FailureClass)) { final $e = jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); $o.release(); @@ -3303,13 +3309,15 @@ class SuspendFun extends jni$_.JObject { _$continuation.pointer) .object(const jni$_.$JObject$Type$()); _$continuation.release(); - final jni$_.JObject $o; + jni$_.JObject $o; if ($r.isInstanceOf(jni$_.coroutineSingletonsClass)) { $r.release(); final $a = await $p.first; $o = jni$_.JObject.fromReference( jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); - if ($o.isInstanceOf(jni$_.result$FailureClass)) { + if ($o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o.isInstanceOf(jni$_.result$FailureClass)) { final $e = jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); $o.release(); @@ -3361,13 +3369,15 @@ class SuspendFun extends jni$_.JObject { _$continuation.pointer) .object(const jni$_.$JObject$Type$()); _$continuation.release(); - final jni$_.JObject $o; + jni$_.JObject $o; if ($r.isInstanceOf(jni$_.coroutineSingletonsClass)) { $r.release(); final $a = await $p.first; $o = jni$_.JObject.fromReference( jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); - if ($o.isInstanceOf(jni$_.result$FailureClass)) { + if ($o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o.isInstanceOf(jni$_.result$FailureClass)) { final $e = jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); $o.release(); @@ -3414,7 +3424,7 @@ class SuspendFun extends jni$_.JObject { _$continuation.pointer) .object(const jni$_.$JObject$NullableType$()); _$continuation.release(); - final jni$_.JObject? $o; + jni$_.JObject? $o; if ($r != null && $r.isInstanceOf(jni$_.coroutineSingletonsClass)) { $r.release(); final $a = await $p.first; @@ -3422,7 +3432,9 @@ class SuspendFun extends jni$_.JObject { ? null : jni$_.JObject.fromReference( jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); - if ($o != null && $o.isInstanceOf(jni$_.result$FailureClass)) { + if ($o != null && $o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o != null && $o.isInstanceOf(jni$_.result$FailureClass)) { final $e = jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); $o.release(); @@ -3469,7 +3481,7 @@ class SuspendFun extends jni$_.JObject { _$continuation.pointer) .object(const jni$_.$JObject$NullableType$()); _$continuation.release(); - final jni$_.JObject? $o; + jni$_.JObject? $o; if ($r != null && $r.isInstanceOf(jni$_.coroutineSingletonsClass)) { $r.release(); final $a = await $p.first; @@ -3477,7 +3489,9 @@ class SuspendFun extends jni$_.JObject { ? null : jni$_.JObject.fromReference( jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); - if ($o != null && $o.isInstanceOf(jni$_.result$FailureClass)) { + if ($o != null && $o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o != null && $o.isInstanceOf(jni$_.result$FailureClass)) { final $e = jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); $o.release(); @@ -3567,6 +3581,770 @@ final class $SuspendFun$Type$ extends jni$_.JType { } } +final _SuspendFunKtClass = + jni$_.JClass.forName(r'com/github/dart_lang/jnigen/SuspendFunKt'); + +final _id_consumeOnAnotherThread = _SuspendFunKtClass.staticMethodId( + r'consumeOnAnotherThread', + r'(Lcom/github/dart_lang/jnigen/SuspendInterface;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;', +); + +final _consumeOnAnotherThread = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, jni$_.JMethodIDPtr, + jni$_.Pointer, jni$_.Pointer)>(); + +/// from: `public suspend fun consumeOnAnotherThread(itf: com.github.dart_lang.jnigen.SuspendInterface): kotlin.String` +/// The returned object must be released after use, by calling the [release] method. +core$_.Future consumeOnAnotherThread( + SuspendInterface suspendInterface, +) async { + final $p = jni$_.ReceivePort(); + final _$continuation = jni$_.ProtectedJniExtensions.newPortContinuation($p); + final _$suspendInterface = suspendInterface.reference; + final $r = _consumeOnAnotherThread( + _SuspendFunKtClass.reference.pointer, + _id_consumeOnAnotherThread as jni$_.JMethodIDPtr, + _$suspendInterface.pointer, + _$continuation.pointer) + .object(const jni$_.$JObject$Type$()); + _$continuation.release(); + jni$_.JObject $o; + if ($r.isInstanceOf(jni$_.coroutineSingletonsClass)) { + $r.release(); + final $a = await $p.first; + $o = jni$_.JObject.fromReference( + jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); + if ($o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o.isInstanceOf(jni$_.result$FailureClass)) { + final $e = + jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); + $o.release(); + jni$_.Jni.throwException($e.reference.toPointer()); + } + } else { + $o = $r; + } + return $o.as( + const jni$_.$JString$Type$(), + releaseOriginal: true, + ); +} + +final _id_consumeOnSameThread = _SuspendFunKtClass.staticMethodId( + r'consumeOnSameThread', + r'(Lcom/github/dart_lang/jnigen/SuspendInterface;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;', +); + +final _consumeOnSameThread = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, jni$_.JMethodIDPtr, + jni$_.Pointer, jni$_.Pointer)>(); + +/// from: `public suspend fun consumeOnSameThread(itf: com.github.dart_lang.jnigen.SuspendInterface): kotlin.String` +/// The returned object must be released after use, by calling the [release] method. +core$_.Future consumeOnSameThread( + SuspendInterface suspendInterface, +) async { + final $p = jni$_.ReceivePort(); + final _$continuation = jni$_.ProtectedJniExtensions.newPortContinuation($p); + final _$suspendInterface = suspendInterface.reference; + final $r = _consumeOnSameThread( + _SuspendFunKtClass.reference.pointer, + _id_consumeOnSameThread as jni$_.JMethodIDPtr, + _$suspendInterface.pointer, + _$continuation.pointer) + .object(const jni$_.$JObject$Type$()); + _$continuation.release(); + jni$_.JObject $o; + if ($r.isInstanceOf(jni$_.coroutineSingletonsClass)) { + $r.release(); + final $a = await $p.first; + $o = jni$_.JObject.fromReference( + jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); + if ($o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o.isInstanceOf(jni$_.result$FailureClass)) { + final $e = + jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); + $o.release(); + jni$_.Jni.throwException($e.reference.toPointer()); + } + } else { + $o = $r; + } + return $o.as( + const jni$_.$JString$Type$(), + releaseOriginal: true, + ); +} + +/// from: `com.github.dart_lang.jnigen.SuspendInterface` +class SuspendInterface extends jni$_.JObject { + @jni$_.internal + @core$_.override + final jni$_.JType $type; + + @jni$_.internal + SuspendInterface.fromReference( + jni$_.JReference reference, + ) : $type = type, + super.fromReference(reference); + + static final _class = + jni$_.JClass.forName(r'com/github/dart_lang/jnigen/SuspendInterface'); + + /// The type which includes information such as the signature of this class. + static const jni$_.JType nullableType = + $SuspendInterface$NullableType$(); + + /// The type which includes information such as the signature of this class. + static const jni$_.JType type = $SuspendInterface$Type$(); + static final _id_sayHello = _class.instanceMethodId( + r'sayHello', + r'(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;', + ); + + static final _sayHello = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public suspend fun sayHello(): kotlin.String` + /// The returned object must be released after use, by calling the [release] method. + core$_.Future sayHello() async { + final $p = jni$_.ReceivePort(); + final _$continuation = jni$_.ProtectedJniExtensions.newPortContinuation($p); + + final $r = _sayHello(reference.pointer, _id_sayHello as jni$_.JMethodIDPtr, + _$continuation.pointer) + .object(const jni$_.$JObject$Type$()); + _$continuation.release(); + jni$_.JObject $o; + if ($r.isInstanceOf(jni$_.coroutineSingletonsClass)) { + $r.release(); + final $a = await $p.first; + $o = jni$_.JObject.fromReference( + jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); + if ($o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o.isInstanceOf(jni$_.result$FailureClass)) { + final $e = + jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); + $o.release(); + jni$_.Jni.throwException($e.reference.toPointer()); + } + } else { + $o = $r; + } + return $o.as( + const jni$_.$JString$Type$(), + releaseOriginal: true, + ); + } + + static final _id_sayHello$1 = _class.instanceMethodId( + r'sayHello', + r'(Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;', + ); + + static final _sayHello$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public suspend fun sayHello(name: kotlin.String): kotlin.String` + /// The returned object must be released after use, by calling the [release] method. + core$_.Future sayHello$1( + jni$_.JString string, + ) async { + final $p = jni$_.ReceivePort(); + final _$continuation = jni$_.ProtectedJniExtensions.newPortContinuation($p); + final _$string = string.reference; + final $r = _sayHello$1( + reference.pointer, + _id_sayHello$1 as jni$_.JMethodIDPtr, + _$string.pointer, + _$continuation.pointer) + .object(const jni$_.$JObject$Type$()); + _$continuation.release(); + jni$_.JObject $o; + if ($r.isInstanceOf(jni$_.coroutineSingletonsClass)) { + $r.release(); + final $a = await $p.first; + $o = jni$_.JObject.fromReference( + jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); + if ($o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o.isInstanceOf(jni$_.result$FailureClass)) { + final $e = + jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); + $o.release(); + jni$_.Jni.throwException($e.reference.toPointer()); + } + } else { + $o = $r; + } + return $o.as( + const jni$_.$JString$Type$(), + releaseOriginal: true, + ); + } + + static final _id_nullableHello = _class.instanceMethodId( + r'nullableHello', + r'(ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;', + ); + + static final _nullableHello = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_ + .VarArgs<(jni$_.Int32, jni$_.Pointer)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, int, jni$_.Pointer)>(); + + /// from: `public suspend fun nullableHello(returnNull: kotlin.Boolean): kotlin.String?` + /// The returned object must be released after use, by calling the [release] method. + core$_.Future nullableHello( + bool z, + ) async { + final $p = jni$_.ReceivePort(); + final _$continuation = jni$_.ProtectedJniExtensions.newPortContinuation($p); + + final $r = _nullableHello( + reference.pointer, + _id_nullableHello as jni$_.JMethodIDPtr, + z ? 1 : 0, + _$continuation.pointer) + .object(const jni$_.$JObject$NullableType$()); + _$continuation.release(); + jni$_.JObject? $o; + if ($r != null && $r.isInstanceOf(jni$_.coroutineSingletonsClass)) { + $r.release(); + final $a = await $p.first; + $o = $a == 0 + ? null + : jni$_.JObject.fromReference( + jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); + if ($o != null && $o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o != null && $o.isInstanceOf(jni$_.result$FailureClass)) { + final $e = + jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); + $o.release(); + jni$_.Jni.throwException($e.reference.toPointer()); + } + } else { + $o = $r; + } + return $o?.as( + const jni$_.$JString$NullableType$(), + releaseOriginal: true, + ); + } + + static final _id_sayInt = _class.instanceMethodId( + r'sayInt', + r'(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;', + ); + + static final _sayInt = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public suspend fun sayInt(): java.lang.Integer` + /// The returned object must be released after use, by calling the [release] method. + core$_.Future sayInt() async { + final $p = jni$_.ReceivePort(); + final _$continuation = jni$_.ProtectedJniExtensions.newPortContinuation($p); + + final $r = _sayInt(reference.pointer, _id_sayInt as jni$_.JMethodIDPtr, + _$continuation.pointer) + .object(const jni$_.$JObject$Type$()); + _$continuation.release(); + jni$_.JObject $o; + if ($r.isInstanceOf(jni$_.coroutineSingletonsClass)) { + $r.release(); + final $a = await $p.first; + $o = jni$_.JObject.fromReference( + jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); + if ($o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o.isInstanceOf(jni$_.result$FailureClass)) { + final $e = + jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); + $o.release(); + jni$_.Jni.throwException($e.reference.toPointer()); + } + } else { + $o = $r; + } + return $o.as( + const jni$_.$JInteger$Type$(), + releaseOriginal: true, + ); + } + + static final _id_sayInt$1 = _class.instanceMethodId( + r'sayInt', + r'(Ljava/lang/Integer;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;', + ); + + static final _sayInt$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public suspend fun sayInt(value: java.lang.Integer): java.lang.Integer` + /// The returned object must be released after use, by calling the [release] method. + core$_.Future sayInt$1( + jni$_.JInteger integer, + ) async { + final $p = jni$_.ReceivePort(); + final _$continuation = jni$_.ProtectedJniExtensions.newPortContinuation($p); + final _$integer = integer.reference; + final $r = _sayInt$1(reference.pointer, _id_sayInt$1 as jni$_.JMethodIDPtr, + _$integer.pointer, _$continuation.pointer) + .object(const jni$_.$JObject$Type$()); + _$continuation.release(); + jni$_.JObject $o; + if ($r.isInstanceOf(jni$_.coroutineSingletonsClass)) { + $r.release(); + final $a = await $p.first; + $o = jni$_.JObject.fromReference( + jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); + if ($o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o.isInstanceOf(jni$_.result$FailureClass)) { + final $e = + jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); + $o.release(); + jni$_.Jni.throwException($e.reference.toPointer()); + } + } else { + $o = $r; + } + return $o.as( + const jni$_.$JInteger$Type$(), + releaseOriginal: true, + ); + } + + static final _id_nullableInt = _class.instanceMethodId( + r'nullableInt', + r'(ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;', + ); + + static final _nullableInt = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_ + .VarArgs<(jni$_.Int32, jni$_.Pointer)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, int, jni$_.Pointer)>(); + + /// from: `public suspend fun nullableInt(returnNull: kotlin.Boolean): java.lang.Integer?` + /// The returned object must be released after use, by calling the [release] method. + core$_.Future nullableInt( + bool z, + ) async { + final $p = jni$_.ReceivePort(); + final _$continuation = jni$_.ProtectedJniExtensions.newPortContinuation($p); + + final $r = _nullableInt( + reference.pointer, + _id_nullableInt as jni$_.JMethodIDPtr, + z ? 1 : 0, + _$continuation.pointer) + .object(const jni$_.$JObject$NullableType$()); + _$continuation.release(); + jni$_.JObject? $o; + if ($r != null && $r.isInstanceOf(jni$_.coroutineSingletonsClass)) { + $r.release(); + final $a = await $p.first; + $o = $a == 0 + ? null + : jni$_.JObject.fromReference( + jni$_.JGlobalReference(jni$_.JObjectPtr.fromAddress($a))); + if ($o != null && $o.isInstanceOf(jni$_.result$Class)) { + $o = jni$_.resultValueField.get($o, const jni$_.$JObject$Type$()); + } else if ($o != null && $o.isInstanceOf(jni$_.result$FailureClass)) { + final $e = + jni$_.failureExceptionField.get($o, const jni$_.$JObject$Type$()); + $o.release(); + jni$_.Jni.throwException($e.reference.toPointer()); + } + } else { + $o = $r; + } + return $o?.as( + const jni$_.$JInteger$NullableType$(), + releaseOriginal: true, + ); + } + + /// Maps a specific port to the implemented interface. + static final core$_.Map _$impls = {}; + static jni$_.JObjectPtr _$invoke( + int port, + jni$_.JObjectPtr descriptor, + jni$_.JObjectPtr args, + ) { + return _$invokeMethod( + port, + jni$_.MethodInvocation.fromAddresses( + 0, + descriptor.address, + args.address, + ), + ); + } + + static final jni$_.Pointer< + jni$_.NativeFunction< + jni$_.JObjectPtr Function( + jni$_.Int64, jni$_.JObjectPtr, jni$_.JObjectPtr)>> + _$invokePointer = jni$_.Pointer.fromFunction(_$invoke); + + static jni$_.Pointer _$invokeMethod( + int $p, + jni$_.MethodInvocation $i, + ) { + try { + final $d = $i.methodDescriptor.toDartString(releaseOriginal: true); + final $a = $i.args; + if ($d == + r'sayHello(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;') { + final $r = _$impls[$p]!.sayHello( + $a![0]!.as(const jni$_.$JObject$Type$(), releaseOriginal: true), + ); + return ($r as jni$_.JObject?) + ?.as(const jni$_.$JObject$Type$()) + .reference + .toPointer() ?? + jni$_.nullptr; + } + if ($d == + r'sayHello(Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;') { + final $r = _$impls[$p]!.sayHello$1( + $a![0]!.as(const jni$_.$JString$Type$(), releaseOriginal: true), + $a![1]!.as(const jni$_.$JObject$Type$(), releaseOriginal: true), + ); + return ($r as jni$_.JObject?) + ?.as(const jni$_.$JObject$Type$()) + .reference + .toPointer() ?? + jni$_.nullptr; + } + if ($d == + r'nullableHello(ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;') { + final $r = _$impls[$p]!.nullableHello( + $a![0]! + .as(const jni$_.$JBoolean$Type$(), releaseOriginal: true) + .booleanValue(releaseOriginal: true), + $a![1]!.as(const jni$_.$JObject$Type$(), releaseOriginal: true), + ); + return ($r as jni$_.JObject?) + ?.as(const jni$_.$JObject$Type$()) + .reference + .toPointer() ?? + jni$_.nullptr; + } + if ($d == r'sayInt(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;') { + final $r = _$impls[$p]!.sayInt( + $a![0]!.as(const jni$_.$JObject$Type$(), releaseOriginal: true), + ); + return ($r as jni$_.JObject?) + ?.as(const jni$_.$JObject$Type$()) + .reference + .toPointer() ?? + jni$_.nullptr; + } + if ($d == + r'sayInt(Ljava/lang/Integer;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;') { + final $r = _$impls[$p]!.sayInt$1( + $a![0]!.as(const jni$_.$JInteger$Type$(), releaseOriginal: true), + $a![1]!.as(const jni$_.$JObject$Type$(), releaseOriginal: true), + ); + return ($r as jni$_.JObject?) + ?.as(const jni$_.$JObject$Type$()) + .reference + .toPointer() ?? + jni$_.nullptr; + } + if ($d == + r'nullableInt(ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;') { + final $r = _$impls[$p]!.nullableInt( + $a![0]! + .as(const jni$_.$JBoolean$Type$(), releaseOriginal: true) + .booleanValue(releaseOriginal: true), + $a![1]!.as(const jni$_.$JObject$Type$(), releaseOriginal: true), + ); + return ($r as jni$_.JObject?) + ?.as(const jni$_.$JObject$Type$()) + .reference + .toPointer() ?? + jni$_.nullptr; + } + } catch (e) { + return jni$_.ProtectedJniExtensions.newDartException(e); + } + return jni$_.nullptr; + } + + static void implementIn( + jni$_.JImplementer implementer, + $SuspendInterface $impl, + ) { + late final jni$_.RawReceivePort $p; + $p = jni$_.RawReceivePort(($m) { + if ($m == null) { + _$impls.remove($p.sendPort.nativePort); + $p.close(); + return; + } + final $i = jni$_.MethodInvocation.fromMessage($m); + final $r = _$invokeMethod($p.sendPort.nativePort, $i); + jni$_.ProtectedJniExtensions.returnResult($i.result, $r); + }); + implementer.add( + r'com.github.dart_lang.jnigen.SuspendInterface', + $p, + _$invokePointer, + [], + ); + final $a = $p.sendPort.nativePort; + _$impls[$a] = $impl; + } + + factory SuspendInterface.implement( + $SuspendInterface $impl, + ) { + final $i = jni$_.JImplementer(); + implementIn($i, $impl); + return SuspendInterface.fromReference( + $i.implementReference(), + ); + } +} + +abstract base mixin class $SuspendInterface { + factory $SuspendInterface({ + required core$_.Future Function() sayHello, + required core$_.Future Function(jni$_.JString string) + sayHello$1, + required core$_.Future Function(bool z) nullableHello, + required core$_.Future Function() sayInt, + required core$_.Future Function(jni$_.JInteger integer) + sayInt$1, + required core$_.Future Function(bool z) nullableInt, + }) = _$SuspendInterface; + + jni$_.JObject sayHello(jni$_.JObject continuation); + jni$_.JObject sayHello$1(jni$_.JString string, jni$_.JObject continuation); + jni$_.JObject? nullableHello(bool z, jni$_.JObject continuation); + jni$_.JObject sayInt(jni$_.JObject continuation); + jni$_.JObject sayInt$1(jni$_.JInteger integer, jni$_.JObject continuation); + jni$_.JObject? nullableInt(bool z, jni$_.JObject continuation); +} + +final class _$SuspendInterface with $SuspendInterface { + _$SuspendInterface({ + required core$_.Future Function() sayHello, + required core$_.Future Function(jni$_.JString string) + sayHello$1, + required core$_.Future Function(bool z) nullableHello, + required core$_.Future Function() sayInt, + required core$_.Future Function(jni$_.JInteger integer) + sayInt$1, + required core$_.Future Function(bool z) nullableInt, + }) : _sayHello = sayHello, + _sayHello$1 = sayHello$1, + _nullableHello = nullableHello, + _sayInt = sayInt, + _sayInt$1 = sayInt$1, + _nullableInt = nullableInt; + + final core$_.Future Function() _sayHello; + final core$_.Future Function(jni$_.JString string) _sayHello$1; + final core$_.Future Function(bool z) _nullableHello; + final core$_.Future Function() _sayInt; + final core$_.Future Function(jni$_.JInteger integer) + _sayInt$1; + final core$_.Future Function(bool z) _nullableInt; + + jni$_.JObject sayHello(jni$_.JObject continuation) { + return jni$_.KotlinContinuation.fromReference(continuation.reference) + .resumeWithFuture(_sayHello()); + } + + jni$_.JObject sayHello$1(jni$_.JString string, jni$_.JObject continuation) { + return jni$_.KotlinContinuation.fromReference(continuation.reference) + .resumeWithFuture(_sayHello$1(string)); + } + + jni$_.JObject? nullableHello(bool z, jni$_.JObject continuation) { + return jni$_.KotlinContinuation.fromReference(continuation.reference) + .resumeWithFuture(_nullableHello(z)); + } + + jni$_.JObject sayInt(jni$_.JObject continuation) { + return jni$_.KotlinContinuation.fromReference(continuation.reference) + .resumeWithFuture(_sayInt()); + } + + jni$_.JObject sayInt$1(jni$_.JInteger integer, jni$_.JObject continuation) { + return jni$_.KotlinContinuation.fromReference(continuation.reference) + .resumeWithFuture(_sayInt$1(integer)); + } + + jni$_.JObject? nullableInt(bool z, jni$_.JObject continuation) { + return jni$_.KotlinContinuation.fromReference(continuation.reference) + .resumeWithFuture(_nullableInt(z)); + } +} + +final class $SuspendInterface$NullableType$ + extends jni$_.JType { + @jni$_.internal + const $SuspendInterface$NullableType$(); + + @jni$_.internal + @core$_.override + String get signature => r'Lcom/github/dart_lang/jnigen/SuspendInterface;'; + + @jni$_.internal + @core$_.override + SuspendInterface? fromReference(jni$_.JReference reference) => + reference.isNull + ? null + : SuspendInterface.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JType get superType => const jni$_.$JObject$Type$(); + + @jni$_.internal + @core$_.override + jni$_.JType get nullableType => this; + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($SuspendInterface$NullableType$).hashCode; + + @core$_.override + bool operator ==(Object other) { + return other.runtimeType == ($SuspendInterface$NullableType$) && + other is $SuspendInterface$NullableType$; + } +} + +final class $SuspendInterface$Type$ extends jni$_.JType { + @jni$_.internal + const $SuspendInterface$Type$(); + + @jni$_.internal + @core$_.override + String get signature => r'Lcom/github/dart_lang/jnigen/SuspendInterface;'; + + @jni$_.internal + @core$_.override + SuspendInterface fromReference(jni$_.JReference reference) => + SuspendInterface.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JType get superType => const jni$_.$JObject$Type$(); + + @jni$_.internal + @core$_.override + jni$_.JType get nullableType => + const $SuspendInterface$NullableType$(); + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($SuspendInterface$Type$).hashCode; + + @core$_.override + bool operator ==(Object other) { + return other.runtimeType == ($SuspendInterface$Type$) && + other is $SuspendInterface$Type$; + } +} + final _TopLevelKtClass = jni$_.JClass.forName(r'com/github/dart_lang/jnigen/TopLevelKt'); diff --git a/pkgs/jnigen/test/kotlin_test/kotlin/src/main/kotlin/com/github/dart_lang/jnigen/SuspendFun.kt b/pkgs/jnigen/test/kotlin_test/kotlin/src/main/kotlin/com/github/dart_lang/jnigen/SuspendFun.kt index cd3a07cf8b..1247c90bb6 100644 --- a/pkgs/jnigen/test/kotlin_test/kotlin/src/main/kotlin/com/github/dart_lang/jnigen/SuspendFun.kt +++ b/pkgs/jnigen/test/kotlin_test/kotlin/src/main/kotlin/com/github/dart_lang/jnigen/SuspendFun.kt @@ -5,7 +5,7 @@ package com.github.dart_lang.jnigen -import kotlinx.coroutines.delay +import kotlinx.coroutines.* import kotlin.coroutines.Continuation public class SuspendFun { @@ -47,3 +47,29 @@ public class SuspendFun { return "Hello!" } } + +public interface SuspendInterface { + suspend fun sayHello(): String + suspend fun sayHello(name: String): String + suspend fun nullableHello(returnNull: Boolean): String? + suspend fun sayInt(): Integer + suspend fun sayInt(value: Integer): Integer + suspend fun nullableInt(returnNull: Boolean): Integer? +} + +suspend fun consumeOnAnotherThread(itf: SuspendInterface): String { + return withContext(Dispatchers.Default) { + consumeOnSameThread(itf) + } +} + +suspend fun consumeOnSameThread(itf: SuspendInterface): String { + return """ +${itf.sayHello()} +${itf.sayHello("Alice")} +${itf.nullableHello(false)} +${itf.sayInt()} +${itf.sayInt(Integer(789))} +${itf.nullableInt(false)} +""".trim(); +} diff --git a/pkgs/jnigen/test/kotlin_test/runtime_test_registrant.dart b/pkgs/jnigen/test/kotlin_test/runtime_test_registrant.dart index 4e41f1a542..e4d07613e4 100644 --- a/pkgs/jnigen/test/kotlin_test/runtime_test_registrant.dart +++ b/pkgs/jnigen/test/kotlin_test/runtime_test_registrant.dart @@ -355,5 +355,198 @@ void registerTests(String groupName, TestRunnerCallback test) { }); }); }); + + group('Interface with suspend functions', () { + test('return immediately', () async { + await using((arena) async { + final itf = SuspendInterface.implement($SuspendInterface( + sayHello: () async => JString.fromString('Hello'), + sayHello$1: (JString name) async => + JString.fromString('Hello ${name.toDartString()}'), + nullableHello: (bool returnNull) async => + returnNull ? null : JString.fromString('Hello'), + sayInt: () async => JInteger(123), + sayInt$1: (JInteger value) async => JInteger(10 * value.intValue()), + nullableInt: (bool returnNull) async => + returnNull ? null : JInteger(123), + )); + + expect((await itf.sayHello()).toDartString(), 'Hello'); + expect( + (await itf.sayHello$1(JString.fromString('Bob'))).toDartString(), + 'Hello Bob'); + expect((await itf.nullableHello(false))?.toDartString(), 'Hello'); + expect(await itf.nullableHello(true), null); + expect((await itf.sayInt()).intValue(), 123); + expect((await itf.sayInt$1(JInteger(456))).intValue(), 4560); + expect((await itf.nullableInt(false))?.intValue(), 123); + expect(await itf.nullableInt(true), null); + + expect( + (await consumeOnSameThread(itf)).toDartString(), + ''' +Hello +Hello Alice +Hello +123 +7890 +123 +''' + .trim()); + expect( + (await consumeOnAnotherThread(itf)).toDartString(), + ''' +Hello +Hello Alice +Hello +123 +7890 +123 +''' + .trim()); + }); + }); + + test('return delayed', () async { + await using((arena) async { + final itf = SuspendInterface.implement($SuspendInterface( + sayHello: () async { + await Future.delayed(const Duration(milliseconds: 100)); + return JString.fromString('Hello'); + }, + sayHello$1: (JString name) async { + await Future.delayed(const Duration(milliseconds: 100)); + return JString.fromString('Hello ${name.toDartString()}'); + }, + nullableHello: (bool returnNull) async { + await Future.delayed(const Duration(milliseconds: 100)); + return returnNull ? null : JString.fromString('Hello'); + }, + sayInt: () async { + await Future.delayed(const Duration(milliseconds: 100)); + return JInteger(123); + }, + sayInt$1: (JInteger value) async { + await Future.delayed(const Duration(milliseconds: 100)); + return JInteger(10 * value.intValue()); + }, + nullableInt: (bool returnNull) async { + await Future.delayed(const Duration(milliseconds: 100)); + return returnNull ? null : JInteger(123); + }, + )); + + expect((await itf.sayHello()).toDartString(), 'Hello'); + expect( + (await itf.sayHello$1(JString.fromString('Bob'))).toDartString(), + 'Hello Bob'); + expect((await itf.nullableHello(false))?.toDartString(), 'Hello'); + expect(await itf.nullableHello(true), null); + expect((await itf.sayInt()).intValue(), 123); + expect((await itf.sayInt$1(JInteger(456))).intValue(), 4560); + expect((await itf.nullableInt(false))?.intValue(), 123); + expect(await itf.nullableInt(true), null); + + expect( + (await consumeOnSameThread(itf)).toDartString(), + ''' +Hello +Hello Alice +Hello +123 +7890 +123 +''' + .trim()); + expect( + (await consumeOnAnotherThread(itf)).toDartString(), + ''' +Hello +Hello Alice +Hello +123 +7890 +123 +''' + .trim()); + }); + }); + + test('throw immediately', () async { + await using((arena) async { + final itf = SuspendInterface.implement($SuspendInterface( + sayHello: () async => throw Exception(), + sayHello$1: (JString name) async => throw Exception(), + nullableHello: (bool returnNull) async => throw Exception(), + sayInt: () async => throw Exception(), + sayInt$1: (JInteger value) async => throw Exception(), + nullableInt: (bool returnNull) async => throw Exception(), + )); + + await expectLater(itf.sayHello(), throwsA(isA())); + await expectLater(itf.sayHello$1(JString.fromString('Bob')), + throwsA(isA())); + await expectLater( + itf.nullableHello(false), throwsA(isA())); + await expectLater(itf.sayInt(), throwsA(isA())); + await expectLater( + itf.sayInt$1(JInteger(456)), throwsA(isA())); + await expectLater( + itf.nullableInt(false), throwsA(isA())); + + await expectLater( + consumeOnSameThread(itf), throwsA(isA())); + await expectLater( + consumeOnAnotherThread(itf), throwsA(isA())); + }); + }); + + test('throw delayed', () async { + await using((arena) async { + final itf = SuspendInterface.implement($SuspendInterface( + sayHello: () async { + await Future.delayed(const Duration(milliseconds: 100)); + throw Exception(); + }, + sayHello$1: (JString name) async { + await Future.delayed(const Duration(milliseconds: 100)); + throw Exception(); + }, + nullableHello: (bool returnNull) async { + await Future.delayed(const Duration(milliseconds: 100)); + throw Exception(); + }, + sayInt: () async { + await Future.delayed(const Duration(milliseconds: 100)); + throw Exception(); + }, + sayInt$1: (JInteger value) async { + await Future.delayed(const Duration(milliseconds: 100)); + throw Exception(); + }, + nullableInt: (bool returnNull) async { + await Future.delayed(const Duration(milliseconds: 100)); + throw Exception(); + }, + )); + + await expectLater(itf.sayHello(), throwsA(isA())); + await expectLater(itf.sayHello$1(JString.fromString('Bob')), + throwsA(isA())); + await expectLater( + itf.nullableHello(false), throwsA(isA())); + await expectLater(itf.sayInt(), throwsA(isA())); + await expectLater( + itf.sayInt$1(JInteger(456)), throwsA(isA())); + await expectLater( + itf.nullableInt(false), throwsA(isA())); + + await expectLater( + consumeOnSameThread(itf), throwsA(isA())); + await expectLater( + consumeOnAnotherThread(itf), throwsA(isA())); + }); + }); + }); }); }