2 assists::{AssistId, AssistKind},
4 search::{FileReference, SearchScope, UsageSearchResult},
6 use itertools::Itertools;
7 use syntax::{TextRange, ast::{self, AstNode, IdentPat, NameOwner}};
9 use crate::assist_context::{AssistBuilder, AssistContext, Assists};
11 // Assist: destructure_tuple_binding
13 // Destructures a tuple binding in place.
24 // let (_0, _1) = (1,2);
30 // And (currently disabled):
31 // Assist: destructure_tuple_binding_in_sub_pattern
33 // Destructures tuple items in sub-pattern (after `@`).
44 // let t @ (_0, _1) = (1,2);
48 pub(crate) fn destructure_tuple_binding(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
49 destructure_tuple_binding_impl(acc, ctx, false)
52 pub(crate) fn destructure_tuple_binding_impl(acc: &mut Assists, ctx: &AssistContext, with_sub_pattern: bool) -> Option<()> {
53 let ident_pat = ctx.find_node_at_offset::<ast::IdentPat>()?;
54 let data = collect_data(ident_pat, ctx)?;
57 AssistId("destructure_tuple", AssistKind::RefactorRewrite),
58 if with_sub_pattern { "Destructure tuple in place" } else { "Destructure tuple" },
61 edit_tuple_assignment(&data, builder, ctx, false);
62 edit_tuple_usages(&data, builder, ctx, false);
68 AssistId("destructure_tuple_in_sub_pattern", AssistKind::RefactorRewrite),
69 "Destructure tuple in sub-pattern",
72 edit_tuple_assignment(&data, builder, ctx, true);
73 edit_tuple_usages(&data, builder, ctx, true);
81 fn collect_data(ident_pat: IdentPat, ctx: &AssistContext) -> Option<TupleData> {
82 if ident_pat.at_token().is_some() {
83 // cannot destructure pattern with sub-pattern:
84 // Only IdentPat can have sub-pattern,
85 // but not TuplePat (`(a,b)`)
86 cov_mark::hit!(destructure_tuple_subpattern);
90 let ty = ctx.sema.type_of_pat(&ident_pat.clone().into())?;
92 let ty = ty.strip_references();
94 let field_types = ty.tuple_fields(ctx.db());
95 if field_types.is_empty() {
96 cov_mark::hit!(destructure_tuple_no_tuple);
100 let name = ident_pat.name()?.to_string();
101 let range = ident_pat.syntax().text_range();
103 let usages = ctx.sema.to_def(&ident_pat).map(|def| {
104 Definition::Local(def)
106 .in_scope(SearchScope::single_file(ctx.frange.file_id))
110 let field_names = (0..field_types.len())
111 .map(|i| generate_name(i, &name, &ident_pat, &usages, ctx))
114 Some(TupleData { ident_pat, range, field_names, usages })
120 _ident_pat: &IdentPat,
121 _usages: &Option<UsageSearchResult>,
122 _ctx: &AssistContext,
124 //TODO: detect if name already used
125 format!("_{}", index)
132 field_names: Vec<String>,
133 // field_types: Vec<Type>,
134 usages: Option<UsageSearchResult>,
136 fn edit_tuple_assignment(data: &TupleData, builder: &mut AssistBuilder, ctx: &AssistContext, in_sub_pattern: bool) {
138 let original = &data.ident_pat;
139 let is_ref = original.ref_token().is_some();
140 let is_mut = original.mut_token().is_some();
145 .map(|name| ast::Pat::from(ast::make::ident_pat(is_ref, is_mut, ast::make::name(name))));
146 ast::make::tuple_pat(fields)
149 let add_cursor = |text: &str| {
150 // place cursor on first tuple item
151 let first_tuple = &data.field_names[0];
152 text.replacen(first_tuple, &format!("$0{}", first_tuple), 1)
155 // with sub_pattern: keep original tuple and add subpattern: `tup @ (_0, _1)`
157 let text = format!(" @ {}", tuple_pat.to_string());
158 match ctx.config.snippet_cap {
160 let snip = add_cursor(&text);
161 builder.insert_snippet(cap, data.range.end(), snip);
163 None => builder.insert(data.range.end(), text),
166 let text = tuple_pat.to_string();
167 match ctx.config.snippet_cap {
169 let snip = add_cursor(&text);
170 builder.replace_snippet(cap, data.range, snip);
172 None => builder.replace(data.range, text),
177 fn edit_tuple_usages(
179 builder: &mut AssistBuilder,
181 in_sub_pattern: bool,
183 if let Some(usages) = data.usages.as_ref() {
184 for (file_id, refs) in usages.iter() {
185 builder.edit_file(*file_id);
188 edit_tuple_usage(r, data, builder, ctx, in_sub_pattern);
194 usage: &FileReference,
196 builder: &mut AssistBuilder,
197 _ctx: &AssistContext,
198 in_sub_pattern: bool,
200 match detect_tuple_index(usage, data) {
202 let text = &data.field_names[index.index];
203 builder.replace(index.range, text);
207 cov_mark::hit!(destructure_tuple_call_with_subpattern);
211 // no index access -> make invalid -> requires handling by user
212 // -> put usage in block comment
213 builder.insert(usage.range.start(), "/*");
214 builder.insert(usage.range.end(), "*/");
224 fn detect_tuple_index(usage: &FileReference, data: &TupleData) -> Option<TupleIndex> {
237 .skip_while(|s| !ast::PathExpr::can_cast(s.kind()))
238 .skip(1) // PATH_EXPR
239 .find(|s| !ast::ParenExpr::can_cast(s.kind()))?; // skip parentheses
241 if let Some(field_expr) = ast::FieldExpr::cast(node) {
242 let idx = field_expr.name_ref()?.as_tuple_field()?;
243 if idx < data.field_names.len() {
245 // special case: in macro call -> range of `field_expr` in applied macro, NOT range in actual file!
246 if field_expr.syntax().ancestors().any(|a| ast::MacroStmts::can_cast(a.kind())) {
247 cov_mark::hit!(destructure_tuple_macro_call);
249 // issue: cannot differentiate between tuple index passed into macro or tuple index as result of macro:
252 // ($t1:expr, $t2:expr) => { $t1; $t2.0 }
257 // -> 2 tuple index usages detected!
259 // -> only handle `t`
265 range: field_expr.syntax().text_range(),
268 // tuple index out of range
280 use crate::tests::{check_assist, check_assist_not_applicable};
282 // Tests for direct tuple destructure:
283 // `let $0t = (1,2);` -> `let (_0, _1) = (1,2);`
285 fn assist(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
286 destructure_tuple_binding_impl(acc, ctx, false)
290 fn dont_trigger_on_unit() {
291 cov_mark::check!(destructure_tuple_no_tuple);
292 check_assist_not_applicable(
302 fn dont_trigger_on_number() {
303 cov_mark::check!(destructure_tuple_no_tuple);
304 check_assist_not_applicable(
315 fn destructure_3_tuple() {
325 let ($0_0, _1, _2) = (1,2,3);
331 fn destructure_2_tuple() {
341 let ($0_0, _1) = (1,2);
347 fn replace_indices() {
360 let ($0_0, _1, _2) = (1,2,3);
370 fn replace_usage_in_parentheses() {
382 let ($0_0, _1, _2) = (1,2,3);
391 fn handle_function_call() {
402 let ($0_0, _1) = (1,2);
403 let v = /*tup*/.into();
410 fn handle_invalid_index() {
421 let ($0_0, _1) = (1,2);
429 fn dont_replace_variable_with_same_name_as_tuple() {
446 let ($0_0, _1, _2) = (1,2,3);
456 fn keep_function_call_in_tuple_item() {
461 let $0t = ("3.14", 0);
462 let pi: f32 = t.0.parse().unwrap();
467 let ($0_0, _1) = ("3.14", 0);
468 let pi: f32 = _0.parse().unwrap();
480 let $0t: (usize, i32) = (1,2);
485 let ($0_0, _1): (usize, i32) = (1,2);
492 fn destructure_reference() {
493 //Note: `v` has different types:
516 fn destructure_multiple_reference() {
529 let ($0_0, _1) = &&t;
537 fn keep_reference() {
541 fn foo(t: &(usize, usize)) -> usize {
548 fn foo(t: &(usize, usize)) -> usize {
559 //Note: `v` has different types:
572 let (ref $0_0, ref _1) = (1,2);
592 let (mut $0_0, mut _1) = (1,2);
602 //Note: `v` has different types:
604 // * in 2nd: `&mut i32`
605 // Note: 2nd `_0 = 42` isn't valid; requires dereferencing (`*_0`), but isn't handled here!
610 let ref mut $0t = (1,2);
617 let (ref mut $0_0, ref mut _1) = (1,2);
626 fn dont_trigger_for_non_tuple_reference() {
627 check_assist_not_applicable(
639 fn dont_trigger_on_static_tuple() {
640 check_assist_not_applicable(
643 static $0TUP: (usize, usize) = (1,2);
649 fn dont_trigger_on_wildcard() {
650 check_assist_not_applicable(
661 fn dont_trigger_in_struct() {
662 check_assist_not_applicable(
666 $0tup: (usize, usize),
673 fn dont_trigger_in_struct_creation() {
674 check_assist_not_applicable(
690 fn dont_trigger_on_tuple_struct() {
691 check_assist_not_applicable(
694 struct S(usize, usize);
703 fn dont_trigger_when_subpattern_exists() {
704 // sub-pattern is only allowed with IdentPat (name), not other patterns (like TuplePat)
705 cov_mark::check!(destructure_tuple_subpattern);
706 check_assist_not_applicable(
709 fn sum(t: (usize, usize)) -> usize {
711 $0t @ (1..=3,1..=3) => t.0 + t.1,
725 let t1 @ (_, $0t2) = (1, (2,3));
726 let v = t1.0 + t2.0 + t2.1;
731 let t1 @ (_, ($0_0, _1)) = (1, (2,3));
732 let v = t1.0 + _0 + _1;
739 fn in_nested_tuple() {
744 let ($0tup, v) = ((1,2),3);
749 let (($0_0, _1), v) = ((1,2),3);
762 let f = |v| v + tup.1;
767 let ($0_0, _1, _2) = (1,2,3);
775 fn in_closure_args() {
780 let f = |$0t| t.0 + t.1;
786 let f = |($0_0, _1)| _0 + _1;
794 fn in_function_args() {
798 fn f($0t: (usize, usize)) {
803 fn f(($0_0, _1): (usize, usize)) {
815 fn f(t: (usize, usize)) {
822 fn f(t: (usize, usize)) {
823 if let ($0_0, _1) = t {
831 fn in_if_let_option() {
836 fn f(o: Option<(usize, usize)>) {
837 if let Some($0t) = o {
843 fn f(o: Option<(usize, usize)>) {
844 if let Some(($0_0, _1)) = o {
874 fn in_match_option() {
889 Some(($0_0, _1)) => _1,
897 fn in_match_reference_option() {
914 Some(($0_0, _1)) => _1,
927 //- minicore: iterators
929 for $0t in core::iter::repeat((1,2)) {
936 for ($0_0, _1) in core::iter::repeat((1,2)) {
948 //- minicore: iterators
950 for (a, $0b) in core::iter::repeat((1,(2,3))) {
957 for (a, ($0_0, _1)) in core::iter::repeat((1,(2,3))) {
966 fn not_applicable_on_tuple_usage() {
967 //Improvement: might be reasonable to allow & implement
968 check_assist_not_applicable(
987 let s = (t.0 + t.1) / 2;
993 (_,2) if t.0 > 2 => 1,
1000 let ($0_0, _1) = (1,2);
1002 let s = (_0 + _1) / 2;
1005 let e = /*t*/ == (9,0);
1008 (_,2) if _0 > 2 => 1,
1017 fn non_trivial_tuple_assignment() {
1059 use crate::tests::check_assist_by_label;
1061 fn assist(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
1062 destructure_tuple_binding_impl(acc, ctx, true)
1065 pub(crate) fn check_in_place_assist(
1067 ra_fixture_before: &str,
1068 ra_fixture_after: &str,
1070 check_assist_by_label(
1074 "Destructure tuple in place"
1078 pub(crate) fn check_sub_pattern_assist(
1079 ra_fixture_before: &str,
1080 ra_fixture_after: &str,
1082 check_assist_by_label(
1086 "Destructure tuple in sub-pattern"
1090 pub(crate) fn check_both_assists(
1091 ra_fixture_before: &str,
1092 ra_fixture_after_in_place: &str,
1093 ra_fixture_after_in_sub_pattern: &str,
1095 check_in_place_assist(ra_fixture_before, ra_fixture_after_in_place);
1096 check_sub_pattern_assist(ra_fixture_before, ra_fixture_after_in_sub_pattern);
1100 /// Tests for destructure of tuple in sub-pattern:
1101 /// `let $0t = (1,2);` -> `let t @ (_0, _1) = (1,2);`
1104 use super::assist::*;
1105 use crate::tests::check_assist_by_label;
1108 fn destructure_in_sub_pattern() {
1109 check_sub_pattern_assist(
1111 #![feature(bindings_after_at)]
1118 #![feature(bindings_after_at)]
1121 let t @ ($0_0, _1) = (1,2);
1128 fn trigger_both_destructure_tuple_assists() {
1129 fn assist(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
1130 destructure_tuple_binding_impl(acc, ctx, true)
1137 check_assist_by_label(
1142 let ($0_0, _1) = (1,2);
1145 "Destructure tuple in place",
1147 check_assist_by_label(
1152 let t @ ($0_0, _1) = (1,2);
1155 "Destructure tuple in sub-pattern",
1160 fn replace_indices() {
1161 check_sub_pattern_assist(
1171 let t @ ($0_0, _1) = (1,2);
1180 fn keep_function_call() {
1181 cov_mark::check!(destructure_tuple_call_with_subpattern);
1182 check_sub_pattern_assist(
1191 let t @ ($0_0, _1) = (1,2);
1200 check_sub_pattern_assist(
1203 let $0t: (usize, i32) = (1,2);
1210 let t @ ($0_0, _1): (usize, i32) = (1,2);
1219 fn in_function_args() {
1220 check_sub_pattern_assist(
1222 fn f($0t: (usize, usize)) {
1228 fn f(t @ ($0_0, _1): (usize, usize)) {
1238 check_sub_pattern_assist(
1241 let ref $0t = (1,2);
1248 let ref t @ (ref $0_0, ref _1) = (1,2);
1257 check_sub_pattern_assist(
1260 let mut $0t = (1,2);
1267 let mut t @ (mut $0_0, mut _1) = (1,2);
1276 check_sub_pattern_assist(
1279 let ref mut $0t = (1,2);
1286 let ref mut t @ (ref mut $0_0, ref mut _1) = (1,2);
1295 /// Tests for tuple usage in macro call:
1298 use super::assist::*;
1302 fn detect_macro_call() {
1303 cov_mark::check!(destructure_tuple_macro_call);
1304 check_in_place_assist(
1307 ($e:expr) => { "foo"; $e };
1317 ($e:expr) => { "foo"; $e };
1321 let ($0_0, _1) = (1,2);
1331 // leading `"foo"` to ensure `$e` doesn't start at position `0`
1334 ($e:expr) => { "foo"; $e };
1344 ($e:expr) => { "foo"; $e };
1348 let ($0_0, _1) = (1,2);
1354 ($e:expr) => { "foo"; $e };
1358 let t @ ($0_0, _1) = (1,2);
1366 fn tuple_function_usage() {
1370 ($e:expr) => { "foo"; $e };
1380 ($e:expr) => { "foo"; $e };
1384 let ($0_0, _1) = (1,2);
1390 ($e:expr) => { "foo"; $e };
1394 let t @ ($0_0, _1) = (1,2);
1402 fn tuple_index_usage() {
1406 ($e:expr) => { "foo"; $e };
1414 // FIXME: replace `t.0` with `_0` (cannot detect range of tuple index in macro call)
1417 ($e:expr) => { "foo"; $e };
1421 let ($0_0, _1) = (1,2);
1425 // FIXME: replace `t.0` with `_0`
1428 ($e:expr) => { "foo"; $e };
1432 let t @ ($0_0, _1) = (1,2);
1440 fn tuple_in_parentheses_index_usage() {
1444 ($e:expr) => { "foo"; $e };
1452 // FIXME: replace `(t).0` with `_0`
1455 ($e:expr) => { "foo"; $e };
1459 let ($0_0, _1) = (1,2);
1463 // FIXME: replace `(t).0` with `_0`
1466 ($e:expr) => { "foo"; $e };
1470 let t @ ($0_0, _1) = (1,2);
1479 check_in_place_assist(
1483 ($e:expr) => { $e; "foo" };
1491 // FIXME: macro allows no arg -> is valid. But assist should result in invalid code
1495 ($e:expr) => { $e; "foo" };
1499 let ($0_0, _1) = (1,2);
1507 fn tuple_index_in_macro() {
1511 ($t:expr, $i:expr) => { $t.0 + $i };
1519 // FIXME: replace `t.0` in macro call (not IN macro) with `_0`
1522 ($t:expr, $i:expr) => { $t.0 + $i };
1526 let ($0_0, _1) = (1,2);
1530 // FIXME: replace `t.0` in macro call with `_0`
1533 ($t:expr, $i:expr) => { $t.0 + $i };
1537 let t @ ($0_0, _1) = (1,2);