fix(resolution): a definition its language makes file-local is not a cross-file target - #1732
fix(resolution): a definition its language makes file-local is not a cross-file target#1732danusha2345 wants to merge 2 commits into
Conversation
…cross-file target
Name matching accepted any same-named definition as the target of a call
from another file, however the language scopes it. isVisibleAcrossFiles
now declines, for a candidate in another file:
C / C++ a function whose definition line carries `static` (read from
source — the extractor records no storage class, and the kernel
arm would need the same field)
Kotlin, Java, C#, Swift, Scala, Dart, PHP
visibility === 'private'
Go a lowercase identifier from another directory (by the name's
case: the extractor's isExported is unset for every Go method)
Rust a non-`pub` item unless the reference is in the item's module
subtree (a child sees its ancestors' private items via super::);
a method in an `impl Trait for Type` block has the trait's
visibility and is exempt
The test runs in ReferenceResolver on the target the whole name-matching
pipeline settled on, so a rejection ends the reference unresolved. Declining
inside matchByExactName instead let the ref fall through to matchFuzzy,
which committed to a same-language namesake the ranking had passed over —
eight edges on one tree, all onto a local `const fail = …` arrow the graph
does not hold. matchFuzzy checks its own survivor too; nothing runs after it.
Five corpora, all against b9ca4b7, wasm arm, edge rows keyed with
resolvedBy:
betaflight fork (2,109 C files) LOST 4,451 GAINED 0 (colbymchenry#1730)
Android/Go/JS app (114 kt, 42 go) LOST 142 GAINED 0 (colbymchenry#1731)
emmc-reader-gui (71 rs) LOST 195 GAINED 0
skylab_hub (35 rs) LOST 92 GAINED 0
vitejs/vite (JS/TS only) LOST 0 GAINED 0
Samples read back: `Vec::new()` onto a private `fn new` in another crate,
`ui.add(…)` (egui) onto a private `add`, `latch.await()` onto a test file's
`private fun await`, `leaflet.js` onto an unexported Go `func add`,
`usbd_get_descriptor` onto a `static get_device_descriptor` in a USB class
file it never links.
Fixes colbymchenry#1730. Fixes colbymchenry#1731.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…udes it The C rule declined any `static` function defined in another file. A `static` in a SOURCE file is local to that translation unit and the rule is right there; a `static` in a header — `static inline`, the whole of MAVLink's generated `mavlink_msg_*.h` — is textually included into every unit that names it, and the call is real. On the betaflight tree 4,306 of the 4,451 rows the first cut removed were exactly that: `testsuite.h` and `mavlink_msg_*.h` calling `protocol.h`'s `_mav_put_char_array`, `mav_array_assign_char` and each other's `_pack` / `_decode` helpers. The rule now applies only to a candidate whose file is a translation unit (`.c .cc .cpp .cxx .c++ .m .mm`). Same tree: LOST 145, GAINED 0, every one onto a `static` in another `.c` — STM32 USB class sources onto GD32's `usbd_enum.c`, and the USB descriptor table shape from colbymchenry#1730. Header targets in the removed set: 0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Correction, pushed as Of the 4,451 rows it removed, 4,306 targeted a The rule now applies to candidates in a source file ( I'll correct #1730's count there too; the 10% figure in it was counting the header calls. |
colbymchenry
left a comment
There was a problem hiding this comment.
Linux verification (Forge) — approve the approach
Reviewed and verified on Linux (Node 22.19, CODEGRAPH_KERNEL=0). The placement is correct: isVisibleAcrossFiles runs in ReferenceResolver after matchReference settles (and again inside matchFuzzy), so a rejection leaves the ref unresolved instead of fuzzy-falling-through — the pitfall called out in #1731.
Repro on main (df435d5) — all three #1731 shapes wrongly resolve:
- Kotlin:
onCreate→Budget.kt:apply(private) - Go:
Run→cmd/probe/main.go:fail(unexported, other package) - Rust:
run→src/util.rs:count(sibling private)
On this PR's logic (rebased onto latest main) — same fixtures decline; __tests__/cross-file-visibility.test.ts 11/11 + resolution.test.ts 189/189 pass. Rust child→ancestor + trait-impl exemptions look right; Go judged by identifier case as required while #1734 is still open.
This branch was two commits behind main (#1742 AGENTS.md, #1743 generators). To land without conflict I rebased your commits and opened #1745 (changelog cite for #1731 + C source-vs-header clarification only). Happy to close this PR in favor of #1745, or take a force-push rebase here instead — your call.
…cross-file target (#1731) (#1745) * fix(resolution): a definition its language makes file-local is not a cross-file target Name matching accepted any same-named definition as the target of a call from another file, however the language scopes it. isVisibleAcrossFiles now declines, for a candidate in another file: C / C++ a function whose definition line carries `static` (read from source — the extractor records no storage class, and the kernel arm would need the same field) Kotlin, Java, C#, Swift, Scala, Dart, PHP visibility === 'private' Go a lowercase identifier from another directory (by the name's case: the extractor's isExported is unset for every Go method) Rust a non-`pub` item unless the reference is in the item's module subtree (a child sees its ancestors' private items via super::); a method in an `impl Trait for Type` block has the trait's visibility and is exempt The test runs in ReferenceResolver on the target the whole name-matching pipeline settled on, so a rejection ends the reference unresolved. Declining inside matchByExactName instead let the ref fall through to matchFuzzy, which committed to a same-language namesake the ranking had passed over — eight edges on one tree, all onto a local `const fail = …` arrow the graph does not hold. matchFuzzy checks its own survivor too; nothing runs after it. Five corpora, all against b9ca4b7, wasm arm, edge rows keyed with resolvedBy: betaflight fork (2,109 C files) LOST 4,451 GAINED 0 (#1730) Android/Go/JS app (114 kt, 42 go) LOST 142 GAINED 0 (#1731) emmc-reader-gui (71 rs) LOST 195 GAINED 0 skylab_hub (35 rs) LOST 92 GAINED 0 vitejs/vite (JS/TS only) LOST 0 GAINED 0 Samples read back: `Vec::new()` onto a private `fn new` in another crate, `ui.add(…)` (egui) onto a private `add`, `latch.await()` onto a test file's `private fun await`, `leaflet.js` onto an unexported Go `func add`, `usbd_get_descriptor` onto a `static get_device_descriptor` in a USB class file it never links. Fixes #1730. Fixes #1731. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(resolution): a static in a header is part of every unit that includes it The C rule declined any `static` function defined in another file. A `static` in a SOURCE file is local to that translation unit and the rule is right there; a `static` in a header — `static inline`, the whole of MAVLink's generated `mavlink_msg_*.h` — is textually included into every unit that names it, and the call is real. On the betaflight tree 4,306 of the 4,451 rows the first cut removed were exactly that: `testsuite.h` and `mavlink_msg_*.h` calling `protocol.h`'s `_mav_put_char_array`, `mav_array_assign_char` and each other's `_pack` / `_decode` helpers. The rule now applies only to a candidate whose file is a translation unit (`.c .cc .cpp .cxx .c++ .m .mm`). Same tree: LOST 145, GAINED 0, every one onto a `static` in another `.c` — STM32 USB class sources onto GD32's `usbd_enum.c`, and the USB descriptor table shape from #1730. Header targets in the removed set: 0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(changelog): cite #1731 and narrow the C file-local note Rebased #1732 onto latest main. Clarify that only a static in another source file is declined (header static inline stays), name the Kotlin/Go/Rust shapes from #1731, and note the post-pipeline placement that avoids fuzzy fallback. --------- Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
Fixes #1730. Fixes #1731. Standalone off
main(b9ca4b7), two commits — the second narrows the C rule to translation units after the first cut removed 4,306 real calls onto headerstatic inlines; details in the comment below.What
Name matching accepted any same-named definition as the target of a reference from another file, however the language scopes that definition. One test,
isVisibleAcrossFiles, now declines a candidate in another file when:.c .cc .cpp .cxx .m .mm) and its definition line carriesstatic— a header'sstatic inlineis textually included into every unit that names it and stays visiblevisibility === 'private'isExportedis unset for every Go method, exported or notpubitem and the reference is outside the item's module subtreesrc/net.rs/src/net/mod.rsownsrc/net/; a child reaches its ancestors' private items viasuper::. A method in animpl Trait for Typeblock has the trait's visibility and is exempt (read from the enclosingimplheader)Same-file candidates are always visible.
Where the test runs, and why there
In
ReferenceResolver, on the target the whole name-matching pipeline settled on (exact-match,fuzzy,instance-method,qualified-nameall come through it), so a rejection ends the reference unresolved. The first cut declined insidematchByExactName, #1720-style, and that let the ref fall through tomatchFuzzy, which committed to a same-language namesake the ranking had passed over: eight new fuzzy edges on one tree, every one onto a JavaScriptfailin a different file, the realfailbeing a localconst fail = (msg) => …the graph does not hold (#1669's shape).matchFuzzychecks its own survivor as well, since nothing runs after it.Measurement
Five corpora, each indexed at
b9ca4b7and at this branch (wasm arm), edge rows joined back to symbols and keyed withresolvedBy:.c.kt, 42.go, 96.js.rs.rs, 4.ktRead back from source, the removals are the shapes in the two issues: STM32 USB class sources →
statichandlers in GD32'susbd_enum.c,usbd_get_descriptor→ astatic get_device_descriptorin a USB class file it never links;editor.apply()→ an unrelated class'sprivate fun apply;latch.await()in tests → a test file'sprivate fun await(×8);leaflet.js/validate.mjs→ unexported Goadd/join(55 cross-language rows);Vec::new()→ a privatefn newin another crate (×7);ui.add(…)(egui) → a privateadd;f.finish()→HashingWriter::finishin another crate. The Rust breakdown after the trait exemption: 171instance-method, 8exact-matchcalls, 16references— none onto a trait-impl method.Not touched: the Go extractor's
isExportedon methods (a separate extractor defect noted in #1731), the C storage class as a node field, and the local-closure gap behind the JSfail(#1669).Tests
__tests__/cross-file-visibility.test.ts, whole pipeline over source fixtures, each invisible shape paired with the visible one of identical form: Cstaticin another source file vs non-static vs a header'sstatic inline(kept) vs same-filestaticwith the keyword on its own line; Kotlinprivate fun applyreached through an SDK-styleeditor.apply()vs a publicfun applyon a typed receiver; Go unexported func across packages vs within the package and an exported func elsewhere; Rust a sibling module's private fn vs a parent's private fn from a child, a trait-impl method through a typed receiver, and apub fnanywhere. 11 cases.resolution.test.ts,frameworks-integration.test.ts,pr19-improvements.test.tsunchanged: 200 acrosscross-file-visibility+resolution.test.ts,tscclean.🤖 Generated with Claude Code