9855: feature: Destructure Tuple Assist r=Veykril a=Booksbaum Part of #8673. This PR only handles tuples, not TupleStruct and RecordStruct. Code Assist to destructure a tuple into its items:  * Should work in nearly all pattern positions, like let assignment, function parameters, match arms, for loops, and nested variables (`if let Some($0t) = Some((1,2))`) -> everywhere `IdentPat` is allowed * Exception: If there's a sub-pattern (``@`):` ```rust if let t @ (1..=3, 1..=3) = ... {} // ^ ``` -> `t` must be a `Name`; `TuplePat` (`(_0, _1)`) isn't allowed * inside subpattern is ok: ```rust let t @ (a, _) = ((1,2), 3); // ^ ``` -> ```rust let t @ ((_0, _1), _) = ((1,2), 3); ``` * Assist triggers only at tuple declaration, not tuple usage. (might be useful especially when it creates a sub-pattern (after ``@`)` and only changes the usage under cursor -- but not part of this PR). ### References References can be destructured: ```rust let t = &(1,2); // ^ let v = t.0; ``` -> ```rust let (_0, _1) = &(1,2); let v = _0; ``` BUT: `t.0` and `_0` have different types (`i32` vs. `&i32`) -> `v` has now a different type. I think that's acceptable: I think the destructure assist is mostly used in simple, immediate scopes and not huge existing code. Additional Notes: * `ref` has same behaviour (-> `ref` is kept for items) ```rust let ref t = (1,2); // ^ ``` -> ```rust let (ref _0, ref _1) = (1,2); ``` * Rust IntelliJ Plugin: doesn't trigger with `&` or `ref` at all ### mutable ```rust let mut t = (1,2); // ^ ``` -> ```rust let (mut _0, mut _1) = (1,2); ``` and ```rust let t = &mut (1,2); // ^ ``` -> ```rust let (_0, _1) = &mut (1,2); ``` Again: with reference (`&mut`), `t.0` and `_0` have different types (`i32` vs `&mut i32`). And there's an additional issue with `&mut` and assignment: ```rust let t = &mut (1,2); // ^ t.0 = 9; ``` -> ```rust let (_0, _1) = &mut (1,2); _0 = 9; // ^ // mismatched types // expected `&mut {integer}`, found integer // consider dereferencing here to assign to the mutable borrowed piece of memory ``` But I think that's quite a niche use case, so I don't catch that (`*_0 = 9;`) Additional Notes: * Rust IntelliJ Plugin: removes the `mut` (`let mut t = ...` -> `let (_0, _1) = ...`), doesn't trigger with `&mut` ### Binding after ``@`` Destructure tuple in sub-pattern is implemented: ```rust let t = (1,2); // ^ let v = t.0; let f = t.into(); ``` -> ```rust let t @ (_0, _1) = (1,2); let v = _0; let f = t.into(); ``` BUT: Bindings after ``@`` aren't currently in stable and require `#![feature(bindings_after_at)]` (though should be generally [available quite soon](https://github.com/rust-lang/rust/pull/85305#event-5072889913) (with `1.56.0`)). But I don't know how to check for an enabled feature -> Destructure tuple in sub-pattern [isn't enabled](a4ee6c7954/crates/ide_assists/src/handlers/destructure_tuple_binding.rs (L32)
) yet. * When Destructure in sub-pattern is enabled there are two assists: * `Destructure tuple in place`: ```rust let t = (1,2); // ^ ``` -> ```rust let (_0, _1) = (1,2); let v = _0; let f = /*t*/.into(); ``` * `Destructure tuple in sub-pattern`: ```rust let t = (1,2); // ^ let v = t.0; let f = t.into(); ``` -> ```rust let t @ (_0, _1) = (1,2); let v = _0; let f = t.into(); ``` * When Destructure in sub-pattern is disabled, only the first one is available and just named `Destructure tuple` <br/> <br/> ### Caveats * Unlike in #8673 or IntelliJ rust plugin, I'm not leaving the previous tuple name at function calls. **Reasoning**: It's not too unlikely the tuple variable shadows another variable. Destructuring the tuple while leaving the function call untouched, results in still a valid function call -- but now with another variable: ```rust let t = (8,9); let t = (1,2); // ^ t.into() ``` => Destructure Tuple ```rust let t = (8,9); let (_0, _1) = (1,2); t.into() ``` `t.into()` is still valid -- using the first tuple. Instead I comment out the tuple usage, which results in invalid code -> must be handled by user: ```rust /*t*/.into() ``` * (though that might be a biased decision: For testing I just declared a lot of `t`s and quite ofen in lines next to each other...) * Issue: there are some cases that results in still valid code: * macro that accept the tuple as well as no arguments: ```rust macro_rules! m { () => { "foo" }; ($e:expr) => { $e; "foo" }; } let t = (1,2); m!(t); m!(/*t*/); ``` -> both calls are valid ([test](a4ee6c7954/crates/ide_assists/src/handlers/destructure_tuple_binding.rs (L1474)
)) * Probably with tuple as return value. Changing the return value most likely results in an error -- but in another place; not where the tuple usage was. -> not sure that's the best way.... Additional the tuple name surrounded by comment is more difficult to edit than just the name. * Code Assists don't support snippet placeholder, and rust analyzer just the first `$0` -> unfortunately no editing of generated tuple item variables. Cursor (`$0`) is placed on first generated item. <br/> <br/> ### Issues * Tuple index usage in macro calls aren't converted: ```rust let t = (1,2); // ^ let v = t.0; println!("{}", t.0); ``` -> ```rust let (_0, _1) = (1,2); let v = _0; println!("{}", /*t*/.0); ``` ([tests](a4ee6c7954/crates/ide_assists/src/handlers/destructure_tuple_binding.rs (L1294)
)) * Issue is: [name.syntax()](a4ee6c7954/crates/ide_assists/src/handlers/destructure_tuple_binding.rs (L242-L244)
) in each [usage](a4ee6c7954/crates/ide_assists/src/handlers/destructure_tuple_binding.rs (L108-L113)
) of a tuple is syntax & text_range in its file. EXCEPT when tuple usage is in a macro call (`m!(t.0)`), the macro is expanded and syntax (and range) is based on that expanded macro, not in actual file. That leads to several things: * I cannot differentiate between calling the macro with the tuple or with tuple item: ```rust macro_rules! m { ($t:expr, $i:expr) => { $t.0 + $i }; } let t = (1,2); m!(t, t.0); ``` -> both `t` usages are resolved as tuple index usage * Range of resolved tuple index usage is in expanded macro, not in actual file -> don't know where to replace index usage -> tuple items passed into a macro are ignored, and only the tuple name itself is handled (uncommented) * I'm not checking if the generated names conflict with already existing variables. ```rust let _0 = 42; // >-| let t = (1,2); // | let v = _0; // <-| // ^ 42 ``` => deconstruct tuple ```rust let _0 = 42; let (_0, _1) = (1,2); // >-| let v = _0; // <-| // ^ now 1 ``` * I tried to get the scope at tuple declaration and its usages. And then iterate all names with [`process_all_names`](145b51f9da/crates/hir/src/semantics.rs (L935)
). But that doesn't find all local names for declarations (`let t = (1,2)`) (for usages it does) * This isn't unique to this Code Assist, but happen in others too (like `extract into variable` or `extract into function`). But here a name conflict is more likely (when destructuring multiple tuples, for examples nested ones (`let t = ((1,2),3)` -> `let (_0, _1) = ...` -> `let ((_0, _1), _1) = ...` -> error)) * IntelliJ rust plugin does handle this (-> name is `_00`) Co-authored-by: BooksBaum <15612932+Booksbaum@users.noreply.github.com>
rust-analyzer is a modular compiler frontend for the Rust language. It is a part of a larger rls-2.0 effort to create excellent IDE support for Rust.
Work on rust-analyzer is sponsored by
Quick Start
https://rust-analyzer.github.io/manual.html#installation
Documentation
If you want to contribute to rust-analyzer or are just curious about how things work under the hood, check the ./docs/dev folder.
If you want to use rust-analyzer's language server with your editor of choice, check the manual folder. It also contains some tips & tricks to help you be more productive when using rust-analyzer.
Security and Privacy
See the corresponding sections of the manual.
Communication
For usage and troubleshooting requests, please use "IDEs and Editors" category of the Rust forum:
https://users.rust-lang.org/c/ide/14
For questions about development and implementation, join rust-analyzer working group on Zulip:
https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer
Quick Links
- Website: https://rust-analyzer.github.io/
- Metrics: https://rust-analyzer.github.io/metrics/
- API docs: https://rust-analyzer.github.io/rust-analyzer/ide/
- Changelog: https://rust-analyzer.github.io/thisweek
License
Rust analyzer is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0).
See LICENSE-APACHE and LICENSE-MIT for details.