Added support for generating Unions (#215)

diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml
index da0cc9b..7cc704e 100644
--- a/.github/workflows/test-package.yml
+++ b/.github/workflows/test-package.yml
@@ -19,7 +19,7 @@
     strategy:
       fail-fast: false
       matrix:
-        sdk: [2.13.0]
+        sdk: [2.14.0-115.0.dev] # TODO: revert to 2.14
     steps:
       - uses: actions/checkout@v2
       - uses: dart-lang/setup-dart@v1.0
@@ -43,7 +43,7 @@
       - uses: actions/checkout@v2
       - uses: dart-lang/setup-dart@v1.0
         with:
-          sdk: 2.13.0
+          sdk: 2.14.0-115.0.dev # TODO: revert to 2.14.
       - name: Install dependencies
         run: dart pub get
       - name: Install libclang-10-dev
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3b1f928..6950615 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 3.1.0-dev.0
+- Added support for generating unions.
+
 # 3.0.0
 - Release for dart sdk `>=2.13` (Support for packed structs and inline arrays).
 
diff --git a/README.md b/README.md
index 099fe04..ae66d69 100644
--- a/README.md
+++ b/README.md
@@ -181,7 +181,7 @@
   </td>
   </tr>
   <tr>
-    <td>functions<br><br>structs<br><br>enums<br><br>unnamed-enums<br><br>macros<br><br>globals</td>
+    <td>functions<br><br>structs<br><br>unions<br><br>enums<br><br>unnamed-enums<br><br>macros<br><br>globals</td>
     <td>Filters for declarations.<br><b>Default: all are included.</b><br><br>
     Options -<br>
     - Include/Exclude declarations.<br>
@@ -273,8 +273,10 @@
   </td>
   </tr>
   <tr>
-    <td>structs -> dependency-only</td>
-    <td>If `opaque`, generates empty `Opaque` structs if structs
+    <td>structs -> dependency-only<br><br>
+        unions -> dependency-only
+    </td>
+    <td>If `opaque`, generates empty `Opaque` structs/unions if they
 were not included in config (but were added since they are a dependency) and
 only passed by reference(pointer).<br>
     <i>Options - full(default) | opaque</i><br>
@@ -284,6 +286,8 @@
 ```yaml
 structs:
   dependency-only: opaque
+unions:
+  dependency-only: opaque
 ```
   </td>
   </tr>
@@ -476,9 +480,9 @@
     'CXType_(.*)': '$1'
 ```
 
-### Why are some struct declarations generated even after excluded them in config?
+### Why are some struct/union declarations generated even after excluded them in config?
 
-This happens when an excluded struct is a dependency to some included declaration.
+This happens when an excluded struct/union is a dependency to some included declaration.
 (A dependency means a struct is being passed/returned by a function or is member of another struct in some way)
 
 Note: If you supply `structs` -> `dependency-only` as `opaque` ffigen will generate
@@ -486,6 +490,8 @@
 ```yaml
 structs:
   dependency-only: opaque
+unions:
+  dependency-only: opaque
 ```
 
 ### How to expose the native pointers and typedefs?
diff --git a/lib/src/code_generator.dart b/lib/src/code_generator.dart
index efbb1a2..0b23674 100644
--- a/lib/src/code_generator.dart
+++ b/lib/src/code_generator.dart
@@ -6,6 +6,7 @@
 library code_generator;
 
 export 'code_generator/binding.dart';
+export 'code_generator/compound.dart';
 export 'code_generator/constant.dart';
 export 'code_generator/enum_class.dart';
 export 'code_generator/func.dart';
@@ -14,3 +15,4 @@
 export 'code_generator/struc.dart';
 export 'code_generator/type.dart';
 export 'code_generator/typedef.dart';
+export 'code_generator/union.dart';
diff --git a/lib/src/code_generator/binding_string.dart b/lib/src/code_generator/binding_string.dart
index a2b721e..2929ee8 100644
--- a/lib/src/code_generator/binding_string.dart
+++ b/lib/src/code_generator/binding_string.dart
@@ -18,6 +18,7 @@
 enum BindingStringType {
   func,
   struc,
+  union,
   constant,
   global,
   enumClass,
diff --git a/lib/src/code_generator/compound.dart b/lib/src/code_generator/compound.dart
new file mode 100644
index 0000000..77868eb
--- /dev/null
+++ b/lib/src/code_generator/compound.dart
@@ -0,0 +1,184 @@
+// Copyright (c) 2021, 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/code_generator.dart';
+import 'package:ffigen/src/code_generator/typedef.dart';
+
+import 'binding.dart';
+import 'binding_string.dart';
+import 'type.dart';
+import 'utils.dart';
+import 'writer.dart';
+
+enum CompoundType { struct, union }
+
+/// A binding for Compound type - Struct/Union.
+abstract class Compound extends NoLookUpBinding {
+  /// Marker for if a struct definition is complete.
+  ///
+  /// A function can be safely pass this struct by value if it's complete.
+  bool isInComplete;
+
+  List<Member> members;
+
+  bool get isOpaque => members.isEmpty;
+
+  /// Value for `@Packed(X)` annotation. Can be null(no packing), 1, 2, 4, 8, 16.
+  ///
+  /// Only supported for [CompoundType.struct].
+  int? pack;
+
+  /// Marker for checking if the dependencies are parsed.
+  bool parsedDependencies = false;
+
+  CompoundType compoundType;
+  bool get isStruct => compoundType == CompoundType.struct;
+  bool get isUnion => compoundType == CompoundType.union;
+
+  Compound({
+    String? usr,
+    String? originalName,
+    required String name,
+    required this.compoundType,
+    this.isInComplete = false,
+    this.pack,
+    String? dartDoc,
+    List<Member>? members,
+  })  : members = members ?? [],
+        super(
+          usr: usr,
+          originalName: originalName,
+          name: name,
+          dartDoc: dartDoc,
+        );
+
+  factory Compound.fromType({
+    required CompoundType type,
+    String? usr,
+    String? originalName,
+    required String name,
+    bool isInComplete = false,
+    int? pack,
+    String? dartDoc,
+    List<Member>? members,
+  }) {
+    switch (type) {
+      case CompoundType.struct:
+        return Struc(
+          usr: usr,
+          originalName: originalName,
+          name: name,
+          isInComplete: isInComplete,
+          pack: pack,
+          dartDoc: dartDoc,
+          members: members,
+        );
+      case CompoundType.union:
+        return Union(
+          usr: usr,
+          originalName: originalName,
+          name: name,
+          isInComplete: isInComplete,
+          pack: pack,
+          dartDoc: dartDoc,
+          members: members,
+        );
+    }
+  }
+
+  List<int> _getArrayDimensionLengths(Type type) {
+    final array = <int>[];
+    var startType = type;
+    while (startType.broadType == BroadType.ConstantArray) {
+      array.add(startType.length!);
+      startType = startType.child!;
+    }
+    return array;
+  }
+
+  String _getInlineArrayTypeString(Type type, Writer w) {
+    if (type.broadType == BroadType.ConstantArray) {
+      return '${w.ffiLibraryPrefix}.Array<${_getInlineArrayTypeString(type.child!, w)}>';
+    }
+    return type.getCType(w);
+  }
+
+  List<Typedef>? _typedefDependencies;
+  @override
+  List<Typedef> getTypedefDependencies(Writer w) {
+    if (_typedefDependencies == null) {
+      _typedefDependencies = <Typedef>[];
+
+      // Write typedef's required by members and resolve name conflicts.
+      for (final m in members) {
+        final base = m.type.getBaseType();
+        if (base.broadType == BroadType.NativeFunction) {
+          _typedefDependencies!.addAll(base.nativeFunc!.getDependencies());
+        }
+      }
+    }
+    return _typedefDependencies ?? [];
+  }
+
+  @override
+  BindingString toBindingString(Writer w) {
+    final s = StringBuffer();
+    final enclosingClassName = name;
+    if (dartDoc != null) {
+      s.write(makeDartDoc(dartDoc!));
+    }
+
+    /// Adding [enclosingClassName] because dart doesn't allow class member
+    /// to have the same name as the class.
+    final localUniqueNamer = UniqueNamer({enclosingClassName});
+
+    /// 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(
+        'class $enclosingClassName extends ${w.ffiLibraryPrefix}.${isOpaque ? 'Opaque' : dartClassName}{\n');
+    const depth = '  ';
+    for (final m in members) {
+      final memberName = localUniqueNamer.makeUnique(m.name);
+      if (m.type.broadType == BroadType.ConstantArray) {
+        s.write(
+            '$depth@${w.ffiLibraryPrefix}.Array.multi(${_getArrayDimensionLengths(m.type)})\n');
+        s.write(
+            '${depth}external ${_getInlineArrayTypeString(m.type, w)} $memberName;\n\n');
+      } else {
+        if (m.dartDoc != null) {
+          s.write(depth + '/// ');
+          s.writeAll(m.dartDoc!.split('\n'), '\n' + depth + '/// ');
+          s.write('\n');
+        }
+        if (m.type.isPrimitive) {
+          s.write('$depth@${m.type.getCType(w)}()\n');
+        }
+        s.write('${depth}external ${m.type.getDartType(w)} $memberName;\n\n');
+      }
+    }
+    s.write('}\n\n');
+
+    return BindingString(
+        type: isStruct ? BindingStringType.struc : BindingStringType.union,
+        string: s.toString());
+  }
+}
+
+class Member {
+  final String? dartDoc;
+  final String originalName;
+  final String name;
+  final Type type;
+
+  const Member({
+    String? originalName,
+    required this.name,
+    required this.type,
+    this.dartDoc,
+  }) : originalName = originalName ?? name;
+}
diff --git a/lib/src/code_generator/global.dart b/lib/src/code_generator/global.dart
index 1951371..cf864cd 100644
--- a/lib/src/code_generator/global.dart
+++ b/lib/src/code_generator/global.dart
@@ -66,8 +66,8 @@
 
     s.write(
         "late final ${w.ffiLibraryPrefix}.Pointer<$cType> $pointerName = ${w.lookupFuncIdentifier}<$cType>('$originalName');\n\n");
-    if (type.broadType == BroadType.Struct) {
-      if (type.struc!.isOpaque) {
+    if (type.broadType == BroadType.Compound) {
+      if (type.compound!.isOpaque) {
         s.write(
             '${w.ffiLibraryPrefix}.Pointer<$cType> get $globalVarName => $pointerName;\n\n');
       } else {
diff --git a/lib/src/code_generator/struc.dart b/lib/src/code_generator/struc.dart
index 8b87891..4f06369 100644
--- a/lib/src/code_generator/struc.dart
+++ b/lib/src/code_generator/struc.dart
@@ -2,13 +2,7 @@
 // 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/code_generator/typedef.dart';
-
-import 'binding.dart';
-import 'binding_string.dart';
-import 'type.dart';
-import 'utils.dart';
-import 'writer.dart';
+import 'package:ffigen/src/code_generator/compound.dart';
 
 /// A binding for C Struct.
 ///
@@ -34,127 +28,23 @@
 ///
 /// }
 /// ```
-class Struc extends NoLookUpBinding {
-  /// Marker for if a struct definition is complete.
-  ///
-  /// A function can be safely pass this struct by value if it's complete.
-  bool isInComplete;
-
-  List<Member> members;
-
-  bool get isOpaque => members.isEmpty;
-
-  /// Value for `@Packed(X)` annotation. Can be null(no packing), 1, 2, 4, 8, 16.
-  int? pack;
-
-  /// Marker for checking if the dependencies are parsed.
-  bool parsedDependencies = false;
-
+class Struc extends Compound {
   Struc({
     String? usr,
     String? originalName,
     required String name,
-    this.isInComplete = false,
-    this.pack,
+    bool isInComplete = false,
+    int? pack,
     String? dartDoc,
     List<Member>? members,
-  })  : members = members ?? [],
-        super(
+  }) : super(
           usr: usr,
           originalName: originalName,
           name: name,
           dartDoc: dartDoc,
+          isInComplete: isInComplete,
+          members: members,
+          pack: pack,
+          compoundType: CompoundType.struct,
         );
-
-  List<int> _getArrayDimensionLengths(Type type) {
-    final array = <int>[];
-    var startType = type;
-    while (startType.broadType == BroadType.ConstantArray) {
-      array.add(startType.length!);
-      startType = startType.child!;
-    }
-    return array;
-  }
-
-  String _getInlineArrayTypeString(Type type, Writer w) {
-    if (type.broadType == BroadType.ConstantArray) {
-      return '${w.ffiLibraryPrefix}.Array<${_getInlineArrayTypeString(type.child!, w)}>';
-    }
-    return type.getCType(w);
-  }
-
-  List<Typedef>? _typedefDependencies;
-  @override
-  List<Typedef> getTypedefDependencies(Writer w) {
-    if (_typedefDependencies == null) {
-      _typedefDependencies = <Typedef>[];
-
-      // Write typedef's required by members and resolve name conflicts.
-      for (final m in members) {
-        final base = m.type.getBaseType();
-        if (base.broadType == BroadType.NativeFunction) {
-          _typedefDependencies!.addAll(base.nativeFunc!.getDependencies());
-        }
-      }
-    }
-    return _typedefDependencies ?? [];
-  }
-
-  @override
-  BindingString toBindingString(Writer w) {
-    final s = StringBuffer();
-    final enclosingClassName = name;
-    if (dartDoc != null) {
-      s.write(makeDartDoc(dartDoc!));
-    }
-
-    /// Adding [enclosingClassName] because dart doesn't allow class member
-    /// to have the same name as the class.
-    final localUniqueNamer = UniqueNamer({enclosingClassName});
-
-    /// Write @Packed(X) annotation if struct is packed.
-    if (pack != null) {
-      s.write('@${w.ffiLibraryPrefix}.Packed($pack)\n');
-    }
-    // Write class declaration.
-    s.write(
-        'class $enclosingClassName extends ${w.ffiLibraryPrefix}.${isOpaque ? 'Opaque' : 'Struct'}{\n');
-    const depth = '  ';
-    for (final m in members) {
-      final memberName = localUniqueNamer.makeUnique(m.name);
-      if (m.type.broadType == BroadType.ConstantArray) {
-        s.write(
-            '$depth@${w.ffiLibraryPrefix}.Array.multi(${_getArrayDimensionLengths(m.type)})\n');
-        s.write(
-            '${depth}external ${_getInlineArrayTypeString(m.type, w)} $memberName;\n\n');
-      } else {
-        if (m.dartDoc != null) {
-          s.write(depth + '/// ');
-          s.writeAll(m.dartDoc!.split('\n'), '\n' + depth + '/// ');
-          s.write('\n');
-        }
-        if (m.type.isPrimitive) {
-          s.write('$depth@${m.type.getCType(w)}()\n');
-        }
-        s.write('${depth}external ${m.type.getDartType(w)} $memberName;\n\n');
-      }
-    }
-    s.write('}\n\n');
-
-    return BindingString(type: BindingStringType.struc, string: s.toString());
-  }
-}
-
-class Member {
-  final String? dartDoc;
-  final String originalName;
-  final String name;
-  final Type type;
-
-  const Member({
-    String? originalName,
-    required this.name,
-    required this.type,
-    this.dartDoc,
-  }) : originalName = originalName ?? name;
 }
diff --git a/lib/src/code_generator/type.dart b/lib/src/code_generator/type.dart
index 74b3aa5..c306395 100644
--- a/lib/src/code_generator/type.dart
+++ b/lib/src/code_generator/type.dart
@@ -2,7 +2,9 @@
 // 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 'struc.dart';
+import 'package:ffigen/src/code_generator.dart';
+
+import 'compound.dart';
 import 'typedef.dart';
 import 'writer.dart';
 
@@ -34,7 +36,7 @@
   Boolean,
   NativeType,
   Pointer,
-  Struct,
+  Compound,
   NativeFunction,
 
   /// Represents a Dart_Handle.
@@ -44,7 +46,7 @@
   ConstantArray,
   IncompleteArray,
 
-  /// Used as a marker, so that functions/structs having these can exclude them.
+  /// Used as a marker, so that declarations having these can exclude them.
   Unimplemented,
 }
 
@@ -66,8 +68,8 @@
     SupportedNativeType.IntPtr: _SubType(c: 'IntPtr', dart: 'int'),
   };
 
-  /// Reference to the [Struc] binding this type refers to.
-  Struc? struc;
+  /// Reference to the [Compound] binding this type refers to.
+  Compound? compound;
 
   /// Reference to the [Typedef] this type refers to.
   Typedef? nativeFunc;
@@ -90,7 +92,7 @@
   Type._({
     required this.broadType,
     this.child,
-    this.struc,
+    this.compound,
     this.nativeType,
     this.nativeFunc,
     this.length,
@@ -100,8 +102,14 @@
   factory Type.pointer(Type child) {
     return Type._(broadType: BroadType.Pointer, child: child);
   }
+  factory Type.compound(Compound compound) {
+    return Type._(broadType: BroadType.Compound, compound: compound);
+  }
   factory Type.struct(Struc struc) {
-    return Type._(broadType: BroadType.Struct, struc: struc);
+    return Type._(broadType: BroadType.Compound, compound: struc);
+  }
+  factory Type.union(Union union) {
+    return Type._(broadType: BroadType.Compound, compound: union);
   }
   factory Type.nativeFunc(Typedef nativeFunc) {
     return Type._(broadType: BroadType.NativeFunction, nativeFunc: nativeFunc);
@@ -162,11 +170,13 @@
   bool get isPrimitive =>
       (broadType == BroadType.NativeType || broadType == BroadType.Boolean);
 
-  /// Returns true if the type is a [Struc] and is incomplete.
-  bool get isIncompleteStruct =>
-      (broadType == BroadType.Struct && struc != null && struc!.isInComplete) ||
+  /// Returns true if the type is a [Compound] and is incomplete.
+  bool get isIncompleteCompound =>
+      (broadType == BroadType.Compound &&
+          compound != null &&
+          compound!.isInComplete) ||
       (broadType == BroadType.ConstantArray &&
-          getBaseArrayType().isIncompleteStruct);
+          getBaseArrayType().isIncompleteCompound);
 
   String getCType(Writer w) {
     switch (broadType) {
@@ -174,8 +184,8 @@
         return '${w.ffiLibraryPrefix}.${_primitives[nativeType!]!.c}';
       case BroadType.Pointer:
         return '${w.ffiLibraryPrefix}.Pointer<${child!.getCType(w)}>';
-      case BroadType.Struct:
-        return '${struc!.name}';
+      case BroadType.Compound:
+        return '${compound!.name}';
       case BroadType.NativeFunction:
         return '${w.ffiLibraryPrefix}.NativeFunction<${nativeFunc!.name}>';
       case BroadType
@@ -199,8 +209,8 @@
         return _primitives[nativeType!]!.dart;
       case BroadType.Pointer:
         return '${w.ffiLibraryPrefix}.Pointer<${child!.getCType(w)}>';
-      case BroadType.Struct:
-        return '${struc!.name}';
+      case BroadType.Compound:
+        return '${compound!.name}';
       case BroadType.NativeFunction:
         return '${w.ffiLibraryPrefix}.NativeFunction<${nativeFunc!.name}>';
       case BroadType
diff --git a/lib/src/code_generator/union.dart b/lib/src/code_generator/union.dart
new file mode 100644
index 0000000..a8a0861
--- /dev/null
+++ b/lib/src/code_generator/union.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2020, 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/code_generator/compound.dart';
+
+/// A binding for a C union -
+///
+/// ```c
+/// union C {
+///   int a;
+///   double b;
+///   float c;
+/// };
+/// ```
+/// The generated dart code is -
+/// ```dart
+/// class Union extends ffi.Union{
+///  @ffi.Int32()
+///  int a;
+///
+///  @ffi.Double()
+///  double b;
+///
+///  @ffi.Float()
+///  float c;
+///
+/// }
+/// ```
+class Union extends Compound {
+  Union({
+    String? usr,
+    String? originalName,
+    required String name,
+    bool isInComplete = false,
+    int? pack,
+    String? dartDoc,
+    List<Member>? members,
+  }) : super(
+          usr: usr,
+          originalName: originalName,
+          name: name,
+          dartDoc: dartDoc,
+          isInComplete: isInComplete,
+          members: members,
+          pack: pack,
+          compoundType: CompoundType.union,
+        );
+}
diff --git a/lib/src/config_provider/config.dart b/lib/src/config_provider/config.dart
index e42ed46..92194dc 100644
--- a/lib/src/config_provider/config.dart
+++ b/lib/src/config_provider/config.dart
@@ -43,6 +43,10 @@
   Declaration get structDecl => _structDecl;
   late Declaration _structDecl;
 
+  /// Declaration config for Unions.
+  Declaration get unionDecl => _unionDecl;
+  late Declaration _unionDecl;
+
   /// Declaration config for Enums.
   Declaration get enumClassDecl => _enumClassDecl;
   late Declaration _enumClassDecl;
@@ -77,8 +81,12 @@
   late CommentType _commentType;
 
   /// Whether structs that are dependencies should be included.
-  StructDependencies get structDependencies => _structDependencies;
-  late StructDependencies _structDependencies;
+  CompoundDependencies get structDependencies => _structDependencies;
+  late CompoundDependencies _structDependencies;
+
+  /// Whether unions that are dependencies should be included.
+  CompoundDependencies get unionDependencies => _unionDependencies;
+  late CompoundDependencies _unionDependencies;
 
   /// Holds config for how struct packing should be overriden.
   StructPackingOverride get structPackingOverride => _structPackingOverride;
@@ -234,6 +242,15 @@
           _structDecl = result as Declaration;
         },
       ),
+      [strings.unions]: Specification<Declaration>(
+        requirement: Requirement.no,
+        validator: declarationConfigValidator,
+        extractor: declarationConfigExtractor,
+        defaultValue: () => Declaration(),
+        extractedResult: (dynamic result) {
+          _unionDecl = result as Declaration;
+        },
+      ),
       [strings.enums]: Specification<Declaration>(
         requirement: Requirement.no,
         validator: declarationConfigValidator,
@@ -312,14 +329,23 @@
         extractedResult: (dynamic result) =>
             _commentType = result as CommentType,
       ),
-      [strings.structs, strings.structDependencies]:
-          Specification<StructDependencies>(
+      [strings.structs, strings.dependencyOnly]:
+          Specification<CompoundDependencies>(
         requirement: Requirement.no,
-        validator: structDependenciesValidator,
-        extractor: structDependenciesExtractor,
-        defaultValue: () => StructDependencies.full,
+        validator: dependencyOnlyValidator,
+        extractor: dependencyOnlyExtractor,
+        defaultValue: () => CompoundDependencies.full,
         extractedResult: (dynamic result) =>
-            _structDependencies = result as StructDependencies,
+            _structDependencies = result as CompoundDependencies,
+      ),
+      [strings.unions, strings.dependencyOnly]:
+          Specification<CompoundDependencies>(
+        requirement: Requirement.no,
+        validator: dependencyOnlyValidator,
+        extractor: dependencyOnlyExtractor,
+        defaultValue: () => CompoundDependencies.full,
+        extractedResult: (dynamic result) =>
+            _unionDependencies = result as CompoundDependencies,
       ),
       [strings.structs, strings.structPack]:
           Specification<StructPackingOverride>(
diff --git a/lib/src/config_provider/config_types.dart b/lib/src/config_provider/config_types.dart
index 7fcc352..42e99f0 100644
--- a/lib/src/config_provider/config_types.dart
+++ b/lib/src/config_provider/config_types.dart
@@ -29,7 +29,7 @@
 enum CommentStyle { doxygen, any }
 enum CommentLength { none, brief, full }
 
-enum StructDependencies { full, opaque }
+enum CompoundDependencies { full, opaque }
 
 /// Holds config for how Structs Packing will be overriden.
 class StructPackingOverride {
diff --git a/lib/src/config_provider/spec_utils.dart b/lib/src/config_provider/spec_utils.dart
index 73c0d0d..a36ee50 100644
--- a/lib/src/config_provider/spec_utils.dart
+++ b/lib/src/config_provider/spec_utils.dart
@@ -658,21 +658,21 @@
   }
 }
 
-StructDependencies structDependenciesExtractor(dynamic value) {
-  var result = StructDependencies.full;
-  if (value == strings.opaqueStructDependencies) {
-    result = StructDependencies.opaque;
+CompoundDependencies dependencyOnlyExtractor(dynamic value) {
+  var result = CompoundDependencies.full;
+  if (value == strings.opaqueCompoundDependencies) {
+    result = CompoundDependencies.opaque;
   }
   return result;
 }
 
-bool structDependenciesValidator(List<String> name, dynamic value) {
+bool dependencyOnlyValidator(List<String> name, dynamic value) {
   var result = true;
   if (value is! String ||
-      !(value == strings.fullStructDependencies ||
-          value == strings.opaqueStructDependencies)) {
+      !(value == strings.fullCompoundDependencies ||
+          value == strings.opaqueCompoundDependencies)) {
     _logger.severe(
-        "'$name' must be one of the following - {${strings.fullStructDependencies}, ${strings.opaqueStructDependencies}}");
+        "'$name' must be one of the following - {${strings.fullCompoundDependencies}, ${strings.opaqueCompoundDependencies}}");
     result = false;
   }
   return result;
diff --git a/lib/src/header_parser/includer.dart b/lib/src/header_parser/includer.dart
index bd82476..1cd4ff1 100644
--- a/lib/src/header_parser/includer.dart
+++ b/lib/src/header_parser/includer.dart
@@ -17,6 +17,16 @@
   }
 }
 
+bool shouldIncludeUnion(String usr, String name) {
+  if (bindingsIndex.isSeenStruct(usr) || name == '') {
+    return false;
+  } else if (config.unionDecl.shouldInclude(name)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
 bool shouldIncludeFunc(String usr, String name) {
   if (bindingsIndex.isSeenFunc(usr) || name == '') {
     return false;
diff --git a/lib/src/header_parser/sub_parsers/compounddecl_parser.dart b/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
new file mode 100644
index 0000000..82bac38
--- /dev/null
+++ b/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
@@ -0,0 +1,303 @@
+// Copyright (c) 2021, 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 'dart:ffi';
+
+import 'package:ffigen/src/code_generator.dart';
+import 'package:ffigen/src/config_provider/config_types.dart';
+import 'package:logging/logging.dart';
+
+import '../../strings.dart' as strings;
+import '../clang_bindings/clang_bindings.dart' as clang_types;
+import '../data.dart';
+import '../includer.dart';
+import '../utils.dart';
+
+final _logger = Logger('ffigen.header_parser.compounddecl_parser');
+
+/// Holds temporary information regarding [compound] while parsing.
+class _ParsedCompound {
+  Compound? compound;
+  bool unimplementedMemberType = false;
+  bool flexibleArrayMember = false;
+  bool bitFieldMember = false;
+  bool dartHandleMember = false;
+  bool incompleteCompoundMember = false;
+
+  bool get isInComplete =>
+      unimplementedMemberType ||
+      flexibleArrayMember ||
+      bitFieldMember ||
+      (dartHandleMember && config.useDartHandle) ||
+      incompleteCompoundMember;
+
+  // 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 allignment = 0;
+
+  bool get _isPacked {
+    if (!hasAttr || isInComplete) return false;
+    if (hasPackedAttr) return true;
+
+    return maxChildAlignment > allignment;
+  }
+
+  /// 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;
+      } else {
+        _logger.warning(
+            'Unsupported pack value "$allignment" for Struct "${compound!.name}".');
+        return null;
+      }
+    } else {
+      return null;
+    }
+  }
+
+  _ParsedCompound();
+}
+
+final _stack = Stack<_ParsedCompound>();
+
+/// Parses a compound declaration.
+Compound? parseCompoundDeclaration(
+  clang_types.CXCursor cursor,
+  CompoundType compoundType, {
+
+  /// Optionally provide name (useful in case declaration is inside a typedef).
+  String? name,
+
+  /// 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,
+
+  /// If the compound name should be updated, if it was already seen.
+  bool updateName = true,
+}) {
+  _stack.push(_ParsedCompound());
+
+  // Parse the cursor definition instead, if this is a forward declaration.
+  if (isForwardDeclaration(cursor)) {
+    cursor = clang.clang_getCursorDefinition(cursor);
+  }
+  final declUsr = cursor.usr();
+  final declName = name ?? cursor.spelling();
+
+  // 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;
+  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;
+  }
+
+  if (declName.isEmpty) {
+    if (ignoreFilter) {
+      // This declaration is defined inside some other declaration and hence
+      // must be generated.
+      _stack.top.compound = Compound.fromType(
+        type: compoundType,
+        name: incrementalNamer.name('unnamed$className'),
+        usr: declUsr,
+        dartDoc: getCursorDocComment(cursor),
+      );
+      _setMembers(cursor, className);
+    } else {
+      _logger.finest('unnamed $className or typedef $className declaration');
+    }
+  } else {
+    if ((ignoreFilter || shouldIncludeDecl(declUsr, declName)) &&
+        (!isSeenDecl(declUsr))) {
+      _logger.fine(
+          '++++ Adding $className: Name: $declName, ${cursor.completeStringRepr()}');
+      _stack.top.compound = 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.');
+    }
+
+    if (updateName) {
+      // If struct is seen, update it's name.
+      _stack.top.compound!.name = configDecl.renameUsingConfig(declName);
+    }
+  }
+
+  return _stack.pop().compound;
+}
+
+void _setMembers(clang_types.CXCursor cursor, String className) {
+  _stack.top.hasAttr = clang.clang_Cursor_hasAttrs(cursor) != 0;
+  _stack.top.allignment = cursor.type().alignment();
+
+  final resultCode = clang.clang_visitChildren(
+    cursor,
+    Pointer.fromFunction(_compoundMembersVisitor,
+        clang_types.CXChildVisitResult.CXChildVisit_Break),
+    nullptr,
+  );
+
+  _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;
+
+  visitChildrenResultChecker(resultCode);
+
+  if (_stack.top.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) {
+    _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) {
+    _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) {
+    _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) {
+    _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.');
+  }
+
+  // Clear all members if declaration is incomplete.
+  if (_stack.top.isInComplete) {
+    _stack.top.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;
+}
+
+/// Visitor for the struct/union cursor [CXCursorKind.CXCursor_StructDecl]/
+/// [CXCursorKind.CXCursor_UnionDecl].
+///
+/// Child visitor invoked on struct/union cursor.
+int _compoundMembersVisitor(clang_types.CXCursor cursor,
+    clang_types.CXCursor parent, Pointer<Void> clientData) {
+  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;
+      }
+
+      final mt = cursor.type().toCodeGenType();
+      if (mt.broadType == BroadType.IncompleteArray) {
+        // TODO(68): Structs with flexible Array Members are not supported.
+        _stack.top.flexibleArrayMember = true;
+      }
+      if (clang.clang_getFieldDeclBitWidth(cursor) != -1) {
+        // TODO(84): Struct with bitfields are not suppoorted.
+        _stack.top.bitFieldMember = true;
+      }
+      if (mt.broadType == BroadType.Handle) {
+        _stack.top.dartHandleMember = true;
+      }
+      if (mt.isIncompleteCompound) {
+        _stack.top.incompleteCompoundMember = true;
+      }
+      if (mt.getBaseType().broadType == BroadType.Unimplemented) {
+        _stack.top.unimplementedMemberType = true;
+      }
+
+      _stack.top.compound!.members.add(
+        Member(
+          dartDoc: getCursorDocComment(
+            cursor,
+            nesting.length + commentPrefix.length,
+          ),
+          originalName: cursor.spelling(),
+          name: config.structDecl.renameMemberUsingConfig(
+            _stack.top.compound!.originalName,
+            cursor.spelling(),
+          ),
+          type: mt,
+        ),
+      );
+    } else if (cursor.kind == clang_types.CXCursorKind.CXCursor_PackedAttr) {
+      _stack.top.hasPackedAttr = true;
+    }
+  } catch (e, s) {
+    _logger.severe(e);
+    _logger.severe(s);
+    rethrow;
+  }
+  return clang_types.CXChildVisitResult.CXChildVisit_Continue;
+}
diff --git a/lib/src/header_parser/sub_parsers/functiondecl_parser.dart b/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
index ac1d11d..a715f0e 100644
--- a/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
+++ b/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
@@ -45,7 +45,7 @@
           .func; // Returning null so that [addToBindings] function excludes this.
     }
 
-    if (rt.isIncompleteStruct || _stack.top.incompleteStructParameter) {
+    if (rt.isIncompleteCompound || _stack.top.incompleteStructParameter) {
       _logger.fine(
           '---- Removed Function, reason: Incomplete struct pass/return by value: ${cursor.completeStringRepr()}');
       _logger.warning(
@@ -101,7 +101,7 @@
     _logger.finer('===== parameter: ${paramCursor.completeStringRepr()}');
 
     final pt = _getParameterType(paramCursor);
-    if (pt.isIncompleteStruct) {
+    if (pt.isIncompleteCompound) {
       _stack.top.incompleteStructParameter = true;
     } else if (pt.getBaseType().broadType == BroadType.Unimplemented) {
       _logger
diff --git a/lib/src/header_parser/sub_parsers/structdecl_parser.dart b/lib/src/header_parser/sub_parsers/structdecl_parser.dart
deleted file mode 100644
index e5970f4..0000000
--- a/lib/src/header_parser/sub_parsers/structdecl_parser.dart
+++ /dev/null
@@ -1,264 +0,0 @@
-// Copyright (c) 2020, 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 'dart:ffi';
-
-import 'package:ffigen/src/code_generator.dart';
-import 'package:ffigen/src/config_provider/config_types.dart';
-import 'package:logging/logging.dart';
-
-import '../../strings.dart' as strings;
-import '../clang_bindings/clang_bindings.dart' as clang_types;
-import '../data.dart';
-import '../includer.dart';
-import '../utils.dart';
-
-final _logger = Logger('ffigen.header_parser.structdecl_parser');
-
-/// Holds temporary information regarding [struc] while parsing.
-class _ParsedStruc {
-  Struc? struc;
-  bool unimplementedMemberType = false;
-  bool flexibleArrayMember = false;
-  bool bitFieldMember = false;
-  bool dartHandleMember = false;
-  bool incompleteStructMember = false;
-
-  bool get isInComplete =>
-      unimplementedMemberType ||
-      flexibleArrayMember ||
-      bitFieldMember ||
-      (dartHandleMember && config.useDartHandle) ||
-      incompleteStructMember;
-
-  // 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 allignment = 0;
-
-  bool get _isPacked {
-    if (!hasAttr || isInComplete) return false;
-    if (hasPackedAttr) return true;
-
-    return maxChildAlignment > allignment;
-  }
-
-  /// Returns pack value of a struct depending on config, returns null for no
-  /// packing.
-  int? get packValue {
-    if (_isPacked) {
-      if (strings.packingValuesMap.containsKey(allignment)) {
-        return allignment;
-      } else {
-        _logger.warning(
-            'Unsupported pack value "$allignment" for Struct "${struc!.name}".');
-        return null;
-      }
-    } else {
-      return null;
-    }
-  }
-
-  _ParsedStruc();
-}
-
-final _stack = Stack<_ParsedStruc>();
-
-/// Parses a struct declaration.
-Struc? parseStructDeclaration(
-  clang_types.CXCursor cursor, {
-
-  /// Optionally provide name (useful in case struct is inside a typedef).
-  String? name,
-
-  /// Option to ignore struct filter (Useful in case of extracting structs
-  /// when they are passed/returned by an included function.)
-  bool ignoreFilter = false,
-
-  /// To track if the struct was used by reference(i.e struct*). (Used to only
-  /// generate these as opaque if `struct-dependencies` was set to opaque).
-  bool pointerReference = false,
-
-  /// If the struct name should be updated, if it was already seen.
-  bool updateName = true,
-}) {
-  _stack.push(_ParsedStruc());
-
-  // Parse the cursor definition instead, if this is a forward declaration.
-  if (isForwardDeclaration(cursor)) {
-    cursor = clang.clang_getCursorDefinition(cursor);
-  }
-  final structUsr = cursor.usr();
-  final structName = name ?? cursor.spelling();
-
-  if (structName.isEmpty) {
-    if (ignoreFilter) {
-      // This struct is defined inside some other struct and hence must be generated.
-      _stack.top.struc = Struc(
-        name: incrementalNamer.name('unnamedStruct'),
-        usr: structUsr,
-        dartDoc: getCursorDocComment(cursor),
-      );
-      _setStructMembers(cursor);
-    } else {
-      _logger.finest('unnamed structure or typedef structure declaration');
-    }
-  } else if ((ignoreFilter || shouldIncludeStruct(structUsr, structName)) &&
-      (!bindingsIndex.isSeenStruct(structUsr))) {
-    _logger.fine(
-        '++++ Adding Structure: structName: $structName, ${cursor.completeStringRepr()}');
-    _stack.top.struc = Struc(
-      usr: structUsr,
-      originalName: structName,
-      name: config.structDecl.renameUsingConfig(structName),
-      dartDoc: getCursorDocComment(cursor),
-    );
-    // Adding to seen here to stop recursion if a struct has itself as a
-    // member, members are updated later.
-    bindingsIndex.addStructToSeen(structUsr, _stack.top.struc!);
-  }
-
-  if (bindingsIndex.isSeenStruct(structUsr)) {
-    _stack.top.struc = bindingsIndex.getSeenStruct(structUsr);
-
-    final skipDependencies = _stack.top.struc!.parsedDependencies ||
-        (config.structDependencies == StructDependencies.opaque &&
-            pointerReference &&
-            ignoreFilter);
-
-    if (!skipDependencies) {
-      // Prevents infinite recursion if struct has a pointer to itself.
-      _stack.top.struc!.parsedDependencies = true;
-      _setStructMembers(cursor);
-    } else if (!_stack.top.struc!.parsedDependencies) {
-      _logger.fine('Skipped dependencies.');
-    }
-
-    if (updateName) {
-      // If struct is seen, update it's name.
-      _stack.top.struc!.name = config.structDecl.renameUsingConfig(structName);
-    }
-  }
-
-  return _stack.pop().struc;
-}
-
-void _setStructMembers(clang_types.CXCursor cursor) {
-  _stack.top.hasAttr = clang.clang_Cursor_hasAttrs(cursor) != 0;
-  _stack.top.allignment = cursor.type().alignment();
-
-  final resultCode = clang.clang_visitChildren(
-    cursor,
-    Pointer.fromFunction(_structMembersVisitor,
-        clang_types.CXChildVisitResult.CXChildVisit_Break),
-    nullptr,
-  );
-
-  _logger.finest(
-      'Opaque: ${_stack.top.isInComplete}, HasAttr: ${_stack.top.hasAttr}, AlignValue: ${_stack.top.allignment}, MaxChildAlignValue: ${_stack.top.maxChildAlignment}, PackValue: ${_stack.top.packValue}.');
-  _stack.top.struc!.pack = _stack.top.packValue;
-
-  visitChildrenResultChecker(resultCode);
-
-  if (_stack.top.unimplementedMemberType) {
-    _logger.fine(
-        '---- Removed Struct members, reason: member with unimplementedtype ${cursor.completeStringRepr()}');
-    _logger.warning(
-        'Removed All Struct Members from ${_stack.top.struc!.name}(${_stack.top.struc!.originalName}), struct member has an unsupported type.');
-  } else if (_stack.top.flexibleArrayMember) {
-    _logger.fine(
-        '---- Removed Struct members, reason: incomplete array member ${cursor.completeStringRepr()}');
-    _logger.warning(
-        'Removed All Struct Members from ${_stack.top.struc!.name}(${_stack.top.struc!.originalName}), Flexible array members not supported.');
-  } else if (_stack.top.bitFieldMember) {
-    _logger.fine(
-        '---- Removed Struct members, reason: bitfield members ${cursor.completeStringRepr()}');
-    _logger.warning(
-        'Removed All Struct Members from ${_stack.top.struc!.name}(${_stack.top.struc!.originalName}), Bit Field members not supported.');
-  } else if (_stack.top.dartHandleMember && config.useDartHandle) {
-    _logger.fine(
-        '---- Removed Struct members, reason: Dart_Handle member. ${cursor.completeStringRepr()}');
-    _logger.warning(
-        'Removed All Struct Members from ${_stack.top.struc!.name}(${_stack.top.struc!.originalName}), Dart_Handle member not supported.');
-  } else if (_stack.top.incompleteStructMember) {
-    _logger.fine(
-        '---- Removed Struct members, reason: Incomplete Nested Struct member. ${cursor.completeStringRepr()}');
-    _logger.warning(
-        'Removed All Struct Members from ${_stack.top.struc!.name}(${_stack.top.struc!.originalName}), Incomplete Nested Struct member not supported.');
-  }
-
-  // Clear all struct members if struct is incomplete.
-  if (_stack.top.isInComplete) {
-    _stack.top.struc!.members.clear();
-  }
-
-  // C allow empty structs, but it's undefined behaviour at runtine. So we need
-  // to mark a struct incomplete if it has no members.
-  _stack.top.struc!.isInComplete =
-      _stack.top.isInComplete || _stack.top.struc!.members.isEmpty;
-}
-
-/// Visitor for the struct cursor [CXCursorKind.CXCursor_StructDecl].
-///
-/// Child visitor invoked on struct cursor.
-int _structMembersVisitor(clang_types.CXCursor cursor,
-    clang_types.CXCursor parent, Pointer<Void> clientData) {
-  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;
-      }
-
-      final mt = cursor.type().toCodeGenType();
-      if (mt.broadType == BroadType.IncompleteArray) {
-        // TODO(68): Structs with flexible Array Members are not supported.
-        _stack.top.flexibleArrayMember = true;
-      }
-      if (clang.clang_getFieldDeclBitWidth(cursor) != -1) {
-        // TODO(84): Struct with bitfields are not suppoorted.
-        _stack.top.bitFieldMember = true;
-      }
-      if (mt.broadType == BroadType.Handle) {
-        _stack.top.dartHandleMember = true;
-      }
-      if (mt.isIncompleteStruct) {
-        _stack.top.incompleteStructMember = true;
-      }
-      if (mt.getBaseType().broadType == BroadType.Unimplemented) {
-        _stack.top.unimplementedMemberType = true;
-      }
-
-      _stack.top.struc!.members.add(
-        Member(
-          dartDoc: getCursorDocComment(
-            cursor,
-            nesting.length + commentPrefix.length,
-          ),
-          originalName: cursor.spelling(),
-          name: config.structDecl.renameMemberUsingConfig(
-            _stack.top.struc!.originalName,
-            cursor.spelling(),
-          ),
-          type: mt,
-        ),
-      );
-    } else if (cursor.kind == clang_types.CXCursorKind.CXCursor_PackedAttr) {
-      _stack.top.hasPackedAttr = true;
-    }
-  } catch (e, s) {
-    _logger.severe(e);
-    _logger.severe(s);
-    rethrow;
-  }
-  return clang_types.CXChildVisitResult.CXChildVisit_Continue;
-}
diff --git a/lib/src/header_parser/sub_parsers/typedefdecl_parser.dart b/lib/src/header_parser/sub_parsers/typedefdecl_parser.dart
index 7c74aec..192c038 100644
--- a/lib/src/header_parser/sub_parsers/typedefdecl_parser.dart
+++ b/lib/src/header_parser/sub_parsers/typedefdecl_parser.dart
@@ -10,8 +10,8 @@
 import '../clang_bindings/clang_bindings.dart' as clang_types;
 import '../data.dart';
 import '../sub_parsers/enumdecl_parser.dart';
-import '../sub_parsers/structdecl_parser.dart';
 import '../utils.dart';
+import 'compounddecl_parser.dart';
 
 final _logger = Logger('ffigen.header_parser.typedefdecl_parser');
 
@@ -82,8 +82,25 @@
           _stack.top.binding = bindingsIndex.getSeenStruct(cursor.usr());
         } else {
           // This will update the name of struct if already seen.
-          _stack.top.binding =
-              parseStructDeclaration(cursor, name: _stack.top.typedefName);
+          _stack.top.binding = parseCompoundDeclaration(
+            cursor,
+            CompoundType.struct,
+            name: _stack.top.typedefName,
+          );
+        }
+        break;
+      case clang_types.CXCursorKind.CXCursor_UnionDecl:
+        if (_stack.top.typedefToPointer &&
+            bindingsIndex.isSeenUnion(cursor.usr())) {
+          // Skip a typedef pointer if struct is seen.
+          _stack.top.binding = bindingsIndex.getSeenUnion(cursor.usr());
+        } else {
+          // This will update the name of struct if already seen.
+          _stack.top.binding = parseCompoundDeclaration(
+            cursor,
+            CompoundType.union,
+            name: _stack.top.typedefName,
+          );
         }
         break;
       case clang_types.CXCursorKind.CXCursor_EnumDecl:
diff --git a/lib/src/header_parser/translation_unit_parser.dart b/lib/src/header_parser/translation_unit_parser.dart
index 69f7f13..5f59707 100644
--- a/lib/src/header_parser/translation_unit_parser.dart
+++ b/lib/src/header_parser/translation_unit_parser.dart
@@ -12,9 +12,9 @@
 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 'sub_parsers/structdecl_parser.dart';
 import 'sub_parsers/typedefdecl_parser.dart';
 import 'utils.dart';
 
@@ -51,7 +51,10 @@
           addToBindings(parseTypedefDeclaration(cursor));
           break;
         case clang_types.CXCursorKind.CXCursor_StructDecl:
-          addToBindings(parseStructDeclaration(cursor));
+          addToBindings(parseCompoundDeclaration(cursor, CompoundType.struct));
+          break;
+        case clang_types.CXCursorKind.CXCursor_UnionDecl:
+          addToBindings(parseCompoundDeclaration(cursor, CompoundType.union));
           break;
         case clang_types.CXCursorKind.CXCursor_EnumDecl:
           addToBindings(parseEnumDeclaration(cursor));
diff --git a/lib/src/header_parser/type_extractor/extractor.dart b/lib/src/header_parser/type_extractor/extractor.dart
index cd0445e..521e71e 100644
--- a/lib/src/header_parser/type_extractor/extractor.dart
+++ b/lib/src/header_parser/type_extractor/extractor.dart
@@ -9,7 +9,7 @@
 
 import '../clang_bindings/clang_bindings.dart' as clang_types;
 import '../data.dart';
-import '../sub_parsers/structdecl_parser.dart';
+import '../sub_parsers/compounddecl_parser.dart';
 import '../translation_unit_parser.dart';
 import '../type_extractor/cxtypekindmap.dart';
 import '../utils.dart';
@@ -36,8 +36,9 @@
 
       // Replace Pointer<_Dart_Handle> with Handle.
       if (config.useDartHandle &&
-          s.broadType == BroadType.Struct &&
-          s.struc!.usr == strings.dartHandleUsr) {
+          s.broadType == BroadType.Compound &&
+          s.compound!.compoundType == CompoundType.struct &&
+          s.compound!.usr == strings.dartHandleUsr) {
         return Type.handle();
       }
       return Type.pointer(s);
@@ -115,48 +116,69 @@
   final cursor = clang.clang_getTypeDeclaration(cxtype);
   _logger.fine('${_padding}_extractfromRecord: ${cursor.completeStringRepr()}');
 
-  switch (clang.clang_getCursorKind(cursor)) {
-    case clang_types.CXCursorKind.CXCursor_StructDecl:
-      final structUsr = cursor.usr();
+  final cursorKind = clang.clang_getCursorKind(cursor);
+  if (cursorKind == clang_types.CXCursorKind.CXCursor_StructDecl ||
+      cursorKind == clang_types.CXCursorKind.CXCursor_UnionDecl) {
+    final declUsr = cursor.usr();
 
-      // Name of typedef (parentName) is used if available.
-      final structName = parentName ?? cursor.spelling();
+    // Name of typedef (parentName) is used if available.
+    final declName = parentName ?? cursor.spelling();
 
-      // Also add a struct binding, if its unseen.
-      // TODO(23): Check if we should auto add struct.
-      if (bindingsIndex.isSeenStruct(structUsr)) {
-        type = Type.struct(bindingsIndex.getSeenStruct(structUsr)!);
+    // Set includer functions according to compoundType.
+    final bool Function(String) isSeenDecl;
+    final Compound? Function(String) getSeenDecl;
+    final CompoundType compoundType;
 
-        // This will parse the dependencies if needed.
-        parseStructDeclaration(
-          cursor,
-          name: structName,
-          ignoreFilter: true,
-          pointerReference: pointerReference,
-          updateName: false,
-        );
-      } else {
-        final struc = parseStructDeclaration(
-          cursor,
-          name: structName,
-          ignoreFilter: true,
-          pointerReference: pointerReference,
-        );
-        type = Type.struct(struc!);
+    switch (cursorKind) {
+      case clang_types.CXCursorKind.CXCursor_StructDecl:
+        isSeenDecl = bindingsIndex.isSeenStruct;
+        getSeenDecl = bindingsIndex.getSeenStruct;
+        compoundType = CompoundType.struct;
+        break;
+      case clang_types.CXCursorKind.CXCursor_UnionDecl:
+        isSeenDecl = bindingsIndex.isSeenUnion;
+        getSeenDecl = bindingsIndex.getSeenUnion;
+        compoundType = CompoundType.union;
+        break;
+      default:
+        throw Exception('Unhandled compound type cursorkind.');
+    }
 
-        // Add to bindings if it's not Dart_Handle and is unseen.
-        if (!(config.useDartHandle && structUsr == strings.dartHandleUsr)) {
-          addToBindings(struc);
-        }
+    // Also add a struct binding, if its unseen.
+    // TODO(23): Check if we should auto add compound declarations.
+    if (isSeenDecl(declUsr)) {
+      type = Type.compound(getSeenDecl(declUsr)!);
+
+      // This will parse the dependencies if needed.
+      parseCompoundDeclaration(
+        cursor,
+        compoundType,
+        name: declName,
+        ignoreFilter: true,
+        pointerReference: pointerReference,
+        updateName: false,
+      );
+    } else {
+      final struc = parseCompoundDeclaration(
+        cursor,
+        compoundType,
+        name: declName,
+        ignoreFilter: true,
+        pointerReference: pointerReference,
+      );
+      type = Type.compound(struc!);
+
+      // Add to bindings if it's not Dart_Handle and is unseen.
+      if (!(config.useDartHandle && declUsr == strings.dartHandleUsr)) {
+        addToBindings(struc);
       }
-
-      break;
-    default:
-      _logger.fine(
-          'typedeclarationCursorVisitor: _extractfromRecord: Not Implemented, ${cursor.completeStringRepr()}');
-      return Type.unimplemented(
-          'Type: ${cxtype.kindSpelling()} not implemented');
+    }
+  } else {
+    _logger.fine(
+        'typedeclarationCursorVisitor: _extractfromRecord: Not Implemented, ${cursor.completeStringRepr()}');
+    return Type.unimplemented('Type: ${cxtype.kindSpelling()} not implemented');
   }
+
   return type;
 }
 
@@ -173,7 +195,7 @@
     final t = clang.clang_getArgType(cxtype, i);
     final pt = t.toCodeGenType();
 
-    if (pt.isIncompleteStruct) {
+    if (pt.isIncompleteCompound) {
       return Type.unimplemented(
           'Incomplete Struct by value in function parameter.');
     } else if (pt.getBaseType().broadType == BroadType.Unimplemented) {
diff --git a/lib/src/header_parser/utils.dart b/lib/src/header_parser/utils.dart
index fda7f65..272f21d 100644
--- a/lib/src/header_parser/utils.dart
+++ b/lib/src/header_parser/utils.dart
@@ -327,6 +327,7 @@
 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, Func> _functions = {};
   final Map<String, EnumClass> _enumClass = {};
   final Map<String, Constant> _unnamedEnumConstants = {};
@@ -339,14 +340,26 @@
     return _structs.containsKey(usr);
   }
 
-  void addStructToSeen(String usr, Struc struc) {
-    _structs[usr] = struc;
+  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);
   }
diff --git a/lib/src/strings.dart b/lib/src/strings.dart
index 0b5d2f1..b551ead 100644
--- a/lib/src/strings.dart
+++ b/lib/src/strings.dart
@@ -46,6 +46,7 @@
 // Declarations.
 const functions = 'functions';
 const structs = 'structs';
+const unions = 'unions';
 const enums = 'enums';
 const unnamedEnums = 'unnamed-enums';
 const globals = 'globals';
@@ -58,10 +59,10 @@
 const memberRename = 'member-rename';
 const symbolAddress = 'symbol-address';
 
-const structDependencies = 'dependency-only';
-// Values for `structDependencies`.
-const fullStructDependencies = 'full';
-const opaqueStructDependencies = 'opaque';
+const dependencyOnly = 'dependency-only';
+// Values for `compoundDependencies`.
+const fullCompoundDependencies = 'full';
+const opaqueCompoundDependencies = 'opaque';
 
 const structPack = 'pack';
 const Map<Object, int?> packingValuesMap = {
diff --git a/pubspec.yaml b/pubspec.yaml
index ddacda0..54793af 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,12 +3,12 @@
 # BSD-style license that can be found in the LICENSE file.
 
 name: ffigen
-version: 3.0.0
+version: 3.1.0-dev.0
 homepage: https://github.com/dart-lang/ffigen
 description: Generator for FFI bindings, using LibClang to parse C header files.
 
 environment:
-  sdk: '>=2.13.0 <3.0.0'
+  sdk: '>=2.14.0-115.0.dev <3.0.0'
 
 dependencies:
   ffi: ^1.0.0
diff --git a/test/code_generator_tests/code_generator_test.dart b/test/code_generator_tests/code_generator_test.dart
index 7b0fa29..79ac352 100644
--- a/test/code_generator_tests/code_generator_test.dart
+++ b/test/code_generator_tests/code_generator_test.dart
@@ -404,6 +404,49 @@
     );
     _matchLib(library, 'packed_structs');
   });
+  test('Union Bindings', () {
+    final struct1 = Struc(name: 'Struct1', members: [
+      Member(name: 'a', type: Type.nativeType(SupportedNativeType.Int8))
+    ]);
+    final union1 = Union(name: 'Union1', members: [
+      Member(name: 'a', type: Type.nativeType(SupportedNativeType.Int8))
+    ]);
+    final library = Library(
+      name: 'Bindings',
+      bindings: [
+        struct1,
+        union1,
+        Union(name: 'EmptyUnion'),
+        Union(name: 'Primitives', members: [
+          Member(name: 'a', type: Type.nativeType(SupportedNativeType.Int8)),
+          Member(name: 'b', type: Type.nativeType(SupportedNativeType.Int32)),
+          Member(name: 'c', type: Type.nativeType(SupportedNativeType.Float)),
+          Member(name: 'd', type: Type.nativeType(SupportedNativeType.Double)),
+        ]),
+        Union(name: 'PrimitivesWithPointers', members: [
+          Member(name: 'a', type: Type.nativeType(SupportedNativeType.Int8)),
+          Member(name: 'b', type: Type.nativeType(SupportedNativeType.Float)),
+          Member(
+              name: 'c',
+              type: Type.pointer(Type.nativeType(SupportedNativeType.Double))),
+          Member(name: 'd', type: Type.pointer(Type.union(union1))),
+          Member(name: 'd', type: Type.pointer(Type.struct(struct1))),
+        ]),
+        Union(name: 'WithArray', members: [
+          Member(
+              name: 'a',
+              type: Type.constantArray(
+                  10, Type.nativeType(SupportedNativeType.Int8))),
+          Member(name: 'b', type: Type.constantArray(10, Type.union(union1))),
+          Member(name: 'b', type: Type.constantArray(10, Type.struct(struct1))),
+          Member(
+              name: 'c',
+              type: Type.constantArray(10, Type.pointer(Type.union(union1)))),
+        ]),
+      ],
+    );
+    _matchLib(library, 'unions');
+  });
 }
 
 /// Utility to match expected bindings to the generated bindings.
diff --git a/test/code_generator_tests/expected_bindings/_expected_unions_bindings.dart b/test/code_generator_tests/expected_bindings/_expected_unions_bindings.dart
new file mode 100644
index 0000000..60399e5
--- /dev/null
+++ b/test/code_generator_tests/expected_bindings/_expected_unions_bindings.dart
@@ -0,0 +1,58 @@
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+import 'dart:ffi' as ffi;
+
+class Struct1 extends ffi.Struct {
+  @ffi.Int8()
+  external int a;
+}
+
+class Union1 extends ffi.Union {
+  @ffi.Int8()
+  external int a;
+}
+
+class EmptyUnion extends ffi.Opaque {}
+
+class Primitives extends ffi.Union {
+  @ffi.Int8()
+  external int a;
+
+  @ffi.Int32()
+  external int b;
+
+  @ffi.Float()
+  external double c;
+
+  @ffi.Double()
+  external double d;
+}
+
+class PrimitivesWithPointers extends ffi.Union {
+  @ffi.Int8()
+  external int a;
+
+  @ffi.Float()
+  external double b;
+
+  external ffi.Pointer<ffi.Double> c;
+
+  external ffi.Pointer<Union1> d;
+
+  external ffi.Pointer<Struct1> d_1;
+}
+
+class WithArray extends ffi.Union {
+  @ffi.Array.multi([10])
+  external ffi.Array<ffi.Int8> a;
+
+  @ffi.Array.multi([10])
+  external ffi.Array<Union1> b;
+
+  @ffi.Array.multi([10])
+  external ffi.Array<Struct1> b_1;
+
+  @ffi.Array.multi([10])
+  external ffi.Array<ffi.Pointer<Union1>> c;
+}
diff --git a/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart b/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart
index 0c3498f..c37dbaf 100644
--- a/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart
+++ b/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart
@@ -29,6 +29,17 @@
 
   late final _func_ptr = _lookup<ffi.NativeFunction<_c_func>>('func');
   late final _dart_func _func = _func_ptr.asFunction<_dart_func>();
+
+  ffi.Pointer<UB> func2(
+    ffi.Pointer<UA> a,
+  ) {
+    return _func2(
+      a,
+    );
+  }
+
+  late final _func2_ptr = _lookup<ffi.NativeFunction<_c_func2>>('func2');
+  late final _dart_func2 _func2 = _func2_ptr.asFunction<_dart_func2>();
 }
 
 class B extends ffi.Opaque {}
@@ -48,6 +59,23 @@
   external D d;
 }
 
+class UB extends ffi.Opaque {}
+
+class UA extends ffi.Opaque {}
+
+class UC extends ffi.Opaque {}
+
+class UD extends ffi.Union {
+  @ffi.Int32()
+  external int a;
+}
+
+class UE extends ffi.Union {
+  external ffi.Pointer<UC> c;
+
+  external UD d;
+}
+
 typedef _c_func = ffi.Pointer<B> Function(
   ffi.Pointer<A> a,
 );
@@ -55,3 +83,11 @@
 typedef _dart_func = ffi.Pointer<B> Function(
   ffi.Pointer<A> a,
 );
+
+typedef _c_func2 = ffi.Pointer<UB> Function(
+  ffi.Pointer<UA> a,
+);
+
+typedef _dart_func2 = ffi.Pointer<UB> Function(
+  ffi.Pointer<UA> a,
+);
diff --git a/test/header_parser_tests/expected_bindings/_expected_unions_bindings.dart b/test/header_parser_tests/expected_bindings/_expected_unions_bindings.dart
new file mode 100644
index 0000000..a06659e
--- /dev/null
+++ b/test/header_parser_tests/expected_bindings/_expected_unions_bindings.dart
@@ -0,0 +1,74 @@
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+import 'dart:ffi' as ffi;
+
+/// Unions Test
+class NativeLibrary {
+  /// Holds the symbol lookup function.
+  final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
+      _lookup;
+
+  /// The symbols are looked up in [dynamicLibrary].
+  NativeLibrary(ffi.DynamicLibrary dynamicLibrary)
+      : _lookup = dynamicLibrary.lookup;
+
+  /// The symbols are looked up with [lookup].
+  NativeLibrary.fromLookup(
+      ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
+          lookup)
+      : _lookup = lookup;
+
+  void func1(
+    ffi.Pointer<Union2> s,
+  ) {
+    return _func1(
+      s,
+    );
+  }
+
+  late final _func1_ptr = _lookup<ffi.NativeFunction<_c_func1>>('func1');
+  late final _dart_func1 _func1 = _func1_ptr.asFunction<_dart_func1>();
+
+  void func2(
+    ffi.Pointer<Union3> s,
+  ) {
+    return _func2(
+      s,
+    );
+  }
+
+  late final _func2_ptr = _lookup<ffi.NativeFunction<_c_func2>>('func2');
+  late final _dart_func2 _func2 = _func2_ptr.asFunction<_dart_func2>();
+}
+
+class Union1 extends ffi.Union {
+  @ffi.Int32()
+  external int a;
+}
+
+class Union2 extends ffi.Union {
+  external Union1 a;
+}
+
+class Union3 extends ffi.Opaque {}
+
+class Union4 extends ffi.Opaque {}
+
+class Union5 extends ffi.Opaque {}
+
+typedef _c_func1 = ffi.Void Function(
+  ffi.Pointer<Union2> s,
+);
+
+typedef _dart_func1 = void Function(
+  ffi.Pointer<Union2> s,
+);
+
+typedef _c_func2 = ffi.Void Function(
+  ffi.Pointer<Union3> s,
+);
+
+typedef _dart_func2 = void Function(
+  ffi.Pointer<Union3> s,
+);
diff --git a/test/header_parser_tests/opaque_dependencies.h b/test/header_parser_tests/opaque_dependencies.h
index fa24993..e30b260 100644
--- a/test/header_parser_tests/opaque_dependencies.h
+++ b/test/header_parser_tests/opaque_dependencies.h
@@ -34,3 +34,36 @@
     struct C *c;
     struct D d;
 };
+
+// Opaque.
+union UA
+{
+    int a;
+};
+
+// Opaque.
+union UB
+{
+    int a;
+};
+
+union UB *func2(union UA *a);
+
+// Opaque.
+union UC
+{
+    int a;
+};
+
+// Full (excluded, but used by value).
+union UD
+{
+    int a;
+};
+
+// Full (included)
+union UE
+{
+    union UC *c;
+    union UD d;
+};
diff --git a/test/header_parser_tests/opaque_dependencies_test.dart b/test/header_parser_tests/opaque_dependencies_test.dart
index aea5468..101e8db 100644
--- a/test/header_parser_tests/opaque_dependencies_test.dart
+++ b/test/header_parser_tests/opaque_dependencies_test.dart
@@ -28,7 +28,11 @@
 ${strings.structs}:
   ${strings.include}:
     - 'E'
-  ${strings.structDependencies}: ${strings.opaqueStructDependencies}
+  ${strings.dependencyOnly}: ${strings.opaqueCompoundDependencies}
+${strings.unions}:
+  ${strings.include}:
+    - 'UE'
+  ${strings.dependencyOnly}: ${strings.opaqueCompoundDependencies}
         ''') as yaml.YamlMap),
       );
     });
diff --git a/test/header_parser_tests/unions.h b/test/header_parser_tests/unions.h
new file mode 100644
index 0000000..1bd9ae1
--- /dev/null
+++ b/test/header_parser_tests/unions.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2021, 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.
+
+union Union1
+{
+    int a;
+};
+
+union Union2
+{
+    union Union1 a;
+};
+
+// Should be marked incomplete, long double not supported.
+union Union3
+{
+    long double a;
+};
+
+// All members should be removed, Bit fields are not supported.
+union Union4
+{
+    int a : 3;
+    int : 2; // Unnamed bit field.
+};
+
+// All members should be removed, Incomplete union members are not supported.
+union Union5
+{
+    int a;
+    union Union3 s; // Incomplete nested union.
+};
+
+void func1(union Union2 *s);
+
+// Incomplete array parameter will be treated as a pointer.
+void func2(union Union3 s[]);
diff --git a/test/header_parser_tests/unions_test.dart b/test/header_parser_tests/unions_test.dart
new file mode 100644
index 0000000..699f93d
--- /dev/null
+++ b/test/header_parser_tests/unions_test.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2021, 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/code_generator.dart';
+import 'package:ffigen/src/config_provider.dart';
+import 'package:ffigen/src/header_parser.dart' as parser;
+import 'package:ffigen/src/strings.dart' as strings;
+import 'package:logging/logging.dart';
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart' as yaml;
+
+import '../test_utils.dart';
+
+late Library actual;
+void main() {
+  group('packed_structs_test', () {
+    setUpAll(() {
+      logWarnings(Level.SEVERE);
+      actual = parser.parse(
+        Config.fromYaml(yaml.loadYaml('''
+${strings.name}: 'NativeLibrary'
+${strings.description}: 'Unions Test'
+${strings.output}: 'unused'
+${strings.headers}:
+  ${strings.entryPoints}:
+    - 'test/header_parser_tests/unions.h'
+        ''') as yaml.YamlMap),
+      );
+    });
+
+    test('Expected bindings', () {
+      matchLibraryWithExpected(actual, [
+        'test',
+        'debug_generated',
+        'unions_test_output.dart'
+      ], [
+        'test',
+        'header_parser_tests',
+        'expected_bindings',
+        '_expected_unions_bindings.dart'
+      ]);
+    });
+  });
+}