From 7c758aaf0ad877155d6b5e12e8caaa1e1741694d Mon Sep 17 00:00:00 2001 From: danusha2345 Date: Mon, 7 Sep 2026 09:53:37 +0300 Subject: [PATCH] fix(resolution): C and C++ nesting is never a scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isLexicallyReachable trusted the graph's nesting for every language. C and C++ have no nested named functions, so a function shown inside another is an extraction artifact: tree-sitter-c cannot parse a macro call whose arguments are designated initializers — betaflight's RESET_CONFIG(pidProfile_t, pidProfile, .pid = { … }, …); — and its error recovery runs the enclosing function_definition (source lines 168–309) to line 1667, nesting the 45 functions after it. That tree has 310 such functions in 73 files. Before this commit exact-match already rejected them as unreachable and the fuzzy fallback picked them up at 0.5; with the survivor-side check alone, fuzzy rejected them too and 117 real calls into pid.c disappeared (base → 4c8f165 on the 2,109-file betaflight fork: LOST 117, GAINED 0, all fuzzy, all pid.c). With the gate the same tree is LOST 117 fuzzy / GAINED 117 exact-match — the identical edges, now resolved by the strategy that should have had them, at 0.9. vite (no C) is unchanged. Co-Authored-By: Claude Fable 5.1 --- __tests__/fuzzy-lexical-reach.test.ts | 9 +++++++++ src/resolution/name-matcher.ts | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/__tests__/fuzzy-lexical-reach.test.ts b/__tests__/fuzzy-lexical-reach.test.ts index 964c348..b0ff1c4 100644 --- a/__tests__/fuzzy-lexical-reach.test.ts +++ b/__tests__/fuzzy-lexical-reach.test.ts @@ -138,6 +138,15 @@ describe('fuzzy reachability rejects a unique guess but never manufactures one', expect(matchFuzzy(callFrom('vite.config.js', 3), contextWith([closure, method]))).toBeNull(); }); + it('trusts no nesting in C, where a nested function is an extraction artifact', () => { + // betaflight: tree-sitter-c's recovery from `RESET_CONFIG(…, .pid = {…})` + // runs resetPidProfile to the end of pid.c, so every function after it is + // "nested" in the graph. C has no nested named functions; the call reaches it. + const cClosure = node({ ...closure, id: 'f:c', language: 'c' as Node['language'], filePath: 'pid.c' }); + const cRef = { ...callFrom('core.c', 3), language: 'c' as UnresolvedRef['language'] }; + expect(matchFuzzy(cRef, contextWith([cClosure]))?.targetNodeId).toBe('f:c'); + }); + it('resolves a lone reachable method as before', () => { expect(matchFuzzy(callFrom('vite.config.js', 3), contextWith([method]))?.targetNodeId).toBe('m:resolve'); }); diff --git a/src/resolution/name-matcher.ts b/src/resolution/name-matcher.ts index 82e567a..943b2d7 100644 --- a/src/resolution/name-matcher.ts +++ b/src/resolution/name-matcher.ts @@ -354,6 +354,9 @@ export function matchFunctionRef( return null; } +/** Languages with no nested named functions: nesting in the graph is never a scope. */ +const NO_NESTED_FUNCTIONS = new Set(['c', 'cpp']); + /** * A function nested inside another FUNCTION is only callable from within its * container — Python, JS/TS, and every closure language scope it lexically. @@ -371,6 +374,14 @@ function isLexicallyReachable( context: ResolutionContext ): boolean { if (candidate.kind !== 'function') return true; + // C and C++ have no nested named functions, so a function the graph shows + // inside another is an extraction artifact, not a scope: tree-sitter-c + // cannot parse a macro call whose arguments are designated initializers + // (betaflight's `RESET_CONFIG(pidProfile_t, pidProfile, .pid = {…})`), and + // its error recovery runs the enclosing function_definition to the end of + // the file, nesting every function after it. Trusting that nesting rejected + // 117 real calls into pid.c on that tree; the functions are reachable. + if (NO_NESTED_FUNCTIONS.has(candidate.language)) return true; const qn = candidate.qualifiedName; if (!qn || !qn.includes('::')) return true; const parentQn = qn.slice(0, qn.lastIndexOf('::'));