Skip to content

feat(generator): port binding generator from libclang to cc/v4 - #65

Merged
flexiondotorg merged 12 commits into
mainfrom
wimpy/ww-137-port-the-binding-generator-from-libclang-to-moderncorgccv4
Aug 1, 2026
Merged

feat(generator): port binding generator from libclang to cc/v4#65
flexiondotorg merged 12 commits into
mainfrom
wimpy/ww-137-port-the-binding-generator-from-libclang-to-moderncorgccv4

Conversation

@flexiondotorg

Copy link
Copy Markdown
Contributor

The generator parsed FFmpeg headers through github.com/Newbluecake/bootstrap, an abandoned libclang fork pinned to clang 14, which broke twice on CXCursor_OMPArraySectionExpr, a cursor kind clang 18 removed. Parsing now runs on modernc.org/cc/v4, a pure Go C frontend, behind a new HeaderParser interface. Output is byte-identical: just generate leaves all five *.gen.go files and all three IR goldens unchanged, with skipCeiling still at 245 and 245 skip markers. cc/v4 has no comment node and no absolute separator position, so comment association now lexes the raw header bytes and applies Clang's own adjacency rule, plus a rule that recovers positions for names written as macro arguments, which FF_PAD_STRUCTURE needs; association reaches all 2907 comments. CI now builds, lints and tests the generator on every platform, with the drift gate unconditional on all four legs. CGO_ENABLED=0 go build ./internal/generator now succeeds; the emitted bindings still use cgo, so the module itself still needs CGO_ENABLED=1.

Whether the two darwin CI legs go green is untested: cc.NewConfig probes the host compiler, so a darwin target cannot be exercised from this Linux host. gcc 13, 14 and 15 produce byte-identical output here, clang 21 fails outright on glibc headers, and nix develop supplies a known-good gcc.

Verified locally on linux/amd64 inside nix develop: just lint (0 issues), just test (0 failures across seven packages), just generate (zero drift), nix flake check, and CGO_ENABLED=0 go build ./internal/generator all pass.

Closes #50. Two premises there do not hold: the generator was already built and run in go-test.yml before this change, with only the linter excluded, and cc/v4 alone does not make regeneration toolchain-free, since cc.NewConfig still probes $CC, then cc, then gcc.

Name the boundary that already existed between parsing and code
generation, so a second parser backend can replace libclang without
touching the emitter.

- Add HeaderParser with Parse(skips *SkipCollector) *Module and
  Version() string
- Turn the existing Parse into a method on the new clangParser type,
  body unchanged
- Drop the clang import from main.go, which now calls through the
  interface
- Assert the implementation satisfies the interface at compile time
- Record the boundary in AGENTS.md and docs/DEVELOPMENT.md

This ships inert. just generate produces a zero-byte diff in *.gen.go
and the skip count stays at 245.

Refs: WW-138
Give a parser swap a structured check. The only prior signal was a
63,333-line byte diff across five generated files, which cannot tell a
wrong typedef name from a reordered struct or a missing comment.

- Add -dump-ir, writing three deterministic goldens under
  internal/generator/testdata/ir/
- Split structure from comments, so a comment-attachment gap does not
  drown the structural signal
- Record skips as sorted (symbol, reason) pairs, since reason text is
  emitted into the generated files and is part of the byte-identical
  gate
- Normalise ctype to a repo-relative include/... form, because libclang
  spells an unnamed union with the header's absolute path and the
  goldens would otherwise match only on the machine that wrote them
- Expand FuncType and UnionType in renderType rather than calling
  String(), which returns the literal "func" and "union"

Comment volume settled at 13,012 IR-side lines across 2,907 records,
asserted by TestCommentGoldenLineCount. The 11,142 figure measures the
emitted side and also holds.

This ships inert. just generate produces a zero-byte diff in *.gen.go
and the skip count stays at 245.

Refs: WW-139
…lang

Drop the cgo dependency on github.com/Newbluecake/bootstrap, an
abandoned fork whose newest code targets clang 14 and which broke twice
on CXCursor_OMPArraySectionExpr, a cursor kind removed in Clang 18. The
generator is now pure Go and carries no clang version pin.

- Add ccParser, implementing HeaderParser on modernc.org/cc/v4 v4.29.2,
  and delete the libclang implementation
- Attach doc comments by lexing raw header bytes, since cc/v4 has no
  comment node and no absolute sep position. Take the nearest preceding
  comment and reject it when the text between holds ; { } # or @, which
  is Clang's own rule; anchor a trailing comment on the name token
- Add spellingSite, which recovers true positions for names written as
  macro arguments. FF_PAD_STRUCTURE collapses every AVBPrint field to
  one line, and cc/v4 keeps no pre-substitution offset
- Replace the gcc -E -v scrape with hostCppConfig, so include discovery
  no longer depends on the Nix-pinned toolchain
- Pin -std=gnu11 with the generator's defines, and surface Translate
  panics as errors, both under test
- Drop the unused import "C" from generator.go, which blocked
  CGO_ENABLED=0

Comment association is 2907 of 2907. Output is byte-identical: just
generate leaves *.gen.go and all three IR goldens unchanged, with
skipCeiling at 245 and 245 markers.

IR is byte-identical across linux/amd64 and linux/arm64, pinned by
TestGeneratorIRIsIdenticalAcrossTargets. The darwin pair stays untested,
because cc.NewConfig probes the host compiler and a darwin target
preprocesses glibc headers under Apple's ABI. Closing that needs a macOS
CI job.

Refs: WW-141
The generator is pure Go since the cc/v4 swap, so the three libclang
workarounds pushed to main on 5 June 2026 are now dead weight.

- Drop the libclang-20-dev apt step and the Homebrew llvm@20 step from
  go-test.yml, with the comments that forbade adding the system include
  path
- Restore plain go build ./... and go test ./... in go-release.yml,
  which had filtered out internal/generator
- Widen golangci-lint to ./..., putting internal/generator back in scope
- Run the drift gate on all four platform legs. IS_GATE and both
  conditionals are gone, so darwin no longer skips it
- Drop the grep -v filter that hid the real darwin compile error
- Remove the llvmPackages_20 pin from flake.nix, along with CGO_LDFLAGS,
  CPATH and the macOS clang include export. Darwin libcxx repoints to
  llvmPackages.libcxx, because the builder reads LIBCXX_INCLUDE for
  zimg. gcc stays, since cc.NewConfig probes a host C compiler

Verified locally: nix flake check, just lint, just test and just
generate all pass, with a zero-byte diff in *.gen.go.

Whether the two darwin legs go green is untested and only a real CI run
settles it. gcc 13, 14 and 15 each produce byte-identical output on
this host, and clang 21 fails outright on glibc headers; macOS runners
have only Apple clang against a different header set.

Refs: WW-142
@cubic-dev-ai

cubic-dev-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

This PR is large and would use a significant portion of your monthly review quota. Comment @cubic-dev-ai review this to confirm that you want cubic to review it.

@flexiondotorg

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai Review this

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai Review this

@flexiondotorg I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot 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.

2 issues found across 37 files

Confidence score: 3/5

  • In internal/generator/type.go, params and recordFunction call parseType without a syntax specifier, so valid anonymous struct parameter/return types can trigger the new guard and abort regeneration; this can break codegen for otherwise valid signatures — pass the correct syntax specifier through these call sites (or relax the guard for this path).
  • In internal/generator/ctypename.go, pointer const qualifiers inside parenthesized declarators are dropped, so emitted CTypeName values can diverge from the declared C type and produce incorrect type metadata/interop output — recurse through nested Declarator nodes when collecting pointer qualifiers.

Not reviewed (too large): internal/generator/testdata/ir/comments.txt (~15,920 lines), internal/generator/testdata/ir/structure.txt (~10,876 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="internal/generator/type.go">

<violation number="1" location="internal/generator/type.go:368">
P1: A valid anonymous struct in a function parameter or return type now aborts regeneration. `params` and `recordFunction` invoke `parseType` without supplying the syntax specifier, so this guard panics instead of registering or representing the type; propagate that specifier through those paths.</violation>
</file>

<file name="internal/generator/ctypename.go">

<violation number="1" location="internal/generator/ctypename.go:85">
P2: Const qualifiers on parenthesized pointer fields are silently omitted, so their `CTypeName` no longer matches the written C type. Recursing through nested `Declarator` nodes while collecting pointer qualifiers would preserve forms such as `int (*const callback)(int)` and `int (*const table)[4]`.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread internal/generator/type.go
Comment thread internal/generator/ctypename.go Outdated
ccPointerQualifiers walked only the outer declarator's Pointer chain. A
parenthesised declarator keeps its '*' on a nested Declarator, so a field
written "int (*const callback)(int)" or "int (*const table)[4]" lost its
pointer const and Field.CTypeName no longer matched the written C type.
struct.go reads that spelling to decide how a field is wrapped, so the
result was a silently wrong string rather than a loud failure.

The walk now takes the outer Pointer chain, then recurses into the nested
declarator a '(' Declarator ')' form wraps, appending what it carries
after. Both steps run left to right, which keeps the whole list in source
order, and source order reversed is the outward-in pointer level order
pointerQual indexes.

The paren wrap now trims the trailing space a pointer qualifier leaves,
because nothing follows it inside parentheses: clang spells
"int (*const)[4]", not "int (*const )[4]".

No FFmpeg header writes this form today, so all five *.gen.go files and
all three IR goldens are unchanged.
@cubic-dev-ai

cubic-dev-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

This PR is large and would use a significant portion of your monthly review quota. Comment @cubic-dev-ai review this to confirm that you want cubic to review it.

Three CI legs failed because the parse read the runner's own C library.
cc.NewConfig runs the host compiler for its predefined macros and its
include search paths, so the emitted bindings were a function of the
build machine. That broke two ways.

Both darwin legs failed on a real difference in structure.txt, confined
to the constant set. libavutil/mathematics.h wraps all 26 M_* constants
in an #ifndef, so every name the C library leaves undefined becomes a
macro of an FFmpeg header, passes the location filter in ccWalk.macros
and enters the bindings. glibc defines all 26 under _GNU_SOURCE and the
Apple SDK defines only the 13 without the f suffix, so macOS emitted 13
constants, M_Ef first, that Linux never produced.

The linux/arm64 leg failed harder, before any comparison. glibc's
aarch64 bits/math-vector.h declares its vector-math prototypes with a
construct cc/v4 cannot parse, so no header translated at all. That
header has no amd64 counterpart, which is why the dev host never met it.

Stub the eleven C library headers the FFmpeg headers include, embed them
into the binary and serve them through cc.Config.FS. Predefined becomes
a literal in ccconfig.go rather than a `cc -dM -E -` dump, and the
target pair now selects a cc.NewABI table and nothing else. The parse
reads only include/ and sysinclude/.

This was chosen over the narrower fix of filtering emitted macros by
defining file, which does not work: M_Ef is defined in
libavutil/mathematics.h, an FFmpeg header, so a file filter keeps it.
Nor would it touch the aarch64 parse failure. Both symptoms have one
cause, and removing the host headers removes both.

The stubs are faithful where FFmpeg's #ifndef fallbacks can see them.
math.h carries the 26 M_* names every libc defines plus NAN and
INFINITY, and omits M_LOG2_10, M_LOG2_10f, M_PHI and M_PHIf, which no
libc defines and which are therefore the four M_* constants the bindings
carry on every platform. Fixed-width typedef widths cannot move the IR:
parseType takes typedef sugar at every level, so size_t and int64_t
reach it as themselves.

Output does not move. *.gen.go and all three IR goldens come back
byte-identical, and the generator runs with CC pointing at a nonexistent
binary.

TestGeneratorIRIsIdenticalAcrossTargets now runs all four targets from
any host rather than only those sharing the host's operating system.
That restriction existed because a darwin target on a Linux host
preprocessed glibc under Apple's ABI, and it is exactly what hid this
defect from every Linux run.

What this does not solve: the FFmpeg headers still have to translate, so
a construct cc/v4 cannot parse in an FFmpeg header still fails. The
stubs must keep up with any new C library name a future FFmpeg release
reaches for; a missing one is a parse failure naming the header, not a
silent difference in output. And it proves nothing about whether the
generator runs on a macOS or arm64 machine, which still needs CI.
@cubic-dev-ai

cubic-dev-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

This PR is large and would use a significant portion of your monthly review quota. Comment @cubic-dev-ai review this to confirm that you want cubic to review it.

The generator step piped through grep -v "cgo-gcc-prolog\|deprecated"
to drop cgo and libclang deprecation noise, then used || true to
swallow grep's exit 1 when nothing matched. That also swallowed a
failing generator, so just build carried on and produced stale
bindings without saying so.

The pure-Go generator emits none of that noise, so the filter now
matches nothing and only the masking remains. This is the same
workaround just removed from go-test.yml, where it hid the real darwin
compile error.

Drop both. set -euo pipefail then fails the recipe as it should.

Refs: WW-142
@cubic-dev-ai

cubic-dev-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

This PR is large and would use a significant portion of your monthly review quota. Comment @cubic-dev-ai review this to confirm that you want cubic to review it.

@flexiondotorg

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai Re-review please

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai Re-review please

@flexiondotorg I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot 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.

2 issues found across 49 files

Confidence score: 4/5

  • In internal/generator/ctypename.go, C type spelling drops volatile/restrict, which can make generated IR diverge from headers and produce incorrect semantics when those qualifiers are used — preserve these supported qualifiers in the emitted type string/path.
  • In docs/API-COVERAGE.md, the Reproduce instructions describe old cc/v4 behavior (probing gcc) and can mislead debugging or verification work — update the note to reflect ccPredefined plus embedded sysinclude/ stubs in newCCConfig behavior.

Not reviewed (too large): internal/generator/testdata/ir/comments.txt (~15,920 lines), internal/generator/testdata/ir/structure.txt (~10,876 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="docs/API-COVERAGE.md">

<violation number="1" location="docs/API-COVERAGE.md:66">
P3: The rewritten Reproduce note is now inaccurate: the cc/v4 parser no longer probes gcc for predefined macros or include paths. Those are a hardcoded ccPredefined literal plus the embedded sysinclude/ stubs (newCCConfig no longer calls cc.NewConfig), and the hardcoded GCC claims 15.3, not 15.2.0, so a reader following the toolchain note would be misled about how the parse is configured.</violation>
</file>

<file name="internal/generator/ctypename.go">

<violation number="1" location="internal/generator/ctypename.go:257">
P2: Fields using `volatile` or `restrict` lose those qualifiers during C type spelling, so the generated IR diverges from the written header as soon as one of these qualifiers is present. Preserving the supported qualifier keywords through both the base and pointer-level spelling paths would keep the replacement printer faithful.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread internal/generator/ctypename.go
Comment thread docs/API-COVERAGE.md Outdated
The parse stopped probing the host compiler in 9182a2f, which missed two
spots.

docs/API-COVERAGE.md told the reader the parser probes gcc 15.2.0 for its
predefined macros and include paths. newCCConfig builds a cc.Config
literal; the macros are the ccPredefined literal and the C library headers
are the embedded sysinclude stubs. The version was wrong too, because
ccPredefined claims GCC 15.3.

The declaredHere comment justified the location filter with glibc's
__mbstate_t and atomic_wide_counter. No glibc header reaches the parse any
more, so the rule now cites a construct the hermetic configuration can
still produce.

Comments and prose only. The generated bindings and all three IR goldens
are unchanged.
@cubic-dev-ai

cubic-dev-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

This PR is large and would use a significant portion of your monthly review quota. Comment @cubic-dev-ai review this to confirm that you want cubic to review it.

@flexiondotorg

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai Review again

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai Review again

@flexiondotorg I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot 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.

2 issues found across 49 files

Confidence score: 4/5

  • In internal/generator/comments.go, comment attribution for macro-generated declarations can pick the wrong source token when replacement-list identifiers overlap invocation arguments, which could produce misleading generated comments and reduce traceability/debuggability — track macro-parameter origin (or narrow the lookup scope) before attaching comments.
  • In internal/generator/crosstarget_test.go, setup scaffolding is duplicated from TestIRGoldensMatchFreshRun, which raises maintenance risk and can let test behavior drift across paths over time — extract a shared setup(t) helper for repo-root/include-dir/AVLibPath, log handling, and chdir management.

Not reviewed (too large): internal/generator/testdata/ir/comments.txt (~15,920 lines), internal/generator/testdata/ir/structure.txt (~10,876 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="internal/generator/crosstarget_test.go">

<violation number="1" location="internal/generator/crosstarget_test.go:123">
P3: The repo-root/include-dir/AVLibPath capture, log-discard, and chdir scaffold is copied nearly verbatim from TestIRGoldensMatchFreshRun. Extracting these into a shared helper (e.g. a setup(t) that returns repoRoot and the include dir, plus a saveLog/restoreLog helper) would keep the two cross-platform tests from drifting apart when the parse setup changes.</violation>
</file>

<file name="internal/generator/comments.go">

<violation number="1" location="internal/generator/comments.go:553">
P2: Macro-generated declarations can receive comments from the wrong source token when a replacement-list identifier also appears in the invocation arguments. Tracking macro-parameter origin, or restricting this lookup to identifiers substituted from parameters, would prevent caller comments from being assigned to replacement-list declarations.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread internal/generator/comments.go
Comment thread internal/generator/crosstarget_test.go Outdated
…rder

spellingSite probed the invocation's argument text before the macro's
replacement list, so a name the macro body declares took a caller
position whenever an argument happened to spell the same identifier, and
that declaration then claimed the caller's comment.

Gate the argument probe on origin instead. A replacement-list token
spelling a macro parameter name is a substitution, so the name did come
from an argument; any other name the replacement list writes is literal
macro text and resolves there. Reordering alone cannot do this, because
the two cases pull opposite ways: AVBPrint.size needs the argument text
even though the macro body writes "size" for its own parameter, while
reserved_padding needs the replacement list. cc/v4 keeps no
pre-substitution token position, but the macro definition carries both
halves of the test, so no origin tracking is needed.

The collision is unreachable in FF_PAD_STRUCTURE, the only macro this
fires on. The sole name its body declares is reserved_padding, which no
AVBPrint field can repeat without colliding with it. The gate is for the
next such macro, and it makes the rule match the model its own comment
claims to follow.

All 14 tokens the rule places land where they did, so the five *.gen.go
files and the three IR goldens are unchanged.
TestIRGoldensMatchFreshRun and TestGeneratorIRIsIdenticalAcrossTargets
opened with the same repo-root resolve, header-absent skip, AVLibPath
repoint and log discard, copied nearly verbatim. These two tests are what
caught the host-dependence failures on darwin and linux/arm64, so they
have to move together when the parse setup changes.

setupHeaderParse now does all four steps and returns the repo root, and
testRepoRoot factors out the resolve so TestGoldensCarryNoMachinePaths
shares that piece too.

Each test keeps its own golden read and its own t.Chdir. The two read
their goldens into different forms, and only TestIRGoldensMatchFreshRun
needs the temporary directory afterwards, to read back the dumps it
writes there. Setup only; no assertion, skip condition or failure message
changed.
@cubic-dev-ai

cubic-dev-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

This PR is large and would use a significant portion of your monthly review quota. Comment @cubic-dev-ai review this to confirm that you want cubic to review it.

@flexiondotorg

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai Review again please

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai Review again please

@flexiondotorg I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot 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.

3 issues found across 49 files

Confidence score: 2/5

  • In flake.nix, Darwin environments can hit clang: command not found because the shell no longer guarantees the compiler the builder selects, which can break local/dev CI builds on macOS — keep an LLVM clang in the shell inputs or align the shell with an LLVM stdenv.
  • In internal/generator/type.go, function-type traversal aborts on valid inline anonymous struct params/returns due to missing field-scoped w.anon handling, so regeneration can fail on legitimate APIs — restore anonymous-struct specifier recovery in function walks.
  • In internal/generator/comments.go, trailing comments that start exactly at the declaration-name end are dropped, which can silently lose generated/commented metadata — treat the exclusive name end as a valid trailing-comment start.

Not reviewed (too large): internal/generator/testdata/ir/comments.txt (~15,920 lines), internal/generator/testdata/ir/structure.txt (~10,876 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="internal/generator/type.go">

<violation number="1" location="internal/generator/type.go:363">
P2: Regeneration aborts on valid functions whose return type is an inline anonymous struct (and likewise on anonymous-struct parameters), because function type walks have no field-scoped `w.anon`. Recover the specifier from the declaration/type context or record this unsupported shape as a skip rather than panicking.</violation>
</file>

<file name="flake.nix">

<violation number="1" location="flake.nix:60">
P1: Darwin builds can fail with `clang: command not found` because the shell no longer provides the compiler that the builder unconditionally selects. Keeping an LLVM clang package (or switching the shell to an LLVM stdenv) alongside libcxx would make the flake self-contained instead of relying on a host Xcode installation.</violation>
</file>

<file name="internal/generator/comments.go">

<violation number="1" location="internal/generator/comments.go:493">
P2: A plain comment immediately following a declaration name is silently dropped, unlike the same comment with one intervening space. Treat the exclusive name end as a valid trailing-comment start.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread flake.nix
Comment thread internal/generator/type.go
Comment thread internal/generator/comments.go Outdated
nameCol is one byte past the name, and a comment cannot begin inside a
name, so a comment starting at exactly that column is the name's own
trailing comment written without a separating space. The guard rejected
that column, so the abutting form was dropped while the same comment one
space further along was claimed.

Reject only a column before the exclusive end, which is the impossible
case. No FFmpeg header writes the abutting form today, so the record
count in the comment golden is unchanged at 2907 and a test holds the
rule instead.
A tagless struct written as a function's return type or parameter reaches
unnamedStructType with no specifier on the walk, and the walk aborts. The
comment there claimed a field declaration always supplies the specifier,
which a probe disproves: C allows the shape and cc/v4 accepts it.

The abort still stands. skipType returns nil, which the parser encodes as
C void, so a skipped return type would emit a Go function that silently
drops its result. A skipped parameter does reach the emitter as a loud
"unhandled arg type" skip, but the return type does not, so aborting is
the one answer that is safe in both positions.

Correct the comment and add TestUnnamedStructWithoutSpecifierAborts,
which pins the abort, the breadcrumb that names the symbol and the slot,
and the tagless union that parses instead because parseUnionType expands
it in place.
@cubic-dev-ai

cubic-dev-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

This PR is large and would use a significant portion of your monthly review quota. Comment @cubic-dev-ai review this to confirm that you want cubic to review it.

@flexiondotorg
flexiondotorg merged commit 8e5180d into main Aug 1, 2026
5 checks passed
@flexiondotorg
flexiondotorg deleted the wimpy/ww-137-port-the-binding-generator-from-libclang-to-moderncorgccv4 branch August 1, 2026 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Evaluate porting the binding generator from libclang to modernc.org/cc/v4

1 participant