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