WIP renamer refactor
diff --git a/pkgs/ffigen/lib/src/code_generator/compound.dart b/pkgs/ffigen/lib/src/code_generator/compound.dart index 2638cd7..89579c3 100644 --- a/pkgs/ffigen/lib/src/code_generator/compound.dart +++ b/pkgs/ffigen/lib/src/code_generator/compound.dart
@@ -110,6 +110,22 @@ bool get isObjCImport => objCBuiltInFunctions?.getBuiltInCompoundName(originalName) != null; + void renameMembers() { + // Adding [name] because dart doesn't allow class member to have the same + // name as the class. + final localUniqueNamer = UniqueNamer()..markUsed(name); + + // Marking type names because dart doesn't allow class member to have the + // same name as a type name used internally. + for (final m in members) { + localUniqueNamer.markUsed(m.type.getFfiDartType(w)); + } + + for (final m in members) { + m.name = localUniqueNamer.makeUnique(m.name); + } + } + @override BindingString toBindingString(Writer w) { final bindingType = isStruct @@ -117,30 +133,18 @@ : BindingStringType.union; final s = StringBuffer(); - final enclosingClassName = name; s.write(makeDartDoc(dartDoc)); - /// Adding [enclosingClassName] because dart doesn't allow class member - /// to have the same name as the class. - final localUniqueNamer = UniqueNamer()..markUsed(enclosingClassName); - - /// Marking type names because dart doesn't allow class member to have the - /// same name as a type name used internally. - for (final m in members) { - localUniqueNamer.markUsed(m.type.getFfiDartType(w)); - } - - /// Write @Packed(X) annotation if struct is packed. + // Write @Packed(X) annotation if struct is packed. if (isStruct && pack != null) { s.write('@${w.ffiLibraryPrefix}.Packed($pack)\n'); } final dartClassName = isStruct ? 'Struct' : 'Union'; // Write class declaration. - s.write('final class $enclosingClassName extends '); + s.write('final class $name extends '); s.write('${w.ffiLibraryPrefix}.${isOpaque ? 'Opaque' : dartClassName}{\n'); const depth = ' '; for (final m in members) { - m.name = localUniqueNamer.makeUnique(m.name); if (m.dartDoc != null) { s.write('$depth/// '); s.writeAll(m.dartDoc!.split('\n'), '\n$depth/// ');
diff --git a/pkgs/ffigen/lib/src/code_generator/func_type.dart b/pkgs/ffigen/lib/src/code_generator/func_type.dart index 38d66d7..d14c692 100644 --- a/pkgs/ffigen/lib/src/code_generator/func_type.dart +++ b/pkgs/ffigen/lib/src/code_generator/func_type.dart
@@ -124,6 +124,9 @@ } @override + void visit(Visitation visitation) => visitation.visitFuncType(this); + + @override void visitChildren(Visitor visitor) { super.visitChildren(visitor); visitor.visit(returnType);
diff --git a/pkgs/ffigen/lib/src/code_generator/global.dart b/pkgs/ffigen/lib/src/code_generator/global.dart index 969d83d..8c086a6 100644 --- a/pkgs/ffigen/lib/src/code_generator/global.dart +++ b/pkgs/ffigen/lib/src/code_generator/global.dart
@@ -28,6 +28,7 @@ final bool exposeSymbolAddress; final FfiNativeConfig nativeConfig; final bool constant; + String? _pointerName; Global({ super.usr, @@ -80,15 +81,13 @@ } } + final pointerName = _pointerName ?? globalVarName; + if (nativeConfig.enabled) { if (type case final ConstantArray arr) { s.writeln(makeArrayAnnotation(w, arr)); } - final pointerName = type.sameDartAndFfiDartType - ? globalVarName - : w.wrapperLevelUniqueNamer.makeUnique('_$globalVarName'); - s ..writeln( makeNativeAnnotation( @@ -117,10 +116,6 @@ ); } } else { - final pointerName = w.wrapperLevelUniqueNamer.makeUnique( - '_$globalVarName', - ); - s.write( 'late final ${w.ffiLibraryPrefix}.Pointer<$cType> $pointerName = ' "${w.lookupFuncIdentifier}<$cType>('$originalName');\n\n", @@ -162,6 +157,12 @@ return BindingString(type: BindingStringType.global, string: s.toString()); } + void fillPointerName(UniqueNamer namer) { + _pointerName = nativeConfig.enabled && type.sameDartAndFfiDartType + ? globalVarName + : namer.makeUnique('_$globalVarName'); + } + @override void visitChildren(Visitor visitor) { super.visitChildren(visitor);
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index e15e7db..2e979f4 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart
@@ -19,6 +19,17 @@ ObjCBlockWrapperFuncs? _blockWrappers; ObjCProtocolMethodTrampoline? protocolTrampoline; + late final String funcPtrTrampoline; + late final String closureTrampoline; + late final String funcPtrCallable; + late final String closureCallable; + late final String listenerTrampoline; + late final String listenerCallable; + late final String blockingTrampoline; + late final String blockingCallable; + late final String blockingListenerCallable; + late final String callExtension; + factory ObjCBlock( Context context, { required Type returnType, @@ -131,37 +142,6 @@ ...params, ]); - final funcPtrTrampoline = w.topLevelUniqueNamer.makeUnique( - '_${name}_fnPtrTrampoline', - ); - final closureTrampoline = w.topLevelUniqueNamer.makeUnique( - '_${name}_closureTrampoline', - ); - final funcPtrCallable = w.topLevelUniqueNamer.makeUnique( - '_${name}_fnPtrCallable', - ); - final closureCallable = w.topLevelUniqueNamer.makeUnique( - '_${name}_closureCallable', - ); - final listenerTrampoline = w.topLevelUniqueNamer.makeUnique( - '_${name}_listenerTrampoline', - ); - final listenerCallable = w.topLevelUniqueNamer.makeUnique( - '_${name}_listenerCallable', - ); - final blockingTrampoline = w.topLevelUniqueNamer.makeUnique( - '_${name}_blockingTrampoline', - ); - final blockingCallable = w.topLevelUniqueNamer.makeUnique( - '_${name}_blockingCallable', - ); - final blockingListenerCallable = w.topLevelUniqueNamer.makeUnique( - '_${name}_blockingListenerCallable', - ); - final callExtension = w.topLevelUniqueNamer.makeUnique( - '${name}_CallExtension', - ); - final newPointerBlock = ObjCBuiltInFunctions.newPointerBlock.gen(w); final newClosureBlock = ObjCBuiltInFunctions.newClosureBlock.gen(w); final getBlockClosure = ObjCBuiltInFunctions.getBlockClosure.gen(w); @@ -423,12 +403,8 @@ final listenerWrapper = _blockWrappers!.listenerWrapper.name; final blockingWrapper = _blockWrappers!.blockingWrapper.name; - final listenerName = UniqueNamer.cSafeName( - w.objCLevelUniqueNamer.makeUnique('ListenerTrampoline'), - ); - final blockingName = UniqueNamer.cSafeName( - w.objCLevelUniqueNamer.makeUnique('BlockingTrampoline'), - ); + final listenerName = _blockWrappers!.listenerName; + final blockingName = _blockWrappers!.blockingName; return ''' @@ -474,9 +450,7 @@ final argRecv = argsReceived.join(', '); final argPass = argsPassed.join(', '); final fnName = protocolTrampoline!.func.name; - final block = UniqueNamer.cSafeName( - w.objCLevelUniqueNamer.makeUnique('ProtocolTrampoline'), - ); + final block = protocolTrampoline!.nativeName; final msgSend = '((id (*)(id, SEL, SEL))objc_msgSend)'; final getterSel = '@selector(getDOBJCDartProtocolMethodForSelector:)'; final blkGetter = '(($block)$msgSend(target, $getterSel, sel))'; @@ -491,6 +465,25 @@ '''; } + void fillInternalNames(UniqueNamer topLevelNamer) { + funcPtrTrampoline = topLevelNamer.makeUnique('_${name}_fnPtrTrampoline'); + closureTrampoline = topLevelNamer.makeUnique('_${name}_closureTrampoline'); + funcPtrCallable = topLevelNamer.makeUnique('_${name}_fnPtrCallable'); + closureCallable = topLevelNamer.makeUnique('_${name}_closureCallable'); + listenerTrampoline = topLevelNamer.makeUnique( + '_${name}_listenerTrampoline', + ); + listenerCallable = topLevelNamer.makeUnique('_${name}_listenerCallable'); + blockingTrampoline = topLevelNamer.makeUnique( + '_${name}_blockingTrampoline', + ); + blockingCallable = topLevelNamer.makeUnique('_${name}_blockingCallable'); + blockingListenerCallable = topLevelNamer.makeUnique( + '_${name}_blockingListenerCallable', + ); + callExtension = topLevelNamer.makeUnique('${name}_CallExtension'); + } + @override String getCType(Writer w) => PointerType(objCBlockType).getCType(w);
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart index 9df82af..6ab7721 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
@@ -218,6 +218,8 @@ final Func listenerWrapper; final Func blockingWrapper; bool objCBindingsGenerated = false; + late String listenerName; + late String blockingName; ObjCBlockWrapperFuncs(this.listenerWrapper, this.blockingWrapper); @@ -227,12 +229,22 @@ visitor.visit(listenerWrapper); visitor.visit(blockingWrapper); } + + @override + void visit(Visitation visitation) => + visitation.visitObjCBlockWrapperFuncs(this); + + void fillNativeNames(UniqueNamer objCNamer) { + listenerName = objCNamer.makeUnique('ListenerTrampoline'); + blockingName = objCNamer.makeUnique('BlockingTrampoline'); + } } /// A native trampoline function for a protocol method. class ObjCProtocolMethodTrampoline extends AstNode { final Func func; bool objCBindingsGenerated = false; + late String nativeName; ObjCProtocolMethodTrampoline(this.func); @@ -245,6 +257,10 @@ @override void visit(Visitation visitation) => visitation.visitObjCProtocolMethodTrampoline(this); + + void fillNativeName(UniqueNamer objCNamer) { + nativeName = objCNamer.makeUnique('ProtocolTrampoline'); + } } /// A function, global variable, or helper type defined in package:objective_c.
diff --git a/pkgs/ffigen/lib/src/code_generator/typealias.dart b/pkgs/ffigen/lib/src/code_generator/typealias.dart index 1c5e70c..a4553ff 100644 --- a/pkgs/ffigen/lib/src/code_generator/typealias.dart +++ b/pkgs/ffigen/lib/src/code_generator/typealias.dart
@@ -94,13 +94,6 @@ @override BindingString toBindingString(Writer w) { - if (_ffiDartAliasName != null) { - _ffiDartAliasName = w.topLevelUniqueNamer.makeUnique(_ffiDartAliasName!); - } - if (dartAliasName != null) { - dartAliasName = w.topLevelUniqueNamer.makeUnique(dartAliasName!); - } - final sb = StringBuffer(); sb.write(makeDartDoc(dartDoc)); sb.write('typedef $name = ${type.getCType(w)};\n'); @@ -116,6 +109,15 @@ ); } + void fillAliasNames(UniqueNamer topLevelNamer) { + if (_ffiDartAliasName != null) { + _ffiDartAliasName = w.topLevelUniqueNamer.makeUnique(_ffiDartAliasName!); + } + if (dartAliasName != null) { + dartAliasName = w.topLevelUniqueNamer.makeUnique(dartAliasName!); + } + } + @override Type get typealiasType => type.typealiasType;
diff --git a/pkgs/ffigen/lib/src/code_generator/writer.dart b/pkgs/ffigen/lib/src/code_generator/writer.dart index f35c0c1..5f369f7 100644 --- a/pkgs/ffigen/lib/src/code_generator/writer.dart +++ b/pkgs/ffigen/lib/src/code_generator/writer.dart
@@ -103,19 +103,6 @@ late String _symbolAddressVariableName; late String _symbolAddressLibraryVarName; - /// Initial namers set after running constructor. Namers are reset to this - /// initial state everytime [generate] is called. - late UniqueNamer _initialTopLevelUniqueNamer; - late UniqueNamer _initialWrapperLevelUniqueNamer; - - /// Used by [Binding]s for generating required code. - late UniqueNamer _topLevelUniqueNamer; - UniqueNamer get topLevelUniqueNamer => _topLevelUniqueNamer; - late UniqueNamer _wrapperLevelUniqueNamer; - UniqueNamer get wrapperLevelUniqueNamer => _wrapperLevelUniqueNamer; - late UniqueNamer _objCLevelUniqueNamer; - UniqueNamer get objCLevelUniqueNamer => _objCLevelUniqueNamer; - /// Set true after calling [generate]. Indicates if /// [generateSymbolOutputYamlMap] can be called. bool get canGenerateSymbolOutput => _canGenerateSymbolOutput; @@ -140,8 +127,9 @@ final globalLevelNames = noLookUpBindings.map((e) => e.name); final wrapperLevelNames = lookUpBindings.map((e) => e.name); - _initialTopLevelUniqueNamer = UniqueNamer()..markAllUsed(globalLevelNames); - _initialWrapperLevelUniqueNamer = UniqueNamer() + final initialTopLevelUniqueNamer = UniqueNamer() + ..markAllUsed(globalLevelNames); + final initialWrapperLevelUniqueNamer = UniqueNamer() ..markAllUsed(wrapperLevelNames); final allLevelsUniqueNamer = UniqueNamer() ..markAllUsed(globalLevelNames) @@ -151,7 +139,7 @@ _className = _resolveNameConflict( name: className, makeUnique: allLevelsUniqueNamer, - markUsed: [_initialWrapperLevelUniqueNamer, _initialTopLevelUniqueNamer], + markUsed: [initialWrapperLevelUniqueNamer, initialTopLevelUniqueNamer], ); /// Library imports prefix should be unique unique among all names. @@ -159,35 +147,30 @@ lib.prefix = _resolveNameConflict( name: lib.prefix, makeUnique: allLevelsUniqueNamer, - markUsed: [ - _initialWrapperLevelUniqueNamer, - _initialTopLevelUniqueNamer, - ], + markUsed: [initialWrapperLevelUniqueNamer, initialTopLevelUniqueNamer], ); } /// [_lookupFuncIdentifier] should be unique in top level. _lookupFuncIdentifier = _resolveNameConflict( name: '_lookup', - makeUnique: _initialTopLevelUniqueNamer, + makeUnique: initialTopLevelUniqueNamer, ); /// Resolve name conflicts of identifiers used for SymbolAddresses. _symbolAddressClassName = _resolveNameConflict( name: '_SymbolAddresses', makeUnique: allLevelsUniqueNamer, - markUsed: [_initialWrapperLevelUniqueNamer, _initialTopLevelUniqueNamer], + markUsed: [initialWrapperLevelUniqueNamer, initialTopLevelUniqueNamer], ); _symbolAddressVariableName = _resolveNameConflict( name: 'addresses', - makeUnique: _initialWrapperLevelUniqueNamer, + makeUnique: initialWrapperLevelUniqueNamer, ); _symbolAddressLibraryVarName = _resolveNameConflict( name: '_library', - makeUnique: _initialWrapperLevelUniqueNamer, + makeUnique: initialWrapperLevelUniqueNamer, ); - - _resetUniqueNamers(); } /// Resolved name conflict using [makeUnique] and marks the result as used in @@ -204,15 +187,6 @@ return s; } - /// Resets the namers to initial state. Namers are reset before generating. - void _resetUniqueNamers() { - _topLevelUniqueNamer = UniqueNamer(parent: _initialTopLevelUniqueNamer); - _wrapperLevelUniqueNamer = UniqueNamer( - parent: _initialWrapperLevelUniqueNamer, - ); - _objCLevelUniqueNamer = UniqueNamer(); - } - void markImportUsed(LibraryImport import) { _usedImports.add(import); } @@ -225,9 +199,6 @@ // referenced. Headers and [s] are then combined into the final result. final result = StringBuffer(); - // Reset unique namers to initial state. - _resetUniqueNamers(); - // Reset [usedEnumCTypes]. usedEnumCTypes.clear();
diff --git a/pkgs/ffigen/lib/src/header_parser/parser.dart b/pkgs/ffigen/lib/src/header_parser/parser.dart index 1f88645..f81b671 100644 --- a/pkgs/ffigen/lib/src/header_parser/parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/parser.dart
@@ -212,7 +212,7 @@ final finalBindingsList = finalBindings.toList(); - /// Sort bindings. + // Sort bindings. if (config.sort) { finalBindingsList.sortBy((b) => b.name); for (final b in finalBindingsList) { @@ -220,12 +220,9 @@ } } - /// Handle any declaration-declaration name conflicts and emit warnings. - final declConflictHandler = UniqueNamer(); - for (final b in finalBindingsList) { - _warnIfPrivateDeclaration(b, context.logger); - _resolveIfNameConflicts(declConflictHandler, b, context.logger); - } + // Handle any declaration-declaration name conflicts and emit warnings. + visit(context, TopLevelRenamerVisitation(context), finalBindingsList); + visit(context, MemberRenamerVisitation(context), finalBindingsList); // Override pack values according to config. We do this after declaration // conflicts have been handled so that users can target the generated names. @@ -240,26 +237,3 @@ return finalBindingsList; } - -/// Logs a warning if generated declaration will be private. -void _warnIfPrivateDeclaration(Binding b, Logger logger) { - if (b.name.startsWith('_') && !b.isInternal) { - logger.warning( - "Generated declaration '${b.name}' starts with '_' " - 'and therefore will be private.', - ); - } -} - -/// Resolves name conflict(if any) and logs a warning. -void _resolveIfNameConflicts(UniqueNamer namer, Binding b, Logger logger) { - // Print warning if name was conflicting and has been changed. - final oldName = b.name; - b.name = namer.makeUnique(b.name); - if (oldName != b.name) { - logger.warning( - "Resolved name conflict: Declaration '$oldName' " - "and has been renamed to '${b.name}'.", - ); - } -}
diff --git a/pkgs/ffigen/lib/src/visitor/renamer.dart b/pkgs/ffigen/lib/src/visitor/renamer.dart new file mode 100644 index 0000000..9b1f431 --- /dev/null +++ b/pkgs/ffigen/lib/src/visitor/renamer.dart
@@ -0,0 +1,121 @@ +// Copyright (c) 2025, 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 '../code_generator.dart'; + +import 'ast.dart'; + +class TopLevelRenamerVisitation extends Visitation { + final Context context; + final _topLevelNamer = UniqueNamer(); + final _wrapperLevelNamer = UniqueNamer(); + final _objCNamer = UniqueNamer(); + + TopLevelRenamerVisitation(this.context); + + void _renameName(Binding node) { + final oldName = node.name; + node.name = _topLevelNamer.makeUnique(node.name); + if (oldName != node.name) { + context.logger.warning( + "Resolved name conflict: Declaration '$oldName' " + "and has been renamed to '${node.name}'.", + ); + } + + if (node.name.startsWith('_') && !node.isInternal) { + context.logger.warning( + "Generated declaration '${node.name}' starts with '_' " + 'and therefore will be private.', + ); + } + } + + @override + void visitBinding(Binding node) { + _renameName(node); + node.visitChildren(visitor); + } + + @override + void visitGlobal(Global node) { + _renameName(node); + node.fillPointerName(_wrapperLevelNamer); + node.visitChildren(visitor); + } + + @override + void visitObjCBlockWrapperFuncs(ObjCBlockWrapperFuncs node) { + node.fillNativeNames(_objCNamer); + node.visitChildren(visitor); + } + + @override + void visitObjCProtocolMethodTrampoline(ObjCProtocolMethodTrampoline node) { + node.fillNativeName(_objCNamer); + node.visitChildren(visitor); + } + + @override + void visitObjCBlock(ObjCBlock node) { + _renameName(node); + node.fillInternalNames(_topLevelNamer); + node.visitChildren(visitor); + } + + @override + void visitTypealias(Typealias node) { + _renameName(node); + node.fillAliasNames(_topLevelNamer); + node.visitChildren(visitor); + } +} + +class MemberRenamerVisitation extends Visitation { + final Context context; + + MemberRenamerVisitation(this.context); + + @override + void visitCompound(Compound node) { + node.renameMembers(); + node.visitChildren(visitor); + } + + @override + void visitEnumClass(EnumClass node) { + // TODO + node.visitChildren(visitor); + } + + @override + void visitFunc(Func node) { + // TODO + node.visitChildren(visitor); + } + + @override + void visitFuncType(FuncType node) { + // TODO + node.visitChildren(visitor); + } + + @override + void visitObjCInterface(ObjCInterface node) { + // TODO + node.visitChildren(visitor); + } + + @override + void visitObjCCategory(ObjCCategory node) { + // TODO + node.visitChildren(visitor); + } + + @override + void visitObjCProtocol(ObjCProtocol node) { + // TODO + node.visitChildren(visitor); + } +}
diff --git a/pkgs/ffigen/lib/src/visitor/visitor.dart b/pkgs/ffigen/lib/src/visitor/visitor.dart index 8be80d0..e955c53 100644 --- a/pkgs/ffigen/lib/src/visitor/visitor.dart +++ b/pkgs/ffigen/lib/src/visitor/visitor.dart
@@ -82,11 +82,14 @@ void visitGlobal(Global node) => visitLookUpBinding(node); void visitTypealias(Typealias node) => visitBindingType(node); void visitPointerType(PointerType node) => visitType(node); + void visitFuncType(FuncType node) => visitType(node); + void visitObjCBlockWrapperFuncs(ObjCBlockWrapperFuncs node) => + visitAstNode(node); void visitObjCProtocolMethodTrampoline(ObjCProtocolMethodTrampoline node) => visitAstNode(node); /// Default behavior for all visit methods. - void visitAstNode(AstNode node) => node..visitChildren(visitor); + void visitAstNode(AstNode node) => node.visitChildren(visitor); } T visit<T extends Visitation>(
diff --git a/pkgs/ffigen/test/unit_tests/objc_inheritance_edge_case_test.dart b/pkgs/ffigen/test/unit_tests/objc_inheritance_edge_case_test.dart new file mode 100644 index 0000000..4fcab48 --- /dev/null +++ b/pkgs/ffigen/test/unit_tests/objc_inheritance_edge_case_test.dart
@@ -0,0 +1,130 @@ +// Copyright (c) 2025, 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 'package:ffigen/src/context.dart'; +import 'package:ffigen/src/code_generator.dart'; +import 'package:ffigen/src/code_generator/unique_namer.dart'; +import 'package:ffigen/src/config_provider/config.dart'; +import 'package:ffigen/src/config_provider/config_types.dart'; +import 'package:ffigen/src/header_parser/parser.dart'; +import 'package:ffigen/src/header_parser/sub_parsers/api_availability.dart'; +import 'package:logging/logging.dart'; +import 'package:test/test.dart'; + +import '../test_utils.dart'; + +void main() { + group('ObjC inheritance edge cases', () { + final builtInFunctions = ObjCBuiltInFunctions('', false); + final availability = ApiAvailability( + externalVersions: const ExternalVersions(), + ); + final config = FfiGen( + Logger.root, + output: Uri.file('unused'), + objcInterfaces: DeclarationFilters.includeAll, + ); + final context = testContext(config); + final voidType = NativeType(SupportedNativeType.voidType); + final intType = NativeType(SupportedNativeType.int32); + final instanceType = Typealias( + name: 'instancetype', + type: ObjCObjectPointer(), + ); + + ObjCInterface makeInterface( + String name, + ObjCInterface? superType, + List<ObjCMethod> methods, + ) { + final itf = ObjCInterface( + context: context, + usr: name, + originalName: name, + apiAvailability: availability, + ); + if (superType != null) { + itf.superType = superType; + superType.subtypes.add(itf); + } + for (final m in methods) itf.addMethod(m); + itf.filled = true; + return itf; + } + + ObjCMethod makeMethod( + String name, + Type returnType, + List<Parameter> params, { + bool isClassMethod = false, + }) => ObjCMethod( + context: context, + originalName: name, + name: name, + kind: ObjCMethodKind.method, + isClassMethod: isClassMethod, + isOptional: false, + returnType: returnType, + family: null, + apiAvailability: availability, + params_: params, + )..finalizeParams(); + + Parameter makeParam(String name, Type type) => + Parameter(name: name, type: type, objCConsumed: false); + + test('simple method inheritance', () { + final ordinaryMethod = makeMethod('m1', voidType, []); + final staticMethod = makeMethod('m2', voidType, [], isClassMethod: true); + final instanceTypeMethod = makeMethod('m3', instanceType, []); + + final parent = makeInterface('Parent', null, [ + ordinaryMethod, + staticMethod, + instanceTypeMethod, + ]); + final child = makeInterface('Child', parent, []); + + final bindings = transformBindings([parent, child], context); + + expect(bindings, contains(parent)); + expect(bindings, contains(child)); + + expect(child.methods, isNot(contains(ordinaryMethod))); + expect(child.methods, contains(staticMethod)); + expect(child.methods, contains(instanceTypeMethod)); + }); + + test('inherited method renaming', () { + final parentMethod = makeMethod('method', instanceType, []); + final childAMethod = makeMethod('method:', instanceType, [ + makeParam('a', intType), + ]); + final childBMethod = makeMethod('method:b:', instanceType, [ + makeParam('a', intType), + makeParam('b', intType), + ]); + + final parent = makeInterface('Parent', null, [parentMethod]); + final childA = makeInterface('ChildA', parent, [childAMethod]); + final childB = makeInterface('ChildB', parent, [childBMethod]); + + final bindings = transformBindings([parent, childA, childB], context); + + expect(bindings, contains(parent)); + expect(bindings, contains(childA)); + expect(bindings, contains(childB)); + + expect(childA.methods, contains(parentMethod)); + expect(childA.methods, contains(childAMethod)); + expect(childB.methods, contains(parentMethod)); + expect(childB.methods, contains(childBMethod)); + + final namer = UniqueNamer(); + print('ZXCV: ${parentMethod.getDartMethodName(namer)}'); + print('ZXCV: ${childAMethod.getDartMethodName(namer)}'); + print('ZXCV: ${childBMethod.getDartMethodName(namer)}'); + }); + }); +}