• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dart-lang / ffigen / 6661371912

27 Oct 2023 12:38AM UTC coverage: 92.169% (-0.09%) from 92.254%
6661371912

push

github

web-flow
Use getDartType rather than getFfiDartType in ObjC block codegen (#632)

* Blocks returning proper user facing types

* More tests and refactors

* fmt

* Fix test

* Fix vararg test

* Partial fix for block ref counts

* More block ref counting fixes

* fmt

* Daco's comments

102 of 102 new or added lines in 7 files covered. (100.0%)

3719 of 4035 relevant lines covered (92.17%)

28.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.01
/lib/src/code_generator/writer.dart
1
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2
// for details. All rights reserved. Use of this source code is governed by a
3
// BSD-style license that can be found in the LICENSE file.
4

5
import 'package:ffigen/src/code_generator.dart';
6
import 'package:ffigen/src/code_generator/utils.dart';
7
import 'package:logging/logging.dart';
8

9
import '../strings.dart' as strings;
10

11
final _logger = Logger('ffigen.code_generator.writer');
3✔
12

13
/// To store generated String bindings.
14
class Writer {
15
  final String? header;
16

17
  /// Holds bindings, which lookup symbols.
18
  final List<Binding> lookUpBindings;
19

20
  /// Holds bindings, which lookup symbols through `FfiNative`.
21
  final List<Binding> ffiNativeBindings;
22

23
  /// Holds bindings which don't lookup symbols.
24
  final List<Binding> noLookUpBindings;
25

26
  /// Manages the `_SymbolAddress` class.
27
  final symbolAddressWriter = SymbolAddressWriter();
28

29
  late String _className;
30
  String get className => _className;
4✔
31

32
  final String? classDocComment;
33

34
  String? _ffiLibraryPrefix;
35
  String get ffiLibraryPrefix {
32✔
36
    if (_ffiLibraryPrefix != null) {
32✔
37
      return _ffiLibraryPrefix!;
32✔
38
    }
39

40
    final import = _usedImports.firstWhere(
64✔
41
        (element) => element.name == ffiImport.name,
×
42
        orElse: () => ffiImport);
64✔
43
    _usedImports.add(import);
64✔
44
    return _ffiLibraryPrefix = import.prefix;
64✔
45
  }
46

47
  String? _ffiPkgLibraryPrefix;
48
  String get ffiPkgLibraryPrefix {
2✔
49
    if (_ffiPkgLibraryPrefix != null) {
2✔
50
      return _ffiPkgLibraryPrefix!;
2✔
51
    }
52

53
    final import = _usedImports.firstWhere(
4✔
54
        (element) => element.name == ffiPkgImport.name,
10✔
55
        orElse: () => ffiPkgImport);
4✔
56
    _usedImports.add(import);
4✔
57
    return _ffiPkgLibraryPrefix = import.prefix;
4✔
58
  }
59

60
  final Set<LibraryImport> _usedImports = {};
61

62
  late String _lookupFuncIdentifier;
63
  String get lookupFuncIdentifier => _lookupFuncIdentifier;
54✔
64

65
  late String _symbolAddressClassName;
66
  late String _symbolAddressVariableName;
67
  late String _symbolAddressLibraryVarName;
68

69
  /// Initial namers set after running constructor. Namers are reset to this
70
  /// initial state everytime [generate] is called.
71
  late UniqueNamer _initialTopLevelUniqueNamer, _initialWrapperLevelUniqueNamer;
72

73
  /// Used by [Binding]s for generating required code.
74
  late UniqueNamer _topLevelUniqueNamer, _wrapperLevelUniqueNamer;
75
  UniqueNamer get topLevelUniqueNamer => _topLevelUniqueNamer;
24✔
76
  UniqueNamer get wrapperLevelUniqueNamer => _wrapperLevelUniqueNamer;
56✔
77

78
  late String _arrayHelperClassPrefix;
79

80
  /// Guaranteed to be a unique prefix.
81
  String get arrayHelperClassPrefix => _arrayHelperClassPrefix;
×
82

83
  /// Set true after calling [generate]. Indicates if
84
  /// [generateSymbolOutputYamlMap] can be called.
85
  bool get canGenerateSymbolOutput => _canGenerateSymbolOutput;
2✔
86
  bool _canGenerateSymbolOutput = false;
87

88
  /// [_usedUpNames] should contain names of all the declarations which are
89
  /// already used. This is used to avoid name collisions.
90
  Writer({
55✔
91
    required this.lookUpBindings,
92
    required this.ffiNativeBindings,
93
    required this.noLookUpBindings,
94
    required String className,
95
    Set<LibraryImport>? additionalImports,
96
    this.classDocComment,
97
    this.header,
98
  }) {
99
    final globalLevelNameSet = noLookUpBindings.map((e) => e.name).toSet();
231✔
100
    final wrapperLevelNameSet = lookUpBindings.map((e) => e.name).toSet();
223✔
101
    final allNameSet = <String>{}
102
      ..addAll(globalLevelNameSet)
55✔
103
      ..addAll(wrapperLevelNameSet);
55✔
104

105
    _initialTopLevelUniqueNamer = UniqueNamer(globalLevelNameSet);
110✔
106
    _initialWrapperLevelUniqueNamer = UniqueNamer(wrapperLevelNameSet);
110✔
107
    final allLevelsUniqueNamer = UniqueNamer(allNameSet);
55✔
108

109
    /// Wrapper class name must be unique among all names.
110
    _className = _resolveNameConflict(
110✔
111
      name: className,
112
      makeUnique: allLevelsUniqueNamer,
113
      markUsed: [_initialWrapperLevelUniqueNamer, _initialTopLevelUniqueNamer],
165✔
114
    );
115

116
    /// Library imports prefix should be unique unique among all names.
117
    if (additionalImports != null) {
118
      for (final lib in additionalImports) {
53✔
119
        lib.prefix = _resolveNameConflict(
4✔
120
          name: lib.prefix,
2✔
121
          makeUnique: allLevelsUniqueNamer,
122
          markUsed: [
2✔
123
            _initialWrapperLevelUniqueNamer,
2✔
124
            _initialTopLevelUniqueNamer
2✔
125
          ],
126
        );
127
      }
128
    }
129

130
    /// [_lookupFuncIdentifier] should be unique in top level.
131
    _lookupFuncIdentifier = _resolveNameConflict(
110✔
132
      name: '_lookup',
133
      makeUnique: _initialTopLevelUniqueNamer,
55✔
134
      markUsed: [_initialTopLevelUniqueNamer],
110✔
135
    );
136

137
    /// Resolve name conflicts of identifiers used for SymbolAddresses.
138
    _symbolAddressClassName = _resolveNameConflict(
110✔
139
      name: '_SymbolAddresses',
140
      makeUnique: allLevelsUniqueNamer,
141
      markUsed: [_initialWrapperLevelUniqueNamer, _initialTopLevelUniqueNamer],
165✔
142
    );
143
    _symbolAddressVariableName = _resolveNameConflict(
110✔
144
      name: 'addresses',
145
      makeUnique: _initialWrapperLevelUniqueNamer,
55✔
146
      markUsed: [_initialWrapperLevelUniqueNamer],
110✔
147
    );
148
    _symbolAddressLibraryVarName = _resolveNameConflict(
110✔
149
      name: '_library',
150
      makeUnique: _initialWrapperLevelUniqueNamer,
55✔
151
      markUsed: [_initialWrapperLevelUniqueNamer],
110✔
152
    );
153

154
    /// Finding a unique prefix for Array Helper Classes and store into
155
    /// [_arrayHelperClassPrefix].
156
    final base = 'ArrayHelper';
157
    _arrayHelperClassPrefix = base;
55✔
158
    var suffixInt = 0;
159
    for (var i = 0; i < allNameSet.length; i++) {
146✔
160
      if (allNameSet.elementAt(i).startsWith(_arrayHelperClassPrefix)) {
108✔
161
        // Not a unique prefix, start over with a new suffix.
162
        i = -1;
1✔
163
        suffixInt++;
1✔
164
        _arrayHelperClassPrefix = '$base$suffixInt';
2✔
165
      }
166
    }
167

168
    _resetUniqueNamersNamers();
55✔
169
  }
170

171
  /// Resolved name conflict using [makeUnique] and marks the result as used in
172
  /// all [markUsed].
173
  String _resolveNameConflict({
55✔
174
    required String name,
175
    required UniqueNamer makeUnique,
176
    List<UniqueNamer> markUsed = const [],
177
  }) {
178
    final s = makeUnique.makeUnique(name);
55✔
179
    for (final un in markUsed) {
110✔
180
      un.markUsed(s);
55✔
181
    }
182
    return s;
183
  }
184

185
  /// Resets the namers to initial state. Namers are reset before generating.
186
  void _resetUniqueNamersNamers() {
55✔
187
    _topLevelUniqueNamer = _initialTopLevelUniqueNamer.clone();
165✔
188
    _wrapperLevelUniqueNamer = _initialWrapperLevelUniqueNamer.clone();
165✔
189
  }
190

191
  void markImportUsed(LibraryImport import) {
29✔
192
    _usedImports.add(import);
58✔
193
  }
194

195
  /// Writes all bindings to a String.
196
  String generate() {
46✔
197
    final s = StringBuffer();
46✔
198

199
    // We write the source first to determine which imports are actually
200
    // referenced. Headers and [s] are then combined into the final result.
201
    final result = StringBuffer();
46✔
202

203
    // Reset unique namers to initial state.
204
    _resetUniqueNamersNamers();
46✔
205

206
    // Write file header (if any).
207
    if (header != null) {
46✔
208
      result.writeln(header);
66✔
209
    }
210

211
    // Write auto generated declaration.
212
    result.write(makeDoc(
92✔
213
        'AUTO GENERATED FILE, DO NOT EDIT.\n\nGenerated by `package:ffigen`.'));
214

215
    // Write lint ignore if not specified by user already.
216
    if (!RegExp(r'ignore_for_file:\s*type\s*=\s*lint').hasMatch(header ?? '')) {
138✔
217
      result.write(makeDoc('ignore_for_file: type=lint'));
92✔
218
    }
219

220
    /// Write [lookUpBindings].
221
    if (lookUpBindings.isNotEmpty) {
92✔
222
      // Write doc comment for wrapper class.
223
      if (classDocComment != null) {
24✔
224
        s.write(makeDartDoc(classDocComment!));
57✔
225
      }
226
      // Write wrapper classs.
227
      s.write('class $_className{\n');
72✔
228
      // Write dylib.
229
      s.write('/// Holds the symbol lookup function.\n');
24✔
230
      s.write(
24✔
231
          'final $ffiLibraryPrefix.Pointer<T> Function<T extends $ffiLibraryPrefix.NativeType>(String symbolName) $lookupFuncIdentifier;\n');
96✔
232
      s.write('\n');
24✔
233
      //Write doc comment for wrapper class constructor.
234
      s.write(makeDartDoc('The symbols are looked up in [dynamicLibrary].'));
48✔
235
      // Write wrapper class constructor.
236
      s.write(
24✔
237
          '$_className($ffiLibraryPrefix.DynamicLibrary dynamicLibrary): $lookupFuncIdentifier = dynamicLibrary.lookup;\n\n');
96✔
238
      //Write doc comment for wrapper class named constructor.
239
      s.write(makeDartDoc('The symbols are looked up with [lookup].'));
48✔
240
      // Write wrapper class named constructor.
241
      s.write(
24✔
242
          '$_className.fromLookup($ffiLibraryPrefix.Pointer<T> Function<T extends $ffiLibraryPrefix.NativeType>(String symbolName) lookup): $lookupFuncIdentifier = lookup;\n\n');
120✔
243
      for (final b in lookUpBindings) {
48✔
244
        s.write(b.toBindingString(this).string);
72✔
245
      }
246
      if (symbolAddressWriter.shouldGenerate) {
48✔
247
        s.write(symbolAddressWriter.writeObject(this));
9✔
248
      }
249

250
      s.write('}\n\n');
24✔
251
    }
252

253
    for (final b in ffiNativeBindings) {
47✔
254
      s.write(b.toBindingString(this).string);
3✔
255
    }
256

257
    if (symbolAddressWriter.shouldGenerate) {
92✔
258
      s.write(symbolAddressWriter.writeClass(this));
9✔
259
    }
260

261
    /// Write [noLookUpBindings].
262
    for (final b in noLookUpBindings) {
70✔
263
      s.write(b.toBindingString(this).string);
72✔
264
    }
265

266
    // Write neccesary imports.
267
    for (final lib in _usedImports) {
74✔
268
      result
269
        ..write("import '${lib.importPath}' as ${lib.prefix};")
112✔
270
        ..write('\n');
28✔
271
    }
272
    result.write(s);
46✔
273

274
    _canGenerateSymbolOutput = true;
46✔
275
    return result.toString();
46✔
276
  }
277

278
  Map<String, dynamic> generateSymbolOutputYamlMap(String importFilePath) {
1✔
279
    final bindings = <Binding>[
1✔
280
      ...noLookUpBindings,
1✔
281
      ...ffiNativeBindings,
1✔
282
      ...lookUpBindings
1✔
283
    ];
284
    if (!canGenerateSymbolOutput) {
1✔
285
      throw Exception(
×
286
          "Invalid state: generateSymbolOutputYamlMap() called before generate()");
287
    }
288
    final result = <String, dynamic>{};
1✔
289

290
    result[strings.formatVersion] = strings.symbolFileFormatVersion;
1✔
291
    result[strings.files] = <String, dynamic>{};
2✔
292
    result[strings.files][importFilePath] = <String, dynamic>{};
3✔
293

294
    final fileConfig = result[strings.files][importFilePath];
2✔
295
    fileConfig[strings.usedConfig] = <String, dynamic>{};
2✔
296

297
    // Warn for macros.
298
    final hasMacroBindings = bindings.any(
1✔
299
        (element) => element is Constant && element.usr.contains('@macro@'));
4✔
300
    if (hasMacroBindings) {
301
      _logger.info(
2✔
302
          'Removing all Macros from symbol file since they cannot be cross referenced reliably.');
303
    }
304
    // Remove internal bindings and macros.
305
    bindings.removeWhere((element) {
2✔
306
      return element.isInternal ||
1✔
307
          (element is Constant && element.usr.contains('@macro@'));
3✔
308
    });
309
    // Sort bindings alphabetically by USR.
310
    bindings.sort((a, b) => a.usr.compareTo(b.usr));
5✔
311
    fileConfig[strings.usedConfig][strings.ffiNative] = bindings
2✔
312
        .whereType<Func>()
1✔
313
        .any((element) => element.ffiNativeConfig.enabled);
4✔
314
    fileConfig[strings.symbols] = <String, dynamic>{};
2✔
315
    final symbolConfig = fileConfig[strings.symbols];
1✔
316
    for (final binding in bindings) {
2✔
317
      symbolConfig[binding.usr] = {
3✔
318
        strings.name: binding.name,
1✔
319
      };
320
    }
321
    return result;
322
  }
323
}
324

325
/// Manages the generated `_SymbolAddress` class.
326
class SymbolAddressWriter {
327
  final List<_SymbolAddressUnit> _addresses = [];
328

329
  /// Used to check if we need to generate `_SymbolAddress` class.
330
  bool get shouldGenerate => _addresses.isNotEmpty;
138✔
331

332
  void addSymbol({
4✔
333
    required String type,
334
    required String name,
335
    required String ptrName,
336
  }) {
337
    _addresses.add(_SymbolAddressUnit(type, name, ptrName));
12✔
338
  }
339

340
  String writeObject(Writer w) {
3✔
341
    return 'late final ${w._symbolAddressVariableName} = ${w._symbolAddressClassName}(this);';
9✔
342
  }
343

344
  String writeClass(Writer w) {
3✔
345
    final sb = StringBuffer();
3✔
346
    sb.write('class ${w._symbolAddressClassName} {\n');
9✔
347
    // Write Library object.
348
    sb.write('final ${w._className} ${w._symbolAddressLibraryVarName};\n');
12✔
349
    // Write Constructor.
350
    sb.write(
3✔
351
        '${w._symbolAddressClassName}(this.${w._symbolAddressLibraryVarName});\n');
9✔
352
    for (final address in _addresses) {
6✔
353
      sb.write(
3✔
354
          '${address.type} get ${address.name} => ${w._symbolAddressLibraryVarName}.${address.ptrName};\n');
15✔
355
    }
356
    sb.write('}\n');
3✔
357
    return sb.toString();
3✔
358
  }
359
}
360

361
/// Holds the data for a single symbol address.
362
class _SymbolAddressUnit {
363
  final String type, name, ptrName;
364

365
  _SymbolAddressUnit(this.type, this.name, this.ptrName);
4✔
366
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc