Просмотр исходного кода

fix(resolution): C and C++ nesting is never a scope

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 <noreply@anthropic.com>
danusha2345 1 день назад
Родитель
Сommit
7c758aaf0a
2 измененных файлов с 20 добавлено и 0 удалено
  1. 9 0
      __tests__/fuzzy-lexical-reach.test.ts
  2. 11 0
      src/resolution/name-matcher.ts

+ 9 - 0
__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();
     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', () => {
   it('resolves a lone reachable method as before', () => {
     expect(matchFuzzy(callFrom('vite.config.js', 3), contextWith([method]))?.targetNodeId).toBe('m:resolve');
     expect(matchFuzzy(callFrom('vite.config.js', 3), contextWith([method]))?.targetNodeId).toBe('m:resolve');
   });
   });

+ 11 - 0
src/resolution/name-matcher.ts

@@ -354,6 +354,9 @@ export function matchFunctionRef(
   return null;
   return null;
 }
 }
 
 
+/** Languages with no nested named functions: nesting in the graph is never a scope. */
+const NO_NESTED_FUNCTIONS = new Set<string>(['c', 'cpp']);
+
 /**
 /**
  * A function nested inside another FUNCTION is only callable from within its
  * A function nested inside another FUNCTION is only callable from within its
  * container — Python, JS/TS, and every closure language scope it lexically.
  * container — Python, JS/TS, and every closure language scope it lexically.
@@ -371,6 +374,14 @@ function isLexicallyReachable(
   context: ResolutionContext
   context: ResolutionContext
 ): boolean {
 ): boolean {
   if (candidate.kind !== 'function') return true;
   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;
   const qn = candidate.qualifiedName;
   if (!qn || !qn.includes('::')) return true;
   if (!qn || !qn.includes('::')) return true;
   const parentQn = qn.slice(0, qn.lastIndexOf('::'));
   const parentQn = qn.slice(0, qn.lastIndexOf('::'));