[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,