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