mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-24 19:30:10 +08:00
fix(rust): a same-file path's inline module is read from its braces, not its indentation (#2713)
PR #2711 and PR #2702 landed a way each to tell which inline `mod` a
place in a Rust file is in. #2711's isInRustInlineModule scanned lines
upward for `mod <name> {` and trusted rustfmt's indentation to find where
the block closes. #2702's rustModBlocks scans the braces, skipping
strings, raw strings, char literals and comments, and keeps the result in
rustUsesOf. isInRustInlineModule now asks rustBlockAt for the innermost
block around the candidate, as rustDeclaredItems does, so there is one
answer to that question.
What changes:
- An item written without indentation inside `mod flat { … }` is found
through `flat::Flat`. The indentation rule rejected it.
- `outer::Shell` is only `outer`'s own `Shell`, not one declared in a
`mod deep` inside it. The upward scan accepted any item below the
`mod outer {` line.
- A file whose braces don't all close says nothing, as it does for the
`use` walk of PR #2702.
The A/B (main 818e7803 vs this change; axum, tokio, clap, serde,
ripgrep, gleam, dioxus, and the gin, serilog, flask and express
controls) is byte-identical. rust-path-same-file.test.ts gains the two
cases. The `flat::Flat` one fails on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
818e780336
commit
e2aa5d205d
@@ -234,6 +234,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- In Rust, a name written on its own, like an `Err(…)` call or a `Response` return type, or through a module, like `io::Error` or `use crate::de::Error`, no longer links to a `type` or `const` declared inside an `impl`, such as a `FromStr` impl's `type Err` or a `Service` impl's `type Response`: only a path like `Self::Err` names those, and they are now listed under their type, as `ValueHint::Err`. Before, serde's and ripgrep's `Ok(…)` and `Err(…)` calls linked to a serializer's or parser's `type Ok` or `type Err`, axum's handlers' `Response` to a `Service` impl's, and ripgrep's `impl Captures for RegexCaptures` to another impl's `type Captures` instead of the `Captures` trait. Re-index Rust projects after upgrading.
|
||||
- In Rust, a name written through a path, like `io::Error`, `std::ffi::OsStr`, `mio::Interest`, `ast::Pattern` or `Source::Record { … }`, now links to what that path names, or to nothing when the path leads into the standard library or another crate, and no longer to an item of the same name that merely sits in the same file. Before, clap's `std::ffi::OsStr` and `io::Error` linked to its own `OsStr` and `Error`, serde's tests' `Token::Struct { … }` to their own `Struct`, and gleam's `ast::Pattern` to the `Pattern` enum beside it. Re-index Rust projects after upgrading.
|
||||
- In Rust, a trait's associated type written as `<T as Trait>::Name`, or bound in a trait's arguments like `Iterator<Item = &str>`, now links only to what it can name: through `<Self as Trait>` the `impl`'s own, through a type that type's `impl` of the trait, and nothing through a generic parameter or from a binding. Before, axum's `<L as Listener>::Io` linked to one listener's `type Io`, its `Iterator<Item = &str>` to an unrelated `enum Item`, and dioxus's `Storage = …` bindings to a `Storage` trait. Re-index Rust projects after upgrading.
|
||||
- In Rust, a path through a module declared inline in the same file, like `inner::Cell` for a `mod inner { … }` above it, now finds the item however the code is indented, and no longer one in a module nested inside it.
|
||||
- In C++, every name a declaration lists now has the declaration's type, like `strings_found` in google/breakpad's `ByteBuffer symbols_found, strings_found;` or `data_` in a class's `Table *index_, *data_;`, so calls on it link to that class's methods. Before, only the first name had its type read, and a call on another name linked to nothing or to the class of an earlier variable with the same name.
|
||||
- In C++, a call on a parameter, a local lambda or functor, a lambda's parameter or a range-`for` variable no longer links to a function or method elsewhere that only shares its name, like protocolbuffers/protobuf's code generators calling `format(…)` on their `Formatter& format` parameter, which linked to the conformance suite's `TestResult::format`, or facebook/rocksdb's tests calling a local `auto key = [](int i) {…};`. C++ finds the local first, so that is what the call calls.
|
||||
|
||||
|
||||
@@ -105,6 +105,27 @@ describe('a Rust path into another module, type or crate', () => {
|
||||
' inner::Cell',
|
||||
'}',
|
||||
'',
|
||||
// Braces, not indentation, say what a module holds.
|
||||
'mod flat {',
|
||||
'pub struct Flat;',
|
||||
'}',
|
||||
'',
|
||||
'pub fn flat() -> flat::Flat {',
|
||||
' flat::Flat',
|
||||
'}',
|
||||
'',
|
||||
// `outer::Shell` is `outer`'s own, not the one in `outer::deep`.
|
||||
'mod outer {',
|
||||
' pub struct Shell;',
|
||||
' pub mod deep {',
|
||||
' pub struct Shell;',
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
'pub fn shell() -> outer::Shell {',
|
||||
' todo!()',
|
||||
'}',
|
||||
'',
|
||||
].join('\n'),
|
||||
};
|
||||
for (const [rel, content] of Object.entries(files)) {
|
||||
@@ -142,6 +163,11 @@ describe('a Rust path into another module, type or crate', () => {
|
||||
it('still names an inline module\'s item', () => {
|
||||
expect(targetsOn(cg, 'src/position.rs', 17, 'Cell')).toEqual(['struct Cell src/position.rs@14']);
|
||||
});
|
||||
|
||||
it('reads an inline module from its braces: an item at any indentation, and not one in a module inside it', () => {
|
||||
expect(targetsOn(cg, 'src/position.rs', 25, 'Flat')).toEqual(['struct Flat src/position.rs@22']);
|
||||
expect(targetsOn(cg, 'src/position.rs', 36, 'Shell')).toEqual(['struct Shell src/position.rs@30']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('a Rust path through a module a `use` binds', () => {
|
||||
|
||||
@@ -9132,26 +9132,17 @@ function matchRustSelfPath(name: string, ref: UnresolvedRef, context: Resolution
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether `candidate` sits in an inline `mod <name> { … }` of its own file —
|
||||
* the only way a path through `name` reaches it, since an inline module adds
|
||||
* nothing to its items' qualified names. rustfmt closes the block at the
|
||||
* indentation it opens at.
|
||||
* Whether `candidate` is declared directly in an inline `mod <name> { … }` of
|
||||
* its own file — the only way a path through `name` reaches it, since an
|
||||
* inline module adds nothing to its items' qualified names. The module is the
|
||||
* innermost block rustModBlocks finds around it; a file whose braces don't
|
||||
* all close says nothing.
|
||||
*/
|
||||
function isInRustInlineModule(candidate: Node, name: string, context: ResolutionContext): boolean {
|
||||
const lines = context.getFileLines?.(candidate.filePath) ?? context.readFile(candidate.filePath)?.split(/\r?\n/) ?? [];
|
||||
const head = new RegExp(`^(\\s*)(?:pub(?:\\([^)]*\\))?\\s+)?mod\\s+${name}\\s*\\{`);
|
||||
for (let i = candidate.startLine - 2; i >= 0; i--) {
|
||||
const open = head.exec(lines[i] ?? '');
|
||||
if (!open) continue;
|
||||
const indent = open[1]!.length;
|
||||
if (candidate.startColumn <= indent) return false;
|
||||
for (let j = i + 1; j < candidate.startLine - 1; j++) {
|
||||
const closer = /^(\s*)\}/.exec(lines[j] ?? '');
|
||||
if (closer && closer[1]!.length <= indent) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
const layout = rustUsesOf(candidate.filePath, context);
|
||||
if (!layout.balanced) return false;
|
||||
const at = (layout.lineStarts[candidate.startLine - 1] ?? 0) + Math.max(0, candidate.startColumn ?? 0);
|
||||
return rustBlockAt(layout.blocks, at)?.name === name;
|
||||
}
|
||||
|
||||
/** Whether `candidate` is declared in the same `impl` / `trait` block as `ref`, above it. */
|
||||
|
||||
Reference in New Issue
Block a user