Repository navigation
fix(extraction): a damaged C #if group no longer turns into functions named if - #2940
Merged
Merged
Conversation
… named `if`
tree-sitter-c parses every branch of a `#if … #elif … #else … #endif`
group as a run of block items, so a group is harmless only when each
branch is complete on its own. Three shapes in real C are not: a branch
that begins with `else` (an arm of an if-chain kept behind `#ifdef`), a
branch that ends with a bare `if (…)` header whose body follows the
`#endif` (ST's HAL latency tables), and a branch whose braces don't
balance (a function head that differs per build). Each leaves a
K&R-shaped `if (…) { … }` that the grammar reads as a function named
`if`, filed inside the enclosing function, and the functions after it
were nested under it or lost. Separately, a block macro in capitals in
statement position (`ATOMIC_BLOCK(NVIC_PRIO_MAX) {`) was skipped by the
statement-macro blank, which matched lowercase iterator macros only.
blankCUnbalancedConditionalBranches runs at the end of preParseCSource,
after the directive restore, since it edits directive lines. A group
where a branch shows one of those shapes, or is a fragment of an
expression or a list (`|| (h4 == NULL)` inside an `if (…)`), loses its
directive lines, offsets kept. When a branch can't stand next to the
others, one branch is kept verbatim: the `#else` of a positive test
(`#ifdef X`, `#if defined(X)`), the first of `#ifndef`, the live one of
`#if 0`. The statement-macro blank also takes ALL_CAPS names (PascalCase
still excluded, for C++ constructors). Both engines see it: the kernel
gets the pre-parsed bytes from the TS route point.
Ours on top:
- A group on a flag whose branches each balance (braces and parens)
keeps every branch, only its directive lines blanked: read in a row
they are still C (`} else if (a) {…} else if (b) {…}`, list entries,
`|| x || y`). Dropping one lost real code of builds the project uses:
betaflight's failsafe.c arms under `#if ENABLE_RESCUE_PLAN` (default-on
in common_post.h), and adc_stm32h7xx.c's adcTagMap, whose inner MCU
chain kept its `#else #error` branch. Unbalanced and literal groups
keep one branch, as above. The PR's first unit test now expects both
arms of its balanced else-led group.
- The group is read on a copy with comments and literals masked. A brace
inside a block comment over several lines counted as code, so Lua's
`** {======` banners made loadlib.c's platform group look damaged, and
its dlopen, Windows and dyld implementations were dropped for the stub;
on betaflight it dropped 19 CMSIS-DSP MVE functions.
- A line splice is read on the raw line: a `\` at the end of a comment
line inside a #define body still continues the #define (masking hid
it, which made jemalloc's tsd.h include guard look damaged).
- Only a directive's own `\` continuations are blanked with it. The PR
blanked every directive line right under a collapsed group's `#if` or
`#else`, so a kept branch lost a leading `#include` or `#define`.
- `#if 1` keeps its first branch; it kept the dead `#else`.
- Unit tests for each, mutation-checked; CHANGELOG bullet rewritten
without numbers.
- Tried and dropped: keeping the feature branch of a flag the file
#defines itself (the same-file evidence PR #2615 uses). Applied per
group, it broke dtoa's coordinated groups in jq (`#ifdef
Avoid_Underflow` kept its branch while a later `#ifndef
Avoid_Underflow` still kept its `}`), cutting jvp_strtod in half.
Validation, main b6379e6 vs this:
- Phantom functions (named if or (…)) removed / real functions added:
betaflight 49679ccd 257 / 40 (spiInternalStartDMA, gyroDetect,
processSmartPortTelemetry, esc4wayProcess, the CMSIS matrix
routines), stm32h7xx_hal_driver 7e541d92 68 / 6 (LL_SetFlashLatency,
HAL_ADC_AnalogWDGConfig), stm32g4xx_hal_driver a6001282 30 / 2, redis 7f440372 8 / 16 (tre_parse, je_mallocx,
lua_cmsgpack), jq 49fbca12 1 / 11 (strptime, gethex, jvp_dtoa). No
real function lost except jq's WIN32-only wmain. Enum members added:
betaflight 701, stm32h7 8.
- Calls move to the real function: on betaflight, 746 calls that sat on
a phantom or on the file node are gone, and 770 now sit on real
functions (369 on restored ones, 401 on functions that existed but
had lost them to a phantom). Lost: references from `#if`
conditions inside an initializer (betaflight TIM_N x12), test-macro
references (redis TEST x10), and a few calls in feature branches of
unbalanced groups.
- leveldb (C++) and gin (Go) byte-identical.
- kernel-parity (betaflight, stm32h7, redis, jq): 0 files with diffs on
main and here; fewer files defer to wasm (betaflight 1216 -> 1095,
redis 169 -> 150, jq 21 -> 15, stm32h7 156 -> 146).
- Cost: C preParse CPU over betaflight 5.7 -> 8.6 s (the PR as written
8.4 s), redis 1.2 -> 1.6 s.
Whole-index CPU, median of 3 interleaved: redis 41.2 vs 40.0 s,
stm32h7xx_hal_driver 21.4 vs 21.9 s (noise).
Original PR #1736 by @danusha2345.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
danusha2345
pushed a commit
to danusha2345/codegraph
that referenced
this pull request
Oct 28, 2026
…mchenry#2972; 10 landed PRs yield to main; EXTRACTION_VERSION 56) Main landed ten more of our PRs as our own commits with the maintainer's additions (colbymchenry#2923 ← colbymchenry#1936, colbymchenry#2924 ← colbymchenry#1951, colbymchenry#2925 ← colbymchenry#1939, colbymchenry#2930 ← colbymchenry#2921, colbymchenry#2940 ← colbymchenry#1736, colbymchenry#2942 ← colbymchenry#1947, colbymchenry#2950 ← colbymchenry#1952, colbymchenry#2961 ← colbymchenry#1954, colbymchenry#2965 ← colbymchenry#1942, colbymchenry#2972 ← colbymchenry#1933) and the module-path half of colbymchenry#2308 (colbymchenry#2955). Where the integration carried our version, main's wins: the enclosing-scope member pick and its tests, the Go and Java receiver typers, the delegating-call self-loop rule, the duplicated-module copy confidence, the C #if collapse, the module-path resolution of Rust paths, the trail/path hardening in utils.ts. Kept beside main: colbymchenry#1875's Verilog hooks and status fields, colbymchenry#2839's implementations command, colbymchenry#1923, colbymchenry#2922, colbymchenry#2258, colbymchenry#1761, colbymchenry#1583, the bare-call half of colbymchenry#2308 (rust-use-bindings.ts). CHANGELOG is main's plus the open PRs' bullets. EXTRACTION_VERSION 56: the C #if group handling (colbymchenry#2940) and the kernel change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
danusha2345
pushed a commit
to danusha2345/codegraph
that referenced
this pull request
Oct 28, 2026
…tests, colbymchenry#1947's cls exemption, locale-tiebreak test) Our copies of the C #if tests colbymchenry#2940 superseded are dropped (one of them was main's own test twice); the library-method rule no longer exempts a receiver named `cls`, as colbymchenry#2942 landed it; ts-this-field-call.test.ts is main's (colbymchenry#2927 landed the same code-unit tiebreak the integration carried from the earlier PR). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A C
#if … #else … #endifgroup whose branches aren't whole statements no longer turns into functions namedif, which used to swallow the rest of the function or the file. A new length-preserving pre-parse pass (blankCUnbalancedConditionalBranches, at the end ofpreParseCSource) removes such a group's directive lines. It keeps every branch when they still read as C side by side, and one branch when they don't. Block macros written in capitals in statement position, likeATOMIC_BLOCK(NVIC_PRIO_MAX) {, are now blanked like the lowercase iterator macros.Root cause
tree-sitter-c parses each branch of a conditional group as a run of block items, so a group is harmless only when each branch is complete on its own. Three shapes in real C are not:
else, an arm of an if-chain kept behind#ifdef;if (…)header whose body follows the#endif, as in ST's HAL latency tables;Each leaves a K&R-shaped
if (…) { … }that the grammar reads as a function namedif, nested in the enclosing function. The functions after it were filed under it or lost. On main, betaflight has 1,147 such phantom functions (namedifor(…)).What came from the contributor, and what we changed
From @danusha2345, kept: the pass, its three damage shapes plus expression fragments, the branch choice when one branch must go (the
#elseof a positive test, the first of#ifndef, the live one of#if 0), the ALL_CAPS statement macros, and the tests.Ours, each with a unit test that fails without it (mutation-checked):
A group on a flag whose branches each balance keeps every branch, and only its directive lines are blanked. Read in a row, such branches are still C:
} else if (a) {…} else if (b) {…}, list entries,|| x || y. Dropping one lost real code of builds the project uses:#if ENABLE_RESCUE_PLAN, which is default-on incommon_post.h;adc_stm32h7xx.c'sadcTagMap, whose inner MCU chain kept its#else #errorbranch.Unbalanced and literal groups keep one branch, as in the PR. The PR's first unit test now expects both arms of its balanced else-led group.
The group is read on a copy with comments and literals masked. A brace inside a block comment that spans several lines counted as code. Lua's
** {======banners madeloadlib.c's platform group look damaged, so its dlopen, Windows and dyld implementations were dropped for the stub. On betaflight, the same blind spot dropped 19 CMSIS-DSP MVE functions.A line splice is read on the raw line. A
\at the end of a comment line inside a#definebody still continues the#define; masking hid it, which made jemalloc'stsd.hinclude guard look damaged.Only a directive's own
\continuations are blanked with it. The PR blanked every directive line right under a collapsed group's#if/#else, so a kept branch lost a leading#includeor#define.#if 1keeps its first branch. It kept the dead#else.CHANGELOG rewritten without numbers, placed with the C++ misparse bullet.
Tried and dropped: keeping the feature branch of a flag the file
#defines itself, the same-file evidence #2615 uses. Applied group by group, it broke dtoa's coordinated groups in jq:#ifdef Avoid_Underflowkept its branch while a later#ifndef Avoid_Underflowstill kept its}, cuttingjvp_strtodin half.Interaction with #2615 (feature-macro variants). Both treat a flag nothing sets as off. #2615 decides at resolution which of several definitions a call reaches, using the file's own
#defines and the project's build files. This pass decides at parse time, per file and with no project context, which branch of a damaged, unbalanced group the parser sees. The two can disagree only there; with (1), that is the 61 dropped feature branches on betaflight, about a third of them testing flags its target configs set. A balanced group now keeps both builds.Validation
Main
b6379e6dvs this (dump-graph; nodes keyed by file, kind, name and line, edges by site):49679ccd(4k C files)spiInternalStartDMA,gyroDetect,processSmartPortTelemetry,esc4wayProcess, the CMSIS matrix routines; 244 locals that had leaked to file scope are gone7e541d92LL_SetFlashLatency,HAL_ADC_AnalogWDGConfig,HAL_RTCEx_SetWakeUpTimera60012827f440372tre_parse, jemalloc'sje_mallocx, lua_cmsgpack; Lua'sluaV_executeno longer "calls" a phantomtre_match::if49fbca12strptime,gethex,jvp_dtoa,jv_number;mainkept, the WIN32-onlywmaindroppedCalls move to the real function. On betaflight, 746 calls that sat on a phantom or the file node are gone, and 770 now sit on real functions: 369 on restored ones and 401 on functions that existed but had lost them to a phantom.
Losses:
#ifconditions inside an initializer, such as betaflight's#if USED_TIMERS & TIM_N(1)lines (12);TEST("…") {(10);arm_mat_inverse_f32. That function had no node at all on main.The PR as written lost 19 real functions on betaflight, the AT32 CMSIS-DSP MVE variants, through the multi-line-comment blind spot; Lua's loadlib platform implementations on redis; betaflight's rescue-plan arms in
failsafeUpdateState(8 calls); andadcTagMap. With (1)–(4), none are lost:failsafeUpdateStatecalls on lines 495–540#if ENABLE_RESCUE_PLANarms lost)adcTagMapconstant (adc_stm32h7xx.c)adcInitandadcFindTagMapEntry#else #error MCU not defined, dropping every entryGroups that must still keep one branch do, each pinned by a unit test:
#if 0 … #else … #endifkeeps the live branch;#if 1keeps its first arm;#ifdef WIN32 int umain(…) { #else int main(…) { #endifkeepsmain;#include.In real code, jq keeps
mainand drops the WIN32 heads, and CMSIS'sarm_mat_inverse_f32keeps its generic branch.Kernel parity (
kernel-parity.mjs --lang c) on betaflight, the STM32H7 HAL, redis and jq: 0 files with diffs, on main and here. The kernel gets the pre-parsed bytes. Fewer files defer to wasm because more parse clean: betaflight 1,216 → 1,095, redis 169 → 150, jq 21 → 15, STM32H7 156 → 146. The STM32H7 HAL trips the >50% deferral backstop on both arms.Cost. C preParse CPU over all of betaflight is 5.7 → 8.6 s (the PR as written: 8.4 s), and redis 1.2 → 1.6 s. Whole-index CPU, median of 3 interleaved: redis 41.2 vs 40.0 s, STM32H7 HAL 21.4 vs 21.9 s, which is noise.
Red→green. The PR's end-to-end test (a phantom
(NVIC_PRIO_MAX)andifon main) fails on main and passes here. Each of our changes has a unit test that fails without it.Full suite: 764 files passed, 6 skipped; 9,092 tests passed, 85 skipped, 0 failed, on
083b9cb8(the same patch one rebase earlier: patch-id 4825eb99 on both; the rebase ontoc3069670brought only fix(cfnptr): a C function-pointer table links only to C and C++ functions #2935, the C function-pointer filter, and a CHANGELOG line elsewhere).Lands #1736 by @danusha2345.
🤖 Generated with Claude Code