diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/compounddecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/compounddecl_parser.dart index 23e01f33b1..8aa05b3572 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/compounddecl_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/compounddecl_parser.dart @@ -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); @@ -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, diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/enumdecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/enumdecl_parser.dart index ffd58fa9c5..1c6d8fbd73 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/enumdecl_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/enumdecl_parser.dart @@ -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, @@ -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, diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart index 6bd83d10ba..3924c82cc2 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart @@ -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, @@ -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(), @@ -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), @@ -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'", @@ -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), diff --git a/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart b/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart index 8d4022f31d..fa0001f17a 100644 --- a/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart @@ -23,7 +23,7 @@ Set parseTranslationUnit( final logger = context.logger; final headers = {}; - 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))) { @@ -33,8 +33,11 @@ Set 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: @@ -60,6 +63,16 @@ Set 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'); @@ -74,7 +87,9 @@ Set parseTranslationUnit( 'rootCursorVisitor:(not included) ${cursor.completeStringRepr()}', ); } - }); + } + + translationUnitCursor.visitChildren(rootCursorVisitor); return bindings; } @@ -87,6 +102,100 @@ void addToBindings(Set 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 bindings, + Map 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 bindings, + Map 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 bindings, + Map 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, @@ -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); } diff --git a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart index 484b38eca0..08a0fa8958 100644 --- a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart +++ b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart @@ -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 = diff --git a/pkgs/ffigen/lib/src/header_parser/utils.dart b/pkgs/ffigen/lib/src/header_parser/utils.dart index b7aefa6a4a..f910ba1fdf 100644 --- a/pkgs/ffigen/lib/src/header_parser/utils.dart +++ b/pkgs/ffigen/lib/src/header_parser/utils.dart @@ -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 `~