Skip to content

Commit efd9b93

Browse files
[jnigen] Add support for Kotlin top-level functions and fields (#1235)
1 parent 59cb3b2 commit efd9b93

File tree

13 files changed

+257
-46
lines changed

13 files changed

+257
-46
lines changed

pkgs/jnigen/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.10.0-wip
2+
3+
- Added support for Kotlin's top-level functions and fields.
4+
15
## 0.9.3
26

37
- Fixed a bug where wrong argument types were generated for varargs.

pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/KotlinMetadataAnnotationVisitor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl;
88
import com.github.dart_lang.jnigen.apisummarizer.elements.KotlinClass;
9+
import com.github.dart_lang.jnigen.apisummarizer.elements.KotlinPackage;
910
import java.util.ArrayList;
1011
import java.util.List;
1112
import kotlinx.metadata.jvm.KotlinClassHeader;
@@ -90,7 +91,8 @@ public void visitEnd() {
9091
decl.kotlinClass =
9192
KotlinClass.fromKmClass(((KotlinClassMetadata.Class) metadata).toKmClass());
9293
} else if (metadata instanceof KotlinClassMetadata.FileFacade) {
93-
// TODO(#301): Handle file facades.
94+
decl.kotlinPackage =
95+
KotlinPackage.fromKmPackage(((KotlinClassMetadata.FileFacade) metadata).toKmPackage());
9496
} else if (metadata instanceof KotlinClassMetadata.SyntheticClass) {
9597
// Ignore synthetic classes such as lambdas.
9698
} else if (metadata instanceof KotlinClassMetadata.MultiFileClassFacade) {

pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public class ClassDecl {
3737
public JavaDocComment javadoc;
3838
public List<JavaAnnotation> annotations;
3939
public KotlinClass kotlinClass;
40+
public KotlinPackage kotlinPackage;
4041

4142
/** In case of enum, names of enum constants */
4243
public List<String> values = new ArrayList<>();
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.github.dart_lang.jnigen.apisummarizer.elements;
2+
3+
import java.util.List;
4+
import java.util.stream.Collectors;
5+
import kotlinx.metadata.KmPackage;
6+
7+
public class KotlinPackage {
8+
public List<KotlinFunction> functions;
9+
10+
public static KotlinPackage fromKmPackage(KmPackage p) {
11+
var pkg = new KotlinPackage();
12+
pkg.functions =
13+
p.getFunctions().stream().map(KotlinFunction::fromKmFunction).collect(Collectors.toList());
14+
return pkg;
15+
}
16+
}

pkgs/jnigen/lib/src/bindings/dart_generator.dart

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ const _typeParamPrefix = '\$';
3636

3737
// Misc.
3838
const _protectedExtension = 'ProtectedJniExtensions';
39-
const _classRef = '_class';
4039

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

268+
void generateFieldsAndMethods(ClassDecl node, String classRef) {
269+
final fieldGenerator = _FieldGenerator(config, resolver, s,
270+
isTopLevel: node.isTopLevel, classRef: classRef);
271+
for (final field in node.fields) {
272+
field.accept(fieldGenerator);
273+
}
274+
final methodGenerator = _MethodGenerator(config, resolver, s,
275+
isTopLevel: node.isTopLevel, classRef: classRef);
276+
for (final method in node.methods) {
277+
method.accept(methodGenerator);
278+
}
279+
}
280+
281+
String writeClassRef(ClassDecl node) {
282+
final internalName = node.internalName;
283+
final modifier = node.isTopLevel ? '' : ' static ';
284+
final classRef = node.isTopLevel ? '_${node.finalName}Class' : '_class';
285+
s.write('''
286+
${modifier}final $classRef = $_jni.JClass.forName(r"$internalName");
287+
288+
''');
289+
return classRef;
290+
}
291+
269292
@override
270293
void visit(ClassDecl node) {
294+
if (node.isTopLevel) {
295+
// If the class is top-level, only generate its methods and fields.
296+
// Fields and Methods
297+
final classRef = writeClassRef(node);
298+
generateFieldsAndMethods(node, classRef);
299+
return;
300+
}
271301
// Docs.
272302
s.write('/// from: ${node.binaryName}\n');
273303
node.javadoc?.accept(_DocGenerator(s, depth: 0));
@@ -326,11 +356,7 @@ class $name$typeParamsDef extends $superName {
326356
327357
''');
328358

329-
final internalName = node.internalName;
330-
s.write('''
331-
static final $_classRef = $_jni.JClass.forName(r"$internalName");
332-
333-
''');
359+
final classRef = writeClassRef(node);
334360

335361
// Static TypeClass getter.
336362
s.writeln(
@@ -358,14 +384,7 @@ class $name$typeParamsDef extends $superName {
358384
}
359385

360386
// Fields and Methods
361-
final fieldGenerator = _FieldGenerator(config, resolver, s);
362-
for (final field in node.fields) {
363-
field.accept(fieldGenerator);
364-
}
365-
final methodGenerator = _MethodGenerator(config, resolver, s);
366-
for (final method in node.methods) {
367-
method.accept(methodGenerator);
368-
}
387+
generateFieldsAndMethods(node, classRef);
369388

370389
// Experimental: Interface implementation.
371390
if (node.declKind == DeclKind.interfaceKind &&
@@ -889,16 +908,26 @@ class _FieldGenerator extends Visitor<Field, void> {
889908
final Config config;
890909
final Resolver resolver;
891910
final StringSink s;
911+
final bool isTopLevel;
912+
final String classRef;
913+
914+
const _FieldGenerator(
915+
this.config,
916+
this.resolver,
917+
this.s, {
918+
required this.isTopLevel,
919+
required this.classRef,
920+
});
892921

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

895-
void writeDartOnlyAccessor(Field node) {
924+
void writeAccessor(Field node) {
896925
final name = node.finalName;
897926
final staticOrInstance = node.isStatic ? 'static' : 'instance';
898927
final descriptor = node.type.descriptor;
899928
s.write('''
900-
static final _id_$name =
901-
$_classRef.${staticOrInstance}FieldId(
929+
${modifier}final _id_$name =
930+
$classRef.${staticOrInstance}FieldId(
902931
r"${node.name}",
903932
r"$descriptor",
904933
);
@@ -907,14 +936,14 @@ class _FieldGenerator extends Visitor<Field, void> {
907936

908937
String dartOnlyGetter(Field node) {
909938
final name = node.finalName;
910-
final self = node.isStatic ? _classRef : _self;
939+
final self = node.isStatic ? classRef : _self;
911940
final type = node.type.accept(_TypeClassGenerator(resolver)).name;
912941
return '_id_$name.get($self, $type)';
913942
}
914943

915944
String dartOnlySetter(Field node) {
916945
final name = node.finalName;
917-
final self = node.isStatic ? _classRef : _self;
946+
final self = node.isStatic ? classRef : _self;
918947
final type = node.type.accept(_TypeClassGenerator(resolver)).name;
919948
return '_id_$name.set($self, $type, value)';
920949
}
@@ -936,19 +965,19 @@ class _FieldGenerator extends Visitor<Field, void> {
936965
final value = node.defaultValue!;
937966
if (value is num || value is bool) {
938967
writeDocs(node, writeReleaseInstructions: false);
939-
s.writeln(' static const $name = $value;');
968+
s.writeln('${modifier}const $name = $value;');
940969
return;
941970
}
942971
}
943972

944973
// Accessors.
945-
writeDartOnlyAccessor(node);
974+
writeAccessor(node);
946975

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

950979
final name = node.finalName;
951-
final ifStatic = node.isStatic ? 'static ' : '';
980+
final ifStatic = node.isStatic && !isTopLevel ? 'static ' : '';
952981
final type = node.type.accept(_TypeGenerator(resolver));
953982
s.write('$ifStatic$type get $name => ');
954983
s.write(dartOnlyGetter(node));
@@ -994,10 +1023,20 @@ class _MethodGenerator extends Visitor<Method, void> {
9941023
final Config config;
9951024
final Resolver resolver;
9961025
final StringSink s;
1026+
final bool isTopLevel;
1027+
final String classRef;
1028+
1029+
const _MethodGenerator(
1030+
this.config,
1031+
this.resolver,
1032+
this.s, {
1033+
required this.isTopLevel,
1034+
required this.classRef,
1035+
});
9971036

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

1000-
void writeDartOnlyAccessor(Method node) {
1039+
void writeAccessor(Method node) {
10011040
final name = node.finalName;
10021041
final kind = node.isCtor
10031042
? 'constructor'
@@ -1006,7 +1045,7 @@ class _MethodGenerator extends Visitor<Method, void> {
10061045
: 'instanceMethod';
10071046
final descriptor = node.descriptor;
10081047
s.write('''
1009-
static final _id_$name = $_classRef.${kind}Id(
1048+
${modifier}final _id_$name = $classRef.${kind}Id(
10101049
''');
10111050
if (!node.isCtor) s.writeln(' r"${node.name}",');
10121051
s.write('''
@@ -1018,7 +1057,7 @@ class _MethodGenerator extends Visitor<Method, void> {
10181057
final methodName = node.accept(const _CallMethodName());
10191058
s.write('''
10201059
1021-
static final _$name = $_protectedExtension
1060+
${modifier}final _$name = $_protectedExtension
10221061
.lookup<$_ffi.NativeFunction<$ffiSig>>("$methodName")
10231062
.asFunction<$dartSig>();
10241063
''');
@@ -1037,7 +1076,7 @@ class _MethodGenerator extends Visitor<Method, void> {
10371076
String dartOnlyCtor(Method node) {
10381077
final name = node.finalName;
10391078
final params = [
1040-
'$_classRef.reference.pointer',
1079+
'$classRef.reference.pointer',
10411080
'_id_$name as $_jni.JMethodIDPtr',
10421081
...node.params.accept(const _ParamCall()),
10431082
].join(', ');
@@ -1057,7 +1096,7 @@ class _MethodGenerator extends Visitor<Method, void> {
10571096
String dartOnlyMethodCall(Method node) {
10581097
final name = node.finalName;
10591098
final params = [
1060-
node.isStatic ? '$_classRef.reference.pointer' : _selfPointer,
1099+
node.isStatic ? '$classRef.reference.pointer' : _selfPointer,
10611100
'_id_$name as $_jni.JMethodIDPtr',
10621101
...node.params.accept(const _ParamCall()),
10631102
].join(', ');
@@ -1068,7 +1107,7 @@ class _MethodGenerator extends Visitor<Method, void> {
10681107
@override
10691108
void visit(Method node) {
10701109
// Accessors
1071-
writeDartOnlyAccessor(node);
1110+
writeAccessor(node);
10721111

10731112
// Docs
10741113
s.write(' /// from: ');
@@ -1145,7 +1184,7 @@ class _MethodGenerator extends Visitor<Method, void> {
11451184
final returnTypeClass = (node.asyncReturnType ?? node.returnType)
11461185
.accept(_TypeClassGenerator(resolver))
11471186
.name;
1148-
final ifStatic = node.isStatic ? 'static ' : '';
1187+
final ifStatic = node.isStatic && !isTopLevel ? 'static ' : '';
11491188
final defArgs = node.params.accept(_ParamDef(resolver)).toList();
11501189
final typeClassDef = _encloseIfNotEmpty(
11511190
'{',

pkgs/jnigen/lib/src/bindings/kotlin_processor.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ class KotlinProcessor extends Visitor<Classes, void> {
2020
class _KotlinClassProcessor extends Visitor<ClassDecl, void> {
2121
@override
2222
void visit(ClassDecl node) {
23-
if (node.kotlinClass == null) {
23+
if (node.kotlinClass == null && node.kotlinPackage == null) {
2424
return;
2525
}
2626
// This [ClassDecl] is actually a Kotlin class.
2727
// Matching methods and functions from the metadata.
2828
final functions = <String, KotlinFunction>{};
29-
for (final function in node.kotlinClass!.functions) {
29+
final kotlinFunctions =
30+
(node.kotlinClass?.functions ?? node.kotlinPackage?.functions)!;
31+
for (final function in kotlinFunctions) {
3032
final signature = function.name + function.descriptor;
3133
functions[signature] = function;
3234
}

pkgs/jnigen/lib/src/bindings/renamer.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,13 @@ class _ClassRenamer implements Visitor<ClassDecl, void> {
156156
node.methodNumsAfterRenaming = {};
157157

158158
final className = _getSimplifiedClassName(node.binaryName);
159-
node.uniqueName = _renameConflict(classNameCounts, className);
160159

161160
// When generating all the classes in a single file
162161
// the names need to be unique.
163162
final uniquifyName =
164163
config.outputConfig.dartConfig.structure == OutputStructure.singleFile;
165-
node.finalName = uniquifyName ? node.uniqueName : className;
164+
node.finalName =
165+
uniquifyName ? _renameConflict(classNameCounts, className) : className;
166166
// TODO(#143): $ at the beginning is a temporary fix for the name collision.
167167
node.typeClassName = '\$${node.finalName}Type';
168168
log.fine('Class ${node.binaryName} is named ${node.finalName}');

pkgs/jnigen/lib/src/elements/elements.dart

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,15 @@ class ClassDecl extends ClassMember implements Element<ClassDecl> {
6969
this.hasInstanceInit = false,
7070
this.values,
7171
this.kotlinClass,
72+
this.kotlinPackage,
7273
});
7374

7475
@override
7576
final Set<String> modifiers;
7677

7778
final List<Annotation> annotations;
7879
final KotlinClass? kotlinClass;
80+
final KotlinPackage? kotlinPackage;
7981
final JavaDocComment? javadoc;
8082
final DeclKind declKind;
8183
final String binaryName;
@@ -121,15 +123,6 @@ class ClassDecl extends ClassMember implements Element<ClassDecl> {
121123
@JsonKey(includeFromJson: false)
122124
late final String typeClassName;
123125

124-
/// Unique name obtained by renaming conflicting names with a number.
125-
///
126-
/// This is used by C bindings instead of fully qualified name to reduce
127-
/// the verbosity of generated bindings.
128-
///
129-
/// Populated by [Renamer].
130-
@JsonKey(includeFromJson: false)
131-
late final String uniqueName;
132-
133126
/// Type parameters including the ones from its ancestors
134127
///
135128
/// Populated by [Linker].
@@ -178,6 +171,9 @@ class ClassDecl extends ClassMember implements Element<ClassDecl> {
178171

179172
@JsonKey(includeFromJson: false)
180173
late final isNested = parentName != null;
174+
175+
/// Whether the class is actually only a number of top-level Kotlin Functions.
176+
bool get isTopLevel => kotlinPackage != null;
181177
}
182178

183179
@JsonEnum()
@@ -653,6 +649,23 @@ class KotlinClass implements Element<KotlinClass> {
653649
}
654650
}
655651

652+
@JsonSerializable(createToJson: false)
653+
class KotlinPackage implements Element<KotlinPackage> {
654+
KotlinPackage({
655+
this.functions = const [],
656+
});
657+
658+
final List<KotlinFunction> functions;
659+
660+
factory KotlinPackage.fromJson(Map<String, dynamic> json) =>
661+
_$KotlinPackageFromJson(json);
662+
663+
@override
664+
R accept<R>(Visitor<KotlinPackage, R> v) {
665+
return v.visit(this);
666+
}
667+
}
668+
656669
@JsonSerializable(createToJson: false)
657670
class KotlinFunction implements Element<KotlinFunction> {
658671
KotlinFunction({

0 commit comments

Comments
 (0)