Skip to content

Commit

Permalink
[jnigen] Add support for Kotlin top-level functions and fields (#1235)
Browse files Browse the repository at this point in the history
  • Loading branch information
HosseinYousefi authored Jun 26, 2024
1 parent 59cb3b2 commit efd9b93
Show file tree
Hide file tree
Showing 13 changed files with 257 additions and 46 deletions.
4 changes: 4 additions & 0 deletions pkgs/jnigen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.10.0-wip

- Added support for Kotlin's top-level functions and fields.

## 0.9.3

- Fixed a bug where wrong argument types were generated for varargs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl;
import com.github.dart_lang.jnigen.apisummarizer.elements.KotlinClass;
import com.github.dart_lang.jnigen.apisummarizer.elements.KotlinPackage;
import java.util.ArrayList;
import java.util.List;
import kotlinx.metadata.jvm.KotlinClassHeader;
Expand Down Expand Up @@ -90,7 +91,8 @@ public void visitEnd() {
decl.kotlinClass =
KotlinClass.fromKmClass(((KotlinClassMetadata.Class) metadata).toKmClass());
} else if (metadata instanceof KotlinClassMetadata.FileFacade) {
// TODO(#301): Handle file facades.
decl.kotlinPackage =
KotlinPackage.fromKmPackage(((KotlinClassMetadata.FileFacade) metadata).toKmPackage());
} else if (metadata instanceof KotlinClassMetadata.SyntheticClass) {
// Ignore synthetic classes such as lambdas.
} else if (metadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class ClassDecl {
public JavaDocComment javadoc;
public List<JavaAnnotation> annotations;
public KotlinClass kotlinClass;
public KotlinPackage kotlinPackage;

/** In case of enum, names of enum constants */
public List<String> values = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.github.dart_lang.jnigen.apisummarizer.elements;

import java.util.List;
import java.util.stream.Collectors;
import kotlinx.metadata.KmPackage;

public class KotlinPackage {
public List<KotlinFunction> functions;

public static KotlinPackage fromKmPackage(KmPackage p) {
var pkg = new KotlinPackage();
pkg.functions =
p.getFunctions().stream().map(KotlinFunction::fromKmFunction).collect(Collectors.toList());
return pkg;
}
}
101 changes: 70 additions & 31 deletions pkgs/jnigen/lib/src/bindings/dart_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ const _typeParamPrefix = '\$';

// Misc.
const _protectedExtension = 'ProtectedJniExtensions';
const _classRef = '_class';

/// Used for C bindings.
const _selfPointer = 'reference.pointer';
Expand Down Expand Up @@ -266,8 +265,39 @@ class _ClassGenerator extends Visitor<ClassDecl, void> {
static const staticTypeGetter = 'type';
static const instanceTypeGetter = '\$$staticTypeGetter';

void generateFieldsAndMethods(ClassDecl node, String classRef) {
final fieldGenerator = _FieldGenerator(config, resolver, s,
isTopLevel: node.isTopLevel, classRef: classRef);
for (final field in node.fields) {
field.accept(fieldGenerator);
}
final methodGenerator = _MethodGenerator(config, resolver, s,
isTopLevel: node.isTopLevel, classRef: classRef);
for (final method in node.methods) {
method.accept(methodGenerator);
}
}

String writeClassRef(ClassDecl node) {
final internalName = node.internalName;
final modifier = node.isTopLevel ? '' : ' static ';
final classRef = node.isTopLevel ? '_${node.finalName}Class' : '_class';
s.write('''
${modifier}final $classRef = $_jni.JClass.forName(r"$internalName");
''');
return classRef;
}

@override
void visit(ClassDecl node) {
if (node.isTopLevel) {
// If the class is top-level, only generate its methods and fields.
// Fields and Methods
final classRef = writeClassRef(node);
generateFieldsAndMethods(node, classRef);
return;
}
// Docs.
s.write('/// from: ${node.binaryName}\n');
node.javadoc?.accept(_DocGenerator(s, depth: 0));
Expand Down Expand Up @@ -326,11 +356,7 @@ class $name$typeParamsDef extends $superName {
''');

final internalName = node.internalName;
s.write('''
static final $_classRef = $_jni.JClass.forName(r"$internalName");
''');
final classRef = writeClassRef(node);

// Static TypeClass getter.
s.writeln(
Expand Down Expand Up @@ -358,14 +384,7 @@ class $name$typeParamsDef extends $superName {
}

// Fields and Methods
final fieldGenerator = _FieldGenerator(config, resolver, s);
for (final field in node.fields) {
field.accept(fieldGenerator);
}
final methodGenerator = _MethodGenerator(config, resolver, s);
for (final method in node.methods) {
method.accept(methodGenerator);
}
generateFieldsAndMethods(node, classRef);

// Experimental: Interface implementation.
if (node.declKind == DeclKind.interfaceKind &&
Expand Down Expand Up @@ -889,16 +908,26 @@ class _FieldGenerator extends Visitor<Field, void> {
final Config config;
final Resolver resolver;
final StringSink s;
final bool isTopLevel;
final String classRef;

const _FieldGenerator(
this.config,
this.resolver,
this.s, {
required this.isTopLevel,
required this.classRef,
});

const _FieldGenerator(this.config, this.resolver, this.s);
String get modifier => isTopLevel ? '' : ' static ';

void writeDartOnlyAccessor(Field node) {
void writeAccessor(Field node) {
final name = node.finalName;
final staticOrInstance = node.isStatic ? 'static' : 'instance';
final descriptor = node.type.descriptor;
s.write('''
static final _id_$name =
$_classRef.${staticOrInstance}FieldId(
${modifier}final _id_$name =
$classRef.${staticOrInstance}FieldId(
r"${node.name}",
r"$descriptor",
);
Expand All @@ -907,14 +936,14 @@ class _FieldGenerator extends Visitor<Field, void> {

String dartOnlyGetter(Field node) {
final name = node.finalName;
final self = node.isStatic ? _classRef : _self;
final self = node.isStatic ? classRef : _self;
final type = node.type.accept(_TypeClassGenerator(resolver)).name;
return '_id_$name.get($self, $type)';
}

String dartOnlySetter(Field node) {
final name = node.finalName;
final self = node.isStatic ? _classRef : _self;
final self = node.isStatic ? classRef : _self;
final type = node.type.accept(_TypeClassGenerator(resolver)).name;
return '_id_$name.set($self, $type, value)';
}
Expand All @@ -936,19 +965,19 @@ class _FieldGenerator extends Visitor<Field, void> {
final value = node.defaultValue!;
if (value is num || value is bool) {
writeDocs(node, writeReleaseInstructions: false);
s.writeln(' static const $name = $value;');
s.writeln('${modifier}const $name = $value;');
return;
}
}

// Accessors.
writeDartOnlyAccessor(node);
writeAccessor(node);

// Getter docs.
writeDocs(node, writeReleaseInstructions: true);

final name = node.finalName;
final ifStatic = node.isStatic ? 'static ' : '';
final ifStatic = node.isStatic && !isTopLevel ? 'static ' : '';
final type = node.type.accept(_TypeGenerator(resolver));
s.write('$ifStatic$type get $name => ');
s.write(dartOnlyGetter(node));
Expand Down Expand Up @@ -994,10 +1023,20 @@ class _MethodGenerator extends Visitor<Method, void> {
final Config config;
final Resolver resolver;
final StringSink s;
final bool isTopLevel;
final String classRef;

const _MethodGenerator(
this.config,
this.resolver,
this.s, {
required this.isTopLevel,
required this.classRef,
});

const _MethodGenerator(this.config, this.resolver, this.s);
String get modifier => isTopLevel ? '' : ' static ';

void writeDartOnlyAccessor(Method node) {
void writeAccessor(Method node) {
final name = node.finalName;
final kind = node.isCtor
? 'constructor'
Expand All @@ -1006,7 +1045,7 @@ class _MethodGenerator extends Visitor<Method, void> {
: 'instanceMethod';
final descriptor = node.descriptor;
s.write('''
static final _id_$name = $_classRef.${kind}Id(
${modifier}final _id_$name = $classRef.${kind}Id(
''');
if (!node.isCtor) s.writeln(' r"${node.name}",');
s.write('''
Expand All @@ -1018,7 +1057,7 @@ class _MethodGenerator extends Visitor<Method, void> {
final methodName = node.accept(const _CallMethodName());
s.write('''
static final _$name = $_protectedExtension
${modifier}final _$name = $_protectedExtension
.lookup<$_ffi.NativeFunction<$ffiSig>>("$methodName")
.asFunction<$dartSig>();
''');
Expand All @@ -1037,7 +1076,7 @@ class _MethodGenerator extends Visitor<Method, void> {
String dartOnlyCtor(Method node) {
final name = node.finalName;
final params = [
'$_classRef.reference.pointer',
'$classRef.reference.pointer',
'_id_$name as $_jni.JMethodIDPtr',
...node.params.accept(const _ParamCall()),
].join(', ');
Expand All @@ -1057,7 +1096,7 @@ class _MethodGenerator extends Visitor<Method, void> {
String dartOnlyMethodCall(Method node) {
final name = node.finalName;
final params = [
node.isStatic ? '$_classRef.reference.pointer' : _selfPointer,
node.isStatic ? '$classRef.reference.pointer' : _selfPointer,
'_id_$name as $_jni.JMethodIDPtr',
...node.params.accept(const _ParamCall()),
].join(', ');
Expand All @@ -1068,7 +1107,7 @@ class _MethodGenerator extends Visitor<Method, void> {
@override
void visit(Method node) {
// Accessors
writeDartOnlyAccessor(node);
writeAccessor(node);

// Docs
s.write(' /// from: ');
Expand Down Expand Up @@ -1145,7 +1184,7 @@ class _MethodGenerator extends Visitor<Method, void> {
final returnTypeClass = (node.asyncReturnType ?? node.returnType)
.accept(_TypeClassGenerator(resolver))
.name;
final ifStatic = node.isStatic ? 'static ' : '';
final ifStatic = node.isStatic && !isTopLevel ? 'static ' : '';
final defArgs = node.params.accept(_ParamDef(resolver)).toList();
final typeClassDef = _encloseIfNotEmpty(
'{',
Expand Down
6 changes: 4 additions & 2 deletions pkgs/jnigen/lib/src/bindings/kotlin_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ class KotlinProcessor extends Visitor<Classes, void> {
class _KotlinClassProcessor extends Visitor<ClassDecl, void> {
@override
void visit(ClassDecl node) {
if (node.kotlinClass == null) {
if (node.kotlinClass == null && node.kotlinPackage == null) {
return;
}
// This [ClassDecl] is actually a Kotlin class.
// Matching methods and functions from the metadata.
final functions = <String, KotlinFunction>{};
for (final function in node.kotlinClass!.functions) {
final kotlinFunctions =
(node.kotlinClass?.functions ?? node.kotlinPackage?.functions)!;
for (final function in kotlinFunctions) {
final signature = function.name + function.descriptor;
functions[signature] = function;
}
Expand Down
4 changes: 2 additions & 2 deletions pkgs/jnigen/lib/src/bindings/renamer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,13 @@ class _ClassRenamer implements Visitor<ClassDecl, void> {
node.methodNumsAfterRenaming = {};

final className = _getSimplifiedClassName(node.binaryName);
node.uniqueName = _renameConflict(classNameCounts, className);

// When generating all the classes in a single file
// the names need to be unique.
final uniquifyName =
config.outputConfig.dartConfig.structure == OutputStructure.singleFile;
node.finalName = uniquifyName ? node.uniqueName : className;
node.finalName =
uniquifyName ? _renameConflict(classNameCounts, className) : className;
// TODO(#143): $ at the beginning is a temporary fix for the name collision.
node.typeClassName = '\$${node.finalName}Type';
log.fine('Class ${node.binaryName} is named ${node.finalName}');
Expand Down
31 changes: 22 additions & 9 deletions pkgs/jnigen/lib/src/elements/elements.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,15 @@ class ClassDecl extends ClassMember implements Element<ClassDecl> {
this.hasInstanceInit = false,
this.values,
this.kotlinClass,
this.kotlinPackage,
});

@override
final Set<String> modifiers;

final List<Annotation> annotations;
final KotlinClass? kotlinClass;
final KotlinPackage? kotlinPackage;
final JavaDocComment? javadoc;
final DeclKind declKind;
final String binaryName;
Expand Down Expand Up @@ -121,15 +123,6 @@ class ClassDecl extends ClassMember implements Element<ClassDecl> {
@JsonKey(includeFromJson: false)
late final String typeClassName;

/// Unique name obtained by renaming conflicting names with a number.
///
/// This is used by C bindings instead of fully qualified name to reduce
/// the verbosity of generated bindings.
///
/// Populated by [Renamer].
@JsonKey(includeFromJson: false)
late final String uniqueName;

/// Type parameters including the ones from its ancestors
///
/// Populated by [Linker].
Expand Down Expand Up @@ -178,6 +171,9 @@ class ClassDecl extends ClassMember implements Element<ClassDecl> {

@JsonKey(includeFromJson: false)
late final isNested = parentName != null;

/// Whether the class is actually only a number of top-level Kotlin Functions.
bool get isTopLevel => kotlinPackage != null;
}

@JsonEnum()
Expand Down Expand Up @@ -653,6 +649,23 @@ class KotlinClass implements Element<KotlinClass> {
}
}

@JsonSerializable(createToJson: false)
class KotlinPackage implements Element<KotlinPackage> {
KotlinPackage({
this.functions = const [],
});

final List<KotlinFunction> functions;

factory KotlinPackage.fromJson(Map<String, dynamic> json) =>
_$KotlinPackageFromJson(json);

@override
R accept<R>(Visitor<KotlinPackage, R> v) {
return v.visit(this);
}
}

@JsonSerializable(createToJson: false)
class KotlinFunction implements Element<KotlinFunction> {
KotlinFunction({
Expand Down
Loading

0 comments on commit efd9b93

Please sign in to comment.