diff --git a/README.md b/README.md index 56fba378..8d47afd5 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,6 @@ A `*` denotes required configuration. | `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/jnigen/CHANGELOG.md b/jnigen/CHANGELOG.md index 4b7ca8e1..6ed1e844 100644 --- a/jnigen/CHANGELOG.md +++ b/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/jnigen/java/README.md b/jnigen/java/README.md index 50c8ab48..8b15049a 100644 --- a/jnigen/java/README.md +++ b/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 analyzes java source code / jars and outputs a JSON representation of the pub 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 [-s ] [-c ] @@ -27,13 +30,14 @@ or 'asm'). -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 @@ There are not many tests at the moment. We plan to add some later. 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/jnigen/java/pom.xml b/jnigen/java/pom.xml index 89c5a9ed..226bb03f 100644 --- a/jnigen/java/pom.xml +++ b/jnigen/java/pom.xml @@ -33,6 +33,11 @@ asm-tree 9.3 + + org.jetbrains.kotlinx + kotlinx-metadata-jvm + 0.6.2 + junit junit diff --git a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java index 061f3b49..50bf2f0d 100644 --- a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java +++ b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java @@ -91,6 +91,7 @@ public MethodVisitor visitMethod( return null; } method.name = name; + method.descriptor = descriptor; var type = Type.getType(descriptor); var params = new ArrayList(); var paramTypes = type.getArgumentTypes(); @@ -117,6 +118,14 @@ public MethodVisitor visitMethod( return new AsmMethodVisitor(method); } + @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/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/KotlinMetadataAnnotationVisitor.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/KotlinMetadataAnnotationVisitor.java new file mode 100644 index 00000000..c4aa8a13 --- /dev/null +++ b/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 data1 = new ArrayList<>(); + private List 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 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/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java index e439ab9f..231f0f5b 100644 --- a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java +++ b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java @@ -37,6 +37,7 @@ public class ClassDecl { public boolean hasInstanceInit; public JavaDocComment javadoc; public List annotations; + public KotlinClass kotlinClass; /** In case of enum, names of enum constants */ public List values = new ArrayList<>(); diff --git a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinClass.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinClass.java new file mode 100644 index 00000000..22906f70 --- /dev/null +++ b/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 functions; + public List properties; + public List constructors; + public List typeParameters; + public List contextReceiverTypes; + public List superTypes; + public List nestedClasses; + public List enumEntries; + public List 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/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinConstructor.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinConstructor.java new file mode 100644 index 00000000..dae3a2f6 --- /dev/null +++ b/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 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/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinFunction.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinFunction.java new file mode 100644 index 00000000..87cdc7be --- /dev/null +++ b/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 valueParameters; + public KotlinType returnType; + public KotlinType receiverParameterType; + public List contextReceiverTypes; + public List 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/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinProperty.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinProperty.java new file mode 100644 index 00000000..2858be4b --- /dev/null +++ b/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 contextReceiverTypes; + public int jvmFlags; + public int flags; + public int setterFlags; + public int getterFlags; + public List 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/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinType.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinType.java new file mode 100644 index 00000000..8e91122e --- /dev/null +++ b/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 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/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeParameter.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeParameter.java new file mode 100644 index 00000000..449aef16 --- /dev/null +++ b/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 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/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeProjection.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeProjection.java new file mode 100644 index 00000000..364634c2 --- /dev/null +++ b/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/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinValueParameter.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinValueParameter.java new file mode 100644 index 00000000..1fba28d6 --- /dev/null +++ b/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/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Method.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Method.java index 0711c10c..33bb0fb8 100644 --- a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Method.java +++ b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Method.java @@ -12,6 +12,7 @@ public class Method { public Set modifiers = new HashSet<>(); public String name; + public String descriptor; public List typeParams = new ArrayList<>(); public List params = new ArrayList<>(); public TypeUsage returnType; diff --git a/jnigen/lib/src/bindings/kotlin_processor.dart b/jnigen/lib/src/bindings/kotlin_processor.dart new file mode 100644 index 00000000..4a48aa8c --- /dev/null +++ b/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 { + @override + void visit(Classes node) { + final classProcessor = _KotlinClassProcessor(); + for (final classDecl in node.decls.values) { + classDecl.accept(classProcessor); + } + } +} + +class _KotlinClassProcessor extends Visitor { + @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 = {}; + 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 { + 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/jnigen/lib/src/bindings/linker.dart b/jnigen/lib/src/bindings/linker.dart index d6b44a5f..8e909102 100644 --- a/jnigen/lib/src/bindings/linker.dart +++ b/jnigen/lib/src/bindings/linker.dart @@ -130,19 +130,6 @@ class _MethodLinker extends Visitor { 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/jnigen/lib/src/config/config_types.dart b/jnigen/lib/src/config/config_types.dart index 58fc9a51..f737b811 100644 --- a/jnigen/lib/src/config/config_types.dart +++ b/jnigen/lib/src/config/config_types.dart @@ -274,7 +274,6 @@ class Config { required this.outputConfig, required this.classes, this.exclude, - this.suspendFunToAsync = false, this.sourcePath, this.classPath, this.preamble, @@ -317,12 +316,6 @@ class Config { /// 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 @@ class Config { methods: regexFilter(_Props.excludeMethods), fields: regexFilter(_Props.excludeFields), ), - suspendFunToAsync: prov.getBool(_Props.suspendFunToAsync) ?? false, outputConfig: OutputConfig( bindingsType: getBindingsType( prov.getString(_Props.bindingsType), @@ -613,8 +605,6 @@ class _Props { 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/jnigen/lib/src/elements/elements.dart b/jnigen/lib/src/elements/elements.dart index 51c19b95..ceaee017 100644 --- a/jnigen/lib/src/elements/elements.dart +++ b/jnigen/lib/src/elements/elements.dart @@ -68,12 +68,14 @@ class ClassDecl extends ClassMember implements Element { this.hasStaticInit = false, this.hasInstanceInit = false, this.values, + this.kotlinClass, }); @override final Set modifiers; final List annotations; + final KotlinClass? kotlinClass; final JavaDocComment? javadoc; final String binaryName; final String? parentName; @@ -444,6 +446,7 @@ class Method extends ClassMember implements Element { this.javadoc, this.modifiers = const {}, required this.name, + this.descriptor, this.typeParams = const [], this.params = const [], required this.returnType, @@ -460,6 +463,11 @@ class Method extends ClassMember implements Element { List 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 @@ class Method extends ClassMember implements Element { @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 @@ class Annotation implements Element { return v.visit(this); } } + +@JsonSerializable(createToJson: false) +class KotlinClass implements Element { + KotlinClass({ + required this.name, + this.functions = const [], + }); + + final String name; + final List functions; + + factory KotlinClass.fromJson(Map json) => + _$KotlinClassFromJson(json); + + @override + R accept(Visitor v) { + return v.visit(this); + } +} + +@JsonSerializable(createToJson: false) +class KotlinFunction implements Element { + 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 json) => + _$KotlinFunctionFromJson(json); + + @override + R accept(Visitor v) { + return v.visit(this); + } +} diff --git a/jnigen/lib/src/elements/elements.g.dart b/jnigen/lib/src/elements/elements.g.dart index 20ce98c9..4531342e 100644 --- a/jnigen/lib/src/elements/elements.g.dart +++ b/jnigen/lib/src/elements/elements.g.dart @@ -43,6 +43,9 @@ ClassDecl _$ClassDeclFromJson(Map json) => ClassDecl( hasInstanceInit: json['hasInstanceInit'] as bool? ?? false, values: (json['values'] as List?)?.map((e) => e as String).toList(), + kotlinClass: json['kotlinClass'] == null + ? null + : KotlinClass.fromJson(json['kotlinClass'] as Map), ); TypeUsage _$TypeUsageFromJson(Map json) => TypeUsage( @@ -97,6 +100,7 @@ Method _$MethodFromJson(Map json) => Method( .toSet() ?? const {}, name: json['name'] as String, + descriptor: json['descriptor'] as String?, typeParams: (json['typeParams'] as List?) ?.map((e) => TypeParam.fromJson(e as Map)) .toList() ?? @@ -158,3 +162,19 @@ Annotation _$AnnotationFromJson(Map json) => Annotation( ) ?? const {}, ); + +KotlinClass _$KotlinClassFromJson(Map json) => KotlinClass( + name: json['name'] as String, + functions: (json['functions'] as List?) + ?.map((e) => KotlinFunction.fromJson(e as Map)) + .toList() ?? + const [], + ); + +KotlinFunction _$KotlinFunctionFromJson(Map 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/jnigen/lib/src/generate_bindings.dart b/jnigen/lib/src/generate_bindings.dart index 87727dce..174828eb 100644 --- a/jnigen/lib/src/generate_bindings.dart +++ b/jnigen/lib/src/generate_bindings.dart @@ -8,6 +8,7 @@ import 'dart:convert'; 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 @@ Future generateJniBindings(Config config) async { } classes.accept(Excluder(config)); + classes.accept(KotlinProcessor()); await classes.accept(Linker(config)); classes.accept(Unnester()); classes.accept(Renamer(config)); diff --git a/jnigen/pubspec.yaml b/jnigen/pubspec.yaml index 65b523b7..75be98a7 100644 --- a/jnigen/pubspec.yaml +++ b/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/jnigen/test/kotlin_test/generate.dart b/jnigen/test/kotlin_test/generate.dart index 1b9b111b..11b89ea5 100644 --- a/jnigen/test/kotlin_test/generate.dart +++ b/jnigen/test/kotlin_test/generate.dart @@ -51,7 +51,6 @@ Config getConfig([BindingsType bindingsType = BindingsType.cBased]) { // way to the generated code. 'com.github.dart_lang.jnigen', ], - suspendFunToAsync: true, logLevel: Level.ALL, outputConfig: OutputConfig( bindingsType: bindingsType,