Added option to generate dependency-only structs as opaque (#181)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index df06dd5..4a849f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+# 2.2.3
+- Added new subkey `dependency-only` (options - `full (default) | opaque`) under `structs`.
+When set to `opaque`, ffigen will generate empty `Opaque` structs if structs
+were excluded in config (i.e added because they were a dependency) and
+only passed by reference(pointer).
+
 # 2.2.2
 - Fixed generation of empty opaque structs due to forward declarations in header files.
 
diff --git a/README.md b/README.md
index 02c9083..7c00f1a 100644
--- a/README.md
+++ b/README.md
@@ -214,6 +214,21 @@
   </td>
   </tr>
   <tr>
+    <td>structs -> dependency-only</td>
+    <td>If `opaque`, generates empty `Opaque` structs if structs
+were not included in config (but were added since they are a dependency) and
+only passed by reference(pointer).<br>
+    Options - full(default) | opaque </i><br>
+    </td>
+    <td>
+
+```yaml
+structs:
+  dependency-only: opaque
+```
+  </td>
+  </tr>
+  <tr>
     <td>sort</td>
     <td>Sort the bindings according to name.<br>
       <b>Default: false</b>, i.e keep the order as in the source files.
@@ -480,6 +495,13 @@
 This happens when an excluded struct 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
+these struct dependencies as `Opaque` if they were only passed by reference(pointer).
+```yaml
+structs:
+  dependency-only: opaque
+```
+
 ### How to expose the native pointers and typedefs?
 
 By default all native pointers and typedefs are hidden, but you can use the
diff --git a/lib/src/code_generator/dart_keywords.dart b/lib/src/code_generator/dart_keywords.dart
index 2f3d4d6..8e0de3c 100644
--- a/lib/src/code_generator/dart_keywords.dart
+++ b/lib/src/code_generator/dart_keywords.dart
@@ -66,5 +66,6 @@
   'show',
   'dynamic',
   'implements',
-  'static'
+  'static',
+  'late',
 };
diff --git a/lib/src/code_generator/struc.dart b/lib/src/code_generator/struc.dart
index d566821..2fc96d6 100644
--- a/lib/src/code_generator/struc.dart
+++ b/lib/src/code_generator/struc.dart
@@ -44,6 +44,9 @@
 
   bool get isOpaque => members.isEmpty;
 
+  /// Marker for checking if the dependencies are parsed.
+  bool parsedDependencies = false;
+
   Struc({
     String? usr,
     String? originalName,
diff --git a/lib/src/config_provider/config.dart b/lib/src/config_provider/config.dart
index a330e60..cf083df 100644
--- a/lib/src/config_provider/config.dart
+++ b/lib/src/config_provider/config.dart
@@ -76,6 +76,10 @@
   CommentType get commentType => _commentType;
   late CommentType _commentType;
 
+  /// Whether structs that are dependencies should be included.
+  StructDependencies get structDependencies => _structDependencies;
+  late StructDependencies _structDependencies;
+
   /// If tool should generate array workarounds.
   ///
   /// If false(default), structs with inline array members will have all its
@@ -121,13 +125,41 @@
     return configspecs;
   }
 
+  /// Checks if there are nested [key] in [map].
+  bool _checkKeyInYaml(List<String> key, YamlMap map) {
+    dynamic last = map;
+    for (final k in key) {
+      if (last is YamlMap) {
+        if (!last.containsKey(k)) return false;
+        last = last[k];
+      } else {
+        return false;
+      }
+    }
+    return last != null;
+  }
+
+  /// Extracts value of nested [key] from [map].
+  dynamic _getKeyValueFromYaml(List<String> key, YamlMap map) {
+    if (_checkKeyInYaml(key, map)) {
+      dynamic last = map;
+      for (final k in key) {
+        last = last[k];
+      }
+      return last;
+    }
+
+    return null;
+  }
+
   /// Validates Yaml according to given specs.
-  bool _checkConfigs(YamlMap map, Map<String, Specification> specs) {
+  bool _checkConfigs(YamlMap map, Map<List<String>, Specification> specs) {
     var _result = true;
     for (final key in specs.keys) {
       final spec = specs[key];
-      if (map.containsKey(key)) {
-        _result = _result && spec!.validator(key, map[key]);
+      if (_checkKeyInYaml(key, map)) {
+        _result =
+            _result && spec!.validator(key, _getKeyValueFromYaml(key, map));
       } else if (spec!.requirement == Requirement.yes) {
         _logger.severe("Key '$key' is required.");
         _result = false;
@@ -137,7 +169,8 @@
     }
     // Warn about unknown keys.
     for (final key in map.keys) {
-      if (!specs.containsKey(key)) {
+      final specString = specs.keys.map((e) => e.join(':')).toSet();
+      if (!specString.contains(key)) {
         _logger.warning("Unknown key '$key' found.");
       }
     }
@@ -148,11 +181,11 @@
   /// Extracts variables from Yaml according to given specs.
   ///
   /// Validation must be done beforehand, using [_checkConfigs].
-  void _extract(YamlMap map, Map<String, Specification> specs) {
+  void _extract(YamlMap map, Map<List<String>, Specification> specs) {
     for (final key in specs.keys) {
       final spec = specs[key];
-      if (map.containsKey(key)) {
-        spec!.extractedResult(spec.extractor(map[key]));
+      if (_checkKeyInYaml(key, map)) {
+        spec!.extractedResult(spec.extractor(_getKeyValueFromYaml(key, map)));
       } else {
         spec!.extractedResult(spec.defaultValue?.call());
       }
@@ -162,28 +195,28 @@
   /// Returns map of various specifications avaialble for our tool.
   ///
   /// Key: Name, Value: [Specification]
-  Map<String, Specification> _getSpecs() {
-    return <String, Specification>{
-      strings.llvmLib: Specification<String>(
+  Map<List<String>, Specification> _getSpecs() {
+    return <List<String>, Specification>{
+      [strings.llvmLib]: Specification<String>(
         requirement: Requirement.no,
         validator: llvmLibValidator,
         extractor: llvmLibExtractor,
         defaultValue: () => findDylibAtDefaultLocations(),
         extractedResult: (dynamic result) => _libclangDylib = result as String,
       ),
-      strings.output: Specification<String>(
+      [strings.output]: Specification<String>(
         requirement: Requirement.yes,
         validator: outputValidator,
         extractor: outputExtractor,
         extractedResult: (dynamic result) => _output = result as String,
       ),
-      strings.headers: Specification<Headers>(
+      [strings.headers]: Specification<Headers>(
         requirement: Requirement.yes,
         validator: headersValidator,
         extractor: headersExtractor,
         extractedResult: (dynamic result) => _headers = result as Headers,
       ),
-      strings.compilerOpts: Specification<List<String>>(
+      [strings.compilerOpts]: Specification<List<String>>(
         requirement: Requirement.no,
         validator: compilerOptsValidator,
         extractor: compilerOptsExtractor,
@@ -191,7 +224,7 @@
         extractedResult: (dynamic result) =>
             _compilerOpts = result as List<String>,
       ),
-      strings.functions: Specification<Declaration>(
+      [strings.functions]: Specification<Declaration>(
         requirement: Requirement.no,
         validator: declarationConfigValidator,
         extractor: declarationConfigExtractor,
@@ -200,7 +233,7 @@
           _functionDecl = result as Declaration;
         },
       ),
-      strings.structs: Specification<Declaration>(
+      [strings.structs]: Specification<Declaration>(
         requirement: Requirement.no,
         validator: declarationConfigValidator,
         extractor: declarationConfigExtractor,
@@ -209,7 +242,7 @@
           _structDecl = result as Declaration;
         },
       ),
-      strings.enums: Specification<Declaration>(
+      [strings.enums]: Specification<Declaration>(
         requirement: Requirement.no,
         validator: declarationConfigValidator,
         extractor: declarationConfigExtractor,
@@ -218,7 +251,7 @@
           _enumClassDecl = result as Declaration;
         },
       ),
-      strings.unnamedEnums: Specification<Declaration>(
+      [strings.unnamedEnums]: Specification<Declaration>(
         requirement: Requirement.no,
         validator: declarationConfigValidator,
         extractor: declarationConfigExtractor,
@@ -226,7 +259,7 @@
         extractedResult: (dynamic result) =>
             _unnamedEnumConstants = result as Declaration,
       ),
-      strings.globals: Specification<Declaration>(
+      [strings.globals]: Specification<Declaration>(
         requirement: Requirement.no,
         validator: declarationConfigValidator,
         extractor: declarationConfigExtractor,
@@ -235,7 +268,7 @@
           _globals = result as Declaration;
         },
       ),
-      strings.macros: Specification<Declaration>(
+      [strings.macros]: Specification<Declaration>(
         requirement: Requirement.no,
         validator: declarationConfigValidator,
         extractor: declarationConfigExtractor,
@@ -244,7 +277,7 @@
           _macroDecl = result as Declaration;
         },
       ),
-      strings.sizemap: Specification<Map<int, SupportedNativeType>>(
+      [strings.sizemap]: Specification<Map<int, SupportedNativeType>>(
         validator: sizemapValidator,
         extractor: sizemapExtractor,
         defaultValue: () => <int, SupportedNativeType>{},
@@ -257,21 +290,21 @@
           }
         },
       ),
-      strings.typedefmap: Specification<Map<String, SupportedNativeType>>(
+      [strings.typedefmap]: Specification<Map<String, SupportedNativeType>>(
         validator: typedefmapValidator,
         extractor: typedefmapExtractor,
         defaultValue: () => <String, SupportedNativeType>{},
         extractedResult: (dynamic result) => _typedefNativeTypeMappings =
             result as Map<String, SupportedNativeType>,
       ),
-      strings.sort: Specification<bool>(
+      [strings.sort]: Specification<bool>(
         requirement: Requirement.no,
         validator: booleanValidator,
         extractor: booleanExtractor,
         defaultValue: () => false,
         extractedResult: (dynamic result) => _sort = result as bool,
       ),
-      strings.useSupportedTypedefs: Specification<bool>(
+      [strings.useSupportedTypedefs]: Specification<bool>(
         requirement: Requirement.no,
         validator: booleanValidator,
         extractor: booleanExtractor,
@@ -279,7 +312,7 @@
         extractedResult: (dynamic result) =>
             _useSupportedTypedefs = result as bool,
       ),
-      strings.comments: Specification<CommentType>(
+      [strings.comments]: Specification<CommentType>(
         requirement: Requirement.no,
         validator: commentValidator,
         extractor: commentExtractor,
@@ -287,28 +320,37 @@
         extractedResult: (dynamic result) =>
             _commentType = result as CommentType,
       ),
-      strings.arrayWorkaround: Specification<bool>(
+      [strings.structs, strings.structDependencies]:
+          Specification<StructDependencies>(
+        requirement: Requirement.no,
+        validator: structDependenciesValidator,
+        extractor: structDependenciesExtractor,
+        defaultValue: () => StructDependencies.full,
+        extractedResult: (dynamic result) =>
+            _structDependencies = result as StructDependencies,
+      ),
+      [strings.arrayWorkaround]: Specification<bool>(
         requirement: Requirement.no,
         validator: booleanValidator,
         extractor: booleanExtractor,
         defaultValue: () => false,
         extractedResult: (dynamic result) => _arrayWorkaround = result as bool,
       ),
-      strings.dartBool: Specification<bool>(
+      [strings.dartBool]: Specification<bool>(
         requirement: Requirement.no,
         validator: booleanValidator,
         extractor: booleanExtractor,
         defaultValue: () => true,
         extractedResult: (dynamic result) => _dartBool = result as bool,
       ),
-      strings.name: Specification<String>(
+      [strings.name]: Specification<String>(
         requirement: Requirement.prefer,
         validator: dartClassNameValidator,
         extractor: stringExtractor,
         defaultValue: () => 'NativeLibrary',
         extractedResult: (dynamic result) => _wrapperName = result as String,
       ),
-      strings.description: Specification<String?>(
+      [strings.description]: Specification<String?>(
         requirement: Requirement.prefer,
         validator: nonEmptyStringValidator,
         extractor: stringExtractor,
@@ -316,13 +358,13 @@
         extractedResult: (dynamic result) =>
             _wrapperDocComment = result as String?,
       ),
-      strings.preamble: Specification<String?>(
+      [strings.preamble]: Specification<String?>(
         requirement: Requirement.no,
         validator: nonEmptyStringValidator,
         extractor: stringExtractor,
         extractedResult: (dynamic result) => _preamble = result as String?,
       ),
-      strings.useDartHandle: Specification<bool>(
+      [strings.useDartHandle]: Specification<bool>(
         requirement: Requirement.no,
         validator: booleanValidator,
         extractor: booleanExtractor,
diff --git a/lib/src/config_provider/config_types.dart b/lib/src/config_provider/config_types.dart
index e412d96..35d24f0 100644
--- a/lib/src/config_provider/config_types.dart
+++ b/lib/src/config_provider/config_types.dart
@@ -26,11 +26,13 @@
 enum CommentStyle { doxygen, any }
 enum CommentLength { none, brief, full }
 
+enum StructDependencies { full, opaque }
+
 /// Represents a single specification in configurations.
 ///
 /// [E] is the return type of the extractedResult.
 class Specification<E> {
-  final bool Function(String name, dynamic value) validator;
+  final bool Function(List<String> name, dynamic value) validator;
   final E Function(dynamic map) extractor;
   final E Function()? defaultValue;
 
diff --git a/lib/src/config_provider/spec_utils.dart b/lib/src/config_provider/spec_utils.dart
index eeab2ae..0351d8c 100644
--- a/lib/src/config_provider/spec_utils.dart
+++ b/lib/src/config_provider/spec_utils.dart
@@ -40,8 +40,8 @@
 
 bool booleanExtractor(dynamic value) => value as bool;
 
-bool booleanValidator(String name, dynamic value) =>
-    checkType<bool>([name], value);
+bool booleanValidator(List<String> name, dynamic value) =>
+    checkType<bool>(name, value);
 
 Map<int, SupportedNativeType> sizemapExtractor(dynamic yamlConfig) {
   final resultMap = <int, SupportedNativeType>{};
@@ -59,8 +59,8 @@
   return resultMap;
 }
 
-bool sizemapValidator(String name, dynamic yamlConfig) {
-  if (!checkType<YamlMap>([name], yamlConfig)) {
+bool sizemapValidator(List<String> name, dynamic yamlConfig) {
+  if (!checkType<YamlMap>(name, yamlConfig)) {
     return false;
   }
   for (final key in (yamlConfig as YamlMap).keys) {
@@ -89,8 +89,8 @@
   return resultMap;
 }
 
-bool typedefmapValidator(String name, dynamic yamlConfig) {
-  if (!checkType<YamlMap>([name], yamlConfig)) {
+bool typedefmapValidator(List<String> name, dynamic yamlConfig) {
+  if (!checkType<YamlMap>(name, yamlConfig)) {
     return false;
   }
   for (final value in (yamlConfig as YamlMap).values) {
@@ -106,8 +106,8 @@
 List<String> compilerOptsExtractor(dynamic value) =>
     (value as String).split(' ');
 
-bool compilerOptsValidator(String name, dynamic value) =>
-    checkType<String>([name], value);
+bool compilerOptsValidator(List<String> name, dynamic value) =>
+    checkType<String>(name, value);
 
 Headers headersExtractor(dynamic yamlConfig) {
   final entryPoints = <String>[];
@@ -147,8 +147,8 @@
   );
 }
 
-bool headersValidator(String name, dynamic value) {
-  if (!checkType<YamlMap>([name], value)) {
+bool headersValidator(List<String> name, dynamic value) {
+  if (!checkType<YamlMap>(name, value)) {
     return false;
   }
   if (!(value as YamlMap).containsKey(strings.entryPoints)) {
@@ -157,7 +157,7 @@
   } else {
     for (final key in value.keys) {
       if (key == strings.entryPoints || key == strings.includeDirectives) {
-        if (!checkType<YamlList>([name, key as String], value[key])) {
+        if (!checkType<YamlList>([...name, key as String], value[key])) {
           return false;
         }
       } else {
@@ -171,8 +171,8 @@
 
 String libclangDylibExtractor(dynamic value) => getDylibPath(value as String);
 
-bool libclangDylibValidator(String name, dynamic value) {
-  if (!checkType<String>([name], value)) {
+bool libclangDylibValidator(List<String> name, dynamic value) {
+  if (!checkType<String>(name, value)) {
     return false;
   } else {
     final dylibPath = getDylibPath(value as String);
@@ -248,8 +248,8 @@
   }
 }
 
-bool llvmLibValidator(String name, dynamic value) {
-  if (!checkType<String>([name], value) ||
+bool llvmLibValidator(List<String> name, dynamic value) {
+  if (!checkType<String>(name, value) ||
       !Directory(value as String).existsSync()) {
     _logger.severe('Expected $name to be a valid folder Path.');
     return false;
@@ -259,8 +259,8 @@
 
 String outputExtractor(dynamic value) => _replaceSeparators(value as String);
 
-bool outputValidator(String name, dynamic value) =>
-    checkType<String>([name], value);
+bool outputValidator(List<String> name, dynamic value) =>
+    checkType<String>(name, value);
 
 /// Returns true if [str] is not a full name.
 ///
@@ -380,37 +380,37 @@
   );
 }
 
-bool declarationConfigValidator(String name, dynamic value) {
+bool declarationConfigValidator(List<String> name, dynamic value) {
   var _result = true;
   if (value is YamlMap) {
     for (final key in value.keys) {
       if (key == strings.include || key == strings.exclude) {
-        if (!checkType<YamlList>([name, key as String], value[key])) {
+        if (!checkType<YamlList>([...name, key as String], value[key])) {
           _result = false;
         }
       } else if (key == strings.rename) {
-        if (!checkType<YamlMap>([name, key as String], value[key])) {
+        if (!checkType<YamlMap>([...name, key as String], value[key])) {
           _result = false;
         } else {
           for (final subkey in value[key].keys) {
             if (!checkType<String>(
-                [name, key, subkey as String], value[key][subkey])) {
+                [...name, key, subkey as String], value[key][subkey])) {
               _result = false;
             }
           }
         }
       } else if (key == strings.memberRename) {
-        if (!checkType<YamlMap>([name, key as String], value[key])) {
+        if (!checkType<YamlMap>([...name, key as String], value[key])) {
           _result = false;
         } else {
           for (final declNameKey in value[key].keys) {
-            if (!checkType<YamlMap>(
-                [name, key, declNameKey as String], value[key][declNameKey])) {
+            if (!checkType<YamlMap>([...name, key, declNameKey as String],
+                value[key][declNameKey])) {
               _result = false;
             } else {
               for (final memberNameKey in value[key][declNameKey].keys) {
                 if (!checkType<String>([
-                  name,
+                  ...name,
                   key,
                   declNameKey,
                   memberNameKey as String,
@@ -422,13 +422,13 @@
           }
         }
       } else if (key == strings.symbolAddress) {
-        if (!checkType<YamlMap>([name, key as String], value[key])) {
+        if (!checkType<YamlMap>([...name, key as String], value[key])) {
           _result = false;
         } else {
           for (final subkey in value[key].keys) {
             if (subkey == strings.include || subkey == strings.exclude) {
               if (!checkType<YamlList>(
-                  [name, key, subkey as String], value[key][subkey])) {
+                  [...name, key, subkey as String], value[key][subkey])) {
                 _result = false;
               }
             } else {
@@ -437,9 +437,6 @@
             }
           }
         }
-      } else {
-        _logger.severe("Unknown key '$key' in '$name'.");
-        _result = false;
       }
     }
   } else {
@@ -467,7 +464,7 @@
 
 String stringExtractor(dynamic value) => value as String;
 
-bool nonEmptyStringValidator(String name, dynamic value) {
+bool nonEmptyStringValidator(List<String> name, dynamic value) {
   if (value is String && value.isNotEmpty) {
     return true;
   } else {
@@ -476,7 +473,7 @@
   }
 }
 
-bool dartClassNameValidator(String name, dynamic value) {
+bool dartClassNameValidator(List<String> name, dynamic value) {
   if (value is String &&
       quiver.matchesFull(RegExp('[a-zA-Z]+[_a-zA-Z0-9]*'), value)) {
     return true;
@@ -516,7 +513,7 @@
   return ct;
 }
 
-bool commentValidator(String name, dynamic value) {
+bool commentValidator(List<String> name, dynamic value) {
   if (value is bool) {
     return true;
   } else if (value is YamlMap) {
@@ -547,3 +544,23 @@
     return false;
   }
 }
+
+StructDependencies structDependenciesExtractor(dynamic value) {
+  var result = StructDependencies.full;
+  if (value == strings.opaqueStructDependencies) {
+    result = StructDependencies.opaque;
+  }
+  return result;
+}
+
+bool structDependenciesValidator(List<String> name, dynamic value) {
+  var result = true;
+  if (value is! String ||
+      !(value == strings.fullStructDependencies ||
+          value == strings.opaqueStructDependencies)) {
+    _logger.severe(
+        "'$name' must be one of the following - {${strings.fullStructDependencies}, ${strings.opaqueStructDependencies}}");
+    result = false;
+  }
+  return result;
+}
diff --git a/lib/src/header_parser/sub_parsers/structdecl_parser.dart b/lib/src/header_parser/sub_parsers/structdecl_parser.dart
index 4a6964b..d9c4023 100644
--- a/lib/src/header_parser/sub_parsers/structdecl_parser.dart
+++ b/lib/src/header_parser/sub_parsers/structdecl_parser.dart
@@ -5,6 +5,7 @@
 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 '../clang_bindings/clang_bindings.dart' as clang_types;
@@ -47,6 +48,13 @@
   /// 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());
 
@@ -83,15 +91,30 @@
     // 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!);
-    _setStructMembers(cursor);
   }
 
   if (bindingsIndex.isSeenStruct(structUsr)) {
-    _stack.top.struc = bindingsIndex.getSeenStruct(structUsr)!;
+    _stack.top.struc = bindingsIndex.getSeenStruct(structUsr);
 
-    // If struct is seen, update it's name.
-    _stack.top.struc!.name = config.structDecl.renameUsingConfig(structName);
+    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;
 }
 
diff --git a/lib/src/header_parser/type_extractor/extractor.dart b/lib/src/header_parser/type_extractor/extractor.dart
index 46d6a41..cd0445e 100644
--- a/lib/src/header_parser/type_extractor/extractor.dart
+++ b/lib/src/header_parser/type_extractor/extractor.dart
@@ -18,14 +18,21 @@
 const _padding = '  ';
 
 /// Converts cxtype to a typestring code_generator can accept.
-Type getCodeGenType(clang_types.CXType cxtype, {String? parentName}) {
+Type getCodeGenType(
+  clang_types.CXType cxtype, {
+  String? parentName,
+
+  /// 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) {
     case clang_types.CXTypeKind.CXType_Pointer:
       final pt = clang.clang_getPointeeType(cxtype);
-      final s = getCodeGenType(pt, parentName: parentName);
+      final s =
+          getCodeGenType(pt, parentName: parentName, pointerReference: true);
 
       // Replace Pointer<_Dart_Handle> with Handle.
       if (config.useDartHandle &&
@@ -53,14 +60,17 @@
       final ct = clang.clang_getTypedefDeclUnderlyingType(
           clang.clang_getTypeDeclaration(cxtype));
 
-      final s = getCodeGenType(ct, parentName: parentName ?? spelling);
+      final s = getCodeGenType(ct,
+          parentName: parentName ?? spelling,
+          pointerReference: pointerReference);
       return s;
     case clang_types.CXTypeKind.CXType_Elaborated:
       final et = clang.clang_Type_getNamedType(cxtype);
-      final s = getCodeGenType(et, parentName: parentName);
+      final s = getCodeGenType(et,
+          parentName: parentName, pointerReference: pointerReference);
       return s;
     case clang_types.CXTypeKind.CXType_Record:
-      return _extractfromRecord(cxtype, parentName);
+      return _extractfromRecord(cxtype, parentName, pointerReference);
     case clang_types.CXTypeKind.CXType_Enum:
       return Type.nativeType(
         enumNativeType,
@@ -98,7 +108,8 @@
   }
 }
 
-Type _extractfromRecord(clang_types.CXType cxtype, String? parentName) {
+Type _extractfromRecord(
+    clang_types.CXType cxtype, String? parentName, bool pointerReference) {
   Type type;
 
   final cursor = clang.clang_getTypeDeclaration(cxtype);
@@ -115,12 +126,25 @@
       // TODO(23): Check if we should auto add struct.
       if (bindingsIndex.isSeenStruct(structUsr)) {
         type = Type.struct(bindingsIndex.getSeenStruct(structUsr)!);
+
+        // 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);
+        final struc = parseStructDeclaration(
+          cursor,
+          name: structName,
+          ignoreFilter: true,
+          pointerReference: pointerReference,
+        );
         type = Type.struct(struc!);
 
-        // Add to bindings if it's not Dart_Handle.
+        // Add to bindings if it's not Dart_Handle and is unseen.
         if (!(config.useDartHandle && structUsr == strings.dartHandleUsr)) {
           addToBindings(struc);
         }
diff --git a/lib/src/strings.dart b/lib/src/strings.dart
index 1929051..885427d 100644
--- a/lib/src/strings.dart
+++ b/lib/src/strings.dart
@@ -49,6 +49,11 @@
 const memberRename = 'member-rename';
 const symbolAddress = 'symbol-address';
 
+const structDependencies = 'dependency-only';
+// Values for `structDependencies`.
+const fullStructDependencies = 'full';
+const opaqueStructDependencies = 'opaque';
+
 const sizemap = 'size-map';
 const typedefmap = 'typedef-map';
 
diff --git a/pubspec.yaml b/pubspec.yaml
index 1b6b447..21c65c3 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: 2.2.2
+version: 2.2.3
 homepage: https://github.com/dart-lang/ffigen
 description: Generator for FFI bindings, using LibClang to parse C header files.
 
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
new file mode 100644
index 0000000..0c3498f
--- /dev/null
+++ b/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart
@@ -0,0 +1,57 @@
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+import 'dart:ffi' as ffi;
+
+/// Opaque Dependencies 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;
+
+  ffi.Pointer<B> func(
+    ffi.Pointer<A> a,
+  ) {
+    return _func(
+      a,
+    );
+  }
+
+  late final _func_ptr = _lookup<ffi.NativeFunction<_c_func>>('func');
+  late final _dart_func _func = _func_ptr.asFunction<_dart_func>();
+}
+
+class B extends ffi.Opaque {}
+
+class A extends ffi.Opaque {}
+
+class C extends ffi.Opaque {}
+
+class D extends ffi.Struct {
+  @ffi.Int32()
+  external int a;
+}
+
+class E extends ffi.Struct {
+  external ffi.Pointer<C> c;
+
+  external D d;
+}
+
+typedef _c_func = ffi.Pointer<B> Function(
+  ffi.Pointer<A> a,
+);
+
+typedef _dart_func = ffi.Pointer<B> Function(
+  ffi.Pointer<A> a,
+);
diff --git a/test/header_parser_tests/opaque_dependencies.h b/test/header_parser_tests/opaque_dependencies.h
new file mode 100644
index 0000000..fa24993
--- /dev/null
+++ b/test/header_parser_tests/opaque_dependencies.h
@@ -0,0 +1,36 @@
+// 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.
+
+// Opaque.
+struct A
+{
+    int a;
+};
+
+// Opaque.
+struct B
+{
+    int a;
+};
+
+struct B *func(struct A *a);
+
+// Opaque.
+struct C
+{
+    int a;
+};
+
+// Full (excluded, but used by value).
+struct D
+{
+    int a;
+};
+
+// Full (included)
+struct E
+{
+    struct C *c;
+    struct D d;
+};
diff --git a/test/header_parser_tests/opaque_dependencies_test.dart b/test/header_parser_tests/opaque_dependencies_test.dart
new file mode 100644
index 0000000..1107767
--- /dev/null
+++ b/test/header_parser_tests/opaque_dependencies_test.dart
@@ -0,0 +1,49 @@
+// 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/header_parser.dart' as parser;
+import 'package:ffigen/src/config_provider.dart';
+import 'package:logging/logging.dart';
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart' as yaml;
+import 'package:ffigen/src/strings.dart' as strings;
+
+import '../test_utils.dart';
+
+late Library actual;
+void main() {
+  group('opaque_dependencies_test', () {
+    setUpAll(() {
+      logWarnings(Level.SEVERE);
+      actual = parser.parse(
+        Config.fromYaml(yaml.loadYaml('''
+${strings.name}: 'NativeLibrary'
+${strings.description}: 'Opaque Dependencies Test'
+${strings.output}: 'unused'
+${strings.headers}:
+  ${strings.entryPoints}:
+    - 'test/header_parser_tests/opaque_dependencies.h'
+${strings.structs}:
+  ${strings.include}:
+    - 'E'
+  ${strings.structDependencies}: ${strings.opaqueStructDependencies}
+        ''') as yaml.YamlMap),
+      );
+    });
+
+    test('Expected bindings', () {
+      matchLibraryWithExpected(actual, [
+        'test',
+        'debug_generated',
+        'opaque_dependencies_test_output.dart'
+      ], [
+        'test',
+        'header_parser_tests',
+        'expected_bindings',
+        '_expected_opaque_dependencies_bindings.dart'
+      ]);
+    });
+  });
+}