blob: 894d3799ed25291ab778f6119e0dd4681e234e53 [file] [log] [blame]
// 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 'binding_string.dart';
import 'utils.dart';
import 'writer.dart';
enum CompoundType { struct, union }
/// A binding for Compound type - Struct/Union.
abstract class Compound extends BindingType {
/// 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,
/// or 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,
bool isInternal = false,
}) : members = members ?? [],
super(
usr: usr,
originalName: originalName,
name: name,
dartDoc: dartDoc,
isInternal: isInternal,
);
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 Struct(
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 is ConstantArray) {
array.add(startType.length);
startType = startType.child;
}
return array;
}
String _getInlineArrayTypeString(Type type, Writer w) {
if (type is ConstantArray) {
return '${w.ffiLibraryPrefix}.Array<'
'${_getInlineArrayTypeString(type.child, w)}>';
}
return type.getCType(w);
}
@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});
/// Marking type names because dart doesn't allow class member to have the
/// same name as a type name used internally.
for (final m in members) {
localUniqueNamer.markUsed(m.type.getDartType(w));
}
/// 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('final class $enclosingClassName extends ');
s.write('${w.ffiLibraryPrefix}.${isOpaque ? 'Opaque' : dartClassName}{\n');
const depth = ' ';
for (final m in members) {
m.name = localUniqueNamer.makeUnique(m.name);
if (m.type is ConstantArray) {
s.write('$depth@${w.ffiLibraryPrefix}.Array.multi(');
s.write('${_getArrayDimensionLengths(m.type)})\n');
s.write('${depth}external ${_getInlineArrayTypeString(m.type, w)} ');
s.write('${m.name};\n\n');
} else {
if (m.dartDoc != null) {
s.write('$depth/// ');
s.writeAll(m.dartDoc!.split('\n'), '\n$depth/// ');
s.write('\n');
}
if (!sameDartAndCType(m.type, w)) {
s.write('$depth@${m.type.getCType(w)}()\n');
}
s.write('${depth}external ${m.type.getDartType(w)} ${m.name};\n\n');
}
}
s.write('}\n\n');
return BindingString(
type: isStruct ? BindingStringType.struct : BindingStringType.union,
string: s.toString());
}
@override
void addDependencies(Set<Binding> dependencies) {
if (dependencies.contains(this)) return;
dependencies.add(this);
for (final m in members) {
m.type.addDependencies(dependencies);
}
}
@override
bool get isIncompleteCompound => isIncomplete;
@override
String getCType(Writer w) => name;
}
class Member {
final String? dartDoc;
final String originalName;
String name;
final Type type;
Member({
String? originalName,
required this.name,
required this.type,
this.dartDoc,
}) : originalName = originalName ?? name;
}