Skip to content

[ffigen] Parse declarations inside extern "C" blocks - #3609

Merged
liamappelbe merged 3 commits into
dart-lang:mainfrom
jakobkordez:feat/extern-c-linkage-spec
Sep 15, 2026
Merged

liamappelbe merged 3 commits into
dart-lang:mainfrom
jakobkordez:feat/extern-c-linkage-spec

Conversation

@jakobkordez

Copy link
Copy Markdown
Contributor

Description

In C++ mode, declarations inside an extern "C" { ... } block are wrapped in a
LinkageSpec cursor. Neither the root cursor visitor nor the USR definition
index looked through it, so every declaration in such a block was silently
dropped, which is most of what a C++ header exposes to C. Recurse into
LinkageSpec cursors in both places. Tests cover the braced and braceless
forms and a nested linkage spec; the case inside a namespace needs a
namespace visitor and is left for that change.

Depends on #3607: the verifyBindings await fix there is what makes the
new golden test actually compare anything. Please land that first.

Related Issues

  • None known

PR Checklist

  • I’ve reviewed the contributor guide and applied the relevant portions to this PR.
  • I've run dart tool/ci.dart --all locally and resolved all issues identified. This ensures the PR is formatted, has no lint errors, and ran all code generators. This applies to the packages part of the toplevel pubspec.yaml workspace.
  • All existing and new tests are passing. I added new tests to check the change I am making.
  • The PR is actually solving the issue. PRs that don't solve the issue will be closed. Please be respectful of the maintainers' time. If it's not clear what the issue is, feel free to ask questions on the GitHub issue before submitting a PR.
  • I have updated CHANGELOG.md for the relevant packages. (Not needed for small changes such as doc typos).
  • I have updated the pubspec package version if necessary.

@liamappelbe liamappelbe left a 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.

Looks good. The CI test failure looks legit tho. Also, you've got the same sync issues with verify_test_bindings as your other PRs (sorry about that). And v22 is published, so your changelog entry should go in v22.0.1-wip

@jakobkordez
jakobkordez force-pushed the feat/extern-c-linkage-spec branch from eba47fb to c29dd76 Compare September 9, 2026 08:04
@jakobkordez

jakobkordez commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor Author

@liamappelbe The Linux job fails because it installs libclang-14. Before LLVM 18, libclang never reported extern "C" blocks as CXCursor_LinkageSpec: the constant exists in the public header, but getCursorKindForDecl had no Decl::LinkageSpec case and fell through to CXCursor_UnexposedDecl.

There is a workaround but I think it's ugly:

  bool isLinkageSpec() {
    switch (kind()) {
      case clang_types.CXCursorKind.CXCursor_LinkageSpec:
        return true;
      case clang_types.CXCursorKind.CXCursor_UnexposedDecl:
        return _firstToken() ==
            (clang_types.CXTokenKind.CXToken_Keyword, 'extern');
      default:
        return false;
    }
  }

  (int, String)? _firstToken() {
    final tu = clang.clang_Cursor_getTranslationUnit(this);
    // An empty range at the start of the extent lexes exactly one token.
    final start = clang.clang_getRangeStart(extent);
    final range = clang.clang_getRange(start, start);
    final tokensPtr = calloc<Pointer<clang_types.CXToken>>();
    final numTokensPtr = calloc<UnsignedInt>();
    try {
      clang.clang_tokenize(tu, range, tokensPtr, numTokensPtr);
      final numTokens = numTokensPtr.value;
      if (numTokens == 0) return null;
      final token = tokensPtr.value.ref;
      final result = (
        clang.clang_getTokenKind(token),
        clang.clang_getTokenSpelling(tu, token).toStringAndDispose(),
      );
      clang.clang_disposeTokens(tu, tokensPtr.value, numTokens);
      return result;
    } finally {
      calloc.free(tokensPtr);
      calloc.free(numTokensPtr);
    }
  }

I'd bump the ubuntu version to ubuntu-24.04 and the libclang to libclang-18-dev

@liamappelbe

Copy link
Copy Markdown
Contributor

I'd bump the ubuntu version to ubuntu-24.04 and the libclang to libclang-18-dev

Sounds good. You can update the test-linux workflow in .github/workflows/ffigen.yml

@liamappelbe

Copy link
Copy Markdown
Contributor

There's also a line in the readme that says "LLVM (9+) must be installed...". That's a super old version. We should probably tell people to use v18+ if that's what we're testing.

@github-actions github-actions Bot added the type-infra A repository infrastructure change or enhancement label Sep 11, 2026
@liamappelbe
liamappelbe merged commit 935c259 into dart-lang:main Sep 15, 2026
42 checks passed
Hassnaa9 pushed a commit to Hassnaa9/native that referenced this pull request Sep 27, 2026
jakobkordez added a commit to jakobkordez/native that referenced this pull request Oct 15, 2026
Most of this branch's C++ parsing work landed upstream as separate PRs
(dart-lang#3591, dart-lang#3607, dart-lang#3608, dart-lang#3609, dart-lang#3644), so conflicts are resolved in favour
of the upstream implementations. Branch-only features are kept on top:

- constexpr variables and static data members: nested VarDecls are
  dispatched again, using qualifiedNameFromCursor, but only bound when
  they have a constant value (others would need a mangled-symbol lookup).
- forward-declared / pointer-only classes are covered by upstream's
  class-as-struct fallback; the existing test still passes.
- cpp_constexpr and cpp_opaque_class tests get per-test config files.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package:ffigen type-infra A repository infrastructure change or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants