Skip to content
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

Generate null-safe Kotlin code #1758

Merged
merged 13 commits into from
Nov 29, 2024
2 changes: 1 addition & 1 deletion pkgs/jnigen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## 0.13.0-wip

- **Breaking Change**([#1644](https://github.com/dart-lang/native/issues/1644)):
Generate null-safe Dart bindings.
Generate null-safe Dart bindings for Java and Kotlin.

## 0.12.2

Expand Down
4 changes: 2 additions & 2 deletions pkgs/jnigen/example/kotlin_plugin/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ class _MyHomePageState extends State<MyHomePage> {
ElevatedButton(
onPressed: () {
setState(() {
answer = example.thinkBeforeAnswering().then((value) =>
value?.toDartString(releaseOriginal: true) ?? 'null');
answer = example.thinkBeforeAnswering().then(
(value) => value.toDartString(releaseOriginal: true));
});
},
child: const Text('Think...'),
Expand Down
12 changes: 6 additions & 6 deletions pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,25 +94,25 @@ class Example extends _$jni.JObject {

/// from: `public final java.lang.Object thinkBeforeAnswering(kotlin.coroutines.Continuation continuation)`
/// The returned object must be released after use, by calling the [release] method.
_$core.Future<_$jni.JString?> thinkBeforeAnswering() async {
_$core.Future<_$jni.JString> thinkBeforeAnswering() async {
final $p = _$jni.ReceivePort();
final _$continuation = _$jni.ProtectedJniExtensions.newPortContinuation($p);

_thinkBeforeAnswering(
reference.pointer,
_id_thinkBeforeAnswering as _$jni.JMethodIDPtr,
_$continuation.pointer)
.object<_$jni.JObject?>(const _$jni.JObjectNullableType());
.object<_$jni.JObject>(const _$jni.JObjectType());
_$continuation.release();
final $o =
_$jni.JGlobalReference(_$jni.JObjectPtr.fromAddress(await $p.first));
final $k = const _$jni.JStringNullableType().jClass.reference;
final $k = const _$jni.JStringType().jClass.reference;
if (!_$jni.Jni.env.IsInstanceOf($o.pointer, $k.pointer)) {
$k.release();
throw 'Failed';
}
$k.release();
return const _$jni.JStringNullableType().fromReference($o);
return const _$jni.JStringType().fromReference($o);
}
}

Expand All @@ -133,7 +133,7 @@ final class $Example$NullableType extends _$jni.JObjType<Example?> {
);
@_$jni.internal
@_$core.override
_$jni.JObjType get superType => const _$jni.JObjectNullableType();
_$jni.JObjType get superType => const _$jni.JObjectType();

@_$jni.internal
@_$core.override
Expand Down Expand Up @@ -168,7 +168,7 @@ final class $Example$Type extends _$jni.JObjType<Example> {
);
@_$jni.internal
@_$core.override
_$jni.JObjType get superType => const _$jni.JObjectNullableType();
_$jni.JObjType get superType => const _$jni.JObjectType();

@_$jni.internal
@_$core.override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@

public class KotlinPackage {
public List<KotlinFunction> functions;
public List<KotlinProperty> properties;

public static KotlinPackage fromKmPackage(KmPackage p) {
var pkg = new KotlinPackage();
pkg.functions =
p.getFunctions().stream().map(KotlinFunction::fromKmFunction).collect(Collectors.toList());
pkg.properties =
p.getProperties().stream().map(KotlinProperty::fromKmProperty).collect(Collectors.toList());
return pkg;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.util.List;
import java.util.stream.Collectors;
import kotlinx.metadata.Flag;
import kotlinx.metadata.KmClassifier;
import kotlinx.metadata.KmType;

Expand All @@ -15,11 +16,14 @@ public class KotlinType {
public String name;
public int id;
public List<KotlinTypeProjection> arguments;
public boolean isNullable;

public static KotlinType fromKmType(KmType t) {
if (t == null) return null;
var type = new KotlinType();
type.flags = t.getFlags();
// Processing the information needed from the flags.
type.isNullable = Flag.Type.IS_NULLABLE.invoke(type.flags);
var classifier = t.getClassifier();
if (classifier instanceof KmClassifier.Class) {
type.kind = "class";
Expand Down
225 changes: 223 additions & 2 deletions pkgs/jnigen/lib/src/bindings/kotlin_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@
import '../elements/elements.dart';
import 'visitor.dart';

String _toJavaBinaryName(String kotlinBinaryName) {
final binaryName =
kotlinBinaryName.replaceAll('.', r'$').replaceAll('/', '.');
return const {
'kotlin.Any': 'java.lang.Object',
'kotlin.Byte': 'java.lang.Byte',
'kotlin.Short': 'java.lang.Short',
'kotlin.Int': 'java.lang.Integer',
'kotlin.Long': 'java.lang.Long',
'kotlin.Char': 'java.lang.Character',
'kotlin.Float': 'java.lang.Float',
'kotlin.Double': 'java.lang.Double',
'kotlin.Boolean': 'java.lang.Boolean',
'kotlin.Cloneable': 'java.lang.Cloneable',
'kotlin.Comparable': 'java.lang.Comparable',
'kotlin.Enum': 'java.lang.Enum',
'kotlin.Annotation': 'java.lang.annotation.Annotation',
'kotlin.CharSequence': 'java.lang.CharSequence',
'kotlin.String': 'java.lang.String',
'kotlin.Number': 'java.lang.Number',
'kotlin.Throwable': 'java.lang.Throwable',
}[binaryName] ??
binaryName;
}

/// A [Visitor] that adds the the information from Kotlin's metadata to the Java
/// classes and methods.
class KotlinProcessor extends Visitor<Classes, void> {
Expand All @@ -24,6 +49,46 @@ class _KotlinClassProcessor extends Visitor<ClassDecl, void> {
return;
}
// This [ClassDecl] is actually a Kotlin class.
if (node.kotlinClass != null) {
for (var i = 0; i < node.kotlinClass!.typeParameters.length; ++i) {
node.typeParams[i].accept(
_KotlinTypeParamProcessor(node.kotlinClass!.typeParameters[i]));
}
if (node.superclass case final superClass?) {
final kotlinSuperTypes = node.kotlinClass!.superTypes.where(
(superType) =>
_toJavaBinaryName(superType.name ?? '') == superClass.name,
);
if (kotlinSuperTypes.isNotEmpty) {
superClass.accept(_KotlinTypeProcessor(kotlinSuperTypes.single));
}
}
}

// Matching fields and properties from the metadata.
final properties = <String, KotlinProperty>{};
final getters = <String, KotlinProperty>{};
final setters = <String, KotlinProperty>{};
final kotlinProperties =
(node.kotlinClass?.properties ?? node.kotlinPackage?.properties)!;
for (final property in kotlinProperties) {
if (property.fieldName case final fieldName?) {
properties[fieldName] = property;
}
if (property.getterName case final getterName?) {
final getterSignature = getterName + property.getterDescriptor!;
getters[getterSignature] = property;
}
if (property.setterName case final setterName?) {
final setterSignature = setterName + property.setterDescriptor!;
setters[setterSignature] = property;
}
}
for (final field in node.fields) {
if (properties[field.name] case final property?) {
field.accept(_KotlinPropertyProcessor(property));
}
}
// Matching methods and functions from the metadata.
final functions = <String, KotlinFunction>{};
final kotlinFunctions =
Expand All @@ -32,22 +97,49 @@ class _KotlinClassProcessor extends Visitor<ClassDecl, void> {
final signature = function.name + function.descriptor;
functions[signature] = function;
}
final constructors = <String, KotlinConstructor>{};
final kotlinConstructors = node.kotlinClass?.constructors ?? [];
for (final constructor in kotlinConstructors) {
final signature = constructor.name + constructor.descriptor;
constructors[signature] = constructor;
}
for (final method in node.methods) {
final signature = method.name + method.descriptor!;
if (functions.containsKey(signature)) {
method.accept(_KotlinMethodProcessor(functions[signature]!));
if (functions[signature] case final function?) {
method.accept(_KotlinMethodProcessor(function));
} else if (constructors[signature] case final constructor?) {
method.accept(_KotlinConstructorProcessor(constructor));
} else if (getters[signature] case final getter?) {
method.accept(_KotlinGetterProcessor(getter));
} else if (setters[signature] case final setter?) {
method.accept(_KotlinSetterProcessor(setter));
}
}
}
}

void _processParams(
List<Param> params, List<KotlinValueParameter> kotlinParams) {
if (params.length != kotlinParams.length) {
return;
}
for (var i = 0; i < params.length; ++i) {
params[i].accept(_KotlinParamProcessor(kotlinParams[i]));
}
}

class _KotlinMethodProcessor extends Visitor<Method, void> {
final KotlinFunction function;

_KotlinMethodProcessor(this.function);

@override
void visit(Method node) {
_processParams(node.params, function.valueParameters);
for (var i = 0; i < node.typeParams.length; ++i) {
node.typeParams[i]
.accept(_KotlinTypeParamProcessor(function.typeParameters[i]));
}
if (function.isSuspend) {
const kotlinContinutationType = 'kotlin.coroutines.Continuation';
assert(node.params.isNotEmpty &&
Expand All @@ -63,6 +155,135 @@ class _KotlinMethodProcessor extends Visitor<Method, void> {
node.asyncReturnType = continuationType == null
? TypeUsage.object
: continuationType.clone();
node.asyncReturnType!.accept(_KotlinTypeProcessor(function.returnType));

// The continuation object is always non-null.
node.returnType.type.annotations ??= [];
node.returnType.type.annotations!.add(Annotation.nonNull);
} else {
node.returnType.accept(_KotlinTypeProcessor(function.returnType));
}
}
}

class _KotlinConstructorProcessor extends Visitor<Method, void> {
final KotlinConstructor constructor;

_KotlinConstructorProcessor(this.constructor);

@override
void visit(Method node) {
_processParams(node.params, constructor.valueParameters);
}
}

class _KotlinGetterProcessor extends Visitor<Method, void> {
final KotlinProperty getter;

_KotlinGetterProcessor(this.getter);

@override
void visit(Method node) {
node.returnType.accept(_KotlinTypeProcessor(getter.returnType));
}
}

class _KotlinSetterProcessor extends Visitor<Method, void> {
final KotlinProperty setter;

_KotlinSetterProcessor(this.setter);

@override
void visit(Method node) {
if (setter.setterParameter case final setterParam?) {
node.params.single.type.accept(_KotlinTypeProcessor(setterParam.type));
}
node.params.single.type.accept(_KotlinTypeProcessor(setter.returnType));
}
}

class _KotlinPropertyProcessor extends Visitor<Field, void> {
final KotlinProperty property;

_KotlinPropertyProcessor(this.property);

@override
void visit(Field node) {
node.type.accept(_KotlinTypeProcessor(property.returnType));
}
}

class _KotlinParamProcessor extends Visitor<Param, void> {
final KotlinValueParameter kotlinParam;

_KotlinParamProcessor(this.kotlinParam);

@override
void visit(Param node) {
node.type.accept(_KotlinTypeProcessor(kotlinParam.type));
}
}

class _KotlinTypeParamProcessor extends Visitor<TypeParam, void> {
final KotlinTypeParameter kotlinTypeParam;

_KotlinTypeParamProcessor(this.kotlinTypeParam);

@override
void visit(TypeParam node) {
final kotlinBounds = kotlinTypeParam.upperBounds;
final bounds = <String, KotlinType>{};
for (final bound in kotlinBounds) {
if (bound.name case final boundName?) {
bounds[_toJavaBinaryName(boundName)] = bound;
}
}
for (final bound in node.bounds) {
if (bounds[bound.name] case final kotlinBound?) {
bound.accept(_KotlinTypeProcessor(kotlinBound));
}
}
}
}

class _KotlinTypeProcessor extends TypeVisitor<void> {
final KotlinType kotlinType;

_KotlinTypeProcessor(this.kotlinType);

@override
void visitDeclaredType(DeclaredType node) {
for (var i = 0; i < node.params.length; ++i) {
node.params[i].accept(_KotlinTypeProcessor(kotlinType.arguments[i].type));
}
super.visitDeclaredType(node);
}

@override
void visitArrayType(ArrayType node) {
if (kotlinType.arguments.isNotEmpty) {
node.elementType
.accept(_KotlinTypeProcessor(kotlinType.arguments.first.type));
}
super.visitArrayType(node);
}

@override
void visitWildcard(Wildcard node) {
node.extendsBound?.accept(_KotlinTypeProcessor(kotlinType));
node.superBound?.accept(_KotlinTypeProcessor(kotlinType));
super.visitWildcard(node);
}

@override
void visitNonPrimitiveType(ReferredType node) {
node.annotations ??= [];
node.annotations!
.add(kotlinType.isNullable ? Annotation.nullable : Annotation.nonNull);
}

@override
void visitPrimitiveType(PrimitiveType node) {
// Do nothing.
}
}
Loading
Loading