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)}');
+    });
+  });
+}