Add support nested anonymous union/struct (#511)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 400cc2f..bd0617d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 7.2.5
+
+- Add support nested anonymous union/struct
+
 # 7.2.4
 
 - Add new supported typedef - `uintptr_t` (mapped to `ffi.UintPtr`).
diff --git a/lib/src/code_generator/utils.dart b/lib/src/code_generator/utils.dart
index c000bdf..5c18c7f 100644
--- a/lib/src/code_generator/utils.dart
+++ b/lib/src/code_generator/utils.dart
@@ -15,6 +15,11 @@
   ///
   /// Adds the resulting name to the used names by default.
   String makeUnique(String name, [bool addToUsedUpNames = true]) {
+    // For example, nested structures/unions may not have a name
+    if (name.isEmpty) {
+      name = 'unnamed';
+    }
+
     var crName = name;
     var i = 1;
     while (_usedUpNames.contains(crName)) {
diff --git a/lib/src/header_parser/sub_parsers/compounddecl_parser.dart b/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
index c2efed6..5a7709c 100644
--- a/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
+++ b/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
@@ -44,10 +44,13 @@
   // A struct without any attribute is definitely not packed. #pragma pack(...)
   // also adds an attribute, but it's unexposed and cannot be travesed.
   bool hasAttr = false;
+
   // A struct which as a __packed__ attribute is definitely packed.
   bool hasPackedAttr = false;
+
   // Stores the maximum alignment from all the children.
   int maxChildAlignment = 0;
+
   // Alignment of this struct.
   int alignment = 0;
 
@@ -240,50 +243,80 @@
     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()}');
+    switch (cursor.kind) {
+      case clang_types.CXCursorKind.CXCursor_FieldDecl:
+        _logger.finer('===== member: ${cursor.completeStringRepr()}');
 
-      // Set maxChildAlignValue.
-      final align = cursor.type().alignment();
-      if (align > parsed.maxChildAlignment) {
-        parsed.maxChildAlignment = align;
-      }
+        // Set maxChildAlignValue.
+        final align = cursor.type().alignment();
+        if (align > parsed.maxChildAlignment) {
+          parsed.maxChildAlignment = align;
+        }
 
-      final mt = cursor.type().toCodeGenType();
-      if (mt is IncompleteArray) {
-        // TODO(68): Structs with flexible Array Members are not supported.
-        parsed.flexibleArrayMember = true;
-      }
-      if (clang.clang_getFieldDeclBitWidth(cursor) != -1) {
-        // TODO(84): Struct with bitfields are not suppoorted.
-        parsed.bitFieldMember = true;
-      }
-      if (mt is HandleType) {
-        parsed.dartHandleMember = true;
-      }
-      if (mt.isIncompleteCompound) {
-        parsed.incompleteCompoundMember = true;
-      }
-      if (mt.baseType is UnimplementedType) {
-        parsed.unimplementedMemberType = true;
-      }
+        final mt = cursor.type().toCodeGenType();
+        if (mt is IncompleteArray) {
+          // TODO(68): Structs with flexible Array Members are not supported.
+          parsed.flexibleArrayMember = true;
+        }
+        if (clang.clang_getFieldDeclBitWidth(cursor) != -1) {
+          // TODO(84): Struct with bitfields are not suppoorted.
+          parsed.bitFieldMember = true;
+        }
+        if (mt is HandleType) {
+          parsed.dartHandleMember = true;
+        }
+        if (mt.isIncompleteCompound) {
+          parsed.incompleteCompoundMember = true;
+        }
+        if (mt.baseType is UnimplementedType) {
+          parsed.unimplementedMemberType = true;
+        }
 
-      parsed.compound.members.add(
-        Member(
-          dartDoc: getCursorDocComment(
-            cursor,
-            nesting.length + commentPrefix.length,
+        parsed.compound.members.add(
+          Member(
+            dartDoc: getCursorDocComment(
+              cursor,
+              nesting.length + commentPrefix.length,
+            ),
+            originalName: cursor.spelling(),
+            name: config.structDecl.renameMemberUsingConfig(
+              parsed.compound.originalName,
+              cursor.spelling(),
+            ),
+            type: mt,
           ),
-          originalName: cursor.spelling(),
-          name: config.structDecl.renameMemberUsingConfig(
-            parsed.compound.originalName,
-            cursor.spelling(),
+        );
+
+        break;
+
+      case clang_types.CXCursorKind.CXCursor_PackedAttr:
+        parsed.hasPackedAttr = true;
+
+        break;
+      case clang_types.CXCursorKind.CXCursor_UnionDecl:
+      case clang_types.CXCursorKind.CXCursor_StructDecl:
+        final mt = cursor.type().toCodeGenType();
+
+        // If the union/struct are anonymous, then we need to add them now,
+        // otherwise they will be added in the next iteration.
+        if (!cursor.isAnonymousRecordDecl()) break;
+
+        parsed.compound.members.add(
+          Member(
+            dartDoc: getCursorDocComment(
+              cursor,
+              nesting.length + commentPrefix.length,
+            ),
+            originalName: cursor.spelling(),
+            name: config.structDecl.renameMemberUsingConfig(
+              parsed.compound.originalName,
+              cursor.spelling(),
+            ),
+            type: mt,
           ),
-          type: mt,
-        ),
-      );
-    } else if (cursor.kind == clang_types.CXCursorKind.CXCursor_PackedAttr) {
-      parsed.hasPackedAttr = true;
+        );
+
+        break;
     }
   } catch (e, s) {
     _logger.severe(e);
diff --git a/lib/src/header_parser/utils.dart b/lib/src/header_parser/utils.dart
index 614d5e5..d53cb8b 100644
--- a/lib/src/header_parser/utils.dart
+++ b/lib/src/header_parser/utils.dart
@@ -97,6 +97,12 @@
     return clang.clang_getCursorType(this);
   }
 
+  /// Determine whether the given cursor
+  /// represents an anonymous record declaration.
+  bool isAnonymousRecordDecl() {
+    return clang.clang_Cursor_isAnonymousRecordDecl(this) == 1;
+  }
+
   /// Only valid for [clang.CXCursorKind.CXCursor_FunctionDecl]. Type will have
   /// kind [clang.CXTypeKind.CXType_Invalid] otherwise.
   clang_types.CXType returnType() {
diff --git a/pubspec.yaml b/pubspec.yaml
index 387e08d..04b52ef 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@
 # BSD-style license that can be found in the LICENSE file.
 
 name: ffigen
-version: 7.2.4
+version: 7.2.5
 description: Generator for FFI bindings, using LibClang to parse C header files.
 repository: https://github.com/dart-lang/ffigen
 
diff --git a/test/header_parser_tests/nested_parsing.h b/test/header_parser_tests/nested_parsing.h
index cb21825..8557b64 100644
--- a/test/header_parser_tests/nested_parsing.h
+++ b/test/header_parser_tests/nested_parsing.h
@@ -40,3 +40,23 @@
     // Incomplete struct array.
     struct EmptyStruct b[3];
 };
+
+struct Struct6
+{
+    // An anonymous, unnamed union.
+    union
+    {
+        float a;
+    };
+
+    // An unnamed union.
+    union
+    {
+        float b;
+    } c;
+
+    union
+    {
+        float d;
+    } e;
+};
diff --git a/test/header_parser_tests/nested_parsing_test.dart b/test/header_parser_tests/nested_parsing_test.dart
index 70c205d..813c747 100644
--- a/test/header_parser_tests/nested_parsing_test.dart
+++ b/test/header_parser_tests/nested_parsing_test.dart
@@ -57,6 +57,10 @@
       expect(actual.getBindingAsString('Struct5'),
           expected.getBindingAsString('Struct5'));
     });
+    test('Struct6', () {
+      expect(actual.getBindingAsString('Struct6'),
+          expected.getBindingAsString('Struct6'));
+    });
   });
 }
 
@@ -110,6 +114,47 @@
       Struct(name: 'EmptyStruct'),
       Struct(name: 'Struct4'),
       Struct(name: 'Struct5'),
+      Struct(
+        name: 'Struct6',
+        members: [
+          Member(
+            name: '',
+            type: Union(
+              name: 'UnnamedUnion1',
+              members: [
+                Member(
+                  name: 'a',
+                  type: floatType,
+                ),
+              ],
+            ),
+          ),
+          Member(
+            name: 'c',
+            type: Union(
+              name: 'UnnamedUnion2',
+              members: [
+                Member(
+                  name: 'b',
+                  type: floatType,
+                ),
+              ],
+            ),
+          ),
+          Member(
+            name: 'e',
+            type: Union(
+              name: 'UnnamedUnion3',
+              members: [
+                Member(
+                  name: 'd',
+                  type: floatType,
+                ),
+              ],
+            ),
+          ),
+        ],
+      ),
     ],
   );
 }