Support for generating Variadic functions (#515)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a9e26eb..f577aa5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 8.0.0-dev.3
+
+- Added support for variadic functions using config `functions -> variadic-arguments`.
+
 # 8.0.0-dev.2
 
 - Use `@Native` syntax instead of deprecated `@FfiNative` syntax.
diff --git a/README.md b/README.md
index 5195ad9..674c347 100644
--- a/README.md
+++ b/README.md
@@ -335,6 +335,26 @@
   </td>
   </tr>
   <tr>
+    <td>functions -> variadic-arguments</td>
+    <td>Generate multiple functions with different variadic arguments.<br>
+    <b>Default: var args for any function are ignored.</b>
+    </td>
+    <td>
+
+```yaml
+functions:
+  variadic-arguments:
+    myfunc:
+      // Native C types are supported
+      - [int, unsigned char, long*, float**]
+      // Common C typedefs (stddef.h) are supported too
+      - [uint8_t, intptr_t, size_t, wchar_t*]
+      // Structs/Unions/Typedefs from generated code or a library import can be referred too.
+      - [MyStruct*, my_custom_lib.CustomUnion]
+```
+  </td>
+  </tr>
+  <tr>
     <td>structs -> pack</td>
     <td>Override the @Packed(X) annotation for generated structs.<br><br>
     <i>Options - none, 1, 2, 4, 8, 16</i><br>
diff --git a/lib/src/code_generator/func.dart b/lib/src/code_generator/func.dart
index 55aec86..f8d32e2 100644
--- a/lib/src/code_generator/func.dart
+++ b/lib/src/code_generator/func.dart
@@ -58,6 +58,7 @@
     String? dartDoc,
     required Type returnType,
     List<Parameter>? parameters,
+    List<Parameter>? varArgParameters,
     this.exposeSymbolAddress = false,
     this.exposeFunctionTypedefs = false,
     this.isLeaf = false,
@@ -66,6 +67,7 @@
   })  : functionType = FunctionType(
           returnType: returnType,
           parameters: parameters ?? const [],
+          varArgParameters: varArgParameters ?? const [],
         ),
         super(
           usr: usr,
@@ -109,7 +111,7 @@
     }
     // Resolve name conflicts in function parameter names.
     final paramNamer = UniqueNamer({});
-    for (final p in functionType.parameters) {
+    for (final p in functionType.dartTypeParameters) {
       p.name = paramNamer.makeUnique(p.name);
     }
 
@@ -130,7 +132,7 @@
 
       s.write(
           'external ${functionType.returnType.getDartType(w)} $enclosingFuncName(\n');
-      for (final p in functionType.parameters) {
+      for (final p in functionType.dartTypeParameters) {
         s.write('  ${p.type.getDartType(w)} ${p.name},\n');
       }
       s.write(');\n\n');
@@ -138,14 +140,14 @@
       // Write enclosing function.
       s.write(
           '${functionType.returnType.getDartType(w)} $enclosingFuncName(\n');
-      for (final p in functionType.parameters) {
+      for (final p in functionType.dartTypeParameters) {
         s.write('  ${p.type.getDartType(w)} ${p.name},\n');
       }
       s.write(') {\n');
       s.write('return $funcVarName');
 
       s.write('(\n');
-      for (final p in functionType.parameters) {
+      for (final p in functionType.dartTypeParameters) {
         s.write('    ${p.name},\n');
       }
       s.write('  );\n');
diff --git a/lib/src/code_generator/func_type.dart b/lib/src/code_generator/func_type.dart
index 5402028..d4c74f6 100644
--- a/lib/src/code_generator/func_type.dart
+++ b/lib/src/code_generator/func_type.dart
@@ -11,13 +11,19 @@
 class FunctionType extends Type {
   final Type returnType;
   final List<Parameter> parameters;
+  final List<Parameter> varArgParameters;
+
+  /// Get all the parameters for generating the dart type. This includes both
+  /// [parameters] and [varArgParameters].
+  List<Parameter> get dartTypeParameters => parameters + varArgParameters;
 
   FunctionType({
     required this.returnType,
     required this.parameters,
+    this.varArgParameters = const [],
   });
 
-  String _getTypeString(
+  String _getCacheKeyString(
       bool writeArgumentNames, String Function(Type) typeToString) {
     final sb = StringBuffer();
 
@@ -35,18 +41,51 @@
   }
 
   @override
-  String getCType(Writer w, {bool writeArgumentNames = true}) =>
-      _getTypeString(writeArgumentNames, (Type t) => t.getCType(w));
+  String getCType(Writer w, {bool writeArgumentNames = true}) {
+    final sb = StringBuffer();
+
+    // Write return Type.
+    sb.write(returnType.getCType(w));
+
+    // Write Function.
+    sb.write(' Function(');
+    sb.write((parameters).map<String>((p) {
+      return '${p.type.getCType(w)} ${writeArgumentNames ? p.name : ""}';
+    }).join(', '));
+    if (varArgParameters.isNotEmpty) {
+      sb.write(", ${w.ffiLibraryPrefix}.VarArgs<(");
+      sb.write((varArgParameters).map<String>((p) {
+        return '${p.type.getCType(w)} ${writeArgumentNames ? p.name : ""}';
+      }).join(', '));
+      sb.write(",)>");
+    }
+    sb.write(')');
+
+    return sb.toString();
+  }
 
   @override
-  String getDartType(Writer w, {bool writeArgumentNames = true}) =>
-      _getTypeString(writeArgumentNames, (Type t) => t.getDartType(w));
+  String getDartType(Writer w, {bool writeArgumentNames = true}) {
+    final sb = StringBuffer();
+
+    // Write return Type.
+    sb.write(returnType.getDartType(w));
+
+    // Write Function.
+    sb.write(' Function(');
+    sb.write(dartTypeParameters.map<String>((p) {
+      return '${p.type.getDartType(w)} ${writeArgumentNames ? p.name : ""}';
+    }).join(', '));
+    sb.write(')');
+
+    return sb.toString();
+  }
 
   @override
-  String toString() => _getTypeString(false, (Type t) => t.toString());
+  String toString() => _getCacheKeyString(false, (Type t) => t.toString());
 
   @override
-  String cacheKey() => _getTypeString(false, (Type t) => t.cacheKey());
+  String cacheKey() => _getCacheKeyString(false, (Type t) => t.cacheKey());
 
   @override
   void addDependencies(Set<Binding> dependencies) {
diff --git a/lib/src/code_generator/imports.dart b/lib/src/code_generator/imports.dart
index 41f7161..d6dcd06 100644
--- a/lib/src/code_generator/imports.dart
+++ b/lib/src/code_generator/imports.dart
@@ -49,6 +49,25 @@
   String? getDefaultValue(Writer w, String nativeLib) => defaultValue;
 }
 
+/// An unchecked type similar to [ImportedType] which exists in the generated
+/// binding itself.
+class SelfImportedType extends Type {
+  final String cType;
+  final String dartType;
+  final String? defaultValue;
+
+  SelfImportedType(this.cType, this.dartType, [this.defaultValue]);
+
+  @override
+  String getCType(Writer w) => cType;
+
+  @override
+  String getDartType(Writer w) => dartType;
+
+  @override
+  String toString() => cType;
+}
+
 final ffiImport = LibraryImport('ffi', 'dart:ffi');
 final ffiPkgImport = LibraryImport('pkg_ffi', 'package:ffi/ffi.dart');
 
diff --git a/lib/src/config_provider/config.dart b/lib/src/config_provider/config.dart
index e012e56..12a7e69 100644
--- a/lib/src/config_provider/config.dart
+++ b/lib/src/config_provider/config.dart
@@ -53,6 +53,10 @@
   List<String> get compilerOpts => _compilerOpts;
   late List<String> _compilerOpts;
 
+  /// VarArg function handling.
+  Map<String, List<VarArgFunction>> get varArgFunctions => _varArgFunctions;
+  late Map<String, List<VarArgFunction>> _varArgFunctions = {};
+
   /// Declaration config for Functions.
   Declaration get functionDecl => _functionDecl;
   late Declaration _functionDecl;
@@ -446,6 +450,17 @@
               result as Map<String, List<String>>, _libraryImports);
         },
       ),
+      [strings.functions, strings.varArgFunctions]:
+          Specification<Map<String, List<RawVarArgFunction>>>(
+        requirement: Requirement.no,
+        validator: varArgFunctionConfigValidator,
+        extractor: varArgFunctionConfigExtractor,
+        defaultValue: () => <String, List<RawVarArgFunction>>{},
+        extractedResult: (dynamic result) {
+          _varArgFunctions = makeVarArgFunctionsMapping(
+              result as Map<String, List<RawVarArgFunction>>, _libraryImports);
+        },
+      ),
       [strings.excludeAllByDefault]: Specification<bool>(
         requirement: Requirement.no,
         validator: booleanValidator,
diff --git a/lib/src/config_provider/config_types.dart b/lib/src/config_provider/config_types.dart
index 8521bdb..1eddfc9 100644
--- a/lib/src/config_provider/config_types.dart
+++ b/lib/src/config_provider/config_types.dart
@@ -5,6 +5,7 @@
 /// Contains all the neccesary classes required by config.
 import 'dart:io';
 
+import 'package:ffigen/src/code_generator.dart';
 import 'package:quiver/pattern.dart' as quiver;
 
 import 'path_finder.dart';
@@ -423,3 +424,17 @@
 
   OutputConfig(this.output, this.symbolFile);
 }
+
+class RawVarArgFunction {
+  String? postfix;
+  final List<String> rawTypeStrings;
+
+  RawVarArgFunction(this.postfix, this.rawTypeStrings);
+}
+
+class VarArgFunction {
+  final String postfix;
+  final List<Type> types;
+
+  VarArgFunction(this.postfix, this.types);
+}
diff --git a/lib/src/config_provider/spec_utils.dart b/lib/src/config_provider/spec_utils.dart
index dfe6bbf..8bb3e2b 100644
--- a/lib/src/config_provider/spec_utils.dart
+++ b/lib/src/config_provider/spec_utils.dart
@@ -6,6 +6,7 @@
 
 import 'package:ffigen/src/code_generator.dart';
 import 'package:ffigen/src/code_generator/utils.dart';
+import 'package:ffigen/src/header_parser/type_extractor/cxtypekindmap.dart';
 import 'package:file/local.dart';
 import 'package:glob/glob.dart';
 import 'package:logging/logging.dart';
@@ -329,6 +330,99 @@
   return typeMappings;
 }
 
+Type makePointerToType(Type type, int pointerCount) {
+  for (var i = 0; i < pointerCount; i++) {
+    type = PointerType(type);
+  }
+  return type;
+}
+
+String makePostfixFromRawVarArgType(List<String> rawVarArgType) {
+  return rawVarArgType
+      .map((e) => e
+          .replaceAll('*', 'Ptr')
+          .replaceAll(RegExp(r'_t$'), '')
+          .replaceAll(' ', '')
+          .replaceAll(RegExp('[^A-Za-z0-9_]'), ''))
+      .map((e) => e.length > 1 ? '${e[0].toUpperCase()}${e.substring(1)}' : e)
+      .join('');
+}
+
+Type makeTypeFromRawVarArgType(
+    String rawVarArgType, Map<String, LibraryImport> libraryImportsMap) {
+  Type baseType;
+  var rawBaseType = rawVarArgType.trim();
+  // Split the raw type based on pointer usage. E.g -
+  // int => [int]
+  // char* => [char,*]
+  // ffi.Hello ** => [ffi.Hello,**]
+  final typeStringRegexp = RegExp(r'([a-zA-Z0-9_\s\.]+)(\**)$');
+  if (!typeStringRegexp.hasMatch(rawBaseType)) {
+    throw Exception('Cannot parse variadic argument type - $rawVarArgType.');
+  }
+  final regExpMatch = typeStringRegexp.firstMatch(rawBaseType)!;
+  final groups = regExpMatch.groups([1, 2]);
+  rawBaseType = groups[0]!;
+  // Handle basic supported types.
+  if (cxTypeKindToImportedTypes.containsKey(rawBaseType)) {
+    baseType = cxTypeKindToImportedTypes[rawBaseType]!;
+  } else if (supportedTypedefToImportedType.containsKey(rawBaseType)) {
+    baseType = supportedTypedefToImportedType[rawBaseType]!;
+  } else if (suportedTypedefToSuportedNativeType.containsKey(rawBaseType)) {
+    baseType = NativeType(suportedTypedefToSuportedNativeType[rawBaseType]!);
+  } else {
+    // Use library import if specified (E.g - ffi.UintPtr or custom.MyStruct)
+    final rawVarArgTypeSplit = rawBaseType.split('.');
+    if (rawVarArgTypeSplit.length == 1) {
+      final typeName = rawVarArgTypeSplit[0].replaceAll(' ', '');
+      baseType = SelfImportedType(typeName, typeName);
+    } else if (rawVarArgTypeSplit.length == 2) {
+      final lib = rawVarArgTypeSplit[0];
+      final libraryImport = strings.predefinedLibraryImports[lib] ??
+          libraryImportsMap[rawVarArgTypeSplit[0]];
+      if (libraryImport == null) {
+        throw Exception('Please declare $lib in library-imports.');
+      }
+      final typeName = rawVarArgTypeSplit[1].replaceAll(' ', '');
+      baseType = ImportedType(libraryImport, typeName, typeName);
+    } else {
+      throw Exception(
+          'Invalid type $rawVarArgType : Expected 0 or 1 .(dot) separators.');
+    }
+  }
+
+  // Handle pointers
+  final pointerCount = groups[1]!.length;
+  return makePointerToType(baseType, pointerCount);
+}
+
+Map<String, List<VarArgFunction>> makeVarArgFunctionsMapping(
+    Map<String, List<RawVarArgFunction>> rawVarArgMappings,
+    Map<String, LibraryImport> libraryImportsMap) {
+  final mappings = <String, List<VarArgFunction>>{};
+  for (final key in rawVarArgMappings.keys) {
+    final varArgList = <VarArgFunction>[];
+    for (final rawVarArg in rawVarArgMappings[key]!) {
+      var postfix = rawVarArg.postfix ?? '';
+      final types = <Type>[];
+      for (final rva in rawVarArg.rawTypeStrings) {
+        types.add(makeTypeFromRawVarArgType(rva, libraryImportsMap));
+      }
+      if (postfix.isEmpty) {
+        if (rawVarArgMappings[key]!.length == 1) {
+          postfix = '';
+        } else {
+          postfix = makePostfixFromRawVarArgType(rawVarArg.rawTypeStrings);
+        }
+      }
+      // Extract postfix from config and/or deduce from var names.
+      varArgList.add(VarArgFunction(postfix, types));
+    }
+    mappings[key] = varArgList;
+  }
+  return mappings;
+}
+
 final _quoteMatcher = RegExp(r'''^["'](.*)["']$''', dotAll: true);
 final _cmdlineArgMatcher = RegExp(r'''['"](\\"|[^"])*?['"]|[^ ]+''');
 List<String> compilerOptsToList(String compilerOpts) {
@@ -743,6 +837,85 @@
   );
 }
 
+Map<String, List<RawVarArgFunction>> varArgFunctionConfigExtractor(
+    dynamic yamlMap) {
+  final result = <String, List<RawVarArgFunction>>{};
+  final configMap = (yamlMap as YamlMap);
+  for (final key in configMap.keys) {
+    final List<RawVarArgFunction> vafuncs = [];
+    for (final rawVaFunc in (configMap[key] as YamlList)) {
+      if (rawVaFunc is YamlList) {
+        vafuncs.add(RawVarArgFunction(null, rawVaFunc.cast()));
+      } else if (rawVaFunc is YamlMap) {
+        vafuncs.add(RawVarArgFunction(rawVaFunc[strings.postfix] as String?,
+            (rawVaFunc[strings.types] as YamlList).cast()));
+      } else {
+        throw Exception("Unexpected type in variadic-argument config.");
+      }
+    }
+    result[key as String] = vafuncs;
+  }
+
+  return result;
+}
+
+bool varArgFunctionConfigValidator(List<String> name, dynamic value) {
+  if (!checkType<YamlMap>(name, value)) {
+    return false;
+  }
+  var result = true;
+  for (final key in (value as YamlMap).keys) {
+    final list = value[key as String];
+    if (!checkType<YamlList>([...name, key], list)) {
+      result = false;
+      continue;
+    }
+    (list as YamlList).asMap().forEach((idx, subList) {
+      if (subList is YamlMap) {
+        if (!subList.containsKey(strings.types)) {
+          result = false;
+          _logger.severe('Missing required key - ${[
+            ...name,
+            key,
+            idx.toString(),
+            strings.types
+          ].join(" -> ")}');
+        }
+        subList.forEach((subkey, subvalue) {
+          subkey = subkey as String;
+          if (subkey == strings.postfix) {
+            if (!checkType<String>(
+                [...name, key, idx.toString(), subkey], subvalue)) {
+              result = false;
+            }
+          } else if (subkey == strings.types) {
+            if (!checkType<YamlList>(
+                [...name, key, idx.toString(), subkey], subvalue)) {
+              result = false;
+            }
+          } else {
+            result = false;
+            _logger.severe('Unknown key - ${[
+              ...name,
+              key,
+              idx.toString(),
+              subkey
+            ].join(" -> ")}');
+          }
+        });
+      } else if (subList is! YamlList) {
+        result = false;
+        _logger.severe('Expected ${[
+          ...name,
+          key,
+          idx
+        ].join(" -> ")} to be a List or a Map.');
+      }
+    });
+  }
+  return result;
+}
+
 Declaration declarationConfigExtractor(dynamic yamlMap) {
   final renamePatterns = <RegExpRenamer>[];
   final renameFull = <String, String>{};
diff --git a/lib/src/header_parser/clang_bindings/clang_bindings.dart b/lib/src/header_parser/clang_bindings/clang_bindings.dart
index 4c4c472..c7d6e2f 100644
--- a/lib/src/header_parser/clang_bindings/clang_bindings.dart
+++ b/lib/src/header_parser/clang_bindings/clang_bindings.dart
@@ -792,6 +792,21 @@
       _clang_Type_getObjCObjectBaseTypePtr
           .asFunction<CXType Function(CXType)>();
 
+  /// Return 1 if the CXType is a variadic function type, and 0 otherwise.
+  int clang_isFunctionTypeVariadic(
+    CXType T,
+  ) {
+    return _clang_isFunctionTypeVariadic(
+      T,
+    );
+  }
+
+  late final _clang_isFunctionTypeVariadicPtr =
+      _lookup<ffi.NativeFunction<ffi.UnsignedInt Function(CXType)>>(
+          'clang_isFunctionTypeVariadic');
+  late final _clang_isFunctionTypeVariadic =
+      _clang_isFunctionTypeVariadicPtr.asFunction<int Function(CXType)>();
+
   /// Retrieve the return type associated with a given cursor.
   ///
   /// This only returns a valid type if the cursor refers to a function or method.
diff --git a/lib/src/header_parser/sub_parsers/functiondecl_parser.dart b/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
index d2d3412..94d5ed0 100644
--- a/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
+++ b/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:ffigen/src/code_generator.dart';
+import 'package:ffigen/src/config_provider/config_types.dart';
 import 'package:ffigen/src/header_parser/data.dart';
 import 'package:logging/logging.dart';
 
@@ -14,7 +15,9 @@
 
 /// Holds temporary information regarding [Func] while parsing.
 class _ParserFunc {
-  Func? func;
+  /// Multiple values are since there may be more than one instance of the
+  /// same base C function with different variadic arguments.
+  List<Func> funcs = [];
   bool incompleteStructParameter = false;
   bool unimplementedParameterType = false;
   _ParserFunc();
@@ -23,7 +26,7 @@
 final _stack = Stack<_ParserFunc>();
 
 /// Parses a function declaration.
-Func? parseFunctionDeclaration(clang_types.CXCursor cursor) {
+List<Func>? parseFunctionDeclaration(clang_types.CXCursor cursor) {
   _stack.push(_ParserFunc());
 
   final funcUsr = cursor.usr();
@@ -39,8 +42,8 @@
           '${cursor.completeStringRepr()}');
       _logger.warning(
           "Skipped Function '$funcName', inline functions are not supported.");
-      // Returning null so that [addToBindings] function excludes this.
-      return _stack.pop().func;
+      // Returning empty so that [addToBindings] function excludes this.
+      return _stack.pop().funcs;
     }
 
     if (rt.isIncompleteCompound || _stack.top.incompleteStructParameter) {
@@ -51,7 +54,7 @@
           "Skipped Function '$funcName', Incomplete struct pass/return by "
           'value not supported.');
       // Returning null so that [addToBindings] function excludes this.
-      return _stack.pop().func;
+      return _stack.pop().funcs;
     }
 
     if (rt.baseType is UnimplementedType ||
@@ -62,32 +65,46 @@
           "Skipped Function '$funcName', function has unsupported return type "
           'or parameter type.');
       // Returning null so that [addToBindings] function excludes this.
-      return _stack.pop().func;
+      return _stack.pop().funcs;
     }
 
-    _stack.top.func = Func(
-      dartDoc: getCursorDocComment(
-        cursor,
-        nesting.length + commentPrefix.length,
-      ),
-      usr: funcUsr,
-      name: config.functionDecl.renameUsingConfig(funcName),
-      originalName: funcName,
-      returnType: rt,
-      parameters: parameters,
-      exposeSymbolAddress:
-          config.functionDecl.shouldIncludeSymbolAddress(funcName),
-      exposeFunctionTypedefs:
-          config.exposeFunctionTypedefs.shouldInclude(funcName),
-      isLeaf: config.leafFunctions.shouldInclude(funcName),
-      ffiNativeConfig: config.ffiNativeConfig,
-    );
-    bindingsIndex.addFuncToSeen(funcUsr, _stack.top.func!);
+    // Initialized with a single value with no prefix and empty var args.
+    var varArgFunctions = [VarArgFunction('', [])];
+    if (clang.clang_isFunctionTypeVariadic(cursor.type()) == 1) {
+      if (config.varArgFunctions.containsKey(funcName)) {
+        varArgFunctions = config.varArgFunctions[funcName]!;
+      } else {
+        _logger.warning(
+            "Skipping variadic-argument config for function $funcName since its not variadic.");
+      }
+    }
+    for (final vaFunc in varArgFunctions) {
+      _stack.top.funcs.add(Func(
+        dartDoc: getCursorDocComment(
+          cursor,
+          nesting.length + commentPrefix.length,
+        ),
+        usr: funcUsr + vaFunc.postfix,
+        name: config.functionDecl.renameUsingConfig(funcName) + vaFunc.postfix,
+        originalName: funcName,
+        returnType: rt,
+        parameters: parameters,
+        varArgParameters:
+            vaFunc.types.map((ta) => Parameter(type: ta, name: 'va')).toList(),
+        exposeSymbolAddress:
+            config.functionDecl.shouldIncludeSymbolAddress(funcName),
+        exposeFunctionTypedefs:
+            config.exposeFunctionTypedefs.shouldInclude(funcName),
+        isLeaf: config.leafFunctions.shouldInclude(funcName),
+        ffiNativeConfig: config.ffiNativeConfig,
+      ));
+    }
+    bindingsIndex.addFuncToSeen(funcUsr, _stack.top.funcs.last);
   } else if (bindingsIndex.isSeenFunc(funcUsr)) {
-    _stack.top.func = bindingsIndex.getSeenFunc(funcUsr);
+    _stack.top.funcs.add(bindingsIndex.getSeenFunc(funcUsr)!);
   }
 
-  return _stack.pop().func;
+  return _stack.pop().funcs;
 }
 
 Type _getFunctionReturnType(clang_types.CXCursor cursor) {
diff --git a/lib/src/header_parser/translation_unit_parser.dart b/lib/src/header_parser/translation_unit_parser.dart
index ea9d594..31f50b8 100644
--- a/lib/src/header_parser/translation_unit_parser.dart
+++ b/lib/src/header_parser/translation_unit_parser.dart
@@ -56,7 +56,7 @@
       _logger.finest('rootCursorVisitor: ${cursor.completeStringRepr()}');
       switch (clang.clang_getCursorKind(cursor)) {
         case clang_types.CXCursorKind.CXCursor_FunctionDecl:
-          addToBindings(parseFunctionDeclaration(cursor));
+          addAllToBindings(parseFunctionDeclaration(cursor) as List<Binding>);
           break;
         case clang_types.CXCursorKind.CXCursor_StructDecl:
         case clang_types.CXCursorKind.CXCursor_UnionDecl:
@@ -96,6 +96,14 @@
   }
 }
 
+/// Adds all binding if not empty.
+void addAllToBindings(List<Binding> b) {
+  if (b.isNotEmpty) {
+    // This is a set, and hence will not have duplicates.
+    _bindings.addAll(b);
+  }
+}
+
 BindingType? _getCodeGenTypeFromCursor(clang_types.CXCursor cursor) {
   final t = getCodeGenType(cursor.type(), ignoreFilter: false);
   return t is BindingType ? t : null;
diff --git a/lib/src/strings.dart b/lib/src/strings.dart
index e0f2761..245dd7c 100644
--- a/lib/src/strings.dart
+++ b/lib/src/strings.dart
@@ -85,6 +85,11 @@
 // Nested under `functions`
 const exposeFunctionTypedefs = 'expose-typedefs';
 const leafFunctions = 'leaf';
+const varArgFunctions = 'variadic-arguments';
+
+// Nested under varArg entries
+const postfix = "postfix";
+const types = "types";
 
 // Sub-fields of ObjC interfaces.
 const objcModule = 'module';
diff --git a/pubspec.yaml b/pubspec.yaml
index d4a7013..fa5c897 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: 8.0.0-dev.2
+version: 8.0.0-dev.3
 description: Generator for FFI bindings, using LibClang to parse C header files.
 repository: https://github.com/dart-lang/ffigen
 
diff --git a/test/header_parser_tests/expected_bindings/_expected_varargs_bindings.dart b/test/header_parser_tests/expected_bindings/_expected_varargs_bindings.dart
new file mode 100644
index 0000000..5b51bcd
--- /dev/null
+++ b/test/header_parser_tests/expected_bindings/_expected_varargs_bindings.dart
@@ -0,0 +1,196 @@
+// ignore_for_file: camel_case_types
+
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+// ignore_for_file: type=lint
+import 'dart:ffi' as ffi;
+
+/// VarArgs 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;
+
+  int myfunc(
+    int a,
+    int va,
+    ffi.Pointer<ffi.Char> va1,
+    SA va2,
+  ) {
+    return _myfunc(
+      a,
+      va,
+      va1,
+      va2,
+    );
+  }
+
+  late final _myfuncPtr = _lookup<
+      ffi.NativeFunction<
+          ffi.Int Function(
+              ffi.Int,
+              ffi.VarArgs<
+                  (
+                    ffi.Int,
+                    ffi.Pointer<ffi.Char>,
+                    SA,
+                  )>)>>('myfunc');
+  late final _myfunc = _myfuncPtr
+      .asFunction<int Function(int, int, ffi.Pointer<ffi.Char>, SA)>();
+
+  void myfunc2CharPtrLongPtrPtr(
+    int a,
+    int b,
+    ffi.Pointer<ffi.Char> va,
+    ffi.Pointer<ffi.Pointer<ffi.Long>> va1,
+  ) {
+    return _myfunc2CharPtrLongPtrPtr(
+      a,
+      b,
+      va,
+      va1,
+    );
+  }
+
+  late final _myfunc2CharPtrLongPtrPtrPtr = _lookup<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Char,
+              ffi.Char,
+              ffi.VarArgs<
+                  (
+                    ffi.Pointer<ffi.Char>,
+                    ffi.Pointer<ffi.Pointer<ffi.Long>>,
+                  )>)>>('myfunc2');
+  late final _myfunc2CharPtrLongPtrPtr =
+      _myfunc2CharPtrLongPtrPtrPtr.asFunction<
+          void Function(int, int, ffi.Pointer<ffi.Char>,
+              ffi.Pointer<ffi.Pointer<ffi.Long>>)>();
+
+  void myfunc2SAIntPtrUnsignedcharPtrPtr(
+    int a,
+    int b,
+    SA va,
+    ffi.Pointer<ffi.Int> va1,
+    ffi.Pointer<ffi.Pointer<ffi.UnsignedChar>> va2,
+  ) {
+    return _myfunc2SAIntPtrUnsignedcharPtrPtr(
+      a,
+      b,
+      va,
+      va1,
+      va2,
+    );
+  }
+
+  late final _myfunc2SAIntPtrUnsignedcharPtrPtrPtr = _lookup<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Char,
+              ffi.Char,
+              ffi.VarArgs<
+                  (
+                    SA,
+                    ffi.Pointer<ffi.Int>,
+                    ffi.Pointer<ffi.Pointer<ffi.UnsignedChar>>,
+                  )>)>>('myfunc2');
+  late final _myfunc2SAIntPtrUnsignedcharPtrPtr =
+      _myfunc2SAIntPtrUnsignedcharPtrPtrPtr.asFunction<
+          void Function(int, int, SA, ffi.Pointer<ffi.Int>,
+              ffi.Pointer<ffi.Pointer<ffi.UnsignedChar>>)>();
+
+  void myfunc2_custompostfix(
+    int a,
+    int b,
+    SA va,
+    ffi.Pointer<ffi.Int> va1,
+    ffi.Pointer<ffi.Pointer<ffi.UnsignedChar>> va2,
+  ) {
+    return _myfunc2_custompostfix(
+      a,
+      b,
+      va,
+      va1,
+      va2,
+    );
+  }
+
+  late final _myfunc2_custompostfixPtr = _lookup<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Char,
+              ffi.Char,
+              ffi.VarArgs<
+                  (
+                    SA,
+                    ffi.Pointer<ffi.Int>,
+                    ffi.Pointer<ffi.Pointer<ffi.UnsignedChar>>,
+                  )>)>>('myfunc2');
+  late final _myfunc2_custompostfix = _myfunc2_custompostfixPtr.asFunction<
+      void Function(int, int, SA, ffi.Pointer<ffi.Int>,
+          ffi.Pointer<ffi.Pointer<ffi.UnsignedChar>>)>();
+
+  void myfunc3Struct_WithLong_Name_testPtrFloatPtr(
+    int a,
+    ffi.Pointer<Struct_WithLong_Name_test> va,
+    ffi.Pointer<ffi.Float> va1,
+  ) {
+    return _myfunc3Struct_WithLong_Name_testPtrFloatPtr(
+      a,
+      va,
+      va1,
+    );
+  }
+
+  late final _myfunc3Struct_WithLong_Name_testPtrFloatPtrPtr = _lookup<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Long,
+              ffi.VarArgs<
+                  (
+                    ffi.Pointer<Struct_WithLong_Name_test>,
+                    ffi.Pointer<ffi.Float>,
+                  )>)>>('myfunc3');
+  late final _myfunc3Struct_WithLong_Name_testPtrFloatPtr =
+      _myfunc3Struct_WithLong_Name_testPtrFloatPtrPtr.asFunction<
+          void Function(int, ffi.Pointer<Struct_WithLong_Name_test>,
+              ffi.Pointer<ffi.Float>)>();
+
+  void myfunc3_custompostfix2(
+    int a,
+    Struct_WithLong_Name_test va,
+  ) {
+    return _myfunc3_custompostfix2(
+      a,
+      va,
+    );
+  }
+
+  late final _myfunc3_custompostfix2Ptr = _lookup<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Long, ffi.VarArgs<(Struct_WithLong_Name_test,)>)>>('myfunc3');
+  late final _myfunc3_custompostfix2 = _myfunc3_custompostfix2Ptr
+      .asFunction<void Function(int, Struct_WithLong_Name_test)>();
+}
+
+final class SA extends ffi.Struct {
+  @ffi.Int()
+  external int a;
+}
+
+final class Struct_WithLong_Name_test extends ffi.Struct {
+  @ffi.Int()
+  external int a;
+}
diff --git a/test/header_parser_tests/varargs.h b/test/header_parser_tests/varargs.h
new file mode 100644
index 0000000..e316b90
--- /dev/null
+++ b/test/header_parser_tests/varargs.h
@@ -0,0 +1,15 @@
+// Copyright (c) 2023, 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.
+
+int myfunc(int a, ...);
+void myfunc2(char a, char b, ...);
+void myfunc3(long a, ...);
+
+struct SA {
+    int a;
+};
+
+struct Struct_WithLong_Name_test {
+    int a;
+};
diff --git a/test/header_parser_tests/varargs_test.dart b/test/header_parser_tests/varargs_test.dart
new file mode 100644
index 0000000..deaa5af
--- /dev/null
+++ b/test/header_parser_tests/varargs_test.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2023, 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:test/test.dart';
+import 'package:yaml/yaml.dart' as yaml;
+
+import '../test_utils.dart';
+
+late Library actual, expected;
+
+void main() {
+  group('varargs_test', () {
+    setUpAll(() {
+      logWarnings();
+      actual = parser.parse(
+        Config.fromYaml(yaml.loadYaml('''
+${strings.name}: 'NativeLibrary'
+${strings.description}: 'VarArgs Test'
+${strings.output}: 'unused'
+
+${strings.headers}:
+  ${strings.entryPoints}:
+    - 'test/header_parser_tests/varargs.h'
+
+${strings.functions}:
+  ${strings.varArgFunctions}:
+    myfunc:
+      - [int, char*, SA]
+    myfunc2:
+      - [char*, long**]
+      - [SA, int*, unsigned char**]
+      - types: [SA, int*, unsigned char**]
+        postfix: _custompostfix
+    myfunc3:
+      - [Struct_WithLong_Name_test*, float*]
+      - types: [Struct_WithLong_Name_test]
+        postfix: _custompostfix2
+
+${strings.preamble}: |
+  // ignore_for_file: camel_case_types
+        ''') as yaml.YamlMap),
+      );
+    });
+    test('Expected Bindings', () {
+      matchLibraryWithExpected(
+          actual, 'header_parser_varargs_test_output.dart', [
+        'test',
+        'header_parser_tests',
+        'expected_bindings',
+        '_expected_varargs_bindings.dart'
+      ]);
+    });
+  });
+}
diff --git a/test/test_utils.dart b/test/test_utils.dart
index 50cb30e..95434ff 100644
--- a/test/test_utils.dart
+++ b/test/test_utils.dart
@@ -57,13 +57,13 @@
 /// This will not delete the actual debug file incase [expect] throws an error.
 void matchLibraryWithExpected(
     Library library, String pathForActual, List<String> pathToExpected,
-    {String Function(String)? codeNormalizer}) {
+    {String Function(String)? codeNormalizer, bool format = true}) {
   _matchFileWithExpected(
     library: library,
     pathForActual: pathForActual,
     pathToExpected: pathToExpected,
     fileWriter: ({required Library library, required File file}) =>
-        library.generateFile(file),
+        library.generateFile(file, format: format),
     codeNormalizer: codeNormalizer,
   );
 }
diff --git a/tool/libclang_config.yaml b/tool/libclang_config.yaml
index 339c52c..6ac924f 100644
--- a/tool/libclang_config.yaml
+++ b/tool/libclang_config.yaml
@@ -91,6 +91,7 @@
     - clang_Cursor_getArgument
     - clang_getNumArgTypes
     - clang_getArgType
+    - clang_isFunctionTypeVariadic
     - clang_getCursorResultType
     - clang_getEnumConstantDeclValue
     - clang_equalRanges