From bdb45b541474a1deb7fda96b5cf56b622cea46d9 Mon Sep 17 00:00:00 2001 From: Hossein Yousefi Date: Mon, 7 Oct 2024 20:11:33 +0200 Subject: [PATCH 1/4] [jnigen] Enable implementing generic interfaces --- pkgs/jnigen/CHANGELOG.md | 4 + .../in_app_java/lib/android_utils.dart | 28 +- .../lib/src/bindings/dart_generator.dart | 295 +++++----- pkgs/jnigen/lib/src/bindings/linker.dart | 128 ++++- pkgs/jnigen/lib/src/bindings/unnester.dart | 90 --- pkgs/jnigen/lib/src/elements/elements.dart | 13 +- pkgs/jnigen/lib/src/generate_bindings.dart | 2 - pkgs/jnigen/pubspec.yaml | 2 +- pkgs/jnigen/test/descriptor_test.dart | 2 - .../test/kotlin_test/bindings/kotlin.dart | 4 +- .../bindings/simple_package.dart | 537 +++++++++++++++++- .../test/simple_package_test/generate.dart | 1 + .../jnigen/interfaces/GenericInterface.java | 23 + .../runtime_test_registrant.dart | 59 ++ 14 files changed, 908 insertions(+), 280 deletions(-) delete mode 100644 pkgs/jnigen/lib/src/bindings/unnester.dart create mode 100644 pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/GenericInterface.java diff --git a/pkgs/jnigen/CHANGELOG.md b/pkgs/jnigen/CHANGELOG.md index e18420eee..c180922bf 100644 --- a/pkgs/jnigen/CHANGELOG.md +++ b/pkgs/jnigen/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.12.1 + +- Support implementing generic functions in interfaces. + ## 0.12.0 - **Breaking Change**([#1530](https://github.com/dart-lang/native/pull/1530)): diff --git a/pkgs/jnigen/example/in_app_java/lib/android_utils.dart b/pkgs/jnigen/example/in_app_java/lib/android_utils.dart index e42b9e681..2b7a33e06 100644 --- a/pkgs/jnigen/example/in_app_java/lib/android_utils.dart +++ b/pkgs/jnigen/example/in_app_java/lib/android_utils.dart @@ -125,13 +125,13 @@ class EmojiCompat_CodepointSequenceMatchResult extends _$jni.JObject { } } -abstract mixin class $EmojiCompat_CodepointSequenceMatchResult { +abstract base mixin class $EmojiCompat_CodepointSequenceMatchResult { factory $EmojiCompat_CodepointSequenceMatchResult() = _$EmojiCompat_CodepointSequenceMatchResult; } -class _$EmojiCompat_CodepointSequenceMatchResult - implements $EmojiCompat_CodepointSequenceMatchResult { +final class _$EmojiCompat_CodepointSequenceMatchResult + with $EmojiCompat_CodepointSequenceMatchResult { _$EmojiCompat_CodepointSequenceMatchResult(); } @@ -729,7 +729,7 @@ class EmojiCompat_GlyphChecker extends _$jni.JObject { } } -abstract mixin class $EmojiCompat_GlyphChecker { +abstract base mixin class $EmojiCompat_GlyphChecker { factory $EmojiCompat_GlyphChecker({ required bool Function(_$jni.JObject charSequence, int i, int i1, int i2) hasGlyph, @@ -738,7 +738,7 @@ abstract mixin class $EmojiCompat_GlyphChecker { bool hasGlyph(_$jni.JObject charSequence, int i, int i1, int i2); } -class _$EmojiCompat_GlyphChecker implements $EmojiCompat_GlyphChecker { +final class _$EmojiCompat_GlyphChecker with $EmojiCompat_GlyphChecker { _$EmojiCompat_GlyphChecker({ required bool Function(_$jni.JObject charSequence, int i, int i1, int i2) hasGlyph, @@ -972,11 +972,11 @@ class EmojiCompat_LoadStrategy extends _$jni.JObject { } } -abstract mixin class $EmojiCompat_LoadStrategy { +abstract base mixin class $EmojiCompat_LoadStrategy { factory $EmojiCompat_LoadStrategy() = _$EmojiCompat_LoadStrategy; } -class _$EmojiCompat_LoadStrategy implements $EmojiCompat_LoadStrategy { +final class _$EmojiCompat_LoadStrategy with $EmojiCompat_LoadStrategy { _$EmojiCompat_LoadStrategy(); } @@ -1137,7 +1137,7 @@ class EmojiCompat_MetadataRepoLoader extends _$jni.JObject { } } -abstract mixin class $EmojiCompat_MetadataRepoLoader { +abstract base mixin class $EmojiCompat_MetadataRepoLoader { factory $EmojiCompat_MetadataRepoLoader({ required void Function( EmojiCompat_MetadataRepoLoaderCallback metadataRepoLoaderCallback) @@ -1149,8 +1149,8 @@ abstract mixin class $EmojiCompat_MetadataRepoLoader { bool get load$async => false; } -class _$EmojiCompat_MetadataRepoLoader - implements $EmojiCompat_MetadataRepoLoader { +final class _$EmojiCompat_MetadataRepoLoader + with $EmojiCompat_MetadataRepoLoader { _$EmojiCompat_MetadataRepoLoader({ required void Function( EmojiCompat_MetadataRepoLoaderCallback metadataRepoLoaderCallback) @@ -1393,11 +1393,11 @@ class EmojiCompat_ReplaceStrategy extends _$jni.JObject { } } -abstract mixin class $EmojiCompat_ReplaceStrategy { +abstract base mixin class $EmojiCompat_ReplaceStrategy { factory $EmojiCompat_ReplaceStrategy() = _$EmojiCompat_ReplaceStrategy; } -class _$EmojiCompat_ReplaceStrategy implements $EmojiCompat_ReplaceStrategy { +final class _$EmojiCompat_ReplaceStrategy with $EmojiCompat_ReplaceStrategy { _$EmojiCompat_ReplaceStrategy(); } @@ -1558,7 +1558,7 @@ class EmojiCompat_SpanFactory extends _$jni.JObject { } } -abstract mixin class $EmojiCompat_SpanFactory { +abstract base mixin class $EmojiCompat_SpanFactory { factory $EmojiCompat_SpanFactory({ required _$jni.JObject Function(_$jni.JObject typefaceEmojiRasterizer) createSpan, @@ -1567,7 +1567,7 @@ abstract mixin class $EmojiCompat_SpanFactory { _$jni.JObject createSpan(_$jni.JObject typefaceEmojiRasterizer); } -class _$EmojiCompat_SpanFactory implements $EmojiCompat_SpanFactory { +final class _$EmojiCompat_SpanFactory with $EmojiCompat_SpanFactory { _$EmojiCompat_SpanFactory({ required _$jni.JObject Function(_$jni.JObject typefaceEmojiRasterizer) createSpan, diff --git a/pkgs/jnigen/lib/src/bindings/dart_generator.dart b/pkgs/jnigen/lib/src/bindings/dart_generator.dart index b87f39a36..4d8f1a4b5 100644 --- a/pkgs/jnigen/lib/src/bindings/dart_generator.dart +++ b/pkgs/jnigen/lib/src/bindings/dart_generator.dart @@ -55,11 +55,11 @@ extension on Iterable { } } -/// Encloses [inside] in the middle of [open] and [close] -/// if [inside] is not empty. -String _encloseIfNotEmpty(String open, String inside, String close) { - if (inside == '') return ''; - return '$open$inside$close'; +extension on String { + String encloseIfNotEmpty(String open, String close) { + if (isEmpty) return this; + return '$open$this$close'; + } } String _newLine({int depth = 0}) { @@ -304,7 +304,6 @@ ${modifier}final $classRef = $_jni.JClass.forName(r'$internalName'); void visit(ClassDecl node) { if (node.isTopLevel) { // If the class is top-level, only generate its methods and fields. - // Fields and Methods final classRef = writeClassRef(node); generateFieldsAndMethods(node, classRef); return; @@ -317,25 +316,17 @@ ${modifier}final $classRef = $_jni.JClass.forName(r'$internalName'); final name = node.finalName; final superName = node.superclass!.accept(_TypeGenerator(resolver)); final implClassName = '\$$name'; - final typeParamsDef = _encloseIfNotEmpty( - '<', - node.allTypeParams - .accept(const _TypeParamGenerator(withExtends: true)) - .join(', '), - '>', - ); - final typeParams = node.allTypeParams - .accept(const _TypeParamGenerator(withExtends: false)); - final typeParamsCall = _encloseIfNotEmpty( - '<', - typeParams.map((typeParam) => '$_typeParamPrefix$typeParam').join(', '), - '>', - ); - final staticTypeGetterCallArgs = _encloseIfNotEmpty( - '(', - typeParams.join(', '), - ')', - ); + final typeParamsDef = node.allTypeParams + .accept(const _TypeParamDef()) + .join(', ') + .encloseIfNotEmpty('<', '>'); + final typeParams = node.allTypeParams.map((typeParam) => typeParam.name); + final typeParamsCall = typeParams + .map((typeParam) => '$_typeParamPrefix$typeParam') + .join(', ') + .encloseIfNotEmpty('<', '>'); + final staticTypeGetterCallArgs = + typeParams.join(', ').encloseIfNotEmpty('(', ')'); final typeClassesDef = typeParams.map((typeParam) => ''' $_internal final $_jType<$_typeParamPrefix$typeParam> $typeParam; @@ -517,17 +508,13 @@ class $name$typeParamsDef extends $superName { .map((typeParam) => '$_jType<$_typeParamPrefix$typeParam> get $typeParam;') .join(_newLine(depth: 1)); - final abstractFactoryArgs = _encloseIfNotEmpty( - '{', - [ - ...typeParams - .map((typeParam) => 'required $_jType<\$$typeParam> $typeParam,'), - ...node.methods.accept(_AbstractImplFactoryArg(resolver)), - ].join(_newLine(depth: 2)), - '}', - ); + final abstractFactoryArgs = [ + ...typeParams + .map((typeParam) => 'required $_jType<\$$typeParam> $typeParam,'), + ...node.methods.accept(_AbstractImplFactoryArg(resolver)), + ].join(_newLine(depth: 2)).encloseIfNotEmpty('{', '}'); s.write(''' -abstract mixin class $implClassName$typeParamsDef { +abstract base mixin class $implClassName$typeParamsDef { factory $implClassName( $abstractFactoryArgs ) = _$implClassName; @@ -543,28 +530,21 @@ abstract mixin class $implClassName$typeParamsDef { // Concrete Impl class. // This is for passing closures instead of implementing the class. - final concreteCtorArgs = _encloseIfNotEmpty( - '{', - [ - ...typeParams.map((typeParam) => 'required this.$typeParam,'), - ...node.methods.accept(_ConcreteImplClosureCtorArg(resolver)), - ].join(_newLine(depth: 2)), - '}', - ); - final setClosures = _encloseIfNotEmpty( - ' : ', - node.methods - .map((method) => '_${method.finalName} = ${method.finalName}') - .join(', '), - '', - ); + final concreteCtorArgs = [ + ...typeParams.map((typeParam) => 'required this.$typeParam,'), + ...node.methods.accept(_ConcreteImplClosureCtorArg(resolver)), + ].join(_newLine(depth: 2)).encloseIfNotEmpty('{', '}'); + final setClosures = node.methods + .map((method) => '_${method.finalName} = ${method.finalName}') + .join(', ') + .encloseIfNotEmpty(' : ', ''); final typeClassesDef = typeParams.map((typeParam) => ''' $_override final $_jType<\$$typeParam> $typeParam; ''').join(_newLine(depth: 1)); s.write(''' -class _$implClassName$typeParamsDef implements $implClassName$typeParamsCall { +final class _$implClassName$typeParamsDef with $implClassName$typeParamsCall { _$implClassName( $concreteCtorArgs )$setClosures; @@ -671,7 +651,16 @@ $indent/// $comments class _TypeGenerator extends TypeVisitor { final Resolver? resolver; - const _TypeGenerator(this.resolver); + final bool forInterfaceImplementation; + + /// Whether the generic types should be erased. + final bool typeErasure; + + const _TypeGenerator( + this.resolver, { + this.forInterfaceImplementation = false, + this.typeErasure = false, + }); @override String visitArrayType(ArrayType node) { @@ -679,7 +668,12 @@ class _TypeGenerator extends TypeVisitor { if (innerType.kind == Kind.primitive) { return '$_jArray<$_jni.j${(innerType.type as PrimitiveType).name}>'; } - return '$_jArray<${innerType.accept(this)}>'; + final typeGenerator = _TypeGenerator( + resolver, + forInterfaceImplementation: forInterfaceImplementation, + typeErasure: forInterfaceImplementation, + ); + return '$_jArray<${innerType.accept(typeGenerator)}>'; } @override @@ -691,10 +685,15 @@ class _TypeGenerator extends TypeVisitor { // All type parameters of this type final allTypeParams = node.classDecl.allTypeParams - .accept(const _TypeParamGenerator(withExtends: false)) + .map((typeParam) => typeParam.name) .toList(); // The ones that are declared. - final definedTypeParams = node.params.accept(this).toList(); + final typeGenerator = _TypeGenerator( + resolver, + forInterfaceImplementation: forInterfaceImplementation, + typeErasure: forInterfaceImplementation, + ); + final definedTypeParams = node.params.accept(typeGenerator).toList(); // Replacing the declared ones. They come at the end. // The rest will be JObject. @@ -714,7 +713,7 @@ class _TypeGenerator extends TypeVisitor { ); } - final typeParams = _encloseIfNotEmpty('<', allTypeParams.join(', '), '>'); + final typeParams = allTypeParams.join(', ').encloseIfNotEmpty('<', '>'); final prefix = resolver?.resolvePrefix(node.classDecl) ?? ''; return '$prefix${node.classDecl.finalName}$typeParams'; } @@ -726,12 +725,20 @@ class _TypeGenerator extends TypeVisitor { @override String visitTypeVar(TypeVar node) { + // TODO(https://github.com/dart-lang/native/issues/704): Tighten to + // typevar bounds instead. + if (typeErasure) { + return _jObject; + } + if (forInterfaceImplementation && node.origin.parent is Method) { + return _jObject; + } return '$_typeParamPrefix${node.name}'; } @override String visitWildcard(Wildcard node) { - // TODO(#141): Support wildcards + // TODO(https://github.com/dart-lang/native/issues/701): Support wildcards. return super.visitWildcard(node); } @@ -751,11 +758,26 @@ class _TypeClass { /// Generates the type class. class _TypeClassGenerator extends TypeVisitor<_TypeClass> { final bool isConst; + + /// Whether or not to return the equivalent boxed type class for primitives. + /// Only for interface implemetation. + final bool boxPrimitives; + + /// Whether or not to find the correct type variable from the static map. + /// Only for interface implemetation. + final bool forInterfaceImplementation; + + /// Whether the generic types should be erased. + final bool typeErasure; + final Resolver resolver; _TypeClassGenerator( this.resolver, { this.isConst = true, + this.boxPrimitives = false, + this.forInterfaceImplementation = false, + this.typeErasure = false, }); @override @@ -763,6 +785,10 @@ class _TypeClassGenerator extends TypeVisitor<_TypeClass> { final innerTypeClass = node.type.accept(_TypeClassGenerator( resolver, isConst: false, + boxPrimitives: false, + forInterfaceImplementation: forInterfaceImplementation, + // Do type erasure for interface implementation. + typeErasure: forInterfaceImplementation, )); final ifConst = innerTypeClass.canBeConst && isConst ? 'const ' : ''; return _TypeClass( @@ -774,13 +800,16 @@ class _TypeClassGenerator extends TypeVisitor<_TypeClass> { @override _TypeClass visitDeclaredType(DeclaredType node) { final allTypeParams = node.classDecl.allTypeParams - .accept(const _TypeParamGenerator(withExtends: false)) + .map((typeParam) => typeParam.name) .toList(); // The ones that are declared. final definedTypeClasses = node.params.accept(_TypeClassGenerator( resolver, isConst: false, + boxPrimitives: false, + forInterfaceImplementation: forInterfaceImplementation, + typeErasure: forInterfaceImplementation, )); // Can be const if all the type parameters are defined and each of them are @@ -822,18 +851,31 @@ class _TypeClassGenerator extends TypeVisitor<_TypeClass> { @override _TypeClass visitPrimitiveType(PrimitiveType node) { final ifConst = isConst ? 'const ' : ''; - final name = 'j${node.name}'; + final name = boxPrimitives ? 'J${node.boxedName}' : 'j${node.name}'; return _TypeClass('$ifConst$_jni.${name}Type()', true); } @override _TypeClass visitTypeVar(TypeVar node) { + // TODO(https://github.com/dart-lang/native/issues/704): Tighten to typevar + // bounds instead. + if (typeErasure) { + final ifConst = isConst ? 'const ' : ''; + return _TypeClass('$ifConst${_jObject}Type()', true); + } + if (forInterfaceImplementation) { + if (node.origin.parent is ClassDecl) { + return _TypeClass('_\$impls[\$p]!.${node.name}', false); + } + final ifConst = isConst ? 'const ' : ''; + return _TypeClass('$ifConst${_jObject}Type()', true); + } return _TypeClass(node.name, false); } @override _TypeClass visitWildcard(Wildcard node) { - // TODO(#141): Support wildcards + // TODO(https://github.com/dart-lang/native/issues/701): Support wildcards. return super.visitWildcard(node); } @@ -844,34 +886,13 @@ class _TypeClassGenerator extends TypeVisitor<_TypeClass> { } } -class _ImplTypeClass extends _TypeClassGenerator { - _ImplTypeClass(super.resolver); - - @override - _TypeClass visitTypeVar(TypeVar node) { - // Get the concrete type variable from the static map. - return _TypeClass('_\$impls[\$p]!.${node.name}', false); - } - - @override - _TypeClass visitPrimitiveType(PrimitiveType node) { - final ifConst = isConst ? 'const ' : ''; - final name = 'J${node.boxedName}'; - return _TypeClass('$ifConst$_jni.${name}Type()', true); - } -} - -class _TypeParamGenerator extends Visitor { - final bool withExtends; - - const _TypeParamGenerator({required this.withExtends}); +class _TypeParamDef extends Visitor { + const _TypeParamDef(); @override String visit(TypeParam node) { - if (!withExtends) { - return node.name; - } - // TODO(#144): resolve the actual type being extended, if any. + // TODO(https://github.com/dart-lang/native/issues/704): resolve the actual + // type being extended, if any. return '$_typeParamPrefix${node.name} extends $_jObject'; } } @@ -1156,14 +1177,9 @@ ${modifier}final _$name = $_protectedExtension final typeLocators = node.params .accept(_ParamTypeLocator(resolver: resolver)) .fold(>{}, _mergeMapValues).map( - (key, value) => MapEntry( - key, - _encloseIfNotEmpty( - '[', - value.delimited(', '), - ']', - ), - )); + (key, value) => + MapEntry(key, value.delimited(', ').encloseIfNotEmpty('[', ']')), + ); bool isRequired(TypeParam typeParam) { return (typeLocators[typeParam.name] ?? '').isEmpty; @@ -1182,17 +1198,13 @@ ${modifier}final _$name = $_protectedExtension final name = node.finalName; final ctorName = name == 'new\$' ? className : '$className.$name'; final paramsDef = node.params.accept(_ParamDef(resolver)).delimited(', '); - final typeClassDef = _encloseIfNotEmpty( - '{', - node.classDecl.allTypeParams - .map((typeParam) => typeParam - .accept(_CtorTypeClassDef(isRequired: isRequired(typeParam)))) - .delimited(', '), - '}', - ); + final typeClassDef = node.classDecl.allTypeParams + .map((typeParam) => typeParam + .accept(_CtorTypeClassDef(isRequired: isRequired(typeParam)))) + .delimited(', ') + .encloseIfNotEmpty('{', '}'); final typeClassCall = node.classDecl.allTypeParams - .map((typeParam) => - typeParam.accept(const _TypeParamGenerator(withExtends: false))) + .map((typeParam) => typeParam.name) .delimited(', '); final ctorExpr = dartOnlyCtor(node); @@ -1219,22 +1231,16 @@ ${modifier}final _$name = $_protectedExtension .name; final ifStatic = node.isStatic && !isTopLevel ? 'static ' : ''; final defArgs = node.params.accept(_ParamDef(resolver)).toList(); - final typeClassDef = _encloseIfNotEmpty( - '{', - node.typeParams - .map((typeParam) => typeParam.accept(_MethodTypeClassDef( - isRequired: isRequired(typeParam), - ))) - .delimited(', '), - '}', - ); - final typeParamsDef = _encloseIfNotEmpty( - '<', - node.typeParams - .accept(const _TypeParamGenerator(withExtends: true)) - .join(', '), - '>', - ); + final typeClassDef = node.typeParams + .map((typeParam) => typeParam.accept(_MethodTypeClassDef( + isRequired: isRequired(typeParam), + ))) + .delimited(', ') + .encloseIfNotEmpty('{', '}'); + final typeParamsDef = node.typeParams + .accept(const _TypeParamDef()) + .join(', ') + .encloseIfNotEmpty('<', '>'); if (isSuspendFun(node)) { defArgs.removeLast(); } @@ -1316,12 +1322,14 @@ class _CtorTypeClassDef extends Visitor { /// ``` class _ParamDef extends Visitor { final Resolver resolver; + final bool methodGenericErasure; - const _ParamDef(this.resolver); + const _ParamDef(this.resolver, {this.methodGenericErasure = false}); @override String visit(Param node) { - final type = node.type.accept(_TypeGenerator(resolver)); + final type = node.type.accept(_TypeGenerator(resolver, + forInterfaceImplementation: methodGenericErasure)); return '$type ${node.finalName}'; } } @@ -1424,7 +1432,7 @@ class _TypeVarLocator extends TypeVisitor>> { @override Map> visitWildcard(Wildcard node) { - // TODO(#141): Support wildcards + // TODO(https://github.com/dart-lang/native/issues/701): Support wildcards. return super.visitWildcard(node); } @@ -1485,9 +1493,12 @@ class _AbstractImplMethod extends Visitor { @override void visit(Method node) { - final returnType = node.returnType.accept(_TypeGenerator(resolver)); + final returnType = node.returnType + .accept(_TypeGenerator(resolver, forInterfaceImplementation: true)); final name = node.finalName; - final args = node.params.accept(_ParamDef(resolver)).join(', '); + final args = node.params + .accept(_ParamDef(resolver, methodGenericErasure: true)) + .join(', '); s.writeln(' $returnType $name($args);'); if (returnType == 'void') { s.writeln(' bool get $name\$async => false;'); @@ -1504,9 +1515,12 @@ class _ConcreteImplClosureDef extends Visitor { @override void visit(Method node) { - final returnType = node.returnType.accept(_TypeGenerator(resolver)); + final returnType = node.returnType + .accept(_TypeGenerator(resolver, forInterfaceImplementation: true)); final name = node.finalName; - final args = node.params.accept(_ParamDef(resolver)).join(', '); + final args = node.params + .accept(_ParamDef(resolver, methodGenericErasure: true)) + .join(', '); s.writeln(' final $returnType Function($args) _$name;'); if (returnType == 'void') { s.writeln(' final bool $name\$async;'); @@ -1523,9 +1537,12 @@ class _AbstractImplFactoryArg extends Visitor { @override String visit(Method node) { - final returnType = node.returnType.accept(_TypeGenerator(resolver)); + final returnType = node.returnType + .accept(_TypeGenerator(resolver, forInterfaceImplementation: true)); final name = node.finalName; - final args = node.params.accept(_ParamDef(resolver)).join(', '); + final args = node.params + .accept(_ParamDef(resolver, methodGenericErasure: true)) + .join(', '); final functionArg = 'required $returnType Function($args) $name,'; if (node.returnType.name == 'void') { return '$functionArg bool $name\$async,'; @@ -1543,9 +1560,12 @@ class _ConcreteImplClosureCtorArg extends Visitor { @override String visit(Method node) { - final returnType = node.returnType.accept(_TypeGenerator(resolver)); + final returnType = node.returnType + .accept(_TypeGenerator(resolver, forInterfaceImplementation: true)); final name = node.finalName; - final args = node.params.accept(_ParamDef(resolver)).join(', '); + final args = node.params + .accept(_ParamDef(resolver, methodGenericErasure: true)) + .join(', '); final functionArg = 'required $returnType Function($args) $name,'; if (node.returnType.name == 'void') { return '$functionArg this.$name\$async = false,'; @@ -1563,9 +1583,12 @@ class _ConcreteImplMethod extends Visitor { @override void visit(Method node) { - final returnType = node.returnType.accept(_TypeGenerator(resolver)); + final returnType = node.returnType + .accept(_TypeGenerator(resolver, forInterfaceImplementation: true)); final name = node.finalName; - final argsDef = node.params.accept(_ParamDef(resolver)).join(', '); + final argsDef = node.params + .accept(_ParamDef(resolver, methodGenericErasure: true)) + .join(', '); final argsCall = node.params.map((param) => param.finalName).join(', '); s.write(''' $returnType $name($argsDef) { @@ -1639,7 +1662,13 @@ class _InterfaceParamCast extends Visitor { @override void visit(Param node) { - final typeClass = node.type.accept(_ImplTypeClass(resolver)).name; + final typeClass = node.type + .accept(_TypeClassGenerator( + resolver, + boxPrimitives: true, + forInterfaceImplementation: true, + )) + .name; s.write('\$a[$paramIndex].as($typeClass, releaseOriginal: true)'); if (node.type.kind == Kind.primitive) { // Convert to Dart type. diff --git a/pkgs/jnigen/lib/src/bindings/linker.dart b/pkgs/jnigen/lib/src/bindings/linker.dart index 8e9091028..c6b8c097b 100644 --- a/pkgs/jnigen/lib/src/bindings/linker.dart +++ b/pkgs/jnigen/lib/src/bindings/linker.dart @@ -79,22 +79,37 @@ class Linker extends Visitor> { class _ClassLinker extends Visitor { final Config config; final _Resolver resolve; - final Set _linked; + final Set linked; + + /// Keeps track of the [TypeParam]s that introduced each type variable. + final typeVarOrigin = {}; _ClassLinker( this.config, this.resolve, - ) : _linked = {...config.importedClasses.values}; + ) : linked = {...config.importedClasses.values}; @override void visit(ClassDecl node) { - if (_linked.contains(node)) return; + if (linked.contains(node)) return; log.finest('Linking ${node.binaryName}.'); - _linked.add(node); + linked.add(node); + final typeLinker = _TypeLinker(resolve, typeVarOrigin); node.parent = node.parentName == null ? null : resolve(node.parentName); + node.parent?.accept(this); + + // Add type params of outer classes to the nested classes. + final allTypeParams = []; + if (!node.isStatic) { + allTypeParams.addAll(node.parent?.allTypeParams ?? []); + } + allTypeParams.addAll(node.typeParams); + node.allTypeParams = allTypeParams; + for (final typeParam in node.allTypeParams) { + typeVarOrigin[typeParam.name] = typeParam; + } - final typeLinker = _TypeLinker(resolve); node.superclass ??= TypeUsage.object; node.superclass!.type.accept(typeLinker); final superclass = (node.superclass!.type as DeclaredType).classDecl; @@ -107,41 +122,95 @@ class _ClassLinker extends Visitor { field.classDecl = node; field.accept(fieldLinker); } - final methodLinker = _MethodLinker(config, typeLinker); + final methodLinker = _MethodLinker(config, resolve, {...typeVarOrigin}); for (final method in node.methods) { method.classDecl = node; method.accept(methodLinker); } - node.interfaces.accept(typeLinker).toList(); - node.typeParams.accept(_TypeParamLinker(typeLinker)).toList(); + for (final interface in node.interfaces) { + interface.accept(typeLinker); + } + for (final typeParam in node.typeParams) { + typeParam.accept(_TypeParamLinker(typeLinker)); + typeParam.parent = node; + } } } class _MethodLinker extends Visitor { - _MethodLinker(this.config, this.typeVisitor); + _MethodLinker(this.config, this.resolve, this.typeVarOrigin) + : typeLinker = _TypeLinker(resolve, typeVarOrigin); final Config config; - final TypeVisitor typeVisitor; + final _Resolver resolve; + final Map typeVarOrigin; + final _TypeLinker typeLinker; @override void visit(Method node) { - node.returnType.accept(typeVisitor); - final typeParamLinker = _TypeParamLinker(typeVisitor); - final paramLinker = _ParamLinker(typeVisitor); - node.typeParams.accept(typeParamLinker).toList(); - node.params.accept(paramLinker).toList(); - node.asyncReturnType?.accept(typeVisitor); + final usedDocklet = node.descriptor == null; + if (usedDocklet && + !node.classDecl.isStatic && + node.classDecl.isNested && + (node.isConstructor || node.isStatic)) { + // For now the nullity of [node.descriptor] identifies if the doclet + // backend was used and the method would potentially need "unnesting". + // Static methods and constructors of non-static nested classes take an + // instance of their outer class as the first parameter. + // + // This is not accounted for by the **doclet** summarizer, so we + // manually add it as the first parameter. + final parentTypeParamCount = node.classDecl.allTypeParams.length - + node.classDecl.typeParams.length; + final parentTypeParams = [ + for (final typeParam + in node.classDecl.allTypeParams.take(parentTypeParamCount)) ...[ + TypeUsage( + shorthand: typeParam.name, kind: Kind.typeVariable, typeJson: {}) + ..type = TypeVar(name: typeParam.name), + ] + ]; + final parentType = DeclaredType( + binaryName: node.classDecl.parent!.binaryName, + params: parentTypeParams, + ); + final parentTypeUsage = TypeUsage( + shorthand: parentType.binaryName, kind: Kind.declared, typeJson: {}) + ..type = parentType; + final param = Param(name: '\$parent', type: parentTypeUsage); + // Make the list modifiable. + if (node.params.isEmpty) node.params = []; + node.params.insert(0, param); + } + for (final typeParam in node.typeParams) { + typeVarOrigin[typeParam.name] = typeParam; + } + node.returnType.accept(typeLinker); + final typeParamLinker = _TypeParamLinker(typeLinker); + final paramLinker = _ParamLinker(typeLinker); + for (final typeParam in node.typeParams) { + typeParam.accept(typeParamLinker); + typeParam.parent = node; + } + for (final param in node.params) { + param.accept(paramLinker); + param.method = node; + } + node.asyncReturnType?.accept(typeLinker); } } class _TypeLinker extends TypeVisitor { - const _TypeLinker(this.resolve); + const _TypeLinker(this.resolve, this.typeVarOrigin); final _Resolver resolve; + final Map typeVarOrigin; @override void visitDeclaredType(DeclaredType node) { - node.params.accept(this).toList(); + for (final param in node.params) { + param.accept(this); + } node.classDecl = resolve(node.binaryName); } @@ -156,6 +225,11 @@ class _TypeLinker extends TypeVisitor { node.type.accept(this); } + @override + void visitTypeVar(TypeVar node) { + node.origin = typeVarOrigin[node.name]!; + } + @override void visitPrimitiveType(PrimitiveType node) { // Do nothing @@ -168,36 +242,36 @@ class _TypeLinker extends TypeVisitor { } class _FieldLinker extends Visitor { - _FieldLinker(this.typeVisitor); + _FieldLinker(this.typeLinker); - final TypeVisitor typeVisitor; + final _TypeLinker typeLinker; @override void visit(Field node) { - node.type.accept(typeVisitor); + node.type.accept(typeLinker); } } class _TypeParamLinker extends Visitor { - _TypeParamLinker(this.typeVisitor); + _TypeParamLinker(this.typeLinker); - final TypeVisitor typeVisitor; + final _TypeLinker typeLinker; @override void visit(TypeParam node) { for (final bound in node.bounds) { - bound.accept(typeVisitor); + bound.accept(typeLinker); } } } class _ParamLinker extends Visitor { - _ParamLinker(this.typeVisitor); + _ParamLinker(this.typeLinker); - final TypeVisitor typeVisitor; + final _TypeLinker typeLinker; @override void visit(Param node) { - node.type.accept(typeVisitor); + node.type.accept(typeLinker); } } diff --git a/pkgs/jnigen/lib/src/bindings/unnester.dart b/pkgs/jnigen/lib/src/bindings/unnester.dart deleted file mode 100644 index abdb2829d..000000000 --- a/pkgs/jnigen/lib/src/bindings/unnester.dart +++ /dev/null @@ -1,90 +0,0 @@ -// 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 processes nested classes. -/// -/// Nested classes are not supported in Dart. So this "unnests" them into -/// separate classes. -class Unnester extends Visitor { - const Unnester(); - - @override - void visit(Classes node) { - final classProcessor = _ClassUnnester(); - for (final classDecl in node.decls.values) { - classDecl.accept(classProcessor); - } - } -} - -class _ClassUnnester extends Visitor { - final processed = {}; - - @override - void visit(ClassDecl node) { - if (processed.contains(node)) return; - processed.add(node); - // We need to visit the ancestors first. - node.parent?.accept(this); - - // Add type params of outer classes to the nested classes. - final allTypeParams = []; - if (!node.isStatic) { - allTypeParams.addAll(node.parent?.allTypeParams ?? []); - } - allTypeParams.addAll(node.typeParams); - node.allTypeParams = allTypeParams; - - if (node.isNested && !node.isStatic) { - const methodProcessor = _MethodUnnester(); - for (final method in node.methods) { - method.accept(methodProcessor); - } - } - } -} - -class _MethodUnnester extends Visitor { - const _MethodUnnester(); - - @override - void visit(Method node) { - assert(!node.classDecl.isStatic); - assert(node.classDecl.isNested); - // TODO(#319): Unnest the methods in APISummarizer itself. - // For now the nullity of [node.descriptor] identifies if the doclet - // backend was used and the method would potentially need "unnesting". - if ((node.isConstructor || node.isStatic) && node.descriptor == null) { - // Non-static nested classes take an instance of their outer class as the - // first parameter. - // - // This is not accounted for by the **doclet** summarizer, so we - // manually add it as the first parameter. - final parentTypeParamCount = node.classDecl.allTypeParams.length - - node.classDecl.typeParams.length; - final parentTypeParams = [ - for (final typeParam - in node.classDecl.allTypeParams.take(parentTypeParamCount)) ...[ - TypeUsage( - shorthand: typeParam.name, kind: Kind.typeVariable, typeJson: {}) - ..type = TypeVar(name: typeParam.name), - ] - ]; - final parentType = DeclaredType( - binaryName: node.classDecl.parent!.binaryName, - params: parentTypeParams, - )..classDecl = node.classDecl.parent!; - final parentTypeUsage = TypeUsage( - shorthand: parentType.binaryName, kind: Kind.declared, typeJson: {}) - ..type = parentType; - final param = Param(name: '\$parent', type: parentTypeUsage); - // Make the list modifiable. - if (node.params.isEmpty) node.params = []; - node.params.insert(0, param); - } - } -} diff --git a/pkgs/jnigen/lib/src/elements/elements.dart b/pkgs/jnigen/lib/src/elements/elements.dart index b22656216..449c12e15 100644 --- a/pkgs/jnigen/lib/src/elements/elements.dart +++ b/pkgs/jnigen/lib/src/elements/elements.dart @@ -393,6 +393,10 @@ class DeclaredType extends ReferredType { @JsonSerializable(createToJson: false) class TypeVar extends ReferredType { + /// Populated by [Linker]. + @JsonKey(includeFromJson: false) + late final TypeParam origin; + TypeVar({required this.name}); @override @@ -542,6 +546,10 @@ class Param implements Element { @JsonKey(includeFromJson: false) late String finalName; + /// Populated by [Linker]. + @JsonKey(includeFromJson: false) + late final Method method; + factory Param.fromJson(Map json) => _$ParamFromJson(json); @override @@ -598,8 +606,11 @@ class TypeParam implements Element { final String name; final List bounds; + /// Can either be a [ClassDecl] or a [Method]. + /// + /// Populated by [Linker]. @JsonKey(includeFromJson: false) - late final String erasure; + late final ClassMember parent; factory TypeParam.fromJson(Map json) => _$TypeParamFromJson(json); diff --git a/pkgs/jnigen/lib/src/generate_bindings.dart b/pkgs/jnigen/lib/src/generate_bindings.dart index 19a6c0e70..89231ebab 100644 --- a/pkgs/jnigen/lib/src/generate_bindings.dart +++ b/pkgs/jnigen/lib/src/generate_bindings.dart @@ -11,7 +11,6 @@ import 'bindings/excluder.dart'; import 'bindings/kotlin_processor.dart'; import 'bindings/linker.dart'; import 'bindings/renamer.dart'; -import 'bindings/unnester.dart'; import 'config/config.dart'; import 'elements/elements.dart'; import 'logging/logging.dart'; @@ -39,7 +38,6 @@ Future generateJniBindings(Config config) async { classes.accept(Excluder(config)); classes.accept(KotlinProcessor()); await classes.accept(Linker(config)); - classes.accept(const Unnester()); classes.accept(const Descriptor()); classes.accept(Renamer(config)); diff --git a/pkgs/jnigen/pubspec.yaml b/pkgs/jnigen/pubspec.yaml index 3c922d5b5..5aa30b5cb 100644 --- a/pkgs/jnigen/pubspec.yaml +++ b/pkgs/jnigen/pubspec.yaml @@ -4,7 +4,7 @@ name: jnigen description: A Dart bindings generator for Java and Kotlin that uses JNI under the hood to interop with Java virtual machine. -version: 0.12.0 +version: 0.12.1 repository: https://github.com/dart-lang/native/tree/main/pkgs/jnigen environment: diff --git a/pkgs/jnigen/test/descriptor_test.dart b/pkgs/jnigen/test/descriptor_test.dart index 17a256543..48b112cd9 100644 --- a/pkgs/jnigen/test/descriptor_test.dart +++ b/pkgs/jnigen/test/descriptor_test.dart @@ -4,7 +4,6 @@ import 'package:jnigen/src/bindings/descriptor.dart'; import 'package:jnigen/src/bindings/linker.dart'; -import 'package:jnigen/src/bindings/unnester.dart'; import 'package:jnigen/src/config/config_types.dart'; import 'package:jnigen/src/summary/summary.dart'; import 'package:test/test.dart'; @@ -28,7 +27,6 @@ void main() { SummarizerOptions(backend: SummarizerBackend.asm); final classes = await getSummary(config); await classes.accept(Linker(config)); - classes.accept(const Unnester()); for (final decl in classes.decls.values) { // Checking if the descriptor from ASM matches the one generated by // [MethodDescriptor]. diff --git a/pkgs/jnigen/test/kotlin_test/bindings/kotlin.dart b/pkgs/jnigen/test/kotlin_test/bindings/kotlin.dart index 7290cad2f..9209bad65 100644 --- a/pkgs/jnigen/test/kotlin_test/bindings/kotlin.dart +++ b/pkgs/jnigen/test/kotlin_test/bindings/kotlin.dart @@ -327,7 +327,7 @@ class MeasureUnit extends _$jni.JObject { } } -abstract mixin class $MeasureUnit { +abstract base mixin class $MeasureUnit { factory $MeasureUnit({ required _$jni.JString Function() getSign, required double Function() getCoefficient, @@ -337,7 +337,7 @@ abstract mixin class $MeasureUnit { double getCoefficient(); } -class _$MeasureUnit implements $MeasureUnit { +final class _$MeasureUnit with $MeasureUnit { _$MeasureUnit({ required _$jni.JString Function() getSign, required double Function() getCoefficient, diff --git a/pkgs/jnigen/test/simple_package_test/bindings/simple_package.dart b/pkgs/jnigen/test/simple_package_test/bindings/simple_package.dart index 6b4e97c81..508836a40 100644 --- a/pkgs/jnigen/test/simple_package_test/bindings/simple_package.dart +++ b/pkgs/jnigen/test/simple_package_test/bindings/simple_package.dart @@ -4632,6 +4632,527 @@ final class $StringValuedMap$Type<$K extends _$jni.JObject> } } +/// from: `com.github.dart_lang.jnigen.interfaces.GenericInterface` +class GenericInterface<$T extends _$jni.JObject> extends _$jni.JObject { + @_$jni.internal + @_$core.override + final _$jni.JObjType> $type; + + @_$jni.internal + final _$jni.JObjType<$T> T; + + @_$jni.internal + GenericInterface.fromReference( + this.T, + _$jni.JReference reference, + ) : $type = type(T), + super.fromReference(reference); + + static final _class = _$jni.JClass.forName( + r'com/github/dart_lang/jnigen/interfaces/GenericInterface'); + + /// The type which includes information such as the signature of this class. + static $GenericInterface$Type<$T> type<$T extends _$jni.JObject>( + _$jni.JObjType<$T> T, + ) { + return $GenericInterface$Type( + T, + ); + } + + static final _id_genericArrayOf = _class.instanceMethodId( + r'genericArrayOf', + r'(Ljava/lang/Object;)[Ljava/lang/Object;', + ); + + static final _genericArrayOf = _$jni.ProtectedJniExtensions.lookup< + _$jni.NativeFunction< + _$jni.JniResult Function( + _$jni.Pointer<_$jni.Void>, + _$jni.JMethodIDPtr, + _$jni.VarArgs<(_$jni.Pointer<_$jni.Void>,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + _$jni.JniResult Function(_$jni.Pointer<_$jni.Void>, + _$jni.JMethodIDPtr, _$jni.Pointer<_$jni.Void>)>(); + + /// from: `public abstract U[] genericArrayOf(U element)` + /// The returned object must be released after use, by calling the [release] method. + _$jni.JArray<$U> genericArrayOf<$U extends _$jni.JObject>( + $U element, { + _$jni.JObjType<$U>? U, + }) { + U ??= _$jni.lowestCommonSuperType([ + element.$type, + ]) as _$jni.JObjType<$U>; + return _genericArrayOf(reference.pointer, + _id_genericArrayOf as _$jni.JMethodIDPtr, element.reference.pointer) + .object(_$jni.JArrayType(U)); + } + + static final _id_arrayOf = _class.instanceMethodId( + r'arrayOf', + r'(Ljava/lang/Object;)[Ljava/lang/Object;', + ); + + static final _arrayOf = _$jni.ProtectedJniExtensions.lookup< + _$jni.NativeFunction< + _$jni.JniResult Function( + _$jni.Pointer<_$jni.Void>, + _$jni.JMethodIDPtr, + _$jni.VarArgs<(_$jni.Pointer<_$jni.Void>,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + _$jni.JniResult Function(_$jni.Pointer<_$jni.Void>, + _$jni.JMethodIDPtr, _$jni.Pointer<_$jni.Void>)>(); + + /// from: `public abstract T[] arrayOf(T element)` + /// The returned object must be released after use, by calling the [release] method. + _$jni.JArray<$T> arrayOf( + $T element, + ) { + return _arrayOf(reference.pointer, _id_arrayOf as _$jni.JMethodIDPtr, + element.reference.pointer) + .object(_$jni.JArrayType(T)); + } + + static final _id_mapOf = _class.instanceMethodId( + r'mapOf', + r'(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map;', + ); + + static final _mapOf = _$jni.ProtectedJniExtensions.lookup< + _$jni.NativeFunction< + _$jni.JniResult Function( + _$jni.Pointer<_$jni.Void>, + _$jni.JMethodIDPtr, + _$jni.VarArgs< + ( + _$jni.Pointer<_$jni.Void>, + _$jni.Pointer<_$jni.Void> + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + _$jni.JniResult Function( + _$jni.Pointer<_$jni.Void>, + _$jni.JMethodIDPtr, + _$jni.Pointer<_$jni.Void>, + _$jni.Pointer<_$jni.Void>)>(); + + /// from: `public abstract java.util.Map mapOf(T key, U value)` + /// The returned object must be released after use, by calling the [release] method. + _$jni.JMap<$T, $U> mapOf<$U extends _$jni.JObject>( + $T key, + $U value, { + _$jni.JObjType<$U>? U, + }) { + U ??= _$jni.lowestCommonSuperType([ + value.$type, + ]) as _$jni.JObjType<$U>; + return _mapOf(reference.pointer, _id_mapOf as _$jni.JMethodIDPtr, + key.reference.pointer, value.reference.pointer) + .object(_$jni.JMapType(T, U)); + } + + static final _id_firstOfGenericArray = _class.instanceMethodId( + r'firstOfGenericArray', + r'([Ljava/lang/Object;)Ljava/lang/Object;', + ); + + static final _firstOfGenericArray = _$jni.ProtectedJniExtensions.lookup< + _$jni.NativeFunction< + _$jni.JniResult Function( + _$jni.Pointer<_$jni.Void>, + _$jni.JMethodIDPtr, + _$jni.VarArgs<(_$jni.Pointer<_$jni.Void>,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + _$jni.JniResult Function(_$jni.Pointer<_$jni.Void>, + _$jni.JMethodIDPtr, _$jni.Pointer<_$jni.Void>)>(); + + /// from: `public abstract U firstOfGenericArray(U[] array)` + /// The returned object must be released after use, by calling the [release] method. + $U firstOfGenericArray<$U extends _$jni.JObject>( + _$jni.JArray<$U> array, { + _$jni.JObjType<$U>? U, + }) { + U ??= _$jni.lowestCommonSuperType([ + ((array.$type as _$jni.JArrayType).elementType as _$jni.JObjType), + ]) as _$jni.JObjType<$U>; + return _firstOfGenericArray( + reference.pointer, + _id_firstOfGenericArray as _$jni.JMethodIDPtr, + array.reference.pointer) + .object(U); + } + + static final _id_firstOfArray = _class.instanceMethodId( + r'firstOfArray', + r'([Ljava/lang/Object;)Ljava/lang/Object;', + ); + + static final _firstOfArray = _$jni.ProtectedJniExtensions.lookup< + _$jni.NativeFunction< + _$jni.JniResult Function( + _$jni.Pointer<_$jni.Void>, + _$jni.JMethodIDPtr, + _$jni.VarArgs<(_$jni.Pointer<_$jni.Void>,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + _$jni.JniResult Function(_$jni.Pointer<_$jni.Void>, + _$jni.JMethodIDPtr, _$jni.Pointer<_$jni.Void>)>(); + + /// from: `public abstract T firstOfArray(T[] array)` + /// The returned object must be released after use, by calling the [release] method. + $T firstOfArray( + _$jni.JArray<$T> array, + ) { + return _firstOfArray(reference.pointer, + _id_firstOfArray as _$jni.JMethodIDPtr, array.reference.pointer) + .object(T); + } + + static final _id_firstKeyOf = _class.instanceMethodId( + r'firstKeyOf', + r'(Ljava/util/Map;)Ljava/lang/Object;', + ); + + static final _firstKeyOf = _$jni.ProtectedJniExtensions.lookup< + _$jni.NativeFunction< + _$jni.JniResult Function( + _$jni.Pointer<_$jni.Void>, + _$jni.JMethodIDPtr, + _$jni.VarArgs<(_$jni.Pointer<_$jni.Void>,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + _$jni.JniResult Function(_$jni.Pointer<_$jni.Void>, + _$jni.JMethodIDPtr, _$jni.Pointer<_$jni.Void>)>(); + + /// from: `public abstract T firstKeyOf(java.util.Map map)` + /// The returned object must be released after use, by calling the [release] method. + $T firstKeyOf<$U extends _$jni.JObject>( + _$jni.JMap<$T, $U> map, { + _$jni.JObjType<$U>? U, + }) { + U ??= _$jni.lowestCommonSuperType([ + (map.$type as _$jni.JMapType).V, + ]) as _$jni.JObjType<$U>; + return _firstKeyOf(reference.pointer, _id_firstKeyOf as _$jni.JMethodIDPtr, + map.reference.pointer) + .object(T); + } + + static final _id_firstValueOf = _class.instanceMethodId( + r'firstValueOf', + r'(Ljava/util/Map;)Ljava/lang/Object;', + ); + + static final _firstValueOf = _$jni.ProtectedJniExtensions.lookup< + _$jni.NativeFunction< + _$jni.JniResult Function( + _$jni.Pointer<_$jni.Void>, + _$jni.JMethodIDPtr, + _$jni.VarArgs<(_$jni.Pointer<_$jni.Void>,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + _$jni.JniResult Function(_$jni.Pointer<_$jni.Void>, + _$jni.JMethodIDPtr, _$jni.Pointer<_$jni.Void>)>(); + + /// from: `public abstract U firstValueOf(java.util.Map map)` + /// The returned object must be released after use, by calling the [release] method. + $U firstValueOf<$U extends _$jni.JObject>( + _$jni.JMap<$T, $U> map, { + _$jni.JObjType<$U>? U, + }) { + U ??= _$jni.lowestCommonSuperType([ + (map.$type as _$jni.JMapType).V, + ]) as _$jni.JObjType<$U>; + return _firstValueOf(reference.pointer, + _id_firstValueOf as _$jni.JMethodIDPtr, map.reference.pointer) + .object(U); + } + + /// Maps a specific port to the implemented interface. + static final _$core.Map _$impls = {}; + static _$jni.JObjectPtr _$invoke( + int port, + _$jni.JObjectPtr descriptor, + _$jni.JObjectPtr args, + ) { + return _$invokeMethod( + port, + _$jni.MethodInvocation.fromAddresses( + 0, + descriptor.address, + args.address, + ), + ); + } + + static final _$jni.Pointer< + _$jni.NativeFunction< + _$jni.JObjectPtr Function( + _$jni.Int64, _$jni.JObjectPtr, _$jni.JObjectPtr)>> + _$invokePointer = _$jni.Pointer.fromFunction(_$invoke); + + static _$jni.Pointer<_$jni.Void> _$invokeMethod( + int $p, + _$jni.MethodInvocation $i, + ) { + try { + final $d = $i.methodDescriptor.toDartString(releaseOriginal: true); + final $a = $i.args; + if ($d == r'genericArrayOf(Ljava/lang/Object;)[Ljava/lang/Object;') { + final $r = _$impls[$p]!.genericArrayOf( + $a[0].as(const _$jni.JObjectType(), releaseOriginal: true), + ); + return ($r as _$jni.JObject) + .as(const _$jni.JObjectType()) + .reference + .toPointer(); + } + if ($d == r'arrayOf(Ljava/lang/Object;)[Ljava/lang/Object;') { + final $r = _$impls[$p]!.arrayOf( + $a[0].as(_$impls[$p]!.T, releaseOriginal: true), + ); + return ($r as _$jni.JObject) + .as(const _$jni.JObjectType()) + .reference + .toPointer(); + } + if ($d == r'mapOf(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map;') { + final $r = _$impls[$p]!.mapOf( + $a[0].as(_$impls[$p]!.T, releaseOriginal: true), + $a[1].as(const _$jni.JObjectType(), releaseOriginal: true), + ); + return ($r as _$jni.JObject) + .as(const _$jni.JObjectType()) + .reference + .toPointer(); + } + if ($d == r'firstOfGenericArray([Ljava/lang/Object;)Ljava/lang/Object;') { + final $r = _$impls[$p]!.firstOfGenericArray( + $a[0].as(const _$jni.JArrayType(_$jni.JObjectType()), + releaseOriginal: true), + ); + return ($r as _$jni.JObject) + .as(const _$jni.JObjectType()) + .reference + .toPointer(); + } + if ($d == r'firstOfArray([Ljava/lang/Object;)Ljava/lang/Object;') { + final $r = _$impls[$p]!.firstOfArray( + $a[0].as(const _$jni.JArrayType(_$jni.JObjectType()), + releaseOriginal: true), + ); + return ($r as _$jni.JObject) + .as(const _$jni.JObjectType()) + .reference + .toPointer(); + } + if ($d == r'firstKeyOf(Ljava/util/Map;)Ljava/lang/Object;') { + final $r = _$impls[$p]!.firstKeyOf( + $a[0].as( + const _$jni.JMapType(_$jni.JObjectType(), _$jni.JObjectType()), + releaseOriginal: true), + ); + return ($r as _$jni.JObject) + .as(const _$jni.JObjectType()) + .reference + .toPointer(); + } + if ($d == r'firstValueOf(Ljava/util/Map;)Ljava/lang/Object;') { + final $r = _$impls[$p]!.firstValueOf( + $a[0].as( + const _$jni.JMapType(_$jni.JObjectType(), _$jni.JObjectType()), + releaseOriginal: true), + ); + return ($r as _$jni.JObject) + .as(const _$jni.JObjectType()) + .reference + .toPointer(); + } + } catch (e) { + return _$jni.ProtectedJniExtensions.newDartException(e); + } + return _$jni.nullptr; + } + + static void implementIn<$T extends _$jni.JObject>( + _$jni.JImplementer implementer, + $GenericInterface<$T> $impl, + ) { + late final _$jni.RawReceivePort $p; + $p = _$jni.RawReceivePort(($m) { + if ($m == null) { + _$impls.remove($p.sendPort.nativePort); + $p.close(); + return; + } + final $i = _$jni.MethodInvocation.fromMessage($m); + final $r = _$invokeMethod($p.sendPort.nativePort, $i); + _$jni.ProtectedJniExtensions.returnResult($i.result, $r); + }); + implementer.add( + r'com.github.dart_lang.jnigen.interfaces.GenericInterface', + $p, + _$invokePointer, + [], + ); + final $a = $p.sendPort.nativePort; + _$impls[$a] = $impl; + } + + factory GenericInterface.implement( + $GenericInterface<$T> $impl, + ) { + final $i = _$jni.JImplementer(); + implementIn($i, $impl); + return GenericInterface.fromReference( + $impl.T, + $i.implementReference(), + ); + } +} + +abstract base mixin class $GenericInterface<$T extends _$jni.JObject> { + factory $GenericInterface({ + required _$jni.JObjType<$T> T, + required _$jni.JArray<_$jni.JObject> Function(_$jni.JObject element) + genericArrayOf, + required _$jni.JArray<_$jni.JObject> Function($T element) arrayOf, + required _$jni.JMap<_$jni.JObject, _$jni.JObject> Function( + $T key, _$jni.JObject value) + mapOf, + required _$jni.JObject Function(_$jni.JArray<_$jni.JObject> array) + firstOfGenericArray, + required $T Function(_$jni.JArray<_$jni.JObject> array) firstOfArray, + required $T Function(_$jni.JMap<_$jni.JObject, _$jni.JObject> map) + firstKeyOf, + required _$jni.JObject Function( + _$jni.JMap<_$jni.JObject, _$jni.JObject> map) + firstValueOf, + }) = _$GenericInterface; + + _$jni.JObjType<$T> get T; + + _$jni.JArray<_$jni.JObject> genericArrayOf(_$jni.JObject element); + _$jni.JArray<_$jni.JObject> arrayOf($T element); + _$jni.JMap<_$jni.JObject, _$jni.JObject> mapOf($T key, _$jni.JObject value); + _$jni.JObject firstOfGenericArray(_$jni.JArray<_$jni.JObject> array); + $T firstOfArray(_$jni.JArray<_$jni.JObject> array); + $T firstKeyOf(_$jni.JMap<_$jni.JObject, _$jni.JObject> map); + _$jni.JObject firstValueOf(_$jni.JMap<_$jni.JObject, _$jni.JObject> map); +} + +final class _$GenericInterface<$T extends _$jni.JObject> + with $GenericInterface<$T> { + _$GenericInterface({ + required this.T, + required _$jni.JArray<_$jni.JObject> Function(_$jni.JObject element) + genericArrayOf, + required _$jni.JArray<_$jni.JObject> Function($T element) arrayOf, + required _$jni.JMap<_$jni.JObject, _$jni.JObject> Function( + $T key, _$jni.JObject value) + mapOf, + required _$jni.JObject Function(_$jni.JArray<_$jni.JObject> array) + firstOfGenericArray, + required $T Function(_$jni.JArray<_$jni.JObject> array) firstOfArray, + required $T Function(_$jni.JMap<_$jni.JObject, _$jni.JObject> map) + firstKeyOf, + required _$jni.JObject Function( + _$jni.JMap<_$jni.JObject, _$jni.JObject> map) + firstValueOf, + }) : _genericArrayOf = genericArrayOf, + _arrayOf = arrayOf, + _mapOf = mapOf, + _firstOfGenericArray = firstOfGenericArray, + _firstOfArray = firstOfArray, + _firstKeyOf = firstKeyOf, + _firstValueOf = firstValueOf; + + @_$core.override + final _$jni.JObjType<$T> T; + + final _$jni.JArray<_$jni.JObject> Function(_$jni.JObject element) + _genericArrayOf; + final _$jni.JArray<_$jni.JObject> Function($T element) _arrayOf; + final _$jni.JMap<_$jni.JObject, _$jni.JObject> Function( + $T key, _$jni.JObject value) _mapOf; + final _$jni.JObject Function(_$jni.JArray<_$jni.JObject> array) + _firstOfGenericArray; + final $T Function(_$jni.JArray<_$jni.JObject> array) _firstOfArray; + final $T Function(_$jni.JMap<_$jni.JObject, _$jni.JObject> map) _firstKeyOf; + final _$jni.JObject Function(_$jni.JMap<_$jni.JObject, _$jni.JObject> map) + _firstValueOf; + + _$jni.JArray<_$jni.JObject> genericArrayOf(_$jni.JObject element) { + return _genericArrayOf(element); + } + + _$jni.JArray<_$jni.JObject> arrayOf($T element) { + return _arrayOf(element); + } + + _$jni.JMap<_$jni.JObject, _$jni.JObject> mapOf($T key, _$jni.JObject value) { + return _mapOf(key, value); + } + + _$jni.JObject firstOfGenericArray(_$jni.JArray<_$jni.JObject> array) { + return _firstOfGenericArray(array); + } + + $T firstOfArray(_$jni.JArray<_$jni.JObject> array) { + return _firstOfArray(array); + } + + $T firstKeyOf(_$jni.JMap<_$jni.JObject, _$jni.JObject> map) { + return _firstKeyOf(map); + } + + _$jni.JObject firstValueOf(_$jni.JMap<_$jni.JObject, _$jni.JObject> map) { + return _firstValueOf(map); + } +} + +final class $GenericInterface$Type<$T extends _$jni.JObject> + extends _$jni.JObjType> { + @_$jni.internal + final _$jni.JObjType<$T> T; + + @_$jni.internal + const $GenericInterface$Type( + this.T, + ); + + @_$jni.internal + @_$core.override + String get signature => + r'Lcom/github/dart_lang/jnigen/interfaces/GenericInterface;'; + + @_$jni.internal + @_$core.override + GenericInterface<$T> fromReference(_$jni.JReference reference) => + GenericInterface.fromReference(T, reference); + + @_$jni.internal + @_$core.override + _$jni.JObjType get superType => const _$jni.JObjectType(); + + @_$jni.internal + @_$core.override + final superCount = 1; + + @_$core.override + int get hashCode => Object.hash($GenericInterface$Type, T); + + @_$core.override + bool operator ==(Object other) { + return other.runtimeType == ($GenericInterface$Type<$T>) && + other is $GenericInterface$Type<$T> && + T == other.T; + } +} + /// from: `com.github.dart_lang.jnigen.interfaces.MyInterface` class MyInterface<$T extends _$jni.JObject> extends _$jni.JObject { @_$jni.internal @@ -4887,7 +5408,7 @@ class MyInterface<$T extends _$jni.JObject> extends _$jni.JObject { static _$core.Map get $impls => _$impls; } -abstract mixin class $MyInterface<$T extends _$jni.JObject> { +abstract base mixin class $MyInterface<$T extends _$jni.JObject> { factory $MyInterface({ required _$jni.JObjType<$T> T, required void Function(_$jni.JString s) voidCallback, @@ -4906,7 +5427,7 @@ abstract mixin class $MyInterface<$T extends _$jni.JObject> { int manyPrimitives(int a, bool b, int c, double d); } -class _$MyInterface<$T extends _$jni.JObject> implements $MyInterface<$T> { +final class _$MyInterface<$T extends _$jni.JObject> with $MyInterface<$T> { _$MyInterface({ required this.T, required void Function(_$jni.JString s) voidCallback, @@ -5297,7 +5818,7 @@ class MyRunnable extends _$jni.JObject { static _$core.Map get $impls => _$impls; } -abstract mixin class $MyRunnable { +abstract base mixin class $MyRunnable { factory $MyRunnable({ required void Function() run, bool run$async, @@ -5307,7 +5828,7 @@ abstract mixin class $MyRunnable { bool get run$async => false; } -class _$MyRunnable implements $MyRunnable { +final class _$MyRunnable with $MyRunnable { _$MyRunnable({ required void Function() run, this.run$async = false, @@ -5711,7 +6232,7 @@ class StringConverter extends _$jni.JObject { } } -abstract mixin class $StringConverter { +abstract base mixin class $StringConverter { factory $StringConverter({ required int Function(_$jni.JString s) parseToInt, }) = _$StringConverter; @@ -5719,7 +6240,7 @@ abstract mixin class $StringConverter { int parseToInt(_$jni.JString s); } -class _$StringConverter implements $StringConverter { +final class _$StringConverter with $StringConverter { _$StringConverter({ required int Function(_$jni.JString s) parseToInt, }) : _parseToInt = parseToInt; @@ -6397,7 +6918,7 @@ class JsonSerializable extends _$jni.JObject { } } -abstract mixin class $JsonSerializable { +abstract base mixin class $JsonSerializable { factory $JsonSerializable({ required JsonSerializable_Case Function() value, }) = _$JsonSerializable; @@ -6405,7 +6926,7 @@ abstract mixin class $JsonSerializable { JsonSerializable_Case value(); } -class _$JsonSerializable implements $JsonSerializable { +final class _$JsonSerializable with $JsonSerializable { _$JsonSerializable({ required JsonSerializable_Case Function() value, }) : _value = value; diff --git a/pkgs/jnigen/test/simple_package_test/generate.dart b/pkgs/jnigen/test/simple_package_test/generate.dart index ba55b3483..78fdbbe8d 100644 --- a/pkgs/jnigen/test/simple_package_test/generate.dart +++ b/pkgs/jnigen/test/simple_package_test/generate.dart @@ -42,6 +42,7 @@ final javaFiles = [ join(javaPrefix, 'inheritance', 'BaseClass.java'), join(javaPrefix, 'inheritance', 'GenericDerivedClass.java'), join(javaPrefix, 'inheritance', 'SpecificDerivedClass.java'), + join(javaPrefix, 'interfaces', 'GenericInterface.java'), join(javaPrefix, 'interfaces', 'MyInterface.java'), join(javaPrefix, 'interfaces', 'MyInterfaceConsumer.java'), join(javaPrefix, 'interfaces', 'MyRunnable.java'), diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/GenericInterface.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/GenericInterface.java new file mode 100644 index 000000000..b4e4aad55 --- /dev/null +++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/GenericInterface.java @@ -0,0 +1,23 @@ +// 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.interfaces; + +import java.util.Map; + +public interface GenericInterface { + U[] genericArrayOf(U element); + + T[] arrayOf(T element); + + Map mapOf(T key, U value); + + U firstOfGenericArray(U[] array); + + T firstOfArray(T[] array); + + T firstKeyOf(Map map); + + U firstValueOf(Map map); +} diff --git a/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart b/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart index e9afc9a0d..41e9e22d0 100644 --- a/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart +++ b/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart @@ -868,6 +868,65 @@ void registerTests(String groupName, TestRunnerCallback test) { }); } }); + test('Generic interface', () { + using((arena) { + final genericInterface = GenericInterface.implement( + $GenericInterface( + T: JString.type, + arrayOf: (element) => JArray(JString.type, 1)..[0] = element, + firstKeyOf: (map) => map.keys.first.as(JString.type), + firstValueOf: (map) => map.values.first, + firstOfArray: (array) => array[0].as(JString.type), + firstOfGenericArray: (array) => array[0], + genericArrayOf: (element) => JArray(JObject.type, 1)..[0] = element, + mapOf: (key, value) => + JMap.hash(JString.type, JObject.type)..[key] = value, + ), + )..releasedBy(arena); + final stringArray = genericInterface + .arrayOf('hello'.toJString()..releasedBy(arena)) + ..releasedBy(arena); + expect(stringArray, hasLength(1)); + expect(stringArray[0].toDartString(releaseOriginal: true), 'hello'); + expect( + genericInterface + .firstOfArray(stringArray) + .toDartString(releaseOriginal: true), + 'hello', + ); + + final intArray = genericInterface + .genericArrayOf(42.toJInteger()..releasedBy(arena)) + ..releasedBy(arena); + expect( + genericInterface + .firstOfGenericArray(intArray) + .intValue(releaseOriginal: true), + 42, + ); + + final jmap = genericInterface.mapOf( + 'hello'.toJString()..releasedBy(arena), + 42.toJInteger()..releasedBy(arena), + )..releasedBy(arena); + expect( + jmap['hello'.toJString()..releasedBy(arena)]! + .intValue(releaseOriginal: true), + 42, + ); + expect( + genericInterface + .firstKeyOf(jmap) + .as(JString.type) + .toDartString(releaseOriginal: true), + 'hello', + ); + expect( + genericInterface.firstValueOf(jmap).intValue(releaseOriginal: true), + 42, + ); + }); + }); }); group('$groupName (load tests)', () { From de23aec1af26df887ee7bf56d09d1fbd5fcbdbd1 Mon Sep 17 00:00:00 2001 From: Hossein Yousefi Date: Mon, 7 Oct 2024 20:21:12 +0200 Subject: [PATCH 2/4] abstract base mixin class! --- pkgs/jnigen/docs/interface_implementation.md | 8 ++++---- .../test/simple_package_test/runtime_test_registrant.dart | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkgs/jnigen/docs/interface_implementation.md b/pkgs/jnigen/docs/interface_implementation.md index f96c10642..566824e78 100644 --- a/pkgs/jnigen/docs/interface_implementation.md +++ b/pkgs/jnigen/docs/interface_implementation.md @@ -36,7 +36,7 @@ class Runnable extends JObject { ) { /* ... */ } } -abstract mixin class $Runnable { +abstract base mixin class $Runnable { factory $Runnable({ required void Function() run, bool run$async, @@ -87,7 +87,7 @@ implementing the interface in Java instead of using the lambdas: ```java // Java -public class Printer with Runnable { +public class Printer implements Runnable { private final String text; public Printer(String text) { @@ -108,7 +108,7 @@ You can do the same in Dart by creating a subclass that implements `$Runnable`: ```dart // Dart -class Printer with $Runnable { +final class Printer with $Runnable { final String text; Printer(this.text); @@ -145,7 +145,7 @@ Similarly, when subclassing ```dart // Dart -class Printer with $Runnable { +final class Printer with $Runnable { final String text; Printer(this.text); diff --git a/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart b/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart index 41e9e22d0..fdabd98d5 100644 --- a/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart +++ b/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart @@ -993,7 +993,7 @@ void registerTests(String groupName, TestRunnerCallback test) { }); } -class DartStringToIntParser implements $StringConverter { +final class DartStringToIntParser with $StringConverter { final int radix; DartStringToIntParser({required this.radix}); @@ -1004,7 +1004,7 @@ class DartStringToIntParser implements $StringConverter { } } -class AsyncRunnable with $MyRunnable { +final class AsyncRunnable with $MyRunnable { final Completer completer; AsyncRunnable(this.completer); From 0010eca0c6c1602627a245b6803fa22430675363 Mon Sep 17 00:00:00 2001 From: Hossein Yousefi Date: Tue, 8 Oct 2024 13:15:33 +0200 Subject: [PATCH 3/4] Clone type usage --- .../lib/src/bindings/kotlin_processor.dart | 2 +- pkgs/jnigen/lib/src/elements/elements.dart | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/pkgs/jnigen/lib/src/bindings/kotlin_processor.dart b/pkgs/jnigen/lib/src/bindings/kotlin_processor.dart index b7d05f630..4980debf9 100644 --- a/pkgs/jnigen/lib/src/bindings/kotlin_processor.dart +++ b/pkgs/jnigen/lib/src/bindings/kotlin_processor.dart @@ -56,7 +56,7 @@ class _KotlinMethodProcessor extends Visitor { final continuationType = node.params.last.type.type as DeclaredType; node.asyncReturnType = continuationType.params.isEmpty ? TypeUsage.object - : continuationType.params.first; + : continuationType.params.first.clone(); } } } diff --git a/pkgs/jnigen/lib/src/elements/elements.dart b/pkgs/jnigen/lib/src/elements/elements.dart index 449c12e15..324fdc2b1 100644 --- a/pkgs/jnigen/lib/src/elements/elements.dart +++ b/pkgs/jnigen/lib/src/elements/elements.dart @@ -253,6 +253,30 @@ class TypeUsage { R accept(TypeVisitor v) { return type.accept(v); } + + TypeUsage clone() { + final ReferredType clonedType; + final clonedTypeJson = typeJson.map(MapEntry.new); + switch (kind) { + case Kind.primitive: + clonedType = PrimitiveType.fromJson(clonedTypeJson); + break; + case Kind.typeVariable: + clonedType = TypeVar.fromJson(clonedTypeJson); + break; + case Kind.wildcard: + clonedType = Wildcard.fromJson(clonedTypeJson); + break; + case Kind.declared: + clonedType = DeclaredType.fromJson(clonedTypeJson); + break; + case Kind.array: + clonedType = ArrayType.fromJson(clonedTypeJson); + break; + } + return TypeUsage(shorthand: shorthand, kind: kind, typeJson: clonedTypeJson) + ..type = clonedType; + } } abstract class ReferredType { From 634b755bb38ac586d793ea9ae13da165fc776376 Mon Sep 17 00:00:00 2001 From: Hossein Yousefi Date: Tue, 8 Oct 2024 13:16:46 +0200 Subject: [PATCH 4/4] Use spread operator --- pkgs/jnigen/lib/src/elements/elements.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/jnigen/lib/src/elements/elements.dart b/pkgs/jnigen/lib/src/elements/elements.dart index 324fdc2b1..b286576f5 100644 --- a/pkgs/jnigen/lib/src/elements/elements.dart +++ b/pkgs/jnigen/lib/src/elements/elements.dart @@ -256,7 +256,7 @@ class TypeUsage { TypeUsage clone() { final ReferredType clonedType; - final clonedTypeJson = typeJson.map(MapEntry.new); + final clonedTypeJson = {...typeJson}; switch (kind) { case Kind.primitive: clonedType = PrimitiveType.fromJson(clonedTypeJson);