diff --git a/pkgs/jnigen/CHANGELOG.md b/pkgs/jnigen/CHANGELOG.md index a52874a94f..c345353ee0 100644 --- a/pkgs/jnigen/CHANGELOG.md +++ b/pkgs/jnigen/CHANGELOG.md @@ -3,6 +3,8 @@ - Add docs about debugging. - Add support for Kotlin interfaces with suspend functions. These can now be implemented using Dart functions that return a `Future`. +- Fixed a bug where Kotlin's `DefaultConstructorMarker` was exposed in the + generated bindings. - Namespace primitive types to avoid collisions with generated API names, eg `bool`. - Kotlin suspend functions with no result (a return type of `Unit`) now return diff --git a/pkgs/jnigen/lib/src/bindings/dart_generator.dart b/pkgs/jnigen/lib/src/bindings/dart_generator.dart index dba4346099..f47e0697ad 100644 --- a/pkgs/jnigen/lib/src/bindings/dart_generator.dart +++ b/pkgs/jnigen/lib/src/bindings/dart_generator.dart @@ -864,6 +864,10 @@ class _TypeGenerator extends TypeVisitor { }, ); + if (!node.classDecl.isRenamed) { + // The class is not generated, fall back to `JObject`. + return super.visitDeclaredType(node); + } final typeParams = allTypeParams.join(', ').encloseIfNotEmpty('<', '>'); final prefix = resolver?.resolvePrefix(node.classDecl) ?? ''; return '$prefix${node.classDecl.finalName}$typeParams$nullable'; @@ -1393,7 +1397,9 @@ ${modifier}final _$name = $_protectedExtension } else { s.writeAll(node.modifiers.map((m) => '$m ')); s.write('${node.returnType} ${node.name}('); - s.writeAll(node.params.map((p) => '${p.type} ${p.name}'), ', '); + // Filter out Kotlin synthetic params from documentation. + final docParams = node.params.where((p) => !p.isKotlinSynthetic); + s.writeAll(docParams.map((p) => '${p.type} ${p.name}'), ', '); s.writeln(')`'); } if (node.returnType is! PrimitiveType || node.isConstructor) { @@ -1402,7 +1408,9 @@ ${modifier}final _$name = $_protectedExtension node.javadoc?.accept(_DocGenerator(s, depth: 1)); // Used for inferring the type parameter from the given parameters. + // Exclude Kotlin synthetic params since they're not part of the Dart API. final typeLocators = node.params + .where((p) => !p.isKotlinSynthetic) .accept(_ParamTypeLocator(resolver: resolver)) .fold(>{}, _mergeMapValues).map( (key, value) => @@ -1424,7 +1432,10 @@ ${modifier}final _$name = $_protectedExtension .join(_newLine(depth: 2)); // This is needed to keep the references alive in the scope while waiting // for the FFI call. + // Filter out Kotlin synthetic params from local references since they're + // not part of the Dart API. final localReferences = node.params + .where((p) => !p.isKotlinSynthetic) .accept(const _ParamReference()) .where((ref) => ref.isNotEmpty) .toList(); @@ -1432,7 +1443,11 @@ ${modifier}final _$name = $_protectedExtension final className = node.classDecl.finalName; final name = node.finalName; final ctorName = name == 'new\$' ? className : '$className.$name'; - final paramsDef = node.params.accept(_ParamDef(resolver)).delimited(', '); + // Filter out Kotlin synthetic params from the Dart API signature. + final dartApiParams = + node.params.where((p) => !p.isKotlinSynthetic).toList(); + final paramsDef = + dartApiParams.accept(_ParamDef(resolver)).delimited(', '); final typeParamsCall = node.classDecl.allTypeParams .map((typeParam) => '$_typeParamPrefix${typeParam.name}') .join(', ') @@ -1705,6 +1720,12 @@ class _ParamCall extends Visitor { @override String visit(Param node) { + // Kotlin synthetic parameters (e.g., DefaultConstructorMarker) should + // always receive jNullReference in JNI calls. + if (node.isKotlinSynthetic) { + return '$_jni.jNullReference.pointer'; + } + final nativeSuffix = node.type.accept(const _ToNativeSuffix()); final nonPrimitive = node.type is PrimitiveType ? '' : r'_$'; final paramCall = '$nonPrimitive${node.finalName}$nativeSuffix'; diff --git a/pkgs/jnigen/lib/src/bindings/excluder.dart b/pkgs/jnigen/lib/src/bindings/excluder.dart index fb4624a635..a0a23a9a9f 100644 --- a/pkgs/jnigen/lib/src/bindings/excluder.dart +++ b/pkgs/jnigen/lib/src/bindings/excluder.dart @@ -67,8 +67,19 @@ class _ClassExcluder extends Visitor { final isPrivate = method.isPrivate; final isAbstractCtor = method.isConstructor && node.isAbstract; final isBridgeMethod = method.isSynthetic && method.isBridge; - final excluded = - isPrivate || isAbstractCtor || isBridgeMethod || isExcluded; + + // Exclude synthetic Kotlin constructors with DefaultConstructorMarker. + // These are compiler-generated overloads for default parameters and + // should not be exposed in the Dart API. + final isSyntheticDefaultCtorMarker = method.isConstructor && + method.isSynthetic && + method.params.any((param) => param.isKotlinSynthetic); + + final excluded = isPrivate || + isAbstractCtor || + isBridgeMethod || + isSyntheticDefaultCtorMarker || + isExcluded; if (excluded) { log.fine('Excluded method ${node.binaryName}#${method.name}'); } diff --git a/pkgs/jnigen/lib/src/bindings/kotlin_processor.dart b/pkgs/jnigen/lib/src/bindings/kotlin_processor.dart index 0b315de7ef..1829e59823 100644 --- a/pkgs/jnigen/lib/src/bindings/kotlin_processor.dart +++ b/pkgs/jnigen/lib/src/bindings/kotlin_processor.dart @@ -116,6 +116,17 @@ class _KotlinClassProcessor extends Visitor { method.accept(_KotlinGetterProcessor(getter)); } else if (setters[signature] case final setter?) { method.accept(_KotlinSetterProcessor(setter)); + } else { + if (method.isConstructor && method.isSynthetic) { + for (final param in method.params) { + if (param.type case final DeclaredType type) { + if (type.binaryName == + 'kotlin.jvm.internal.DefaultConstructorMarker') { + param.isKotlinSynthetic = true; + } + } + } + } } } } @@ -179,6 +190,17 @@ class _KotlinConstructorProcessor extends Visitor { @override void visit(Method node) { _processParams(node.params, constructor.valueParameters); + + // Mark DefaultConstructorMarker parameters as Kotlin synthetic. + // These are compiler-generated parameters for constructors with default + // values and should not be exposed in the Dart API. + for (final param in node.params) { + if (param.type case final DeclaredType type) { + if (type.binaryName == 'kotlin.jvm.internal.DefaultConstructorMarker') { + param.isKotlinSynthetic = true; + } + } + } } } diff --git a/pkgs/jnigen/lib/src/bindings/renamer.dart b/pkgs/jnigen/lib/src/bindings/renamer.dart index c1a8d854c9..444c4385cf 100644 --- a/pkgs/jnigen/lib/src/bindings/renamer.dart +++ b/pkgs/jnigen/lib/src/bindings/renamer.dart @@ -165,11 +165,16 @@ class _ClassRenamer implements Visitor { _ClassRenamer( this.config, - ) : renamed = {...config.importedClasses.values}; + ) : renamed = {...config.importedClasses.values} { + for (final node in renamed) { + node.isRenamed = true; + } + } @override void visit(ClassDecl node) { if (renamed.contains(node)) return; + node.isRenamed = true; log.finest('Renaming ${node.binaryName}.'); renamed.add(node); diff --git a/pkgs/jnigen/lib/src/elements/elements.dart b/pkgs/jnigen/lib/src/elements/elements.dart index 268d2d04ce..7782cda543 100644 --- a/pkgs/jnigen/lib/src/elements/elements.dart +++ b/pkgs/jnigen/lib/src/elements/elements.dart @@ -20,9 +20,9 @@ enum GenerationStage { // `../generate_bindings.dart`. unprocessed, userVisitors, - excluder, kotlinProcessor, linker, + excluder, renamer, dartGenerator; @@ -141,10 +141,12 @@ class ClassDecl with ClassMember, Annotated implements Element { /// Final name of this class. /// /// Populated by [Renamer]. - @JsonKey(includeFromJson: false) @override late String finalName; + @JsonKey(includeFromJson: false) + bool isRenamed = false; + /// Name of the type class. @JsonKey(includeFromJson: false) String get typeClassName => '\$$finalName\$Type\$'; @@ -830,6 +832,16 @@ class Param with Annotated implements Element { @JsonKey(includeFromJson: false) late String finalName; + /// Whether this parameter is a Kotlin synthetic parameter + /// (e.g., DefaultConstructorMarker). + /// + /// These parameters should be hidden from the generated Dart API but still + /// passed to the JNI constructor (as jNullReference). + /// + /// Populated by [KotlinProcessor]. + @JsonKey(includeFromJson: false) + bool isKotlinSynthetic = false; + factory Param.fromJson(Map json) => _$ParamFromJson(json); Param clone({GenerationStage until = GenerationStage.userVisitors}) { @@ -842,6 +854,9 @@ class Param with Annotated implements Element { if (GenerationStage.linker <= until) { cloned.method = method; } + if (GenerationStage.kotlinProcessor <= until) { + cloned.isKotlinSynthetic = isKotlinSynthetic; + } if (GenerationStage.renamer <= until) { cloned.finalName = finalName; } diff --git a/pkgs/jnigen/lib/src/generate_bindings.dart b/pkgs/jnigen/lib/src/generate_bindings.dart index d49c84740f..b141914744 100644 --- a/pkgs/jnigen/lib/src/generate_bindings.dart +++ b/pkgs/jnigen/lib/src/generate_bindings.dart @@ -51,9 +51,9 @@ Future generateJniBindings(Config config) async { return classes.accept(visitor); } - runStage(Excluder(config)); runStage(KotlinProcessor()); await runStage(Linker(config)); + runStage(Excluder(config)); runStage(Renamer(config)); // classes.accept(const Printer()); diff --git a/pkgs/jnigen/test/kotlin_test/bindings/kotlin.dart b/pkgs/jnigen/test/kotlin_test/bindings/kotlin.dart index 735c300f5d..67eecc9b6c 100644 --- a/pkgs/jnigen/test/kotlin_test/bindings/kotlin.dart +++ b/pkgs/jnigen/test/kotlin_test/bindings/kotlin.dart @@ -40,6 +40,250 @@ import 'dart:core' show Object, String, double, int; import 'package:jni/_internal.dart' as jni$_; import 'package:jni/jni.dart' as jni$_; +/// from: `com.github.dart_lang.jnigen.AllDefaults` +class AllDefaults extends jni$_.JObject { + @jni$_.internal + @core$_.override + final jni$_.JType $type; + + @jni$_.internal + AllDefaults.fromReference( + jni$_.JReference reference, + ) : $type = type, + super.fromReference(reference); + + static final _class = + jni$_.JClass.forName(r'com/github/dart_lang/jnigen/AllDefaults'); + + /// The type which includes information such as the signature of this class. + static const jni$_.JType nullableType = + $AllDefaults$NullableType$(); + + /// The type which includes information such as the signature of this class. + static const jni$_.JType type = $AllDefaults$Type$(); + static final _id_new$ = _class.constructorId( + r'(ILjava/lang/String;Z)V', + ); + + static final _new$ = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Int32, + jni$_.Pointer, + jni$_.Int32 + )>)>>('globalEnv_NewObject') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, int, jni$_.Pointer, int)>(); + + /// from: `public void (int i, java.lang.String string, boolean z)` + /// The returned object must be released after use, by calling the [release] method. + factory AllDefaults( + int i, + jni$_.JString string, + core$_.bool z, + ) { + final _$string = string.reference; + return AllDefaults.fromReference(_new$(_class.reference.pointer, + _id_new$ as jni$_.JMethodIDPtr, i, _$string.pointer, z ? 1 : 0) + .reference); + } + + static final _id_getA = _class.instanceMethodId( + r'getA', + r'()I', + ); + + static final _getA = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallIntMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public final int getA()` + int getA() { + return _getA(reference.pointer, _id_getA as jni$_.JMethodIDPtr).integer; + } + + static final _id_getB = _class.instanceMethodId( + r'getB', + r'()Ljava/lang/String;', + ); + + static final _getB = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public final java.lang.String getB()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JString getB() { + return _getB(reference.pointer, _id_getB as jni$_.JMethodIDPtr) + .object(const jni$_.$JString$Type$()); + } + + static final _id_getC = _class.instanceMethodId( + r'getC', + r'()Z', + ); + + static final _getC = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallBooleanMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public final boolean getC()` + core$_.bool getC() { + return _getC(reference.pointer, _id_getC as jni$_.JMethodIDPtr).boolean; + } + + static final _id_summary = _class.instanceMethodId( + r'summary', + r'()Ljava/lang/String;', + ); + + static final _summary = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public fun summary(): kotlin.String` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JString summary() { + return _summary(reference.pointer, _id_summary as jni$_.JMethodIDPtr) + .object(const jni$_.$JString$Type$()); + } + + static final _id_new$1 = _class.constructorId( + r'()V', + ); + + static final _new$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_NewObject') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public void ()` + /// The returned object must be released after use, by calling the [release] method. + factory AllDefaults.new$1() { + return AllDefaults.fromReference( + _new$1(_class.reference.pointer, _id_new$1 as jni$_.JMethodIDPtr) + .reference); + } +} + +final class $AllDefaults$NullableType$ extends jni$_.JType { + @jni$_.internal + const $AllDefaults$NullableType$(); + + @jni$_.internal + @core$_.override + String get signature => r'Lcom/github/dart_lang/jnigen/AllDefaults;'; + + @jni$_.internal + @core$_.override + AllDefaults? fromReference(jni$_.JReference reference) => reference.isNull + ? null + : AllDefaults.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JType get superType => const jni$_.$JObject$Type$(); + + @jni$_.internal + @core$_.override + jni$_.JType get nullableType => this; + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($AllDefaults$NullableType$).hashCode; + + @core$_.override + core$_.bool operator ==(Object other) { + return other.runtimeType == ($AllDefaults$NullableType$) && + other is $AllDefaults$NullableType$; + } +} + +final class $AllDefaults$Type$ extends jni$_.JType { + @jni$_.internal + const $AllDefaults$Type$(); + + @jni$_.internal + @core$_.override + String get signature => r'Lcom/github/dart_lang/jnigen/AllDefaults;'; + + @jni$_.internal + @core$_.override + AllDefaults fromReference(jni$_.JReference reference) => + AllDefaults.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JType get superType => const jni$_.$JObject$Type$(); + + @jni$_.internal + @core$_.override + jni$_.JType get nullableType => + const $AllDefaults$NullableType$(); + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($AllDefaults$Type$).hashCode; + + @core$_.override + core$_.bool operator ==(Object other) { + return other.runtimeType == ($AllDefaults$Type$) && + other is $AllDefaults$Type$; + } +} + /// from: `com.github.dart_lang.jnigen.CanDoA` class CanDoA extends jni$_.JObject { @jni$_.internal @@ -470,6 +714,224 @@ final class $CanDoB$Type$ extends jni$_.JType { } } +/// from: `com.github.dart_lang.jnigen.DefaultParams` +class DefaultParams extends jni$_.JObject { + @jni$_.internal + @core$_.override + final jni$_.JType $type; + + @jni$_.internal + DefaultParams.fromReference( + jni$_.JReference reference, + ) : $type = type, + super.fromReference(reference); + + static final _class = + jni$_.JClass.forName(r'com/github/dart_lang/jnigen/DefaultParams'); + + /// The type which includes information such as the signature of this class. + static const jni$_.JType nullableType = + $DefaultParams$NullableType$(); + + /// The type which includes information such as the signature of this class. + static const jni$_.JType type = $DefaultParams$Type$(); + static final _id_new$ = _class.constructorId( + r'(ILjava/lang/String;)V', + ); + + static final _new$ = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_ + .VarArgs<(jni$_.Int32, jni$_.Pointer)>)>>( + 'globalEnv_NewObject') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, int, jni$_.Pointer)>(); + + /// from: `public void (int i, java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + factory DefaultParams( + int i, + jni$_.JString string, + ) { + final _$string = string.reference; + return DefaultParams.fromReference(_new$(_class.reference.pointer, + _id_new$ as jni$_.JMethodIDPtr, i, _$string.pointer) + .reference); + } + + static final _id_getX = _class.instanceMethodId( + r'getX', + r'()I', + ); + + static final _getX = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallIntMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public final int getX()` + int getX() { + return _getX(reference.pointer, _id_getX as jni$_.JMethodIDPtr).integer; + } + + static final _id_getY = _class.instanceMethodId( + r'getY', + r'()Ljava/lang/String;', + ); + + static final _getY = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public final java.lang.String getY()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JString getY() { + return _getY(reference.pointer, _id_getY as jni$_.JMethodIDPtr) + .object(const jni$_.$JString$Type$()); + } + + static final _id_greet = _class.instanceMethodId( + r'greet', + r'()Ljava/lang/String;', + ); + + static final _greet = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public fun greet(): kotlin.String` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JString greet() { + return _greet(reference.pointer, _id_greet as jni$_.JMethodIDPtr) + .object(const jni$_.$JString$Type$()); + } + + static final _id_new$1 = _class.constructorId( + r'()V', + ); + + static final _new$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_NewObject') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public void ()` + /// The returned object must be released after use, by calling the [release] method. + factory DefaultParams.new$1() { + return DefaultParams.fromReference( + _new$1(_class.reference.pointer, _id_new$1 as jni$_.JMethodIDPtr) + .reference); + } +} + +final class $DefaultParams$NullableType$ extends jni$_.JType { + @jni$_.internal + const $DefaultParams$NullableType$(); + + @jni$_.internal + @core$_.override + String get signature => r'Lcom/github/dart_lang/jnigen/DefaultParams;'; + + @jni$_.internal + @core$_.override + DefaultParams? fromReference(jni$_.JReference reference) => reference.isNull + ? null + : DefaultParams.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JType get superType => const jni$_.$JObject$Type$(); + + @jni$_.internal + @core$_.override + jni$_.JType get nullableType => this; + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($DefaultParams$NullableType$).hashCode; + + @core$_.override + core$_.bool operator ==(Object other) { + return other.runtimeType == ($DefaultParams$NullableType$) && + other is $DefaultParams$NullableType$; + } +} + +final class $DefaultParams$Type$ extends jni$_.JType { + @jni$_.internal + const $DefaultParams$Type$(); + + @jni$_.internal + @core$_.override + String get signature => r'Lcom/github/dart_lang/jnigen/DefaultParams;'; + + @jni$_.internal + @core$_.override + DefaultParams fromReference(jni$_.JReference reference) => + DefaultParams.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JType get superType => const jni$_.$JObject$Type$(); + + @jni$_.internal + @core$_.override + jni$_.JType get nullableType => + const $DefaultParams$NullableType$(); + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($DefaultParams$Type$).hashCode; + + @core$_.override + core$_.bool operator ==(Object other) { + return other.runtimeType == ($DefaultParams$Type$) && + other is $DefaultParams$Type$; + } +} + /// from: `com.github.dart_lang.jnigen.Measure` class Measure<$T extends jni$_.JObject> extends jni$_.JObject { @jni$_.internal @@ -926,6 +1388,203 @@ final class $MeasureUnit$Type$ extends jni$_.JType { } } +/// from: `com.github.dart_lang.jnigen.MixedParams` +class MixedParams extends jni$_.JObject { + @jni$_.internal + @core$_.override + final jni$_.JType $type; + + @jni$_.internal + MixedParams.fromReference( + jni$_.JReference reference, + ) : $type = type, + super.fromReference(reference); + + static final _class = + jni$_.JClass.forName(r'com/github/dart_lang/jnigen/MixedParams'); + + /// The type which includes information such as the signature of this class. + static const jni$_.JType nullableType = + $MixedParams$NullableType$(); + + /// The type which includes information such as the signature of this class. + static const jni$_.JType type = $MixedParams$Type$(); + static final _id_new$ = _class.constructorId( + r'(Ljava/lang/String;I)V', + ); + + static final _new$ = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_ + .VarArgs<(jni$_.Pointer, jni$_.Int32)>)>>( + 'globalEnv_NewObject') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer, int)>(); + + /// from: `public void (java.lang.String string, int i)` + /// The returned object must be released after use, by calling the [release] method. + factory MixedParams( + jni$_.JString string, + int i, + ) { + final _$string = string.reference; + return MixedParams.fromReference(_new$(_class.reference.pointer, + _id_new$ as jni$_.JMethodIDPtr, _$string.pointer, i) + .reference); + } + + static final _id_getRequired = _class.instanceMethodId( + r'getRequired', + r'()Ljava/lang/String;', + ); + + static final _getRequired = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public final java.lang.String getRequired()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JString getRequired() { + return _getRequired( + reference.pointer, _id_getRequired as jni$_.JMethodIDPtr) + .object(const jni$_.$JString$Type$()); + } + + static final _id_getOptional = _class.instanceMethodId( + r'getOptional', + r'()I', + ); + + static final _getOptional = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallIntMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public final int getOptional()` + int getOptional() { + return _getOptional( + reference.pointer, _id_getOptional as jni$_.JMethodIDPtr) + .integer; + } + + static final _id_describe = _class.instanceMethodId( + r'describe', + r'()Ljava/lang/String;', + ); + + static final _describe = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public fun describe(): kotlin.String` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JString describe() { + return _describe(reference.pointer, _id_describe as jni$_.JMethodIDPtr) + .object(const jni$_.$JString$Type$()); + } +} + +final class $MixedParams$NullableType$ extends jni$_.JType { + @jni$_.internal + const $MixedParams$NullableType$(); + + @jni$_.internal + @core$_.override + String get signature => r'Lcom/github/dart_lang/jnigen/MixedParams;'; + + @jni$_.internal + @core$_.override + MixedParams? fromReference(jni$_.JReference reference) => reference.isNull + ? null + : MixedParams.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JType get superType => const jni$_.$JObject$Type$(); + + @jni$_.internal + @core$_.override + jni$_.JType get nullableType => this; + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($MixedParams$NullableType$).hashCode; + + @core$_.override + core$_.bool operator ==(Object other) { + return other.runtimeType == ($MixedParams$NullableType$) && + other is $MixedParams$NullableType$; + } +} + +final class $MixedParams$Type$ extends jni$_.JType { + @jni$_.internal + const $MixedParams$Type$(); + + @jni$_.internal + @core$_.override + String get signature => r'Lcom/github/dart_lang/jnigen/MixedParams;'; + + @jni$_.internal + @core$_.override + MixedParams fromReference(jni$_.JReference reference) => + MixedParams.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JType get superType => const jni$_.$JObject$Type$(); + + @jni$_.internal + @core$_.override + jni$_.JType get nullableType => + const $MixedParams$NullableType$(); + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($MixedParams$Type$).hashCode; + + @core$_.override + core$_.bool operator ==(Object other) { + return other.runtimeType == ($MixedParams$Type$) && + other is $MixedParams$Type$; + } +} + /// from: `com.github.dart_lang.jnigen.Nullability$InnerClass` class Nullability$InnerClass<$T extends jni$_.JObject?, $U extends jni$_.JObject, $V extends jni$_.JObject?> extends jni$_.JObject { diff --git a/pkgs/jnigen/test/kotlin_test/kotlin/src/main/kotlin/com/github/dart_lang/jnigen/DefaultParams.kt b/pkgs/jnigen/test/kotlin_test/kotlin/src/main/kotlin/com/github/dart_lang/jnigen/DefaultParams.kt new file mode 100644 index 0000000000..4198503f15 --- /dev/null +++ b/pkgs/jnigen/test/kotlin_test/kotlin/src/main/kotlin/com/github/dart_lang/jnigen/DefaultParams.kt @@ -0,0 +1,31 @@ +/* Copyright (c) 2024, 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 + +// Kotlin classes with default parameters should not expose the +// DefaultConstructorMarker in generated Dart bindings. + +class DefaultParams( + val x: Int = 42, + val y: String = "hello" +) { + fun greet(): String = "x=$x, y=$y" +} + +class MixedParams( + val required: String, + val optional: Int = 100 +) { + fun describe(): String = "required=$required, optional=$optional" +} + +class AllDefaults( + val a: Int = 1, + val b: String = "two", + val c: Boolean = true +) { + fun summary(): String = "a=$a, b=$b, c=$c" +} diff --git a/pkgs/jnigen/test/kotlin_test/runtime_test_registrant.dart b/pkgs/jnigen/test/kotlin_test/runtime_test_registrant.dart index c75cf2158b..f73232f5d6 100644 --- a/pkgs/jnigen/test/kotlin_test/runtime_test_registrant.dart +++ b/pkgs/jnigen/test/kotlin_test/runtime_test_registrant.dart @@ -565,5 +565,69 @@ kotlin.Unit consumeOnAnotherThread(itf), throwsA(isA())); }); }); + + group('Default parameters', () { + test('DefaultParams - no-arg constructor', () { + using((arena) { + final obj = DefaultParams.new$1()..releasedBy(arena); + expect( + obj.greet().toDartString(releaseOriginal: true), 'x=42, y=hello'); + }); + }); + + test('DefaultParams - constructor with explicit arguments', () { + using((arena) { + final obj = DefaultParams(100, 'world'.toJString()..releasedBy(arena)) + ..releasedBy(arena); + expect(obj.greet().toDartString(releaseOriginal: true), + 'x=100, y=world'); + }); + }); + + test('MixedParams - required and optional parameters', () { + using((arena) { + // Both parameters provided + final obj = MixedParams('test'.toJString()..releasedBy(arena), 200) + ..releasedBy(arena); + expect(obj.describe().toDartString(releaseOriginal: true), + 'required=test, optional=200'); + }); + }); + + test('AllDefaults - no-arg constructor', () { + using((arena) { + final obj = AllDefaults.new$1()..releasedBy(arena); + expect(obj.summary().toDartString(releaseOriginal: true), + 'a=1, b=two, c=true'); + }); + }); + + test('AllDefaults - constructor with explicit arguments', () { + using((arena) { + final obj = AllDefaults( + 42, + 'forty-two'.toJString()..releasedBy(arena), + false, + )..releasedBy(arena); + expect(obj.summary().toDartString(releaseOriginal: true), + 'a=42, b=forty-two, c=false'); + }); + }); + + test('No DefaultConstructorMarker in generated API', () { + // This is a compile-time check - if DefaultConstructorMarker + // parameter is exposed in constructors that should use defaults, + // it would require passing it explicitly, breaking the API. + // By successfully instantiating these classes using simple constructors + // we prove the synthetic parameter is correctly handled internally. + using((arena) { + DefaultParams.new$1().releasedBy(arena); + // ignore: avoid_single_cascade_in_expression_statements + MixedParams('test'.toJString()..releasedBy(arena), 123) + ..releasedBy(arena); + AllDefaults.new$1().releasedBy(arena); + }); + }); + }); }); }