[jnigen] Parse Kotlin's metadata + Remove `suspend_fun_to_async` flag in config (https://github.com/dart-lang/jnigen/issues/308)
* Parse Kotlin's metadata in API Summarizer
* Remove `suspend_fun_to_async` from config now that `isSuspend` reliably detect `suspend fun`s.
diff --git a/pkgs/jnigen/CHANGELOG.md b/pkgs/jnigen/CHANGELOG.md
index 4b7ca8e..6ed1e84 100644
--- a/pkgs/jnigen/CHANGELOG.md
+++ b/pkgs/jnigen/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 0.6.0-dev.0
+* **Breaking Change** Removed `suspend_fun_to_async` flag from the config. It's now happening by default since we read the Kotlin's metadata and reliably identify the `suspend fun`s.
+
## 0.5.0
* **Breaking Change** ([#72](https://github.com/dart-lang/jnigen/issues/72)): Removed support for `importMap` in favor of the newly added interop mechanism with importing yaml files.
* **Breaking Change** ([#72](https://github.com/dart-lang/jnigen/issues/72)): `java.util.Set`, `java.util.Map`, `java.util.List`, `java.util.Iterator` and the boxed types like `java.lang.Integer`, `java.lang.Double`, ... will be generated as their corresponding classes in `package:jni`.
diff --git a/pkgs/jnigen/README.md b/pkgs/jnigen/README.md
index 56fba37..8d47afd 100644
--- a/pkgs/jnigen/README.md
+++ b/pkgs/jnigen/README.md
@@ -218,7 +218,6 @@
| `source_path` | List of directory paths | Directories to search for source files. Note: source_path for dependencies downloaded using `maven_downloads` configuration is added automatically without the need to specify here. |
| `class_path` | List of directory / JAR paths | Classpath for API summary generation. This should include any JAR dependencies of the source files in `source_path`. |
| `classes` * | List of qualified class / package names | List of qualified class / package names. `source_path` will be scanned assuming the sources follow standard java-ish hierarchy. That is a.b.c either maps to a directory `a/b/c` or a class file `a/b/c.java`. |
-| `suspend_fun_to_async` | True/False | Converting Kotlin's suspend functions to Dart's async functions. Defaults to False. |
| `output:` | (Subsection) | This subsection will contain configuration related to output files. |
| `output:` >> `bindings_type` | `c_based` (default) or `dart_only` | Binding generation strategy. [Trade-offs](#pure-dart-bindings) are explained at the end of this document. |
| `output:` >> `c:` | (Subsection) | This subsection specified C output configuration. Required if `bindings_type` is `c_based`. |
diff --git a/pkgs/jnigen/java/README.md b/pkgs/jnigen/java/README.md
index 50c8ab4..8b15049 100644
--- a/pkgs/jnigen/java/README.md
+++ b/pkgs/jnigen/java/README.md
@@ -1,4 +1,5 @@
## ApiSummarizer
+
An early version of ApiSummarizer.
It analyzes java source code / jars and outputs a JSON representation of the public API.
@@ -6,11 +7,13 @@
It's currently used in `jnigen` to get the information of the Java API.
## Build
+
When using it via `jnigen`, the `jnigen:setup` script will take care of building the jar in appropriate location.
To build the jar manually, run `mvn compile` in project root. To build the jar and run the tests as well, run `mvn test`. The jar will be created in `target/` directory.
## Command line
+
```
usage: java -jar <JAR> [-s <SOURCE_DIR=.>] [-c <CLASSES_JAR>]
<CLASS_OR_PACKAGE_NAMES>
@@ -27,13 +30,14 @@
-v,--verbose Enable verbose output
```
-Here class or package names are specified as fully qualified names, for example `org.apache.pdfbox.pdmodel.PDDocument` will load `org/apache/pdfbox/pdmodel/PDDocument.java`. It assumes the package naming reflects directory structure. If such mapping results in a directory, for example `android.os` is given and a directory `android/os` is found under the source path, it is considered as a package and all Java source files under that directory are loaded recursively.
+Here class or package names are specified as fully qualified names, for example `org.apache.pdfbox.pdmodel.PDDocument` will load `org/apache/pdfbox/pdmodel/PDDocument.java`. It assumes the package naming reflects directory structure. If such mapping results in a directory, for example `android.os` is given and a directory `android/os` is found under the source path, it is considered as a package and all Java source files under that directory are loaded recursively.
Note that some options are directly forwarded to the underlying tool.
ApiSummarizer's current use is in `jnigen` for obtaining public API of java packages. Only the features strictly required for that purpose are focused upon.
## Running tests
+
Run `mvn surefire:test`
There are not many tests at the moment. We plan to add some later.
@@ -43,4 +47,5 @@
The main backend is based on javadoc API and generates summary based on java sources. A more experimental ASM backend also exists, and works somewhat okay-ish. It can summarize the compiled JARs. However, compiled jars without debug information do not include method parameter names. Some basic renaming is applied, i.e If type is `Object`, the parameter name will be output as `object` if an actual name is absent.
## TODO
-See issue #23.
\ No newline at end of file
+
+See issue #23.
diff --git a/pkgs/jnigen/java/pom.xml b/pkgs/jnigen/java/pom.xml
index 89c5a9e..226bb03 100644
--- a/pkgs/jnigen/java/pom.xml
+++ b/pkgs/jnigen/java/pom.xml
@@ -34,6 +34,11 @@
<version>9.3</version>
</dependency>
<dependency>
+ <groupId>org.jetbrains.kotlinx</groupId>
+ <artifactId>kotlinx-metadata-jvm</artifactId>
+ <version>0.6.2</version>
+ </dependency>
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java
index 061f3b4..50bf2f0 100644
--- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java
@@ -91,6 +91,7 @@
return null;
}
method.name = name;
+ method.descriptor = descriptor;
var type = Type.getType(descriptor);
var params = new ArrayList<Param>();
var paramTypes = type.getArgumentTypes();
@@ -118,6 +119,14 @@
}
@Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ if (descriptor.equals("Lkotlin/Metadata;")) {
+ return new KotlinMetadataAnnotationVisitor(peekVisiting());
+ }
+ return super.visitAnnotation(descriptor, visible);
+ }
+
+ @Override
public void addAnnotation(JavaAnnotation annotation) {
peekVisiting().annotations.add(annotation);
}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/KotlinMetadataAnnotationVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/KotlinMetadataAnnotationVisitor.java
new file mode 100644
index 0000000..c4aa8a1
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/KotlinMetadataAnnotationVisitor.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.disasm;
+
+import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl;
+import com.github.dart_lang.jnigen.apisummarizer.elements.KotlinClass;
+import java.util.ArrayList;
+import java.util.List;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+import org.objectweb.asm.AnnotationVisitor;
+
+/**
+ * The format of Kotlin's metadata can be found here:
+ * https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-metadata/
+ */
+public class KotlinMetadataAnnotationVisitor extends AnnotationVisitor {
+ private ClassDecl decl;
+
+ private int kind;
+ private int[] metadataVersion;
+ private List<String> data1 = new ArrayList<>();
+ private List<String> data2 = new ArrayList<>();
+ private String extraString;
+ private String packageName;
+ private int extraInt;
+
+ public KotlinMetadataAnnotationVisitor(ClassDecl decl) {
+ super(AsmConstants.API);
+ this.decl = decl;
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ switch (name) {
+ case "k":
+ kind = (int) value;
+ return;
+ case "mv":
+ metadataVersion = (int[]) value;
+ return;
+ case "xs":
+ extraString = (String) value;
+ return;
+ case "pn":
+ packageName = (String) value;
+ return;
+ case "xi":
+ extraInt = (int) value;
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ List<String> arr;
+ switch (name) {
+ case "d1":
+ arr = data1;
+ break;
+ case "d2":
+ arr = data2;
+ break;
+ default:
+ return super.visitArray(name);
+ }
+ return new AnnotationVisitor(AsmConstants.API) {
+ @Override
+ public void visit(String name, Object value) {
+ arr.add((String) value);
+ super.visit(name, value);
+ }
+ };
+ }
+
+ @Override
+ public void visitEnd() {
+ var header =
+ new KotlinClassHeader(
+ kind,
+ metadataVersion,
+ data1.toArray(String[]::new),
+ data2.toArray(String[]::new),
+ extraString,
+ packageName,
+ extraInt);
+ var metadata = KotlinClassMetadata.read(header);
+ if (metadata instanceof KotlinClassMetadata.Class) {
+ decl.kotlinClass =
+ KotlinClass.fromKmClass(((KotlinClassMetadata.Class) metadata).toKmClass());
+ } else if (metadata instanceof KotlinClassMetadata.FileFacade) {
+ // TODO(#301): Handle file facades.
+ } else if (metadata instanceof KotlinClassMetadata.SyntheticClass) {
+ // Ignore synthetic classes such as lambdas.
+ } else if (metadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
+ // Ignore multi-file classes
+ } else if (metadata instanceof KotlinClassMetadata.MultiFileClassPart) {
+ // Ignore multi-file classes
+ } else if (metadata instanceof KotlinClassMetadata.Unknown) {
+ // Unsupported
+ }
+ super.visitEnd();
+ }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java
index e439ab9..231f0f5 100644
--- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java
@@ -37,6 +37,7 @@
public boolean hasInstanceInit;
public JavaDocComment javadoc;
public List<JavaAnnotation> annotations;
+ public KotlinClass kotlinClass;
/** In case of enum, names of enum constants */
public List<String> values = new ArrayList<>();
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinClass.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinClass.java
new file mode 100644
index 0000000..22906f7
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinClass.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmClass;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+
+public class KotlinClass {
+ public String name;
+ public String moduleName;
+ public List<KotlinFunction> functions;
+ public List<KotlinProperty> properties;
+ public List<KotlinConstructor> constructors;
+ public List<KotlinTypeParameter> typeParameters;
+ public List<KotlinType> contextReceiverTypes;
+ public List<KotlinType> superTypes;
+ public List<String> nestedClasses;
+ public List<String> enumEntries;
+ public List<String> sealedClasses;
+ public String companionObject;
+ public String inlineClassUnderlyingPropertyName;
+ public KotlinType inlineClassUnderlyingType;
+ public int flags;
+ public int jvmFlags;
+
+ public static KotlinClass fromKmClass(KmClass c) {
+ var klass = new KotlinClass();
+ klass.name = c.getName();
+ klass.moduleName = JvmExtensionsKt.getModuleName(c);
+ klass.functions =
+ c.getFunctions().stream().map(KotlinFunction::fromKmFunction).collect(Collectors.toList());
+ klass.properties =
+ c.getProperties().stream().map(KotlinProperty::fromKmProperty).collect(Collectors.toList());
+ klass.constructors =
+ c.getConstructors().stream()
+ .map(KotlinConstructor::fromKmConstructor)
+ .collect(Collectors.toList());
+ klass.typeParameters =
+ c.getTypeParameters().stream()
+ .map(KotlinTypeParameter::fromKmTypeParameter)
+ .collect(Collectors.toList());
+ klass.contextReceiverTypes =
+ c.getContextReceiverTypes().stream()
+ .map(KotlinType::fromKmType)
+ .collect(Collectors.toList());
+ klass.superTypes =
+ c.getSupertypes().stream().map(KotlinType::fromKmType).collect(Collectors.toList());
+ klass.enumEntries = c.getEnumEntries();
+ klass.flags = c.getFlags();
+ klass.jvmFlags = JvmExtensionsKt.getJvmFlags(c);
+ klass.nestedClasses = c.getNestedClasses();
+ klass.companionObject = c.getCompanionObject();
+ klass.inlineClassUnderlyingPropertyName = c.getInlineClassUnderlyingPropertyName();
+ klass.inlineClassUnderlyingType = KotlinType.fromKmType(c.getInlineClassUnderlyingType());
+ klass.sealedClasses = c.getSealedSubclasses();
+ return klass;
+ }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinConstructor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinConstructor.java
new file mode 100644
index 0000000..dae3a2f
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinConstructor.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmConstructor;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+
+public class KotlinConstructor {
+ public String name;
+ public String descriptor;
+ public List<KotlinValueParameter> valueParameters;
+ public int flags;
+
+ public static KotlinConstructor fromKmConstructor(KmConstructor c) {
+ var ctor = new KotlinConstructor();
+ ctor.flags = c.getFlags();
+ var signature = JvmExtensionsKt.getSignature(c);
+ ctor.name = signature == null ? null : signature.getName();
+ ctor.descriptor = signature == null ? null : signature.getDesc();
+ ctor.valueParameters =
+ c.getValueParameters().stream()
+ .map(KotlinValueParameter::fromKmValueParameter)
+ .collect(Collectors.toList());
+ return ctor;
+ }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinFunction.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinFunction.java
new file mode 100644
index 0000000..87cdc7b
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinFunction.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.Flag;
+import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+
+public class KotlinFunction {
+ /** Name in the byte code. */
+ public String name;
+
+ public String descriptor;
+
+ /** Name in the Kotlin's metadata. */
+ public String kotlinName;
+
+ public List<KotlinValueParameter> valueParameters;
+ public KotlinType returnType;
+ public KotlinType receiverParameterType;
+ public List<KotlinType> contextReceiverTypes;
+ public List<KotlinTypeParameter> typeParameters;
+ public int flags;
+ public boolean isSuspend;
+
+ public static KotlinFunction fromKmFunction(KmFunction f) {
+ var fun = new KotlinFunction();
+ var signature = JvmExtensionsKt.getSignature(f);
+ fun.descriptor = signature == null ? null : signature.getDesc();
+ fun.name = signature == null ? null : signature.getName();
+ fun.kotlinName = f.getName();
+ fun.flags = f.getFlags();
+ // Processing the information needed from the flags.
+ fun.isSuspend = Flag.Function.IS_SUSPEND.invoke(fun.flags);
+ fun.valueParameters =
+ f.getValueParameters().stream()
+ .map(KotlinValueParameter::fromKmValueParameter)
+ .collect(Collectors.toList());
+ fun.returnType = KotlinType.fromKmType(f.getReturnType());
+ fun.receiverParameterType = KotlinType.fromKmType(f.getReceiverParameterType());
+ fun.contextReceiverTypes =
+ f.getContextReceiverTypes().stream()
+ .map(KotlinType::fromKmType)
+ .collect(Collectors.toList());
+ fun.typeParameters =
+ f.getTypeParameters().stream()
+ .map(KotlinTypeParameter::fromKmTypeParameter)
+ .collect(Collectors.toList());
+ return fun;
+ }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinProperty.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinProperty.java
new file mode 100644
index 0000000..2858be4
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinProperty.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+
+public class KotlinProperty {
+ public String fieldName;
+ public String fieldDescriptor;
+
+ /** Getter's name in the byte code. */
+ public String getterName;
+
+ public String getterDescriptor;
+
+ /** Setter's name in the byte code. */
+ public String setterName;
+
+ public String setterDescriptor;
+
+ /** Name in the Kotlin's metadata. */
+ public String kotlinName;
+
+ public KotlinType returnType;
+ public KotlinType receiverParameterType;
+ public List<KotlinType> contextReceiverTypes;
+ public int jvmFlags;
+ public int flags;
+ public int setterFlags;
+ public int getterFlags;
+ public List<KotlinTypeParameter> typeParameters;
+ public KotlinValueParameter setterParameter;
+
+ public static KotlinProperty fromKmProperty(KmProperty p) {
+ var prop = new KotlinProperty();
+ var fieldSignature = JvmExtensionsKt.getFieldSignature(p);
+ prop.fieldDescriptor = fieldSignature == null ? null : fieldSignature.getDesc();
+ prop.fieldName = fieldSignature == null ? null : fieldSignature.getName();
+ var getterSignature = JvmExtensionsKt.getGetterSignature(p);
+ prop.getterDescriptor = getterSignature == null ? null : getterSignature.getDesc();
+ prop.getterName = getterSignature == null ? null : getterSignature.getName();
+ var setterSignature = JvmExtensionsKt.getSetterSignature(p);
+ prop.setterDescriptor = setterSignature == null ? null : setterSignature.getDesc();
+ prop.setterName = setterSignature == null ? null : setterSignature.getName();
+ prop.kotlinName = p.getName();
+ prop.returnType = KotlinType.fromKmType(p.getReturnType());
+ prop.receiverParameterType = KotlinType.fromKmType(p.getReceiverParameterType());
+ prop.contextReceiverTypes =
+ p.getContextReceiverTypes().stream()
+ .map(KotlinType::fromKmType)
+ .collect(Collectors.toList());
+ prop.jvmFlags = JvmExtensionsKt.getJvmFlags(p);
+ prop.flags = p.getFlags();
+ prop.setterFlags = p.getSetterFlags();
+ prop.getterFlags = p.getGetterFlags();
+ prop.typeParameters =
+ p.getTypeParameters().stream()
+ .map(KotlinTypeParameter::fromKmTypeParameter)
+ .collect(Collectors.toList());
+ prop.setterParameter = KotlinValueParameter.fromKmValueParameter(p.getSetterParameter());
+ return prop;
+ }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinType.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinType.java
new file mode 100644
index 0000000..8e91122
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinType.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmClassifier;
+import kotlinx.metadata.KmType;
+
+public class KotlinType {
+ public int flags;
+ public String kind;
+ public String name;
+ public int id;
+ public List<KotlinTypeProjection> arguments;
+
+ public static KotlinType fromKmType(KmType t) {
+ if (t == null) return null;
+ var type = new KotlinType();
+ type.flags = t.getFlags();
+ var classifier = t.getClassifier();
+ if (classifier instanceof KmClassifier.Class) {
+ type.kind = "class";
+ type.name = ((KmClassifier.Class) classifier).getName();
+ } else if (classifier instanceof KmClassifier.TypeAlias) {
+ type.kind = "typeAlias";
+ type.name = ((KmClassifier.TypeAlias) classifier).getName();
+ } else if (classifier instanceof KmClassifier.TypeParameter) {
+ type.kind = "typeParameter";
+ type.id = ((KmClassifier.TypeParameter) classifier).getId();
+ }
+ type.arguments =
+ t.getArguments().stream()
+ .map(KotlinTypeProjection::fromKmTypeProjection)
+ .collect(Collectors.toList());
+ return type;
+ }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeParameter.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeParameter.java
new file mode 100644
index 0000000..449aef1
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeParameter.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmTypeParameter;
+import kotlinx.metadata.KmVariance;
+
+public class KotlinTypeParameter {
+ public String name;
+ public int id;
+ public int flags;
+ public List<KotlinType> upperBounds;
+ public KmVariance variance;
+
+ public static KotlinTypeParameter fromKmTypeParameter(KmTypeParameter t) {
+ var typeParam = new KotlinTypeParameter();
+ typeParam.name = t.getName();
+ typeParam.id = t.getId();
+ typeParam.flags = t.getFlags();
+ typeParam.upperBounds =
+ t.getUpperBounds().stream().map(KotlinType::fromKmType).collect(Collectors.toList());
+ typeParam.variance = t.getVariance();
+ return typeParam;
+ }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeProjection.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeProjection.java
new file mode 100644
index 0000000..364634c
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeProjection.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmVariance;
+
+public class KotlinTypeProjection {
+ public KotlinType type;
+ public KmVariance variance;
+
+ public static KotlinTypeProjection fromKmTypeProjection(KmTypeProjection t) {
+ var proj = new KotlinTypeProjection();
+ proj.type = KotlinType.fromKmType(t.getType());
+ proj.variance = t.getVariance();
+ return proj;
+ }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinValueParameter.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinValueParameter.java
new file mode 100644
index 0000000..1fba28d
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinValueParameter.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import kotlinx.metadata.KmValueParameter;
+
+public class KotlinValueParameter {
+ public String name;
+ public int flags;
+ public KotlinType type;
+ public KotlinType varargElementType;
+
+ public static KotlinValueParameter fromKmValueParameter(KmValueParameter p) {
+ if (p == null) return null;
+ var param = new KotlinValueParameter();
+ param.name = p.getName();
+ param.flags = p.getFlags();
+ param.type = KotlinType.fromKmType(p.getType());
+ param.varargElementType = KotlinType.fromKmType(p.getVarargElementType());
+ return param;
+ }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Method.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Method.java
index 0711c10..33bb0fb 100644
--- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Method.java
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Method.java
@@ -12,6 +12,7 @@
public class Method {
public Set<String> modifiers = new HashSet<>();
public String name;
+ public String descriptor;
public List<TypeParam> typeParams = new ArrayList<>();
public List<Param> params = new ArrayList<>();
public TypeUsage returnType;
diff --git a/pkgs/jnigen/lib/src/bindings/kotlin_processor.dart b/pkgs/jnigen/lib/src/bindings/kotlin_processor.dart
new file mode 100644
index 0000000..4a48aa8
--- /dev/null
+++ b/pkgs/jnigen/lib/src/bindings/kotlin_processor.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../elements/elements.dart';
+import 'visitor.dart';
+
+/// A [Visitor] that adds the the information from Kotlin's metadata to the Java
+/// classes and methods.
+class KotlinProcessor extends Visitor<Classes, void> {
+ @override
+ void visit(Classes node) {
+ final classProcessor = _KotlinClassProcessor();
+ for (final classDecl in node.decls.values) {
+ classDecl.accept(classProcessor);
+ }
+ }
+}
+
+class _KotlinClassProcessor extends Visitor<ClassDecl, void> {
+ @override
+ void visit(ClassDecl node) {
+ if (node.kotlinClass == 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 signature = function.name + function.descriptor;
+ functions[signature] = function;
+ }
+ for (final method in node.methods) {
+ final signature = method.name + method.descriptor!;
+ if (functions.containsKey(signature)) {
+ method.accept(_KotlinMethodProcessor(functions[signature]!));
+ }
+ }
+ }
+}
+
+class _KotlinMethodProcessor extends Visitor<Method, void> {
+ final KotlinFunction function;
+
+ _KotlinMethodProcessor(this.function);
+
+ @override
+ void visit(Method node) {
+ if (function.isSuspend) {
+ const kotlinContinutationType = 'kotlin.coroutines.Continuation';
+ assert(node.params.isNotEmpty &&
+ node.params.last.type.kind == Kind.declared &&
+ node.params.last.type.name == kotlinContinutationType);
+ final continuationType = node.params.last.type.type as DeclaredType;
+ node.asyncReturnType = continuationType.params.isEmpty
+ ? TypeUsage.object
+ : continuationType.params.first;
+ }
+ }
+}
diff --git a/pkgs/jnigen/lib/src/bindings/linker.dart b/pkgs/jnigen/lib/src/bindings/linker.dart
index d6b44a5..8e90910 100644
--- a/pkgs/jnigen/lib/src/bindings/linker.dart
+++ b/pkgs/jnigen/lib/src/bindings/linker.dart
@@ -130,19 +130,6 @@
final paramLinker = _ParamLinker(typeVisitor);
node.typeParams.accept(typeParamLinker).toList();
node.params.accept(paramLinker).toList();
- // Kotlin specific
- const kotlinContinutationType = 'kotlin.coroutines.Continuation';
- if (config.suspendFunToAsync &&
- node.params.isNotEmpty &&
- node.params.last.type.kind == Kind.declared &&
- node.params.last.type.name == kotlinContinutationType) {
- final continuationType = node.params.last.type.type as DeclaredType;
- node.asyncReturnType = continuationType.params.isEmpty
- ? TypeUsage.object
- : continuationType.params.first;
- } else {
- node.asyncReturnType = null;
- }
node.asyncReturnType?.accept(typeVisitor);
}
}
diff --git a/pkgs/jnigen/lib/src/config/config_types.dart b/pkgs/jnigen/lib/src/config/config_types.dart
index 58fc9a5..f737b81 100644
--- a/pkgs/jnigen/lib/src/config/config_types.dart
+++ b/pkgs/jnigen/lib/src/config/config_types.dart
@@ -274,7 +274,6 @@
required this.outputConfig,
required this.classes,
this.exclude,
- this.suspendFunToAsync = false,
this.sourcePath,
this.classPath,
this.preamble,
@@ -317,12 +316,6 @@
/// Common text to be pasted on top of generated C and Dart files.
final String? preamble;
- /// Whether or not to change Kotlin's suspend functions to Dart async ones.
- ///
- /// This will remove the final Continuation argument.
- /// Defaults to [false].
- final bool suspendFunToAsync;
-
/// Configuration to search for Android SDK libraries (Experimental).
final AndroidSdkConfig? androidSdkConfig;
@@ -522,7 +515,6 @@
methods: regexFilter<Method>(_Props.excludeMethods),
fields: regexFilter<Field>(_Props.excludeFields),
),
- suspendFunToAsync: prov.getBool(_Props.suspendFunToAsync) ?? false,
outputConfig: OutputConfig(
bindingsType: getBindingsType(
prov.getString(_Props.bindingsType),
@@ -613,8 +605,6 @@
static const excludeMethods = '$exclude.methods';
static const excludeFields = '$exclude.fields';
- static const suspendFunToAsync = 'suspend_fun_to_async';
-
static const import = 'import';
static const outputConfig = 'output';
static const bindingsType = '$outputConfig.bindings_type';
diff --git a/pkgs/jnigen/lib/src/elements/elements.dart b/pkgs/jnigen/lib/src/elements/elements.dart
index 51c19b9..ceaee01 100644
--- a/pkgs/jnigen/lib/src/elements/elements.dart
+++ b/pkgs/jnigen/lib/src/elements/elements.dart
@@ -68,12 +68,14 @@
this.hasStaticInit = false,
this.hasInstanceInit = false,
this.values,
+ this.kotlinClass,
});
@override
final Set<String> modifiers;
final List<Annotation> annotations;
+ final KotlinClass? kotlinClass;
final JavaDocComment? javadoc;
final String binaryName;
final String? parentName;
@@ -444,6 +446,7 @@
this.javadoc,
this.modifiers = const {},
required this.name,
+ this.descriptor,
this.typeParams = const [],
this.params = const [],
required this.returnType,
@@ -460,6 +463,11 @@
List<Param> params;
final TypeUsage returnType;
+ /// Can be used to match with [KotlinFunction]'s descriptor.
+ ///
+ /// Can create a unique signature in combination with [name].
+ final String? descriptor;
+
/// The [ClassDecl] where this method is defined.
///
/// Populated by [Linker].
@@ -474,12 +482,11 @@
@JsonKey(includeFromJson: false)
late bool isOverridden;
- /// This gets populated in the preprocessing stage.
+ /// The actual return type when the method is a Kotlin's suspend fun.
///
- /// It will contain a type only when the suspendFunToAsync flag is on
- /// and the method has a `kotlin.coroutines.Continuation` final argument.
+ /// Populated by [KotlinProcessor].
@JsonKey(includeFromJson: false)
- late TypeUsage? asyncReturnType;
+ TypeUsage? asyncReturnType;
@JsonKey(includeFromJson: false)
late String javaSig = _javaSig();
@@ -620,3 +627,49 @@
return v.visit(this);
}
}
+
+@JsonSerializable(createToJson: false)
+class KotlinClass implements Element<KotlinClass> {
+ KotlinClass({
+ required this.name,
+ this.functions = const [],
+ });
+
+ final String name;
+ final List<KotlinFunction> functions;
+
+ factory KotlinClass.fromJson(Map<String, dynamic> json) =>
+ _$KotlinClassFromJson(json);
+
+ @override
+ R accept<R>(Visitor<KotlinClass, R> v) {
+ return v.visit(this);
+ }
+}
+
+@JsonSerializable(createToJson: false)
+class KotlinFunction implements Element<KotlinFunction> {
+ KotlinFunction({
+ required this.name,
+ required this.descriptor,
+ required this.kotlinName,
+ required this.isSuspend,
+ });
+
+ final String name;
+
+ /// Used to match with [Method]'s descriptor.
+ ///
+ /// Creates a unique signature in combination with [name].
+ final String descriptor;
+ final String kotlinName;
+ final bool isSuspend;
+
+ factory KotlinFunction.fromJson(Map<String, dynamic> json) =>
+ _$KotlinFunctionFromJson(json);
+
+ @override
+ R accept<R>(Visitor<KotlinFunction, R> v) {
+ return v.visit(this);
+ }
+}
diff --git a/pkgs/jnigen/lib/src/elements/elements.g.dart b/pkgs/jnigen/lib/src/elements/elements.g.dart
index 20ce98c..4531342 100644
--- a/pkgs/jnigen/lib/src/elements/elements.g.dart
+++ b/pkgs/jnigen/lib/src/elements/elements.g.dart
@@ -43,6 +43,9 @@
hasInstanceInit: json['hasInstanceInit'] as bool? ?? false,
values:
(json['values'] as List<dynamic>?)?.map((e) => e as String).toList(),
+ kotlinClass: json['kotlinClass'] == null
+ ? null
+ : KotlinClass.fromJson(json['kotlinClass'] as Map<String, dynamic>),
);
TypeUsage _$TypeUsageFromJson(Map<String, dynamic> json) => TypeUsage(
@@ -97,6 +100,7 @@
.toSet() ??
const {},
name: json['name'] as String,
+ descriptor: json['descriptor'] as String?,
typeParams: (json['typeParams'] as List<dynamic>?)
?.map((e) => TypeParam.fromJson(e as Map<String, dynamic>))
.toList() ??
@@ -158,3 +162,19 @@
) ??
const {},
);
+
+KotlinClass _$KotlinClassFromJson(Map<String, dynamic> json) => KotlinClass(
+ name: json['name'] as String,
+ functions: (json['functions'] as List<dynamic>?)
+ ?.map((e) => KotlinFunction.fromJson(e as Map<String, dynamic>))
+ .toList() ??
+ const [],
+ );
+
+KotlinFunction _$KotlinFunctionFromJson(Map<String, dynamic> json) =>
+ KotlinFunction(
+ name: json['name'] as String,
+ descriptor: json['descriptor'] as String,
+ kotlinName: json['kotlinName'] as String,
+ isSuspend: json['isSuspend'] as bool,
+ );
diff --git a/pkgs/jnigen/lib/src/generate_bindings.dart b/pkgs/jnigen/lib/src/generate_bindings.dart
index 87727dc..174828e 100644
--- a/pkgs/jnigen/lib/src/generate_bindings.dart
+++ b/pkgs/jnigen/lib/src/generate_bindings.dart
@@ -8,6 +8,7 @@
import 'bindings/c_generator.dart';
import 'bindings/dart_generator.dart';
import 'bindings/excluder.dart';
+import 'bindings/kotlin_processor.dart';
import 'bindings/linker.dart';
import 'bindings/unnester.dart';
import 'bindings/renamer.dart';
@@ -36,6 +37,7 @@
}
classes.accept(Excluder(config));
+ classes.accept(KotlinProcessor());
await classes.accept(Linker(config));
classes.accept(Unnester());
classes.accept(Renamer(config));
diff --git a/pkgs/jnigen/pubspec.yaml b/pkgs/jnigen/pubspec.yaml
index 65b523b..75be98a 100644
--- a/pkgs/jnigen/pubspec.yaml
+++ b/pkgs/jnigen/pubspec.yaml
@@ -3,7 +3,7 @@
# BSD-style license that can be found in the LICENSE file.
name: jnigen
-version: 0.5.0
+version: 0.6.0-dev.0
description: Experimental generator for FFI+JNI bindings.
repository: https://github.com/dart-lang/jnigen/tree/main/jnigen
diff --git a/pkgs/jnigen/test/kotlin_test/generate.dart b/pkgs/jnigen/test/kotlin_test/generate.dart
index 1b9b111..11b89ea 100644
--- a/pkgs/jnigen/test/kotlin_test/generate.dart
+++ b/pkgs/jnigen/test/kotlin_test/generate.dart
@@ -51,7 +51,6 @@
// way to the generated code.
'com.github.dart_lang.jnigen',
],
- suspendFunToAsync: true,
logLevel: Level.ALL,
outputConfig: OutputConfig(
bindingsType: bindingsType,