Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
1b014b7
[ffigen] Vibe coded prototype of transformer API
liamappelbe Jul 20, 2026
7044019
fix tests and migrate existing Dart API ffigen configs
liamappelbe Jul 20, 2026
39c2a1b
nits
liamappelbe Jul 20, 2026
bd69dfe
fix tests
liamappelbe Jul 21, 2026
66b3ab2
Delete old API, and migrate a bunch more configs
liamappelbe Jul 23, 2026
68bba3a
visitChildren
liamappelbe Jul 29, 2026
dbba048
callback based visitor
liamappelbe Jul 29, 2026
dcd9218
shard the IncludeSetVisitor
liamappelbe Jul 29, 2026
4e08daf
flip isExcluded to isIncluded, and make visitor list non-null
liamappelbe Jul 29, 2026
36358a9
per-enum warning
liamappelbe Jul 29, 2026
cc8cb0b
remove include transitive flags
liamappelbe Jul 29, 2026
13f4370
missed some files
liamappelbe Jul 29, 2026
c2ed9c8
Move more bools to the AST
liamappelbe Jul 29, 2026
30fde52
cleaning
liamappelbe Jul 30, 2026
e1fb622
bug fixes
liamappelbe Jul 30, 2026
173954c
Headers -> Input
liamappelbe Jul 30, 2026
22839e7
Move varArgs
liamappelbe Jul 30, 2026
983d31f
more cleanup
liamappelbe Jul 30, 2026
cceba6d
Split out _VisitorImpl
liamappelbe Jul 30, 2026
1de61b7
migrate all configs
liamappelbe Jul 30, 2026
bd53b4f
Merge branch 'main' into ffigen_transformer
liamappelbe Jul 31, 2026
a532462
config docs
liamappelbe Aug 3, 2026
b86079b
typedefTypeMappings -> typedefImports
liamappelbe Aug 3, 2026
1b767f7
mac only test
liamappelbe Aug 3, 2026
b57df8c
fix large_test
liamappelbe Aug 3, 2026
2b5a5b3
cleaning up ObjC bindings
liamappelbe Aug 3, 2026
4f3aacc
fixes
liamappelbe Aug 3, 2026
e60f0c8
clean up
liamappelbe Aug 3, 2026
ac6d86f
clean up
liamappelbe Aug 3, 2026
3df1a17
clean up
liamappelbe Aug 3, 2026
a2006b2
cleaning
liamappelbe Aug 3, 2026
eaad4da
more cleanup
liamappelbe Aug 4, 2026
c4b1e14
clean up
liamappelbe Aug 4, 2026
98c09b9
fix enum recommendation behavior
liamappelbe Aug 4, 2026
c425c00
clean
liamappelbe Aug 4, 2026
eb0f393
clean up
liamappelbe Aug 4, 2026
aa9fb00
clean up
liamappelbe Aug 4, 2026
a68ad13
clean up
liamappelbe Aug 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pkgs/code_assets/example/host_name/tool/ffigen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import 'package:ffigen/ffigen.dart';

void main() {
final packageRoot = Platform.script.resolve('../');
final functions = Functions.includeSet({'gethostname'});
const visitors = [IncludeSetVisitor({'gethostname'})];
final FfiGenerator generator;
if (Platform.isWindows) {
generator = FfiGenerator(
headers: Headers(entryPoints: [packageRoot.resolve('src/windows.h')]),
functions: functions,
visitors: visitors,
output: Output(
dartFile: packageRoot.resolve('lib/src/third_party/windows.dart'),
preamble: '''
Expand All @@ -27,7 +27,7 @@ void main() {
} else {
generator = FfiGenerator(
headers: Headers(entryPoints: [packageRoot.resolve('src/unix.h')]),
functions: functions,
visitors: visitors,
output: Output(
dartFile: packageRoot.resolve('lib/src/third_party/unix.dart'),
preamble: '''
Expand Down
20 changes: 8 additions & 12 deletions pkgs/code_assets/example/mini_audio/tool/ffigen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,17 @@ void main() {
headers: Headers(
entryPoints: [packageRoot.resolve('third_party/miniaudio.h')],
),
functions: Functions(
include: (decl) => {
visitors: const [
Comment thread
dcharkes marked this conversation as resolved.
IncludeSetVisitor({
'ma_engine_init',
'ma_engine_play_sound',
'ma_engine_uninit',
}.contains(decl.originalName),
recordUse: (_) => true,
),
structs: Structs(
include: (decl) => {'ma_engine'}.contains(decl.originalName),
),
enums: Enums(
include: (decl) => {'ma_result'}.contains(decl.originalName),
silenceWarning: true,
),
'ma_engine',
'ma_result',
}),
RecordUseVisitor(),
],
enums: const Enums(silenceWarning: true),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, we need to think where to put everything that doesn't end up in visitors that fit next to the declaration-type-specific config before. How many of such elements are there?

@liamappelbe liamappelbe Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYM? I figured we'd just leave those fields where they are, in the hierarchy. Are you suggesting we delete the whole existing hierarchy?

output: Output(
dartFile: packageRoot.resolve('lib/src/third_party/miniaudio.g.dart'),
recordUseMapping: packageRoot.resolve(
Expand Down
8 changes: 4 additions & 4 deletions pkgs/code_assets/example/sqlite/tool/ffigen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ void main() {
headers: Headers(
entryPoints: [packageRoot.resolve('third_party/sqlite/sqlite3.h')],
),
functions: Functions(
include: (decl) => {'sqlite3_libversion'}.contains(decl.originalName),
recordUse: (_) => true,
),
visitors: const [
IncludeSetVisitor({'sqlite3_libversion'}),
RecordUseVisitor(),
],
output: Output(
dartFile: packageRoot.resolve('lib/src/third_party/sqlite3.g.dart'),
recordUseMapping: packageRoot.resolve(
Expand Down
5 changes: 4 additions & 1 deletion pkgs/code_assets/example/sqlite_no_link/tool/ffigen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ void main() {
headers: Headers(
entryPoints: [packageRoot.resolve('third_party/sqlite/sqlite3.h')],
),
functions: Functions.includeSet({'sqlite3_libversion'}),
visitors: const [
IncludeSetVisitor({'sqlite3_libversion'}),
RecordUseVisitor(),
],
output: Output(
dartFile: packageRoot.resolve('lib/src/third_party/sqlite3.g.dart'),
preamble: '''
Expand Down
5 changes: 4 additions & 1 deletion pkgs/code_assets/example/sqlite_prebuilt/tool/ffigen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ void main() {
headers: Headers(
entryPoints: [packageRoot.resolve('third_party/sqlite/sqlite3.h')],
),
functions: Functions.includeSet({'sqlite3_libversion'}),
visitors: const [
IncludeSetVisitor({'sqlite3_libversion'}),
RecordUseVisitor(),
],
output: Output(
dartFile: packageRoot.resolve('lib/src/third_party/sqlite3.g.dart'),
preamble: '''
Expand Down
8 changes: 4 additions & 4 deletions pkgs/code_assets/example/stb_image/tool/ffigen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ void main() {
headers: Headers(
entryPoints: [packageRoot.resolve('third_party/stb_image.h')],
),
functions: Functions(
include: (decl) => {'stbi_info'}.contains(decl.originalName),
recordUse: (_) => true,
),
visitors: const [
IncludeSetVisitor({'stbi_info'}),
RecordUseVisitor(),
],
output: Output(
dartFile: packageRoot.resolve('lib/src/third_party/stb_image.g.dart'),
recordUseMapping: packageRoot.resolve(
Expand Down
2 changes: 1 addition & 1 deletion pkgs/ffigen/example/add/tool/ffigen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ FfiGenerator getConfig(Uri packageRoot) {
return FfiGenerator(
output: Output(dartFile: packageRoot.resolve('lib/add.g.dart')),
headers: Headers(entryPoints: [packageRoot.resolve('src/add.h')]),
functions: Functions.includeSet({'add'}),
visitors: [const IncludeSetVisitor({'add'})],
);
}

Expand Down
7 changes: 2 additions & 5 deletions pkgs/ffigen/example/objective_c/generate_code.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,8 @@ final config = FfiGenerator(

// To tell FFIgen to generate Objective-C bindings, rather than C bindings,
// set the objectiveC field to a non-null value.
objectiveC: ObjectiveC(
// The interfaces field is used to tell FFIgen which interfaces to generate
// bindings for. There's also a protocols and a categories field.
interfaces: Interfaces.includeSet({'AVAudioPlayer'}),
),
objectiveC: const ObjectiveC(),
visitors: const [IncludeSetVisitor({'AVAudioPlayer'})],

output: Output(
// The Dart file where the bindings will be generated.
Expand Down
1 change: 1 addition & 0 deletions pkgs/ffigen/lib/ffigen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,4 @@ export 'src/config_provider.dart'
macSdkUri,
xcodePath,
xcodeUri;
export 'src/public_ast/public_ast.dart' hide Declaration;
3 changes: 3 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ abstract class Binding extends AstNode implements Declaration {
final String? dartDoc;
final bool isInternal;

/// Whether this binding was explicitly excluded by a user visitor or filter.
bool userDefinedIsExcluded = false;

/// Whether these bindings should be generated.
///
/// Set by MarkBindingsVisitation.
Expand Down
2 changes: 2 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/compound.dart
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,10 @@ class CompoundMember extends AstNode {
final String? dartDoc;
final String originalName;
final Type type;
bool userDefinedIsExcluded = false;

final Symbol _symbol;
Symbol get symbol => _symbol;
String get name => _symbol.name;

CompoundMember({
Expand Down
1 change: 1 addition & 0 deletions pkgs/ffigen/lib/src/code_generator/cpp_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class CppMethod extends AstNode with HasLocalScope {
final bool isConstant;
final bool isStatic;
final CppMethodKind kind;
bool userDefinedIsExcluded = false;

CppMethod({
required this.name,
Expand Down
2 changes: 2 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/enum_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,10 @@ class EnumConstant extends AstNode {
final String? originalName;
final String? dartDoc;
final int value;
bool userDefinedIsExcluded = false;

final Symbol _symbol;
Symbol get symbol => _symbol;
String get name => _symbol.name;

EnumConstant({
Expand Down
9 changes: 5 additions & 4 deletions pkgs/ffigen/lib/src/code_generator/func.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ import 'writer.dart';
/// ```
class Func extends LookUpBinding with HasLocalScope {
final FunctionType functionType;
final bool exposeSymbolAddress;
final bool exposeFunctionTypedefs;
final bool isLeaf;
bool exposeSymbolAddress;
bool exposeFunctionTypedefs;
bool isLeaf;
final bool objCReturnsRetained;
final bool useNameForLookup;
final bool recordUse;
bool recordUse;
final ApiAvailability? apiAvailability;

@override
Expand Down Expand Up @@ -289,6 +289,7 @@ class Parameter extends AstNode {
final String originalName;
Type type;
final bool objCConsumed;
bool userDefinedIsExcluded = false;

Symbol symbol;
String get name => symbol.name;
Expand Down
2 changes: 1 addition & 1 deletion pkgs/ffigen/lib/src/code_generator/global.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import 'writer.dart';
/// ```
class Global extends LookUpBinding with HasLocalScope {
final Type type;
final bool exposeSymbolAddress;
bool exposeSymbolAddress;
final bool constant;

@override
Expand Down
2 changes: 1 addition & 1 deletion pkgs/ffigen/lib/src/code_generator/objc_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class ObjCInterface extends BindingType with ObjCMethods, HasLocalScope {
ObjCInterface? superType;
bool filled = false;

final String? module;
String? module;
late final NoLookUpBinding classObject;
late final ObjCInternalGlobal _isKindOfClass;
late final ObjCMsgSendFunc _isKindOfClassMsgSend;
Expand Down
1 change: 1 addition & 0 deletions pkgs/ffigen/lib/src/code_generator/objc_methods.dart
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ class ObjCMethod extends AstNode with HasLocalScope {
final String? dartDoc;
final String originalName;
Symbol symbol;
bool userDefinedIsExcluded = false;
final String originalProtocolMethodName;
Type returnType;
final List<Parameter> _params;
Expand Down
2 changes: 1 addition & 1 deletion pkgs/ffigen/lib/src/code_generator/objc_protocol.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class ObjCProtocol extends BindingType with ObjCMethods, HasLocalScope {
@override
final Context context;
final superProtocols = <ObjCProtocol>[];
final String? module;
String? module;
final Symbol loaderSymbol;
late final ObjCProtocolGlobal _protocolPointer;
late final ObjCInternalGlobal _conformsTo;
Expand Down
5 changes: 5 additions & 0 deletions pkgs/ffigen/lib/src/config_provider/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ import 'package:meta/meta.dart';

import '../code_generator.dart';
import '../ffigen.dart';
import '../public_ast/public_ast.dart' show Visitor;
import 'config_types.dart';

/// The generator that generates bindings for `dart:ffi` from C and Objective-C
/// headers.
// TODO: Add a code snippet example.
final class FfiGenerator {
/// User custom visitors to modify/filter AST elements.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the other fields are called "configuration".

Maybe we should say something along the lines of:

/// Visitors to configure the generation options for target language declarations.
///
/// You can filter:
/// code snippet
///
/// You can rename:
/// code snippet
///
/// The visitors run in order.
/// some explanation or example

final List<Visitor>? visitors;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which order do these run?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One at a time, first to last.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add that to the doc comment :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a difference between setting this to null and setting it to []? If not, I would make this non-nullable and use [] to represent the "no visitor set" state. If there is a difference, let document.


/// The configuration for header parsing of [FfiGenerator].
final Headers headers;

Expand Down Expand Up @@ -88,6 +92,7 @@ final class FfiGenerator {
final Uri? libclangDylib;

const FfiGenerator({
this.visitors,
this.headers = const Headers(),
this.enums = Enums.excludeAll,
this.functions = Functions.excludeAll,
Expand Down
8 changes: 8 additions & 0 deletions pkgs/ffigen/lib/src/header_parser/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import '../code_generator/scope.dart';
import '../config_provider.dart';
import '../config_provider/utils.dart';
import '../context.dart';
import '../public_ast/public_ast.dart' as public_ast;
import '../strings.dart' as strings;
import '../visitor/apply_config_filters.dart';
import '../visitor/ast.dart';
Expand Down Expand Up @@ -176,6 +177,13 @@ List<Binding> transformBindings(List<Binding> rawBindings, Context context) {
visit(context, CopyMethodsFromSuperTypesVisitation(), allBindings);
visit(context, FixOverriddenMethodsVisitation(context), allBindings);

// Execute Public AST visitors.
final publicAst = public_ast.PublicAst.fromBindings(allBindings.toList());
publicAst.accept(public_ast.LegacyCallbacksVisitor(config));
for (final v in config.visitors ?? const <public_ast.Visitor>[]) {
publicAst.accept(v);
}

final applyConfigFiltersVisitation = ApplyConfigFiltersVisitation(config);
visit(context, applyConfigFiltersVisitation, allBindings);
final directlyIncluded = applyConfigFiltersVisitation.directlyIncluded;
Expand Down
Loading
Loading