Skip to content

fix: bind Rust module-qualified calls inside the named module via its re-exports - #1034

Merged
vitali87 merged 3 commits into
mainfrom
fix/rust-module-qualified-call-resolution
Aug 2, 2026
Merged

vitali87 merged 3 commits into
mainfrom
fix/rust-module-qualified-call-resolution

Conversation

@vitali87

@vitali87 vitali87 commented Aug 2, 2026 •

Copy link
Copy Markdown
Owner

Closes #1009.

What

A Rust module::item call whose import probes found nothing fell through to the simple-name trie, which binds an arbitrary same-named function anywhere in the project. ripgrep's main calls flags::parse(), where flags/mod.rs re-exports pub(crate) use crate::flags::parse::{ParseResult, parse};: the re-export sits in the flags module's import mapping (correct since #1007), but resolution never consulted it and emitted main.main CALLS flags.config.parse, the unrelated config-file parser. That one fabricated edge both marks config.parse falsely live and severs main from the whole flag pipeline, keeping flags.parse.parse, Parser.parse, HiArgs.from_low_args and transitively the ~926 Flag impl methods dead in the dead-code report even with OVERRIDES present.

The fix adds _try_resolve_rust_module_qualified, consulted after the import probes and before every name-based fallback. The first segment binds where Rust binds it: the caller's inner scopes first (an inline mod's own child module or use shadows the file's), then the file module's use (at most one binding per scope is legal, E0255/E0428), then the file's own submodule tree through the same relative-path machinery its use declarations resolve with, so entry files attach submodules beside themselves via the declaring scan and other files nest them. crate::/self::/super:: prefixed call paths resolve through the #1007 rewrite machinery too, so the prefixed spellings of the same call bind identically. A use that binds the segment to a path outside the indexed project (std, or a workspace crate by name) stops the probe undecided rather than letting the module tree claim a shadowed name.

Once a module is decided, the item resolves directly or through the module's own re-exports: named pub use hops chain as before, Rust 2018 uniform paths (pub use pool::connect;) resolve relative to the owning module, and glob re-exports (pub use m::*;) expand when their base is indexable. An item the module verifiably does not hold is a drop, never a trie guess; a glob whose base cannot be indexed keeps the probe undecided so no edge is dropped on incomplete knowledge. Registered types (Ping::new()) are untouched.

Adversarial review

The single pre-push adversarial round found five defects in the first cut, four fixed here with RED tests: glob re-exports and uniform-path re-exports became decided drops (both regressions), inline-mod callers bound the file's same-named module, and the crate::-prefixed spelling of the issue's own failure mode was untouched. The fifth (a use std::fmt; shadowing a first-party sibling fmt module still yields the trie's fabricated edge downstream) is narrowed but not eliminated: the probe now correctly stops undecided instead of authoritatively binding the sibling, and a decided drop there would need workspace crate-name resolution first, since cross-crate calls (use grep_searcher::sinks; sinks::UTF8(...)) are served only by the trie today. Follow-up: #1033.

TDD

RED first: test_rust_module_qualified_calls.py holds the issue's exact repro (the re-export shape bound config.parse, observed failing), a direct submodule call with a same-named decoy (bound decoy.go, observed failing), the deep flags::defs::build() path, the Type::assoc() pinning case, and six review-round repros (crate-glob, uniform-path, and self-glob re-exports, inline-mod shadowing, crate:: and self:: prefixed spellings), each observed failing before its fix. Full suite: 6707 passed, 10 skipped, against a main baseline of 6697 with the same skips.

Summary by CodeRabbit

  • Bug Fixes

    • Improved resolution of Rust module-qualified calls, including nested modules, re-exports, glob imports, relative paths, and crate::/self:: prefixes.
    • Prevented calls to missing module items from incorrectly binding to unrelated functions with the same name.
    • Preserved correct handling of external paths and associated functions.
  • Tests

    • Added comprehensive coverage for module paths, re-exports, inline modules, and associated function calls.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f0e6bd84-c302-4d52-8799-8c185b8445f2

📥 Commits

Reviewing files that changed from the base of the PR and between b1324ad and eb9e791.

📒 Files selected for processing (2)
  • codebase_rag/parsers/call_resolver.py
  • codebase_rag/tests/test_rust_module_qualified_calls.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • codebase_rag/tests/test_rust_module_qualified_calls.py
  • codebase_rag/parsers/call_resolver.py

📝 Walkthrough

Walkthrough

Rust qualified-call resolution now follows module paths, scopes, imports, relative prefixes, and recursive re-exports before trie fallback. Tests cover direct modules, re-exports, inline modules, crate:: and self:: paths, decoy functions, and associated functions.

Changes

Rust qualified call resolution

Layer / File(s) Summary
Qualified call dispatch
codebase_rag/parsers/call_resolver.py, codebase_rag/tests/test_rust_module_qualified_calls.py
Module-qualified calls resolve before unrelated trie matches. Associated-type calls retain type binding. Tests cover direct, deep, inline, shadowed, and associated-function calls.
Scoped module path resolution
codebase_rag/parsers/call_resolver.py, codebase_rag/tests/test_rust_module_qualified_calls.py
Resolution checks enclosing scopes, imports, relative paths, and crate::, self::, and super:: prefixes.
Recursive re-export traversal
codebase_rag/parsers/call_resolver.py, codebase_rag/tests/test_rust_module_qualified_calls.py
Named and glob re-exports support recursive traversal, cycle prevention, and unresolved-glob fallback. Tests cover mod.rs, uniform paths, and glob imports.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

  • vitali87/code-graph-rag issue 1009: Covers Rust module-qualified resolution through mod.rs re-exports and prevents unrelated same-name fallback.

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant CallResolver
  participant ModulePathResolver
  participant ReexportResolver
  participant TrieFallback
  CallResolver->>ModulePathResolver: resolve qualified module path
  ModulePathResolver->>ReexportResolver: follow imports and re-exports
  ReexportResolver-->>ModulePathResolver: project-qualified target or unresolved status
  ModulePathResolver-->>CallResolver: matched item or decisive module miss
  CallResolver->>TrieFallback: fallback for external or unresolved paths
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main Rust module-qualified call resolution fix and uses a concise Conventional Commits format.
Description check ✅ Passed The description explains the bug, implementation, related issue, adversarial review, regression tests, and full-suite results, but omits several template sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/rust-module-qualified-call-resolution

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 2, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

The change adds Rust module-qualified call resolution, including direct module items and named or glob re-exports. A compiled Rust fixture confirmed that calls within an inline module can be linked to an enclosing same-named module instead of the inline module’s re-exported functions.

Confidence Score: 4/5

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced proof for a posted P1 finding and linked it to the related review comment.
  • T-Rex produced a second proof for a posted P1 finding and linked it to its corresponding review comment.
  • T-Rex ran cargo check with exit=0 on the fixture’s inner api module and its re-exports to verify compilation.
  • The validation shows that the recorded edges target inline_reexport_shadowing.src.main.api.named and .api.globbed, while the expected main.inner.api.* edges are missing.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Inline Rust module re-exports lose to an enclosing same-named module

    • Bug
      • Within an inline inner module, calls to api::named() and api::globbed() were resolved to the enclosing main.api functions. The inner api module exposes these items through, respectively, a named pub use and a glob pub use, so Rust name shadowing requires both calls to bind inside inner.api.
    • Cause
      • The qualified Rust module-resolution path did not resolve the inline child module’s re-export namespace before accepting the enclosing same-named module target.
    • Fix
      • Adjust Rust qualified-call resolution to traverse the inline enclosing scope’s child-module namespace and follow its named/glob re-export mappings before falling back to outer scopes.

    T-Rex Ran code and verified through T-Rex

Prompt To Fix All With AI
### Issue 1
codebase_rag/parsers/call_resolver.py:1389-1397
**Inline re-exports lose shadowing**

When an inline module's child exposes the called item through a named or glob re-export, this loop skips that child namespace and continues to outer scopes. An enclosing same-named module can therefore receive the call edge instead of the inline module's re-exported item. The executed Rust fixture shows both `api::named()` and `api::globbed()` inside `inner` resolving to `main.api.*`, although Rust binds them to `main.inner.api.*`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix: bind Rust qualified calls through c..." | Re-trigger Greptile

Comment thread codebase_rag/parsers/call_resolver.py Resolved
@vitali87

vitali87 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Bot reports addressed: Greptile's P1 (inline child-module re-exports losing to an enclosing same-named module) is fixed in eb9e791 with a RED test reproducing its executed fixture. SonarCloud's one new issue (S3776 cognitive complexity 26 on the re-export follow) is resolved in the same commit by extracting the glob expansion into its own function. CodeRabbit had no actionable comments.

@sonarqubecloud

sonarqubecloud Bot commented Aug 2, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

fix: Rust module-qualified calls ignore mod.rs re-exports and bind the wrong same-named function

1 participant