[ffigen] BindingsIndex refactor (#283)

Unify and simplify how Types are cached and cycles are broken in the type graph.

- All types that need to be cached (those that have a declaration cursor) are cached in `BindingsIndex._declaredTypes`
- All calls to convert a `CXType` to an ffigen `Type` now go through `getCodeGenType`
- All calls to the related sub-parsers (`parseCompoundDeclaration`, `parseEnumDeclaration`, `parseTypedefDeclaration`) also go through `getCodeGenType`
- Those sub-parsers (mostly) don't know about `BindingsIndex`, and don't know about caching or cycle breaking. That's all handled in one place in `getCodeGenType`
- Cycles are broken by dividing type conversion into 2 stages (the only conversion this matters for is compound types):
    - First the `Type` is created, with as many fields filled in as possible, without a recursive call to `getCodeGenType` (everything except the list of members)
    - Next, `getCodeGenType` inserts the type into the cache
    - Finally, the members are filled in. Recursive calls to `getCodeGenType` are allowed here, as they will hit the cache, breaking any cycles.
diff --git a/pkgs/ffigen/example/libclang-example/generated_bindings.dart b/pkgs/ffigen/example/libclang-example/generated_bindings.dart
index 125e78c..6f73232 100644
--- a/pkgs/ffigen/example/libclang-example/generated_bindings.dart
+++ b/pkgs/ffigen/example/libclang-example/generated_bindings.dart
@@ -9296,8 +9296,6 @@
 typedef DartClang_Cursor_getTranslationUnit = CXTranslationUnit Function(
     CXCursor arg0);
 
-class CXCursorSetImpl extends ffi.Opaque {}
-
 /// A fast container representing a set of CXCursors.
 typedef CXCursorSet = ffi.Pointer<custom_import.CXCursorSetImpl>;
 typedef NativeClang_createCXCursorSet = CXCursorSet Function();
diff --git a/pkgs/ffigen/lib/src/header_parser/includer.dart b/pkgs/ffigen/lib/src/header_parser/includer.dart
index 9211d5f..aaad693 100644
--- a/pkgs/ffigen/lib/src/header_parser/includer.dart
+++ b/pkgs/ffigen/lib/src/header_parser/includer.dart
@@ -20,22 +20,22 @@
 
 bool shouldIncludeStruct(String usr, String name) {
   return _shouldIncludeDecl(
-      usr, name, bindingsIndex.isSeenStruct, config.structDecl.shouldInclude);
+      usr, name, bindingsIndex.isSeenType, config.structDecl.shouldInclude);
 }
 
 bool shouldIncludeUnion(String usr, String name) {
   return _shouldIncludeDecl(
-      usr, name, bindingsIndex.isSeenUnion, config.unionDecl.shouldInclude);
+      usr, name, bindingsIndex.isSeenType, config.unionDecl.shouldInclude);
 }
 
 bool shouldIncludeFunc(String usr, String name) {
   return _shouldIncludeDecl(
-      usr, name, bindingsIndex.isSeenFunc, config.functionDecl.shouldInclude);
+      usr, name, bindingsIndex.isSeenType, config.functionDecl.shouldInclude);
 }
 
 bool shouldIncludeEnumClass(String usr, String name) {
-  return _shouldIncludeDecl(usr, name, bindingsIndex.isSeenEnumClass,
-      config.enumClassDecl.shouldInclude);
+  return _shouldIncludeDecl(
+      usr, name, bindingsIndex.isSeenType, config.enumClassDecl.shouldInclude);
 }
 
 bool shouldIncludeUnnamedEnumConstant(String usr, String name) {
@@ -55,7 +55,7 @@
 
 bool shouldIncludeTypealias(String usr, String name) {
   return _shouldIncludeDecl(
-      usr, name, bindingsIndex.isSeenTypealias, config.typedefs.shouldInclude);
+      usr, name, bindingsIndex.isSeenType, config.typedefs.shouldInclude);
 }
 
 /// True if a cursor should be included based on headers config, used on root
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/compounddecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
index 431334a..f1eb651 100644
--- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
+++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
@@ -18,13 +18,15 @@
 
 /// Holds temporary information regarding [compound] while parsing.
 class _ParsedCompound {
-  Compound? compound;
+  Compound compound;
   bool unimplementedMemberType = false;
   bool flexibleArrayMember = false;
   bool bitFieldMember = false;
   bool dartHandleMember = false;
   bool incompleteCompoundMember = false;
 
+  _ParsedCompound(this.compound);
+
   bool get isInComplete =>
       unimplementedMemberType ||
       flexibleArrayMember ||
@@ -40,32 +42,30 @@
   // Stores the maximum alignment from all the children.
   int maxChildAlignment = 0;
   // Alignment of this struct.
-  int allignment = 0;
+  int alignment = 0;
 
   bool get _isPacked {
     if (!hasAttr || isInComplete) return false;
     if (hasPackedAttr) return true;
 
-    return maxChildAlignment > allignment;
+    return maxChildAlignment > alignment;
   }
 
   /// Returns pack value of a struct depending on config, returns null for no
   /// packing.
   int? get packValue {
-    if (compound!.isStruct && _isPacked) {
-      if (strings.packingValuesMap.containsKey(allignment)) {
-        return allignment;
+    if (compound.isStruct && _isPacked) {
+      if (strings.packingValuesMap.containsKey(alignment)) {
+        return alignment;
       } else {
         _logger.warning(
-            'Unsupported pack value "$allignment" for Struct "${compound!.name}".');
+            'Unsupported pack value "$alignment" for Struct "${compound.name}".');
         return null;
       }
     } else {
       return null;
     }
   }
-
-  _ParsedCompound();
 }
 
 final _stack = Stack<_ParsedCompound>();
@@ -83,31 +83,18 @@
   /// generate these as opaque if `dependency-only` was set to opaque).
   bool pointerReference = false,
 }) {
-  _stack.push(_ParsedCompound());
-
   // Set includer functions according to compoundType.
   final bool Function(String, String) shouldIncludeDecl;
-  final bool Function(String) isSeenDecl;
-  final Compound? Function(String) getSeenDecl;
-  final void Function(String, Compound) addDeclToSeen;
   final Declaration configDecl;
-  final String className;
+  final String className = _compoundTypeDebugName(compoundType);
   switch (compoundType) {
     case CompoundType.struct:
       shouldIncludeDecl = shouldIncludeStruct;
-      isSeenDecl = bindingsIndex.isSeenStruct;
-      getSeenDecl = bindingsIndex.getSeenStruct;
-      addDeclToSeen = bindingsIndex.addStructToSeen;
       configDecl = config.structDecl;
-      className = 'Struct';
       break;
     case CompoundType.union:
       shouldIncludeDecl = shouldIncludeUnion;
-      isSeenDecl = bindingsIndex.isSeenUnion;
-      getSeenDecl = bindingsIndex.getSeenUnion;
-      addDeclToSeen = bindingsIndex.addUnionToSeen;
       configDecl = config.unionDecl;
-      className = 'Union';
       break;
   }
 
@@ -134,110 +121,110 @@
     if (ignoreFilter) {
       // This declaration is defined inside some other declaration and hence
       // must be generated.
-      _stack.top.compound = Compound.fromType(
+      return Compound.fromType(
         type: compoundType,
         name: incrementalNamer.name('Unnamed$className'),
         usr: declUsr,
         dartDoc: getCursorDocComment(cursor),
       );
-      _setMembers(cursor, className);
     } else {
       _logger.finest('unnamed $className declaration');
     }
-  } else if ((ignoreFilter || shouldIncludeDecl(declUsr, declName)) &&
-      (!isSeenDecl(declUsr))) {
+  } else if (ignoreFilter || shouldIncludeDecl(declUsr, declName)) {
     _logger.fine(
         '++++ Adding $className: Name: $declName, ${cursor.completeStringRepr()}');
-    _stack.top.compound = Compound.fromType(
+    return Compound.fromType(
       type: compoundType,
       usr: declUsr,
       originalName: declName,
       name: configDecl.renameUsingConfig(declName),
       dartDoc: getCursorDocComment(cursor),
     );
-    // Adding to seen here to stop recursion if a declaration has itself as a
-    // member, members are updated later.
-    addDeclToSeen(declUsr, _stack.top.compound!);
   }
-
-  if (isSeenDecl(declUsr)) {
-    _stack.top.compound = getSeenDecl(declUsr);
-
-    // Skip dependencies if already seen OR user has specified `dependency-only`
-    // as opaque AND this is a pointer reference AND the declaration was not
-    // included according to config (ignoreFilter).
-    final skipDependencies = _stack.top.compound!.parsedDependencies ||
-        (pointerReference &&
-            ignoreFilter &&
-            ((compoundType == CompoundType.struct &&
-                    config.structDependencies == CompoundDependencies.opaque) ||
-                (compoundType == CompoundType.union &&
-                    config.unionDependencies == CompoundDependencies.opaque)));
-
-    if (!skipDependencies) {
-      // Prevents infinite recursion if struct has a pointer to itself.
-      _stack.top.compound!.parsedDependencies = true;
-      _setMembers(cursor, className);
-    } else if (!_stack.top.compound!.parsedDependencies) {
-      _logger.fine('Skipped dependencies.');
-    }
-  }
-
-  return _stack.pop().compound;
+  return null;
 }
 
-void _setMembers(clang_types.CXCursor cursor, String className) {
-  _stack.top.hasAttr = clang.clang_Cursor_hasAttrs(cursor) != 0;
-  _stack.top.allignment = cursor.type().alignment();
+void fillCompoundMembersIfNeeded(
+  Compound compound,
+  clang_types.CXCursor cursor, {
 
+  /// Option to ignore declaration filter (Useful in case of extracting
+  /// declarations when they are passed/returned by an included function.)
+  bool ignoreFilter = false,
+
+  /// To track if the declaration was used by reference(i.e T*). (Used to only
+  /// generate these as opaque if `dependency-only` was set to opaque).
+  bool pointerReference = false,
+}) {
+  final compoundType = compound.compoundType;
+
+  // Skip dependencies if already seen OR user has specified `dependency-only`
+  // as opaque AND this is a pointer reference AND the declaration was not
+  // included according to config (ignoreFilter).
+  final skipDependencies = compound.parsedDependencies ||
+      (pointerReference &&
+          ignoreFilter &&
+          ((compoundType == CompoundType.struct &&
+                  config.structDependencies == CompoundDependencies.opaque) ||
+              (compoundType == CompoundType.union &&
+                  config.unionDependencies == CompoundDependencies.opaque)));
+  if (skipDependencies) return;
+
+  final parsed = _ParsedCompound(compound);
+  final String className = _compoundTypeDebugName(compoundType);
+  parsed.hasAttr = clang.clang_Cursor_hasAttrs(cursor) != 0;
+  parsed.alignment = cursor.type().alignment();
+  compound.parsedDependencies = true; // Break cycles.
+
+  _stack.push(parsed);
   final resultCode = clang.clang_visitChildren(
     cursor,
     Pointer.fromFunction(_compoundMembersVisitor, exceptional_visitor_return),
     nullptr,
   );
+  _stack.pop();
 
   _logger.finest(
-      'Opaque: ${_stack.top.isInComplete}, HasAttr: ${_stack.top.hasAttr}, AlignValue: ${_stack.top.allignment}, MaxChildAlignValue: ${_stack.top.maxChildAlignment}, PackValue: ${_stack.top.packValue}.');
-  _stack.top.compound!.pack = _stack.top.packValue;
+      'Opaque: ${parsed.isInComplete}, HasAttr: ${parsed.hasAttr}, AlignValue: ${parsed.alignment}, MaxChildAlignValue: ${parsed.maxChildAlignment}, PackValue: ${parsed.packValue}.');
+  compound.pack = parsed.packValue;
 
   visitChildrenResultChecker(resultCode);
 
-  if (_stack.top.unimplementedMemberType) {
+  if (parsed.unimplementedMemberType) {
     _logger.fine(
         '---- Removed $className members, reason: member with unimplementedtype ${cursor.completeStringRepr()}');
     _logger.warning(
-        'Removed All $className Members from ${_stack.top.compound!.name}(${_stack.top.compound!.originalName}), struct member has an unsupported type.');
-  } else if (_stack.top.flexibleArrayMember) {
+        'Removed All $className Members from ${compound.name}(${compound.originalName}), struct member has an unsupported type.');
+  } else if (parsed.flexibleArrayMember) {
     _logger.fine(
         '---- Removed $className members, reason: incomplete array member ${cursor.completeStringRepr()}');
     _logger.warning(
-        'Removed All $className Members from ${_stack.top.compound!.name}(${_stack.top.compound!.originalName}), Flexible array members not supported.');
-  } else if (_stack.top.bitFieldMember) {
+        'Removed All $className Members from ${compound.name}(${compound.originalName}), Flexible array members not supported.');
+  } else if (parsed.bitFieldMember) {
     _logger.fine(
         '---- Removed $className members, reason: bitfield members ${cursor.completeStringRepr()}');
     _logger.warning(
-        'Removed All $className Members from ${_stack.top.compound!.name}(${_stack.top.compound!.originalName}), Bit Field members not supported.');
-  } else if (_stack.top.dartHandleMember && config.useDartHandle) {
+        'Removed All $className Members from ${compound.name}(${compound.originalName}), Bit Field members not supported.');
+  } else if (parsed.dartHandleMember && config.useDartHandle) {
     _logger.fine(
         '---- Removed $className members, reason: Dart_Handle member. ${cursor.completeStringRepr()}');
     _logger.warning(
-        'Removed All $className Members from ${_stack.top.compound!.name}(${_stack.top.compound!.originalName}), Dart_Handle member not supported.');
-  } else if (_stack.top.incompleteCompoundMember) {
+        'Removed All $className Members from ${compound.name}(${compound.originalName}), Dart_Handle member not supported.');
+  } else if (parsed.incompleteCompoundMember) {
     _logger.fine(
         '---- Removed $className members, reason: Incomplete Nested Struct member. ${cursor.completeStringRepr()}');
     _logger.warning(
-        'Removed All $className Members from ${_stack.top.compound!.name}(${_stack.top.compound!.originalName}), Incomplete Nested Struct member not supported.');
+        'Removed All $className Members from ${compound.name}(${compound.originalName}), Incomplete Nested Struct member not supported.');
   }
 
   // Clear all members if declaration is incomplete.
-  if (_stack.top.isInComplete) {
-    _stack.top.compound!.members.clear();
+  if (parsed.isInComplete) {
+    compound.members.clear();
   }
 
   // C allows empty structs/union, but it's undefined behaviour at runtine.
   // So we need to mark a declaration incomplete if it has no members.
-  _stack.top.compound!.isInComplete =
-      _stack.top.isInComplete || _stack.top.compound!.members.isEmpty;
+  compound.isInComplete = parsed.isInComplete || compound.members.isEmpty;
 }
 
 /// Visitor for the struct/union cursor [CXCursorKind.CXCursor_StructDecl]/
@@ -246,36 +233,37 @@
 /// Child visitor invoked on struct/union cursor.
 int _compoundMembersVisitor(clang_types.CXCursor cursor,
     clang_types.CXCursor parent, Pointer<Void> clientData) {
+  final parsed = _stack.top;
   try {
     if (cursor.kind == clang_types.CXCursorKind.CXCursor_FieldDecl) {
       _logger.finer('===== member: ${cursor.completeStringRepr()}');
 
       // Set maxChildAlignValue.
       final align = cursor.type().alignment();
-      if (align > _stack.top.maxChildAlignment) {
-        _stack.top.maxChildAlignment = align;
+      if (align > parsed.maxChildAlignment) {
+        parsed.maxChildAlignment = align;
       }
 
       final mt = cursor.type().toCodeGenType();
       if (mt.broadType == BroadType.IncompleteArray) {
         // TODO(68): Structs with flexible Array Members are not supported.
-        _stack.top.flexibleArrayMember = true;
+        parsed.flexibleArrayMember = true;
       }
       if (clang.clang_getFieldDeclBitWidth(cursor) != -1) {
         // TODO(84): Struct with bitfields are not suppoorted.
-        _stack.top.bitFieldMember = true;
+        parsed.bitFieldMember = true;
       }
       if (mt.broadType == BroadType.Handle) {
-        _stack.top.dartHandleMember = true;
+        parsed.dartHandleMember = true;
       }
       if (mt.isIncompleteCompound) {
-        _stack.top.incompleteCompoundMember = true;
+        parsed.incompleteCompoundMember = true;
       }
       if (mt.getBaseType().broadType == BroadType.Unimplemented) {
-        _stack.top.unimplementedMemberType = true;
+        parsed.unimplementedMemberType = true;
       }
 
-      _stack.top.compound!.members.add(
+      parsed.compound.members.add(
         Member(
           dartDoc: getCursorDocComment(
             cursor,
@@ -283,14 +271,14 @@
           ),
           originalName: cursor.spelling(),
           name: config.structDecl.renameMemberUsingConfig(
-            _stack.top.compound!.originalName,
+            parsed.compound.originalName,
             cursor.spelling(),
           ),
           type: mt,
         ),
       );
     } else if (cursor.kind == clang_types.CXCursorKind.CXCursor_PackedAttr) {
-      _stack.top.hasPackedAttr = true;
+      parsed.hasPackedAttr = true;
     }
   } catch (e, s) {
     _logger.severe(e);
@@ -299,3 +287,7 @@
   }
   return clang_types.CXChildVisitResult.CXChildVisit_Continue;
 }
+
+String _compoundTypeDebugName(CompoundType compoundType) {
+  return compoundType == CompoundType.struct ? "Struct" : "Union";
+}
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/enumdecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/enumdecl_parser.dart
index c4b2ab8..4e5e150 100644
--- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/enumdecl_parser.dart
+++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/enumdecl_parser.dart
@@ -54,8 +54,7 @@
   if (enumName.isEmpty) {
     _logger.fine('Saving anonymous enum.');
     saveUnNamedEnum(cursor);
-  } else if ((ignoreFilter || shouldIncludeEnumClass(enumUsr, enumName)) &&
-      (!bindingsIndex.isSeenEnumClass(enumUsr))) {
+  } else if (ignoreFilter || shouldIncludeEnumClass(enumUsr, enumName)) {
     _logger.fine('++++ Adding Enum: ${cursor.completeStringRepr()}');
     _stack.top.enumClass = EnumClass(
       usr: enumUsr,
@@ -63,12 +62,8 @@
       originalName: enumName,
       name: config.enumClassDecl.renameUsingConfig(enumName),
     );
-    bindingsIndex.addEnumClassToSeen(enumUsr, _stack.top.enumClass!);
     _addEnumConstant(cursor);
   }
-  if (bindingsIndex.isSeenEnumClass(enumUsr)) {
-    _stack.top.enumClass = bindingsIndex.getSeenEnumClass(enumUsr);
-  }
 
   return _stack.pop().enumClass;
 }
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
index 3b8cd63..870cf47 100644
--- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
+++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
@@ -39,9 +39,8 @@
           '---- Removed Function, reason: inline function: ${cursor.completeStringRepr()}');
       _logger.warning(
           "Skipped Function '$funcName', inline functions are not supported.");
-      return _stack
-          .pop()
-          .func; // Returning null so that [addToBindings] function excludes this.
+      // Returning null so that [addToBindings] function excludes this.
+      return _stack.pop().func;
     }
 
     if (rt.isIncompleteCompound || _stack.top.incompleteStructParameter) {
@@ -49,9 +48,8 @@
           '---- Removed Function, reason: Incomplete struct pass/return by value: ${cursor.completeStringRepr()}');
       _logger.warning(
           "Skipped Function '$funcName', Incomplete struct pass/return by value not supported.");
-      return _stack
-          .pop()
-          .func; // Returning null so that [addToBindings] function excludes this.
+      // Returning null so that [addToBindings] function excludes this.
+      return _stack.pop().func;
     }
 
     if (rt.getBaseType().broadType == BroadType.Unimplemented ||
@@ -60,9 +58,8 @@
           '---- Removed Function, reason: unsupported return type or parameter type: ${cursor.completeStringRepr()}');
       _logger.warning(
           "Skipped Function '$funcName', function has unsupported return type or parameter type.");
-      return _stack
-          .pop()
-          .func; // Returning null so that [addToBindings] function excludes this.
+      // Returning null so that [addToBindings] function excludes this.
+      return _stack.pop().func;
     }
 
     _stack.top.func = Func(
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/typedefdecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/typedefdecl_parser.dart
index 90d5664..d8f84cf 100644
--- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/typedefdecl_parser.dart
+++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/typedefdecl_parser.dart
@@ -13,8 +13,7 @@
 
 final _logger = Logger('ffigen.header_parser.typedefdecl_parser');
 
-/// Holds temporary information regarding a typedef referenced [Binding]
-/// while parsing.
+/// Parses a typedef declaration.
 ///
 /// Notes:
 /// - Pointer to Typedefs structs are skipped if the struct is seen.
@@ -30,16 +29,6 @@
 ///
 /// typedef A D; // Typeref.
 /// ```
-class _ParsedTypealias {
-  Typealias? typealias;
-  String? typedefName;
-  bool typedefToPointer = false;
-  _ParsedTypealias();
-}
-
-final _stack = Stack<_ParsedTypealias>();
-
-/// Parses a typedef declaration.
 ///
 /// Returns `null` if the typedef could not be generated or has been excluded
 /// by the config.
@@ -47,7 +36,6 @@
   clang_types.CXCursor cursor, {
   bool pointerReference = false,
 }) {
-  _stack.push(_ParsedTypealias());
   final typedefName = cursor.spelling();
   final typedefUsr = cursor.usr();
   if (shouldIncludeTypealias(typedefUsr, typedefName)) {
@@ -85,20 +73,14 @@
       bindingsIndex.addUnsupportedTypealiasToSeen(typedefUsr);
     } else {
       // Create typealias.
-      _stack.top.typealias = Typealias(
+      return Typealias(
         usr: typedefUsr,
         originalName: typedefName,
         name: config.typedefs.renameUsingConfig(typedefName),
         type: s,
         dartDoc: getCursorDocComment(cursor),
       );
-      bindingsIndex.addTypealiasToSeen(typedefUsr, _stack.top.typealias!);
     }
   }
-
-  if (bindingsIndex.isSeenTypealias(typedefUsr)) {
-    _stack.top.typealias = bindingsIndex.getSeenTypealias(typedefUsr);
-  }
-
-  return _stack.pop().typealias;
+  return null;
 }
diff --git a/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart b/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart
index 9a576c0..60c623e 100644
--- a/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart
+++ b/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart
@@ -12,9 +12,8 @@
 import 'clang_bindings/clang_bindings.dart' as clang_types;
 import 'data.dart';
 import 'includer.dart';
-import 'sub_parsers/compounddecl_parser.dart';
-import 'sub_parsers/enumdecl_parser.dart';
 import 'sub_parsers/functiondecl_parser.dart';
+import 'type_extractor/extractor.dart';
 import 'utils.dart';
 
 final _logger = Logger('ffigen.header_parser.translation_unit_parser');
@@ -46,13 +45,11 @@
           addToBindings(parseFunctionDeclaration(cursor));
           break;
         case clang_types.CXCursorKind.CXCursor_StructDecl:
-          addToBindings(parseCompoundDeclaration(cursor, CompoundType.struct));
-          break;
         case clang_types.CXCursorKind.CXCursor_UnionDecl:
-          addToBindings(parseCompoundDeclaration(cursor, CompoundType.union));
+          addToBindings(_getCodeGenTypeFromCursor(cursor)?.compound);
           break;
         case clang_types.CXCursorKind.CXCursor_EnumDecl:
-          addToBindings(parseEnumDeclaration(cursor));
+          addToBindings(_getCodeGenTypeFromCursor(cursor)?.enumClass);
           break;
         case clang_types.CXCursorKind.CXCursor_MacroDefinition:
           saveMacroDefinition(cursor);
@@ -82,3 +79,7 @@
     _bindings.add(b);
   }
 }
+
+Type? _getCodeGenTypeFromCursor(clang_types.CXCursor cursor) {
+  return getCodeGenType(cursor.type(), ignoreFilter: false);
+}
diff --git a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
index 6063035..8ea5d96 100644
--- a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
+++ b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
@@ -22,13 +22,47 @@
 Type getCodeGenType(
   clang_types.CXType cxtype, {
 
+  /// Option to ignore declaration filter (Useful in case of extracting
+  /// declarations when they are passed/returned by an included function.)
+  bool ignoreFilter = true,
+
   /// Passed on if a value was marked as a pointer before this one.
   bool pointerReference = false,
 }) {
   _logger.fine('${_padding}getCodeGenType ${cxtype.completeStringRepr()}');
-  final kind = cxtype.kind;
 
-  switch (kind) {
+  // Special case: Elaborated types just refer to another type.
+  if (cxtype.kind == clang_types.CXTypeKind.CXType_Elaborated) {
+    return getCodeGenType(clang.clang_Type_getNamedType(cxtype),
+        ignoreFilter: ignoreFilter, pointerReference: pointerReference);
+  }
+
+  // If the type has a declaration cursor, then use the BindingsIndex to break
+  // any potential cycles, and dedupe the Type.
+  final cursor = clang.clang_getTypeDeclaration(cxtype);
+  if (cursor.kind != clang_types.CXCursorKind.CXCursor_NoDeclFound) {
+    final usr = cursor.usr();
+    var type = bindingsIndex.getSeenType(usr);
+    if (type == null) {
+      final result =
+          _createTypeFromCursor(cxtype, cursor, ignoreFilter, pointerReference);
+      type = result.type;
+      if (type == null) {
+        return Type.unimplemented(
+            'Type: ${cxtype.kindSpelling()} not implemented');
+      }
+      if (result.addToCache) {
+        bindingsIndex.addTypeToSeen(usr, type);
+      }
+    }
+    _fillFromCursorIfNeeded(type, cursor, ignoreFilter, pointerReference);
+    return type;
+  }
+
+  // If the type doesn't have a declaration cursor, then it's a basic type such
+  // as int, or a simple derived type like a pointer, so doesn't need to be
+  // cached.
+  switch (cxtype.kind) {
     case clang_types.CXTypeKind.CXType_Pointer:
       final pt = clang.clang_getPointeeType(cxtype);
       final s = getCodeGenType(pt, pointerReference: true);
@@ -41,67 +75,6 @@
         return Type.handle();
       }
       return Type.pointer(s);
-    case clang_types.CXTypeKind.CXType_Typedef:
-      final spelling = clang.clang_getTypedefName(cxtype).toStringAndDispose();
-      if (config.typedefTypeMappings.containsKey(spelling)) {
-        _logger.fine('  Type $spelling mapped from type-map');
-        return Type.importedType(config.typedefTypeMappings[spelling]!);
-      }
-      // Get name from supported typedef name if config allows.
-      if (config.useSupportedTypedefs) {
-        if (suportedTypedefToSuportedNativeType.containsKey(spelling)) {
-          _logger.fine('  Type Mapped from supported typedef');
-          return Type.nativeType(
-              suportedTypedefToSuportedNativeType[spelling]!);
-        } else if (supportedTypedefToImportedType.containsKey(spelling)) {
-          _logger.fine('  Type Mapped from supported typedef');
-          return Type.importedType(supportedTypedefToImportedType[spelling]!);
-        }
-      }
-
-      // This is important or we get stuck in infinite recursion.
-      final cursor = clang.clang_getTypeDeclaration(cxtype);
-      final typedefUsr = cursor.usr();
-
-      if (bindingsIndex.isSeenTypealias(typedefUsr)) {
-        return Type.typealias(bindingsIndex.getSeenTypealias(typedefUsr)!);
-      } else {
-        final typealias =
-            parseTypedefDeclaration(cursor, pointerReference: pointerReference);
-
-        if (typealias != null) {
-          return Type.typealias(typealias);
-        } else {
-          // Use underlying type if typealias couldn't be created or if
-          // the user excluded this typedef.
-          final ct = clang.clang_getTypedefDeclUnderlyingType(cursor);
-          return getCodeGenType(ct, pointerReference: pointerReference);
-        }
-      }
-    case clang_types.CXTypeKind.CXType_Elaborated:
-      final et = clang.clang_Type_getNamedType(cxtype);
-      final s = getCodeGenType(et, pointerReference: pointerReference);
-      return s;
-    case clang_types.CXTypeKind.CXType_Record:
-      return _extractfromRecord(cxtype, pointerReference);
-    case clang_types.CXTypeKind.CXType_Enum:
-      final cursor = clang.clang_getTypeDeclaration(cxtype);
-      final usr = cursor.usr();
-
-      if (bindingsIndex.isSeenEnumClass(usr)) {
-        return Type.enumClass(bindingsIndex.getSeenEnumClass(usr)!);
-      } else {
-        final enumClass = parseEnumDeclaration(
-          cursor,
-          ignoreFilter: true,
-        );
-        if (enumClass == null) {
-          // Handle anonymous enum declarations within another declaration.
-          return Type.nativeType(Type.enumNativeType);
-        } else {
-          return Type.enumClass(enumClass);
-        }
-      }
     case clang_types.CXTypeKind.CXType_FunctionProto:
       // Primarily used for function pointers.
       return _extractFromFunctionProto(cxtype);
@@ -141,34 +114,105 @@
   }
 }
 
-Type _extractfromRecord(clang_types.CXType cxtype, bool pointerReference) {
-  Type type;
+class _CreateTypeFromCursorResult {
+  final Type? type;
 
-  final cursor = clang.clang_getTypeDeclaration(cxtype);
+  // Flag that controls whether the type is added to the cache. It should not
+  // be added to the cache if it's just a fallback implementation, such as the
+  // int that is returned when an enum is excluded by the config. Later we might
+  // need to build the full enum type (eg if it's part of an included struct),
+  // and if we put the fallback int in the cache then the full enum will never
+  // be created.
+  final bool addToCache;
+
+  _CreateTypeFromCursorResult(this.type, {this.addToCache = true});
+}
+
+_CreateTypeFromCursorResult _createTypeFromCursor(clang_types.CXType cxtype,
+    clang_types.CXCursor cursor, bool ignoreFilter, bool pointerReference) {
+  switch (cxtype.kind) {
+    case clang_types.CXTypeKind.CXType_Typedef:
+      final spelling = clang.clang_getTypedefName(cxtype).toStringAndDispose();
+      if (config.typedefTypeMappings.containsKey(spelling)) {
+        _logger.fine('  Type $spelling mapped from type-map');
+        return _CreateTypeFromCursorResult(
+            Type.importedType(config.typedefTypeMappings[spelling]!));
+      }
+      // Get name from supported typedef name if config allows.
+      if (config.useSupportedTypedefs) {
+        if (suportedTypedefToSuportedNativeType.containsKey(spelling)) {
+          _logger.fine('  Type Mapped from supported typedef');
+          return _CreateTypeFromCursorResult(
+              Type.nativeType(suportedTypedefToSuportedNativeType[spelling]!));
+        } else if (supportedTypedefToImportedType.containsKey(spelling)) {
+          _logger.fine('  Type Mapped from supported typedef');
+          return _CreateTypeFromCursorResult(
+              Type.importedType(supportedTypedefToImportedType[spelling]!));
+        }
+      }
+
+      final typealias =
+          parseTypedefDeclaration(cursor, pointerReference: pointerReference);
+
+      if (typealias != null) {
+        return _CreateTypeFromCursorResult(Type.typealias(typealias));
+      } else {
+        // Use underlying type if typealias couldn't be created or if the user
+        // excluded this typedef.
+        final ct = clang.clang_getTypedefDeclUnderlyingType(cursor);
+        return _CreateTypeFromCursorResult(
+            getCodeGenType(ct, pointerReference: pointerReference),
+            addToCache: false);
+      }
+    case clang_types.CXTypeKind.CXType_Record:
+      return _CreateTypeFromCursorResult(
+          _extractfromRecord(cxtype, cursor, ignoreFilter, pointerReference));
+    case clang_types.CXTypeKind.CXType_Enum:
+      final enumClass = parseEnumDeclaration(
+        cursor,
+        ignoreFilter: ignoreFilter,
+      );
+      if (enumClass == null) {
+        // Handle anonymous enum declarations within another declaration.
+        return _CreateTypeFromCursorResult(Type.nativeType(Type.enumNativeType),
+            addToCache: false);
+      } else {
+        return _CreateTypeFromCursorResult(Type.enumClass(enumClass));
+      }
+    default:
+      throw UnimplementedError(
+          'Unknown cursor kind: ${cursor.completeStringRepr()}');
+  }
+}
+
+void _fillFromCursorIfNeeded(Type? type, clang_types.CXCursor cursor,
+    bool ignoreFilter, bool pointerReference) {
+  if (type == null) return;
+  if (type.compound != null) {
+    fillCompoundMembersIfNeeded(type.compound!, cursor,
+        ignoreFilter: ignoreFilter, pointerReference: pointerReference);
+  }
+}
+
+Type? _extractfromRecord(clang_types.CXType cxtype, clang_types.CXCursor cursor,
+    bool ignoreFilter, bool pointerReference) {
   _logger.fine('${_padding}_extractfromRecord: ${cursor.completeStringRepr()}');
 
   final cursorKind = clang.clang_getCursorKind(cursor);
   if (cursorKind == clang_types.CXCursorKind.CXCursor_StructDecl ||
       cursorKind == clang_types.CXCursorKind.CXCursor_UnionDecl) {
-    final declUsr = cursor.usr();
     final declSpelling = cursor.spelling();
 
     // Set includer functions according to compoundType.
-    final bool Function(String) isSeenDecl;
-    final Compound? Function(String) getSeenDecl;
     final CompoundType compoundType;
     final Map<String, ImportedType> compoundTypeMappings;
 
     switch (cursorKind) {
       case clang_types.CXCursorKind.CXCursor_StructDecl:
-        isSeenDecl = bindingsIndex.isSeenStruct;
-        getSeenDecl = bindingsIndex.getSeenStruct;
         compoundType = CompoundType.struct;
         compoundTypeMappings = config.structTypeMappings;
         break;
       case clang_types.CXCursorKind.CXCursor_UnionDecl:
-        isSeenDecl = bindingsIndex.isSeenUnion;
-        getSeenDecl = bindingsIndex.getSeenUnion;
         compoundType = CompoundType.union;
         compoundTypeMappings = config.unionTypeMappings;
         break;
@@ -181,32 +225,20 @@
     if (compoundTypeMappings.containsKey(declSpelling)) {
       _logger.fine('  Type Mapped from type-map');
       return Type.importedType(compoundTypeMappings[declSpelling]!);
-    } else if (isSeenDecl(declUsr)) {
-      type = Type.compound(getSeenDecl(declUsr)!);
-
-      // This will parse the dependencies if needed.
-      parseCompoundDeclaration(
-        cursor,
-        compoundType,
-        ignoreFilter: true,
-        pointerReference: pointerReference,
-      );
     } else {
-      final struc = parseCompoundDeclaration(
+      final struct = parseCompoundDeclaration(
         cursor,
         compoundType,
-        ignoreFilter: true,
+        ignoreFilter: ignoreFilter,
         pointerReference: pointerReference,
       );
-      type = Type.compound(struc!);
+      if (struct == null) return null;
+      return Type.compound(struct);
     }
-  } else {
-    _logger.fine(
-        'typedeclarationCursorVisitor: _extractfromRecord: Not Implemented, ${cursor.completeStringRepr()}');
-    return Type.unimplemented('Type: ${cxtype.kindSpelling()} not implemented');
   }
-
-  return type;
+  _logger.fine(
+      'typedeclarationCursorVisitor: _extractfromRecord: Not Implemented, ${cursor.completeStringRepr()}');
+  return Type.unimplemented('Type: ${cxtype.kindSpelling()} not implemented');
 }
 
 // Used for function pointer arguments.
diff --git a/pkgs/ffigen/lib/src/header_parser/utils.dart b/pkgs/ffigen/lib/src/header_parser/utils.dart
index bac2eaf..b110134 100644
--- a/pkgs/ffigen/lib/src/header_parser/utils.dart
+++ b/pkgs/ffigen/lib/src/header_parser/utils.dart
@@ -89,14 +89,14 @@
     return s;
   }
 
-  /// Dispose type using [type.dispose].
+  /// Type associated with the pointer if any. Type will have kind
+  /// [clang.CXTypeKind.CXType_Invalid] otherwise.
   clang_types.CXType type() {
     return clang.clang_getCursorType(this);
   }
 
-  /// Only valid for [clang.CXCursorKind.CXCursor_FunctionDecl].
-  ///
-  /// Dispose type using [type.dispose].
+  /// Only valid for [clang.CXCursorKind.CXCursor_FunctionDecl]. Type will have
+  /// kind [clang.CXTypeKind.CXType_Invalid] otherwise.
   clang_types.CXType returnType() {
     return clang.clang_getResultType(type());
   }
@@ -329,134 +329,42 @@
 /// Tracks if a binding is 'seen' or not.
 class BindingsIndex {
   // Tracks if bindings are already seen, Map key is USR obtained from libclang.
-  final Map<String, Struc> _structs = {};
-  final Map<String, Union> _unions = {};
+  final Map<String, Type> _declaredTypes = {};
   final Map<String, Func> _functions = {};
-  final Map<String, EnumClass> _enumClass = {};
   final Map<String, Constant> _unnamedEnumConstants = {};
   final Map<String, String> _macros = {};
   final Map<String, Global> _globals = {};
 
   /// Contains usr for typedefs which cannot be generated.
   final Set<String> _unsupportedTypealiases = {};
-  final Map<String, Typealias> _typealiases = {};
 
   /// Index for headers.
   final Map<String, bool> _headerCache = {};
 
-  bool isSeenStruct(String usr) {
-    return _structs.containsKey(usr);
-  }
-
-  void addStructToSeen(String usr, Compound struc) {
-    _structs[usr] = struc as Struc;
-  }
-
-  Struc? getSeenStruct(String usr) {
-    return _structs[usr];
-  }
-
-  bool isSeenUnion(String usr) {
-    return _unions.containsKey(usr);
-  }
-
-  void addUnionToSeen(String usr, Compound union) {
-    _unions[usr] = union as Union;
-  }
-
-  Union? getSeenUnion(String usr) {
-    return _unions[usr];
-  }
-
-  bool isSeenFunc(String usr) {
-    return _functions.containsKey(usr);
-  }
-
-  void addFuncToSeen(String usr, Func func) {
-    _functions[usr] = func;
-  }
-
-  Func? getSeenFunc(String usr) {
-    return _functions[usr];
-  }
-
-  bool isSeenEnumClass(String usr) {
-    return _enumClass.containsKey(usr);
-  }
-
-  void addEnumClassToSeen(String usr, EnumClass enumClass) {
-    _enumClass[usr] = enumClass;
-  }
-
-  EnumClass? getSeenEnumClass(String usr) {
-    return _enumClass[usr];
-  }
-
-  bool isSeenUnnamedEnumConstant(String usr) {
-    return _unnamedEnumConstants.containsKey(usr);
-  }
-
-  void addUnnamedEnumConstantToSeen(String usr, Constant enumConstant) {
-    _unnamedEnumConstants[usr] = enumConstant;
-  }
-
-  Constant? getSeenUnnamedEnumConstant(String usr) {
-    return _unnamedEnumConstants[usr];
-  }
-
-  bool isSeenGlobalVar(String usr) {
-    return _globals.containsKey(usr);
-  }
-
-  void addGlobalVarToSeen(String usr, Global global) {
-    _globals[usr] = global;
-  }
-
-  Global? getSeenGlobalVar(String usr) {
-    return _globals[usr];
-  }
-
-  bool isSeenMacro(String usr) {
-    return _macros.containsKey(usr);
-  }
-
-  void addMacroToSeen(String usr, String macro) {
-    _macros[usr] = macro;
-  }
-
-  String? getSeenMacro(String usr) {
-    return _macros[usr];
-  }
-
-  bool isSeenTypealias(String usr) {
-    return _typealiases.containsKey(usr);
-  }
-
-  void addTypealiasToSeen(String usr, Typealias t) {
-    _typealiases[usr] = t;
-  }
-
-  bool isSeenUnsupportedTypealias(String usr) {
-    return _unsupportedTypealiases.contains(usr);
-  }
-
-  void addUnsupportedTypealiasToSeen(String usr) {
-    _unsupportedTypealiases.add(usr);
-  }
-
-  Typealias? getSeenTypealias(String usr) {
-    return _typealiases[usr];
-  }
-
-  bool isSeenHeader(String source) {
-    return _headerCache.containsKey(source);
-  }
-
-  void addHeaderToSeen(String source, bool includeStatus) {
-    _headerCache[source] = includeStatus;
-  }
-
-  bool? getSeenHeaderStatus(String source) {
-    return _headerCache[source];
-  }
+  bool isSeenType(String usr) => _declaredTypes.containsKey(usr);
+  void addTypeToSeen(String usr, Type type) => _declaredTypes[usr] = type;
+  Type? getSeenType(String usr) => _declaredTypes[usr];
+  bool isSeenFunc(String usr) => _functions.containsKey(usr);
+  void addFuncToSeen(String usr, Func func) => _functions[usr] = func;
+  Func? getSeenFunc(String usr) => _functions[usr];
+  bool isSeenUnnamedEnumConstant(String usr) =>
+      _unnamedEnumConstants.containsKey(usr);
+  void addUnnamedEnumConstantToSeen(String usr, Constant enumConstant) =>
+      _unnamedEnumConstants[usr] = enumConstant;
+  Constant? getSeenUnnamedEnumConstant(String usr) =>
+      _unnamedEnumConstants[usr];
+  bool isSeenGlobalVar(String usr) => _globals.containsKey(usr);
+  void addGlobalVarToSeen(String usr, Global global) => _globals[usr] = global;
+  Global? getSeenGlobalVar(String usr) => _globals[usr];
+  bool isSeenMacro(String usr) => _macros.containsKey(usr);
+  void addMacroToSeen(String usr, String macro) => _macros[usr] = macro;
+  String? getSeenMacro(String usr) => _macros[usr];
+  bool isSeenUnsupportedTypealias(String usr) =>
+      _unsupportedTypealiases.contains(usr);
+  void addUnsupportedTypealiasToSeen(String usr) =>
+      _unsupportedTypealiases.add(usr);
+  bool isSeenHeader(String source) => _headerCache.containsKey(source);
+  void addHeaderToSeen(String source, bool includeStatus) =>
+      _headerCache[source] = includeStatus;
+  bool? getSeenHeaderStatus(String source) => _headerCache[source];
 }
diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart
index cbfc610..58e9b5d 100644
--- a/pkgs/ffigen/lib/src/strings.dart
+++ b/pkgs/ffigen/lib/src/strings.dart
@@ -178,6 +178,8 @@
   '/usr/lib/llvm-9/lib/',
   '/usr/lib/llvm-10/lib/',
   '/usr/lib/llvm-11/lib/',
+  '/usr/lib/llvm-12/lib/',
+  '/usr/lib/llvm-13/lib/',
   '/usr/lib/',
   '/usr/lib64/',
 };