Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,13 @@ Compound? _parseCompoundDeclaration(
return null;
}

final decl = Declaration(usr: usr, originalName: declName);
// For C++ compounds declared inside one or more scopes, [qualifiedName] is
// the fully-qualified name (e.g. `outer::Point` or `Outer::Inner`). At
// global scope it equals [declName].
final qualifiedName = declName.isEmpty
? declName
: qualifiedNameFromUsr(usr, declName);
final decl = Declaration(usr: usr, originalName: qualifiedName);
final Compound compound;
if (declName.isEmpty) {
cursor = context.cursorIndex.getDefinition(cursor);
Expand All @@ -155,12 +161,19 @@ Compound? _parseCompoundDeclaration(
} else {
cursor = context.cursorIndex.getDefinition(cursor);
context.logger.fine(
'++++ Adding $className: Name: $declName, ${cursor.completeStringRepr()}',
'++++ Adding $className: Name: $qualifiedName, '
'${cursor.completeStringRepr()}',
);
var dartName = configDecl.rename(decl);
if (dartName.contains('::')) {
// C++ scope separators are not valid in Dart identifiers. Flatten any
// scopes left after user renaming, preserving the renamed prefix if any.
dartName = flattenQualifiedName(dartName);
}
compound = constructor(
usr: usr,
originalName: declName,
name: configDecl.rename(decl),
originalName: qualifiedName,
name: dartName,
dartDoc: getCursorDocComment(
context,
cursor,
Expand Down
16 changes: 13 additions & 3 deletions pkgs/ffigen/lib/src/header_parser/sub_parsers/enumdecl_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,17 @@ EnumClass parseEnumDeclaration(clang_types.CXCursor cursor, Context context) {
.where((c) => c.rawValue.startsWith('-'))
.isNotEmpty;
} else {
final decl = Declaration(usr: usr, originalName: enumName);
// For C++ enums declared inside one or more scopes, [qualifiedName] is the
// fully-qualified name (e.g. `outer::inner::Color` or
// `outer::Class::Color`). At global scope it equals [enumName].
final qualifiedName = qualifiedNameFromUsr(usr, enumName);
final decl = Declaration(usr: usr, originalName: qualifiedName);
var dartName = config.enums.rename(decl);
if (dartName.contains('::')) {
// C++ scope separators are not valid in Dart identifiers. Flatten any
// scopes left after user renaming, preserving the renamed prefix if any.
dartName = flattenQualifiedName(dartName);
}
logger.fine('++++ Adding Enum: ${cursor.completeStringRepr()}');
enumClass = EnumClass(
usr: usr,
Expand All @@ -64,8 +74,8 @@ EnumClass parseEnumDeclaration(clang_types.CXCursor cursor, Context context) {
cursor,
availability: apiAvailability.dartDoc,
),
originalName: enumName,
name: config.enums.rename(decl),
originalName: qualifiedName,
name: dartName,
nativeType: nativeType,
context: context,
apiAvailability: apiAvailability,
Expand Down
30 changes: 21 additions & 9 deletions pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,19 @@ Binding? parseVarDeclaration(Context context, clang_types.CXCursor cursor) {
return bindingsIndex.getSeenVariableConstant(usr);
}

final decl = Declaration(usr: usr, originalName: name);
// For C++ variables (and static data members) declared inside one or more
// scopes, [qualifiedName] is the fully-qualified name (e.g. `ns::nsVar` or
// `Box::member`). At file scope it equals [name].
final qualifiedName = qualifiedVarNameFromUsr(usr, name);
final decl = Declaration(usr: usr, originalName: qualifiedName);

// C++ scope separators are not valid in Dart identifiers. Flatten any scopes
// left after user renaming, preserving the renamed prefix if any.
final renamed = config.globals.rename(decl);
final dartName = renamed.contains('::')
? flattenQualifiedName(renamed)
: renamed;

final cType = cursor.type();

// Try to evaluate as a constant first,
Expand All @@ -40,8 +52,8 @@ Binding? parseVarDeclaration(Context context, clang_types.CXCursor cursor) {
final value = clang.clang_EvalResult_getAsLongLong(evalResult);
constant = Constant(
usr: usr,
originalName: name,
name: config.globals.rename(decl),
originalName: qualifiedName,
name: dartName,
dartDoc: getCursorDocComment(context, cursor),
rawType: 'int',
rawValue: value.toString(),
Expand All @@ -51,8 +63,8 @@ Binding? parseVarDeclaration(Context context, clang_types.CXCursor cursor) {
final value = clang.clang_EvalResult_getAsDouble(evalResult);
constant = Constant(
usr: usr,
originalName: name,
name: config.globals.rename(decl),
originalName: qualifiedName,
name: dartName,
dartDoc: getCursorDocComment(context, cursor),
rawType: 'double',
rawValue: writeDoubleAsString(value),
Expand All @@ -63,8 +75,8 @@ Binding? parseVarDeclaration(Context context, clang_types.CXCursor cursor) {
final rawValue = getWrittenStringRepresentation(name, value, context);
constant = Constant(
usr: usr,
originalName: name,
name: config.globals.rename(decl),
originalName: qualifiedName,
name: dartName,
dartDoc: getCursorDocComment(context, cursor),
rawType: 'String',
rawValue: "'$rawValue'",
Expand Down Expand Up @@ -100,8 +112,8 @@ Binding? parseVarDeclaration(Context context, clang_types.CXCursor cursor) {
}

final global = Global(
originalName: name,
name: config.globals.rename(decl),
originalName: qualifiedName,
name: dartName,
usr: usr,
type: type,
dartDoc: getCursorDocComment(context, cursor),
Expand Down
130 changes: 124 additions & 6 deletions pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Set<Binding> parseTranslationUnit(
final logger = context.logger;
final headers = <String, bool>{};

translationUnitCursor.visitChildren((cursor) {
void rootCursorVisitor(clang_types.CXCursor cursor) {
final file = cursor.sourceFileName();
if (file.isEmpty) return;
if (headers[file] ??= context.config.headers.include(Uri.file(file))) {
Expand All @@ -33,8 +33,11 @@ Set<Binding> parseTranslationUnit(
case clang_types.CXCursorKind.CXCursor_FunctionDecl:
bindings.addAll(parseFunctionDeclaration(context, cursor));
break;
case clang_types.CXCursorKind.CXCursor_StructDecl:
case clang_types.CXCursorKind.CXCursor_UnionDecl:
case clang_types.CXCursorKind.CXCursor_StructDecl:
addToBindings(bindings, _getCodeGenTypeFromCursor(context, cursor));
_visitRecordForNestedDecls(context, cursor, bindings, headers);
break;
case clang_types.CXCursorKind.CXCursor_EnumDecl:
case clang_types.CXCursorKind.CXCursor_ObjCInterfaceDecl:
case clang_types.CXCursorKind.CXCursor_TypedefDecl:
Expand All @@ -60,6 +63,16 @@ Set<Binding> parseTranslationUnit(
break;
case clang_types.CXCursorKind.CXCursor_ClassDecl:
addToBindings(bindings, parseClassDeclaration(context, cursor));
_visitRecordForNestedDecls(context, cursor, bindings, headers);
break;
case clang_types.CXCursorKind.CXCursor_Namespace:
_visitNamespaceForNestedDecls(context, cursor, bindings, headers);
break;
case clang_types.CXCursorKind.CXCursor_LinkageSpec:
// Declarations inside `extern "C" { ... }` blocks are wrapped in
// a LinkageSpec cursor when parsing in C++ mode. Recurse into it
// so they're dispatched like top-level declarations.
cursor.visitChildren(rootCursorVisitor);
break;
default:
logger.finer('rootCursorVisitor: CursorKind not implemented');
Expand All @@ -74,7 +87,9 @@ Set<Binding> parseTranslationUnit(
'rootCursorVisitor:(not included) ${cursor.completeStringRepr()}',
);
}
});
}

translationUnitCursor.visitChildren(rootCursorVisitor);

return bindings;
}
Expand All @@ -87,6 +102,100 @@ void addToBindings(Set<Binding> bindings, Binding? b) {
}
}

/// Recurses into a C++ namespace, surfacing enum, struct, union and variable
/// (including constant) declarations.
// TODO: Dispatch ClassDecl, FunctionDecl, etc. here for full C++ namespace
// support. Class declarations are currently visited only to find nested
// enums, structs, unions and static data members.
void _visitNamespaceForNestedDecls(
Context context,
clang_types.CXCursor namespaceCursor,
Set<Binding> bindings,
Map<String, bool> headers,
) {
final logger = context.logger;
if (clang.clang_Cursor_isAnonymous(namespaceCursor) != 0) {
logger.fine('Skipping anonymous namespace.');
return;
}
_visitChildrenForNestedDecls(context, namespaceCursor, bindings, headers);
}

/// Recurses into a C++ record to surface enum, struct and union declarations,
/// and static data members (e.g. `static constexpr` constants), nested inside
/// it.
void _visitRecordForNestedDecls(
Context context,
clang_types.CXCursor recordCursor,
Set<Binding> bindings,
Map<String, bool> headers,
) {
final logger = context.logger;
if (clang.clang_Cursor_isAnonymous(recordCursor) != 0) {
logger.fine('Skipping anonymous record.');
return;
}
_visitChildrenForNestedDecls(context, recordCursor, bindings, headers);
}

void _visitChildrenForNestedDecls(
Context context,
clang_types.CXCursor parentCursor,
Set<Binding> bindings,
Map<String, bool> headers,
) {
final logger = context.logger;
parentCursor.visitChildren((cursor) {
final file = cursor.sourceFileName();
if (file.isEmpty) return;
if (!(headers[file] ??= context.config.headers.include(Uri.file(file)))) {
logger.finest(
'nestedDeclCursorVisitor:(not included) ${cursor.completeStringRepr()}',
);
return;
}
try {
logger.finest('nestedDeclCursorVisitor: ${cursor.completeStringRepr()}');
switch (clang.clang_getCursorKind(cursor)) {
case clang_types.CXCursorKind.CXCursor_Namespace:
_visitNamespaceForNestedDecls(context, cursor, bindings, headers);
break;
case clang_types.CXCursorKind.CXCursor_LinkageSpec:
// Recurse into `extern "C" { ... }` blocks.
_visitChildrenForNestedDecls(context, cursor, bindings, headers);
break;
case clang_types.CXCursorKind.CXCursor_UnionDecl:
case clang_types.CXCursorKind.CXCursor_StructDecl:
// Anonymous records are handled as members of their parent record,
// not as top-level bindings.
if (clang.clang_Cursor_isAnonymous(cursor) == 0) {
addToBindings(bindings, _getCodeGenTypeFromCursor(context, cursor));
}
_visitRecordForNestedDecls(context, cursor, bindings, headers);
break;
case clang_types.CXCursorKind.CXCursor_ClassDecl:
_visitRecordForNestedDecls(context, cursor, bindings, headers);
break;
case clang_types.CXCursorKind.CXCursor_EnumDecl:
addToBindings(bindings, _getCodeGenTypeFromCursor(context, cursor));
break;
case clang_types.CXCursorKind.CXCursor_VarDecl:
// A namespace-scoped variable or a static data member (e.g. a
// `static constexpr` constant). Non-static instance fields are
// FieldDecl, not VarDecl, so they aren't surfaced here.
addToBindings(bindings, parseVarDeclaration(context, cursor));
break;
default:
logger.finer('nestedDeclCursorVisitor: CursorKind not implemented');
}
} catch (e, s) {
logger.severe(e);
logger.severe(s);
rethrow;
}
});
}

BindingType? _getCodeGenTypeFromCursor(
Context context,
clang_types.CXCursor cursor,
Expand All @@ -101,13 +210,22 @@ void buildUsrCursorDefinitionMap(
clang_types.CXCursor translationUnitCursor,
) {
final logger = context.logger;
translationUnitCursor.visitChildren((cursor) {
void visitor(clang_types.CXCursor cursor) {
try {
context.cursorIndex.saveDefinition(cursor);
if (clang.clang_getCursorKind(cursor) ==
clang_types.CXCursorKind.CXCursor_LinkageSpec) {
// Declarations inside `extern "C" { ... }` blocks are wrapped in a
// LinkageSpec cursor when parsing in C++ mode.
cursor.visitChildren(visitor);
} else {
context.cursorIndex.saveDefinition(cursor);
}
} catch (e, s) {
logger.severe(e);
logger.severe(s);
rethrow;
}
});
}

translationUnitCursor.visitChildren(visitor);
}
11 changes: 10 additions & 1 deletion pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,18 @@ Type getCodeGenType(
return BooleanType();
case clang_types.CXTypeKind.CXType_Attributed:
case clang_types.CXTypeKind.CXType_Unexposed:
// For attributed types, the modified type is the underlying type. Other
// unexposed types (e.g. a C++ type referenced through a
// using-declaration, like `std::uint16_t`) have no modified type;
// resolve those via their canonical type instead.
var innerCxType = clang.clang_Type_getModifiedType(cxtype);
if (innerCxType.kind == clang_types.CXTypeKind.CXType_Invalid) {
final canonical = clang.clang_getCanonicalType(cxtype);
if (canonical.kind != cxtype.kind) innerCxType = canonical;
}
final innerType = getCodeGenType(
context,
clang.clang_Type_getModifiedType(cxtype),
innerCxType,
originalCursor: originalCursor,
);
final isNullable =
Expand Down
56 changes: 55 additions & 1 deletion pkgs/ffigen/lib/src/header_parser/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,10 @@ extension CXCursorExt on clang_types.CXCursor {

String usr() {
var res = clang.clang_getCursorUSR(this).toStringAndDispose();
assert(!res.contains(synthUsrChar));
// Raw USRs must not collide with the synthesized `~ <label>:` suffixes
// added below and elsewhere. A bare [synthUsrChar] can legitimately appear
// in a raw USR, e.g. in a C++ destructor name like `@F@~Foo#`.
assert(!res.contains('$synthUsrChar '));
if (isAnonymousRecordDecl()) {
res += '$synthUsrChar anonRec: offset:${sourceFileOffset()}';
}
Expand Down Expand Up @@ -729,3 +732,54 @@ String _getWritableChar(int char, {bool utf8 = true}) {
/// In all other cases, simply convert to string.
return String.fromCharCode(char);
}

/// Builds the fully-qualified C++ name of a declaration from its [usr].
///
/// A USR like `c:@N@outer@S@Palette@E@Tone` yields
/// `outer::Palette::Tone`. At global scope (no enclosing namespace or class)
/// this just returns [leafName].
String qualifiedNameFromUsr(String usr, String leafName) {
// After the `c:` prefix, USR tokens alternate between a single-char kind
// marker (`N` for namespace, `S` for class/struct, `U` for union, `E` for
// enum, etc.) and its name. Collect the names of the enclosing scopes by
// stepping over each marker/name pair, skipping the last pair which is the
// declaration itself.
final parts = usr.split('@');
final scopes = <String>[];
for (var i = 1; i + 3 < parts.length; i += 2) {
if (parts[i] == 'N' || parts[i] == 'S' || parts[i] == 'U') {
scopes.add(parts[i + 1]);
}
}
if (scopes.isEmpty) return leafName;
return [...scopes, leafName].join('::');
}

/// Builds the fully-qualified C++ name of a variable (or static data member)
/// from its [usr].
///
/// Unlike [qualifiedNameFromUsr], a variable's USR ends in a bare name token
/// with no preceding kind marker, e.g. `c:@N@ns@nsInt` or `c:@S@Box@memberInt`
/// (const variables also carry a leading file token, e.g.
/// `c:foo.h@N@ns@nsInt`). These yield `ns::nsInt` and `Box::memberInt`
/// respectively. At file scope (no enclosing namespace or class) this just
/// returns [leafName].
String qualifiedVarNameFromUsr(String usr, String leafName) {
// Scope markers/names sit between the `c:`(+file) prefix (parts[0]) and the
// trailing leaf token (parts.last), alternating marker/name.
final parts = usr.split('@');
final scopes = <String>[];
for (var i = 1; i + 1 < parts.length; i += 2) {
if (parts[i] == 'N' || parts[i] == 'S' || parts[i] == 'U') {
scopes.add(parts[i + 1]);
}
}
if (scopes.isEmpty) return leafName;
return [...scopes, leafName].join('::');
}

/// Flattens a `::`-qualified C++ name into a single Dart identifier by joining
/// the path segments with `$`, e.g. `outer::inner::Color` becomes
/// `outer$inner$Color`.
String flattenQualifiedName(String qualifiedName) =>
qualifiedName.split('::').where((s) => s.isNotEmpty).join(r'$');
Loading
Loading