Skip to content
This repository was archived by the owner on Jan 28, 2024. It is now read-only.

Added support for generating Unions #215

Merged
merged 11 commits into from
May 20, 2021
4 changes: 2 additions & 2 deletions .github/workflows/test-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
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/[email protected]
Expand All @@ -43,7 +43,7 @@ jobs:
- uses: actions/checkout@v2
- uses: dart-lang/[email protected]
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
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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).

Expand Down
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ compiler-opts-automatic:
</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>
Expand Down Expand Up @@ -273,8 +273,10 @@ comments:
</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>
Expand All @@ -284,6 +286,8 @@ only passed by reference(pointer).<br>
```yaml
structs:
dependency-only: opaque
unions:
dependency-only: opaque
```
</td>
</tr>
Expand Down Expand Up @@ -476,16 +480,18 @@ unnamed-enums:
'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
these struct dependencies as `Opaque` if they were only passed by reference(pointer).
```yaml
structs:
dependency-only: opaque
unions:
dependency-only: opaque
```

### How to expose the native pointers and typedefs?
Expand Down
2 changes: 2 additions & 0 deletions lib/src/code_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -14,3 +15,4 @@ export 'code_generator/library.dart';
export 'code_generator/struc.dart';
export 'code_generator/type.dart';
export 'code_generator/typedef.dart';
export 'code_generator/union.dart';
1 change: 1 addition & 0 deletions lib/src/code_generator/binding_string.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class BindingString {
enum BindingStringType {
func,
struc,
union,
constant,
global,
enumClass,
Expand Down
184 changes: 184 additions & 0 deletions lib/src/code_generator/compound.dart
Original file line number Diff line number Diff line change
@@ -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;
}
4 changes: 2 additions & 2 deletions lib/src/code_generator/global.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ class Global extends LookUpBinding {

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 {
Expand Down
Loading