Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion pkgs/jni/lib/_internal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
43 changes: 43 additions & 0 deletions pkgs/jni/lib/src/kotlin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import 'package:meta/meta.dart' show internal;

import 'jni.dart';
import 'jobject.dart';
import 'types.dart';

@internal
Expand All @@ -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<JObject?> future) {
future.then(resumeWith, onError: resumeWithException);
return _coroutineSuspended;
}
}
2 changes: 1 addition & 1 deletion pkgs/jni/lib/src/plugin/generated_plugin.dart
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 2 additions & 0 deletions pkgs/jnigen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
64 changes: 42 additions & 22 deletions pkgs/jnigen/lib/src/bindings/dart_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<Param> get paramsMaybeAsync {
final p = params.toList();
if (isSuspendFun) {
p.removeLast();
}
return p;
}
}

String _newLine({int depth = 0}) {
return '\n${' ' * depth}';
}
Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -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
Expand All @@ -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 =
Expand All @@ -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();
Expand Down Expand Up @@ -1862,11 +1873,10 @@ class _ConcreteImplClosureDef extends Visitor<Method, void> {

@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;');
Expand All @@ -1885,11 +1895,11 @@ class _AbstractImplFactoryArg extends Visitor<Method, String> {

@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,';
Expand All @@ -1909,11 +1919,11 @@ class _ConcreteImplClosureCtorArg extends Visitor<Method, String> {

@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,';
Expand All @@ -1940,11 +1950,21 @@ class _ConcreteImplMethod extends Visitor<Method, void> {
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);
}''');
}
}
}

Expand Down
Loading
Loading