diff --git a/CHANGELOG.md b/CHANGELOG.md index 10b6e3dc..d7037d9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 `::Name`, or bound in a trait's arguments like `Iterator`, now links only to what it can name: through `` 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 `::Io` linked to one listener's `type Io`, its `Iterator` 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. diff --git a/__tests__/rust-path-same-file.test.ts b/__tests__/rust-path-same-file.test.ts index bbd2794a..6c467bd8 100644 --- a/__tests__/rust-path-same-file.test.ts +++ b/__tests__/rust-path-same-file.test.ts @@ -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', () => { diff --git a/src/resolution/name-matcher.ts b/src/resolution/name-matcher.ts index b9533622..7451396e 100644 --- a/src/resolution/name-matcher.ts +++ b/src/resolution/name-matcher.ts @@ -9132,26 +9132,17 @@ function matchRustSelfPath(name: string, ref: UnresolvedRef, context: Resolution } /** - * Whether `candidate` sits in an inline `mod { … }` 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 { … }` 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. */