Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- In Rust, a `for` loop's variable now has the type of an item of what the loop goes over, like `for item in &self.items` over a `Vec<Item>`, `for (key, value) in &map` over a `HashMap`, or `for sc in cmd.get_subcommands()` over the `impl Iterator` a method returns, and each name in a tuple pattern like `let (tx, rx) = mpsc::channel(8);` has the type of its part of the tuple, so calls on them link to that type's methods. Before, such calls were linked by name: tokio's `tx.send(…)` calls on channel senders went to an internal type's `send`. Re-index Rust projects after upgrading.
- In Rust, a call on a field of `self`, like `self.store.save()` or `self.inner.lock()`, now links to the method of the type the struct declares for that field, also when the field's type is a parameter the `impl` bounds by a trait (`impl<S: Store> Worker<S>`), a trait object, or held in an `Arc` or a `Box`. A field whose type comes from the standard library or a dependency, like `jar: cookie::CookieJar` or `inner: std::sync::Mutex<…>`, no longer links to the project's own type of the same name, which often made a wrapper's method look like it called itself. Re-index Rust projects after upgrading.
- In Rust, a method called through a chain of fields or on a struct literal, like `vis.map.get("a")`, `self.inner.config.port()` or `Xyz { x: 1.0 }.to_val()`, now links to the method of the type the last field is declared with, or of the literal's own type. A call through a type from the standard library or a dependency, like `J::from(i)` after `use serde_json::Value as J` or `Value::default()` after `use serde_json::Value`, no longer links to a project function of the same name. Before, such calls were linked by the method's name alone, so a `HashMap` field's `get` reached an unrelated project `get` and a struct literal's `to_val()` reached another type's. Re-index Rust projects after upgrading. Thanks @Egrn for the report and @danusha2345. (#2614)
- In Rust, a method call made through more than one field, or on what another method returns, like `self.services.store.put(…)` or `self.store().put(…)`, now keeps that whole receiver in the name the index records for the call, on one line or split over several by rustfmt, as a call through one field like `self.store.put(…)` already did. A call on what a method returns now links to the method of the type that method is declared to return, so builder chains like `builder.line_terminator(…).binary_detection(…)` reach the builder's own methods and a call on a standard-library value, like `buf.filled().len()`, no longer links to a project method of the same name. In a chain that calls the same method twice, like `self.table.get("a").get(0)`, each call links to its own receiver's method. Before, the index kept only the method's name, so tools reading a call couldn't tell which field it went through, `self.store().put(…)` linked to nothing, and the second `get` was guessed by its name. Re-index Rust projects after upgrading. Thanks @jocax for the report. (#2836)
- In Rust, a call written with a turbofish, like `matches.get_one::<String>("name")`, `parse_config::<Settings>()` or `Pool::<u8>::new()`, now links to the function it calls. Before, the type arguments stayed in the call's name, so no such call was linked. Re-index Rust projects after upgrading.
- In Rust, a call through a value's declared type, like `reader.as_mut().poll_read(cx)` on `reader: Pin<&mut R>`, now reads the declaration in scope at the call. Before, a `let` of the same name inside an earlier block, or a struct literal's field of that name written above the call, could be taken for it. Re-index Rust projects after upgrading.
- 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.
Expand Down
21 changes: 11 additions & 10 deletions __tests__/extraction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1670,7 +1670,7 @@ impl From<u32> for Own {
).toBe(true);
});

it('keeps the owner shape for `self.<method>()` and `self.<field>.<method>()`, and collapses every other receiver (#1585, #1861)', () => {
it('keeps the owner shape for `self.<method>()` and `self.<field>.<method>()`, a longer receiver chain whole, and collapses a parenthesized receiver (#1585, #1861, #2836)', () => {
const code = `
pub struct Outer { pub inner: Inner, pub deep: Deep }
impl Outer {
Expand All @@ -1689,22 +1689,23 @@ impl Outer {
const calls = result.unresolvedReferences
.filter((r) => r.referenceKind === 'calls')
.map((r) => r.referenceName);
// Two shapes keep an owner the resolver can act on: the single-hop field
// receiver, whose type it reads off the owner struct (#1585), and the bare
// `self` receiver, whose type is the calling method's own owner (#1861).
// `self.make().run()` contributes `self.make` — the inner call — and its
// OUTER call collapses, because a method's return type is not read here.
// The single-hop field receiver keeps its owner, whose type the resolver
// reads off the owner struct (#1585), and so does the bare `self`
// receiver, whose type is the calling method's own owner (#1861). A
// longer chain of fields and method calls is kept whole, a call written
// `name()` (#2836): `self.make().run()` contributes `self.make`, the inner
// call, and `self.make().run`.
expect(calls.filter((c) => c.startsWith('self.')).sort()).toEqual([
'self.deep.inner.run',
'self.inner.run',
'self.make',
'self.make().run',
'self.run',
]);
// A local receiver keeps its name as before…
expect(calls).toContain('local.run');
// …and the deeper chain, the call receiver and the parenthesized receiver
// still collapse to the method name. `self.run()` no longer does, so this
// is three rather than four.
expect(calls.filter((c) => c === 'run')).toHaveLength(3);
// …and the parenthesized receiver still collapses to the method name.
expect(calls.filter((c) => c === 'run')).toHaveLength(1);
const outerRun = result.nodes.find((n) => n.qualifiedName === 'Outer::run');
expect(outerRun).toBeDefined();
const fieldRef = result.unresolvedReferences.find((r) => r.referenceName === 'self.inner.run');
Expand Down
36 changes: 36 additions & 0 deletions __tests__/kernel-rustlang-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,42 @@ fn uses() {
]);
});

it('a call keeps its receiver chain of fields and method calls (#2836)', () => {
const links = Array.from({ length: 17 }, (_, i) => `l${i}()`).join('.');
const source = `
pub struct Svc { services: Services, store: Box<dyn Store>, pair: (u8, Config) }
impl Svc {
fn chains(&self, vis: &Vis) {
self.services.store.put("a"); let _ = self.services.store.put("ü");
self
.services // rustfmt
.store
.put("c");
self.store().put("m"); self.store.put("d"); self.put("e"); vis.put(1);
vis.map.get("k").get(0); self.0.len(); self.pair.1.port();
vis.get::<u8>(1).put::<u16>(2); vis.a.b().c.d();
Foo::new().a().b(); (vis).a.b(); self.a?.b(); self.a.b().await.c(); vis[0].a.b();
r#vis.a.b(); vis.${links}.m();
}
}
`;
assertParity('chains.rs', source);
assertParity('chains.rs', source.replace(/\n/g, '\r\n'));

// Not vacuous: the chains are kept, the other receivers keep their old names.
process.env.CODEGRAPH_KERNEL = '0';
const names = new Set(extractFromSource('chains.rs', source, 'rust').unresolvedReferences
.filter((r) => r.referenceKind === 'calls').map((r) => r.referenceName));
delete process.env.CODEGRAPH_KERNEL;
for (const name of [
'self.services.store.put', 'self.store().put', 'self.store.put', 'self.put', 'vis.put', 'vis.map.get',
'vis.map.get().get', 'self.0.len', 'self.pair.1.port', 'vis.get', 'vis.get().put', 'vis.a.b().c.d',
'Foo::new().a', 'b', 'c', 'm',
]) {
expect(names, name).toContain(name);
}
});

it('torture fixture: impl/trait quirks, use bindings, chains, fn-refs, value-refs, route macros', () => {
const file = path.join(FIXTURE_DIR, 'torture.rs');
assertParity('fixtures/torture.rs', fs.readFileSync(file, 'utf8'), 20);
Expand Down
Loading