Skip to content

fix(rust): a call through a type a path names reaches that type's own function, not a namesake's - #3203

Open
bob-codedaptive wants to merge 1 commit into
colbymchenry:mainfrom
codedaptive:pr/rust-path-call-edge-cases
Open

bob-codedaptive wants to merge 1 commit into
colbymchenry:mainfrom
codedaptive:pr/rust-path-call-edge-cases

Conversation

@bob-codedaptive

Copy link
Copy Markdown

#3025 made Type::member calls go through the module walk, but the member lookup (rustTypeMember) still matched a type's function by its qualified name and crate alone, and a few heads the walk read wrongly. The result was a wrong link, which is worse than none, in six shapes:

  • A namesake type in another inline module of the same file: crate::a::Cache::open() took b::Cache::open when a::Cache has only a trait's provided open.
  • A namesake type a function declares in its own body (fn f() { struct Cache; impl Cache { fn open() … } }) took the call to the module-level Cache::open.
  • An impl for a reference to the type (impl Open for &Cache { fn open() … }) is the reference's, not Cache's, and was linked as Cache::open.
  • A use in the function's body that binds the path's head (use crate::b as a; a::Cache::open()) was ignored, so the call went to the module a beside it.
  • A mod a { … } the function declares in its body was ignored the same way.
  • A function that declares a mod a of its own before the module's pub mod a made rustSubmodule take the function's a for the module's, because it took the first mod a in source order.

What changes:

  • rustTypeMember takes a struct, enum or union's function, const or variant only from an impl whose header names that type, walked from the module the impl is written in (rustImplSelfItems). An impl in another file than its type, by path or through a use, still counts. A trait's own declaration is still the trait body's. rustImplSelfType is the header reader that rustImplSelfArgs now shares.
  • rustSubmodule ignores a mod written in a function's body or another block (RustModBlock.scope, found with the existing rustUseScopes); it is no item of the module around it.
  • rustPathTarget reads a head that a use in the body binds as the path that use names, and returns nothing for a head a mod in the body declares. This applies to module paths (a::make()) as well as type paths.

Tests: npx vitest run __tests__/rust-path-call-edge-cases.test.ts (6 cases). On main 6 of 6 fail; with the change all pass. The 36 other rust-* suites (247 tests with the new one) and resolution, qualified-name-boundary, qualified-lookup-boundary, self-call-delegation, reference-target-kind, cross-file-visibility, function-ref, value-reference-edges and tauri-ipc pass; npx tsc --noEmit -p . is clean.

Verified against upstream main 47a9c7d.

Not measured: the cost of rustImplSelfItems per candidate on a large Rust repository. It runs once per (type, member) pair, after the name and crate filters, and reads the scopes of the candidate's file (cached).

Testing

  • Platform: tested on macOS only; not run on Linux or Windows.

🤖 Generated with Claude Code

… function, not a namesake's

A call written through a type's path, `crate::a::Cache::open()`, took the
function of any type of that name in the crate: rustTypeMember matched by
qualified name and crate alone. A namesake type in another inline module of
the same file, one a function declares in its own body, and an `impl` for a
reference to the type (`impl Open for &Cache`, the reference's, not the
type's) could each be linked instead of the type the path walks to. The
walk itself also took a head wrongly: a `use` in a function's body that
binds it (`use crate::b as a;`) was ignored, and so was a `mod a { … }` the
function declares, which rustSubmodule also took for the module's own `a`
when it was written before the module's `pub mod a`.

- rustTypeMember takes a function, `const` or variant of a struct, enum or
  union only from an `impl` whose header names that type (rustImplSelfItems,
  walked from the module the `impl` is in); a trait's own declaration is
  still the trait body's.
- rustImplSelfItems reads the `impl` header with the new rustImplSelfType,
  which rustImplSelfArgs shares; a header naming a reference to the type,
  or a bare name in an `impl` inside a function's body, names no type here.
- rustSubmodule skips a `mod` that sits in a function's body or another
  block (RustModBlock.scope, read with rustUseScopes), which is no item of
  its module.
- rustPathTarget reads a head that a `use` in the body binds as the path
  that `use` names, and declines one a `mod` in the body declares.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-01T15:50:16.968049Z b57434f PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b57434f41d

ℹ️ 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".

Comment on lines +11174 to +11176
segs = [...via, ...segs.slice(1)];
head = segs[0]!;
bare = head !== 'crate' && head !== 'self' && head !== 'super';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Follow local import aliases before walking the rewritten path

When a block-scoped import names another local import, this single substitution can produce a wrong call edge. For example, with sibling modules a and b both exposing make, use crate::a as b; { use b as a; a::make() } calls crate::a::make. The substitution instead produces b::make, and the subsequent module walk ignores the outer local binding of b and selects the sibling module's function. The parent revision resolves this example correctly, and the later Rust scope gate accepts the incorrect target. Resolve the imported head recursively in the import site's lexical scope before walking modules.

Useful? React with 👍 / 👎.

// module of the file, or one a function declares for itself) does not.
const within = (n: Node): boolean => type.kind === 'trait'
? n.filePath === type.filePath && n.startLine >= type.startLine && n.startLine <= (type.endLine ?? type.startLine)
: rustImplSelfItems(n, context)?.has(type.id) === true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve methods on impls with callback bounds

For a valid inherent impl such as impl<F: Fn() -> usize> Cache<F>, this newly mandatory ownership check rejects all its methods. The generic-stripping regex in rustImplSelfType treats the > in -> as the closing generic bracket, leaving usize> Cache<F> for rustPathHead, which returns null. Consequently, a previously resolved call such as crate::a::Cache::open(|| 1) loses its call edge even though the indexed method unambiguously belongs to Cache; the name-matching fallbacks do not resolve that full path. Parse the impl's generic parameters with an arrow-aware balanced-bracket reader before using the header to reject candidates.

Useful? React with 👍 / 👎.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant