fix(code-index): resolve calls through block-scoped use - #2272
Conversation
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1274b537c5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let path = format!("{}::{imported}", import.module_specifier); | ||
| let leaves_file = Self::block_use_path_leaves_file(state, function, &path); | ||
| paths.insert(local, leaves_file.then_some(path)); |
There was a problem hiding this comment.
Retain the target of same-file aliased imports
When a block import aliases an item defined in the same file, such as use self::inner::x as y, then_some(path) stores None and the call remains named y. The same-file resolver therefore cannot select inner::x; worse, if another same-file function is actually named y, it emits a false call edge to that function even though the block import shadows it. Preserve enough path information to resolve aliases rather than treating every same-file import as safely bindable by its local name.
Useful? React with 👍 / 👎.
| if !file_relative { | ||
| return true; | ||
| } | ||
| let Some(module) = segments.next() else { |
There was a problem hiding this comment.
Traverse nested modules before classifying imports as same-file
For a valid path such as crate::outer::disk::g where outer is an inline module but disk is declared as mod disk;, this examines only the first segment (outer) and concludes that the target remains in the current file. The call is consequently left bare and cannot resolve to outer/disk.rs::g. Classification needs to walk the complete module path, since a later segment can cross a file boundary.
Useful? React with 👍 / 👎.
| { | ||
| let path = format!("{}::{imported}", import.module_specifier); | ||
| let leaves_file = Self::block_use_path_leaves_file(state, function, &path); | ||
| paths.insert(local, leaves_file.then_some(path)); |
There was a problem hiding this comment.
Do not overwrite cfg-selected block imports
Valid platform-selected imports can bind the same local name in one block, for example #[cfg(unix)] use crate::unix::g; followed by #[cfg(windows)] use crate::windows::g;. This unconditional map insertion discards the first binding without inspecting either cfg, so every call is attributed to whichever declaration appears last, even when that declaration is inactive for the indexed platform. Retain all conditional alternatives or abstain instead of producing a deterministic false caller edge.
Useful? React with 👍 / 👎.
Fixes #2253
Cause
Rust import rows are file-scoped. The extractor records bindings only for a
usewhose parent is the source file. Ausedeclared inside a function body or a nested block therefore produced no binding, so a call through it, such ascontext::with_current(..)afteruse crate::runtime::{context, task};, never bound. When the name also had a module-scope import, the call bound to that import instead, which the blockuseshadows. Nothing marked the site as unresolved, socallersstayedcomplete.Change
In
tracedecay-code-extraction, the Rust extractor collects, for each block in a function, the names itsusedeclarations bind. These are the sameextract_use_bindingsrows a module-scopeuseproduces, canonicalized the same way. Each call reference from that function whose site lies inside the block, and whose head segment the innermost such block binds, is rewritten to the declared path. The existing qualified resolver then bindscrate::runtime::context::with_currentexactly like a module-scope import, through re-exports.useshadows a module import.self::…and a crate root'scrate::…unless they go through a rootmod name;file module, andsuper::…from inside an inline module. The cross-file resolver never binds into the referencing file.v14becomesv15, so retained generations re-extract.use:extract.rsRUST_SOURCE, and the partitioned codec fixturesrc/alpha.rs,beta.rs,unresolved.rs. Segment sizes are unchanged except one deflate byte (1262 to 1263).Still unmodeled: a glob
useinside a block, and auseinside an inlinemod. They are filed as #2270 with a reproduction. A typed omission for them needs a per-site gap channel from extraction to caller coverage that does not exist today.Fail before / pass after
tracedecay-code-indexcode_index_suite::import_evidence::rust_calls_bind_through_a_use_declared_in_the_calling_block. The literal fixture includespub fn f() { use crate::m::g; g(); }beside a module-scopeuse crate::n::g;, plus the tokio shapes (use crate::runtime::{context, task};at the top of a function, anduse crate::runtime::context;inside a#[cfg(feature = "rt")] { .. }block). On master:On the branch it passes. It asserts:
m::g←[f]n::g←[module_scope], which is the shadowing checkwith_current←[spawn_inner, timer]task::schedule←[spawn_inner]inner::x←[same_file], for a blockuse self::inner::xJourney (debug CLI from this branch, fresh isolated profile, one daemon under
systemd-run --user --scope -p MemoryMax=6G -p MemorySwapMax=1G)tokio
tokio-1.53.1(75fef53),tracedecay tool tracedecay_callersontokio/src/runtime/context/current.rs::with_current, depth 1.Before (pre-change binary, same profile shape):
After:
rg 'context::with_current\(' tokio/srcfinds the same 7 call sites.Suites
Counts after the rebase onto
beefa817c9:tracedecay-code-extraction: lib 39,extract_alloc7,main597tracedecay-code-index: lib 258,code_index_suite170,resident_accounting1 (+2, +3 in the other targets)tracedecay-code-index-runtimelib: 516 (2 ignored), run with--test-threads=4. Two unthrottled runs under host load average 50–65 had deadline-only failures (Elapsed, "seat never arrived"). Thereconcilemodule alone passed 148/148.mcp_suite(test-transport,callers|callees): 19cargo clippy -p tracedecay-code-extraction -p tracedecay-code-index -p tracedecay-code-index-runtime --all-targets -- -D warnings: clean. Notracedecaycrate sources changed, sotest-transportdoes not change what is linted here.cargo fmt --all -- --check: clean. No contract or schema shape changed.