diff options
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs')
-rw-r--r-- | src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs | 2147 |
1 files changed, 2147 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs new file mode 100644 index 000000000..c1f57532b --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs @@ -0,0 +1,2147 @@ +use ide_db::{ + assists::{AssistId, AssistKind}, + defs::Definition, + search::{FileReference, SearchScope, UsageSearchResult}, +}; +use syntax::{ + ast::{self, AstNode, FieldExpr, HasName, IdentPat, MethodCallExpr}, + TextRange, +}; + +use crate::assist_context::{AssistBuilder, AssistContext, Assists}; + +// Assist: destructure_tuple_binding +// +// Destructures a tuple binding in place. +// +// ``` +// fn main() { +// let $0t = (1,2); +// let v = t.0; +// } +// ``` +// -> +// ``` +// fn main() { +// let ($0_0, _1) = (1,2); +// let v = _0; +// } +// ``` +pub(crate) fn destructure_tuple_binding(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + destructure_tuple_binding_impl(acc, ctx, false) +} + +// And when `with_sub_pattern` enabled (currently disabled): +// Assist: destructure_tuple_binding_in_sub_pattern +// +// Destructures tuple items in sub-pattern (after `@`). +// +// ``` +// fn main() { +// let $0t = (1,2); +// let v = t.0; +// } +// ``` +// -> +// ``` +// fn main() { +// let t @ ($0_0, _1) = (1,2); +// let v = _0; +// } +// ``` +pub(crate) fn destructure_tuple_binding_impl( + acc: &mut Assists, + ctx: &AssistContext<'_>, + with_sub_pattern: bool, +) -> Option<()> { + let ident_pat = ctx.find_node_at_offset::<ast::IdentPat>()?; + let data = collect_data(ident_pat, ctx)?; + + if with_sub_pattern { + acc.add( + AssistId("destructure_tuple_binding_in_sub_pattern", AssistKind::RefactorRewrite), + "Destructure tuple in sub-pattern", + data.range, + |builder| { + edit_tuple_assignment(ctx, builder, &data, true); + edit_tuple_usages(&data, builder, ctx, true); + }, + ); + } + + acc.add( + AssistId("destructure_tuple_binding", AssistKind::RefactorRewrite), + if with_sub_pattern { "Destructure tuple in place" } else { "Destructure tuple" }, + data.range, + |builder| { + edit_tuple_assignment(ctx, builder, &data, false); + edit_tuple_usages(&data, builder, ctx, false); + }, + ); + + Some(()) +} + +fn collect_data(ident_pat: IdentPat, ctx: &AssistContext<'_>) -> Option<TupleData> { + if ident_pat.at_token().is_some() { + // Cannot destructure pattern with sub-pattern: + // Only IdentPat can have sub-pattern, + // but not TuplePat (`(a,b)`). + cov_mark::hit!(destructure_tuple_subpattern); + return None; + } + + let ty = ctx.sema.type_of_pat(&ident_pat.clone().into())?.adjusted(); + let ref_type = if ty.is_mutable_reference() { + Some(RefType::Mutable) + } else if ty.is_reference() { + Some(RefType::ReadOnly) + } else { + None + }; + // might be reference + let ty = ty.strip_references(); + // must be tuple + let field_types = ty.tuple_fields(ctx.db()); + if field_types.is_empty() { + cov_mark::hit!(destructure_tuple_no_tuple); + return None; + } + + let name = ident_pat.name()?.to_string(); + let range = ident_pat.syntax().text_range(); + + let usages = ctx.sema.to_def(&ident_pat).map(|def| { + Definition::Local(def) + .usages(&ctx.sema) + .in_scope(SearchScope::single_file(ctx.file_id())) + .all() + }); + + let field_names = (0..field_types.len()) + .map(|i| generate_name(ctx, i, &name, &ident_pat, &usages)) + .collect::<Vec<_>>(); + + Some(TupleData { ident_pat, range, ref_type, field_names, usages }) +} + +fn generate_name( + _ctx: &AssistContext<'_>, + index: usize, + _tuple_name: &str, + _ident_pat: &IdentPat, + _usages: &Option<UsageSearchResult>, +) -> String { + // FIXME: detect if name already used + format!("_{}", index) +} + +enum RefType { + ReadOnly, + Mutable, +} +struct TupleData { + ident_pat: IdentPat, + // name: String, + range: TextRange, + ref_type: Option<RefType>, + field_names: Vec<String>, + // field_types: Vec<Type>, + usages: Option<UsageSearchResult>, +} +fn edit_tuple_assignment( + ctx: &AssistContext<'_>, + builder: &mut AssistBuilder, + data: &TupleData, + in_sub_pattern: bool, +) { + let tuple_pat = { + let original = &data.ident_pat; + let is_ref = original.ref_token().is_some(); + let is_mut = original.mut_token().is_some(); + let fields = data.field_names.iter().map(|name| { + ast::Pat::from(ast::make::ident_pat(is_ref, is_mut, ast::make::name(name))) + }); + ast::make::tuple_pat(fields) + }; + + let add_cursor = |text: &str| { + // place cursor on first tuple item + let first_tuple = &data.field_names[0]; + text.replacen(first_tuple, &format!("$0{}", first_tuple), 1) + }; + + // with sub_pattern: keep original tuple and add subpattern: `tup @ (_0, _1)` + if in_sub_pattern { + let text = format!(" @ {}", tuple_pat); + match ctx.config.snippet_cap { + Some(cap) => { + let snip = add_cursor(&text); + builder.insert_snippet(cap, data.range.end(), snip); + } + None => builder.insert(data.range.end(), text), + }; + } else { + let text = tuple_pat.to_string(); + match ctx.config.snippet_cap { + Some(cap) => { + let snip = add_cursor(&text); + builder.replace_snippet(cap, data.range, snip); + } + None => builder.replace(data.range, text), + }; + } +} + +fn edit_tuple_usages( + data: &TupleData, + builder: &mut AssistBuilder, + ctx: &AssistContext<'_>, + in_sub_pattern: bool, +) { + if let Some(usages) = data.usages.as_ref() { + for (file_id, refs) in usages.iter() { + builder.edit_file(*file_id); + + for r in refs { + edit_tuple_usage(ctx, builder, r, data, in_sub_pattern); + } + } + } +} +fn edit_tuple_usage( + ctx: &AssistContext<'_>, + builder: &mut AssistBuilder, + usage: &FileReference, + data: &TupleData, + in_sub_pattern: bool, +) { + match detect_tuple_index(usage, data) { + Some(index) => edit_tuple_field_usage(ctx, builder, data, index), + None => { + if in_sub_pattern { + cov_mark::hit!(destructure_tuple_call_with_subpattern); + return; + } + + // no index access -> make invalid -> requires handling by user + // -> put usage in block comment + // + // Note: For macro invocations this might result in still valid code: + // When a macro accepts the tuple as argument, as well as no arguments at all, + // uncommenting the tuple still leaves the macro call working (see `tests::in_macro_call::empty_macro`). + // But this is an unlikely case. Usually the resulting macro call will become erroneous. + builder.insert(usage.range.start(), "/*"); + builder.insert(usage.range.end(), "*/"); + } + } +} + +fn edit_tuple_field_usage( + ctx: &AssistContext<'_>, + builder: &mut AssistBuilder, + data: &TupleData, + index: TupleIndex, +) { + let field_name = &data.field_names[index.index]; + + if data.ref_type.is_some() { + let ref_data = handle_ref_field_usage(ctx, &index.field_expr); + builder.replace(ref_data.range, ref_data.format(field_name)); + } else { + builder.replace(index.range, field_name); + } +} +struct TupleIndex { + index: usize, + range: TextRange, + field_expr: FieldExpr, +} +fn detect_tuple_index(usage: &FileReference, data: &TupleData) -> Option<TupleIndex> { + // usage is IDENT + // IDENT + // NAME_REF + // PATH_SEGMENT + // PATH + // PATH_EXPR + // PAREN_EXRP* + // FIELD_EXPR + + let node = usage + .name + .syntax() + .ancestors() + .skip_while(|s| !ast::PathExpr::can_cast(s.kind())) + .skip(1) // PATH_EXPR + .find(|s| !ast::ParenExpr::can_cast(s.kind()))?; // skip parentheses + + if let Some(field_expr) = ast::FieldExpr::cast(node) { + let idx = field_expr.name_ref()?.as_tuple_field()?; + if idx < data.field_names.len() { + // special case: in macro call -> range of `field_expr` in applied macro, NOT range in actual file! + if field_expr.syntax().ancestors().any(|a| ast::MacroStmts::can_cast(a.kind())) { + cov_mark::hit!(destructure_tuple_macro_call); + + // issue: cannot differentiate between tuple index passed into macro or tuple index as result of macro: + // ```rust + // macro_rules! m { + // ($t1:expr, $t2:expr) => { $t1; $t2.0 } + // } + // let t = (1,2); + // m!(t.0, t) + // ``` + // -> 2 tuple index usages detected! + // + // -> only handle `t` + return None; + } + + Some(TupleIndex { index: idx, range: field_expr.syntax().text_range(), field_expr }) + } else { + // tuple index out of range + None + } + } else { + None + } +} + +struct RefData { + range: TextRange, + needs_deref: bool, + needs_parentheses: bool, +} +impl RefData { + fn format(&self, field_name: &str) -> String { + match (self.needs_deref, self.needs_parentheses) { + (true, true) => format!("(*{})", field_name), + (true, false) => format!("*{}", field_name), + (false, true) => format!("({})", field_name), + (false, false) => field_name.to_string(), + } + } +} +fn handle_ref_field_usage(ctx: &AssistContext<'_>, field_expr: &FieldExpr) -> RefData { + let s = field_expr.syntax(); + let mut ref_data = + RefData { range: s.text_range(), needs_deref: true, needs_parentheses: true }; + + let parent = match s.parent().map(ast::Expr::cast) { + Some(Some(parent)) => parent, + Some(None) => { + ref_data.needs_parentheses = false; + return ref_data; + } + None => return ref_data, + }; + + match parent { + ast::Expr::ParenExpr(it) => { + // already parens in place -> don't replace + ref_data.needs_parentheses = false; + // there might be a ref outside: `&(t.0)` -> can be removed + if let Some(it) = it.syntax().parent().and_then(ast::RefExpr::cast) { + ref_data.needs_deref = false; + ref_data.range = it.syntax().text_range(); + } + } + ast::Expr::RefExpr(it) => { + // `&*` -> cancel each other out + ref_data.needs_deref = false; + ref_data.needs_parentheses = false; + // might be surrounded by parens -> can be removed too + match it.syntax().parent().and_then(ast::ParenExpr::cast) { + Some(parent) => ref_data.range = parent.syntax().text_range(), + None => ref_data.range = it.syntax().text_range(), + }; + } + // higher precedence than deref `*` + // https://doc.rust-lang.org/reference/expressions.html#expression-precedence + // -> requires parentheses + ast::Expr::PathExpr(_it) => {} + ast::Expr::MethodCallExpr(it) => { + // `field_expr` is `self_param` (otherwise it would be in `ArgList`) + + // test if there's already auto-ref in place (`value` -> `&value`) + // -> no method accepting `self`, but `&self` -> no need for deref + // + // other combinations (`&value` -> `value`, `&&value` -> `&value`, `&value` -> `&&value`) might or might not be able to auto-ref/deref, + // but there might be trait implementations an added `&` might resolve to + // -> ONLY handle auto-ref from `value` to `&value` + fn is_auto_ref(ctx: &AssistContext<'_>, call_expr: &MethodCallExpr) -> bool { + fn impl_(ctx: &AssistContext<'_>, call_expr: &MethodCallExpr) -> Option<bool> { + let rec = call_expr.receiver()?; + let rec_ty = ctx.sema.type_of_expr(&rec)?.original(); + // input must be actual value + if rec_ty.is_reference() { + return Some(false); + } + + // doesn't resolve trait impl + let f = ctx.sema.resolve_method_call(call_expr)?; + let self_param = f.self_param(ctx.db())?; + // self must be ref + match self_param.access(ctx.db()) { + hir::Access::Shared | hir::Access::Exclusive => Some(true), + hir::Access::Owned => Some(false), + } + } + impl_(ctx, call_expr).unwrap_or(false) + } + + if is_auto_ref(ctx, &it) { + ref_data.needs_deref = false; + ref_data.needs_parentheses = false; + } + } + ast::Expr::FieldExpr(_it) => { + // `t.0.my_field` + ref_data.needs_deref = false; + ref_data.needs_parentheses = false; + } + ast::Expr::IndexExpr(_it) => { + // `t.0[1]` + ref_data.needs_deref = false; + ref_data.needs_parentheses = false; + } + ast::Expr::TryExpr(_it) => { + // `t.0?` + // requires deref and parens: `(*_0)` + } + // lower precedence than deref `*` -> no parens + _ => { + ref_data.needs_parentheses = false; + } + }; + + ref_data +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::tests::{check_assist, check_assist_not_applicable}; + + // Tests for direct tuple destructure: + // `let $0t = (1,2);` -> `let (_0, _1) = (1,2);` + + fn assist(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + destructure_tuple_binding_impl(acc, ctx, false) + } + + #[test] + fn dont_trigger_on_unit() { + cov_mark::check!(destructure_tuple_no_tuple); + check_assist_not_applicable( + assist, + r#" +fn main() { +let $0v = (); +} + "#, + ) + } + #[test] + fn dont_trigger_on_number() { + cov_mark::check!(destructure_tuple_no_tuple); + check_assist_not_applicable( + assist, + r#" +fn main() { +let $0v = 32; +} + "#, + ) + } + + #[test] + fn destructure_3_tuple() { + check_assist( + assist, + r#" +fn main() { + let $0tup = (1,2,3); +} + "#, + r#" +fn main() { + let ($0_0, _1, _2) = (1,2,3); +} + "#, + ) + } + #[test] + fn destructure_2_tuple() { + check_assist( + assist, + r#" +fn main() { + let $0tup = (1,2); +} + "#, + r#" +fn main() { + let ($0_0, _1) = (1,2); +} + "#, + ) + } + #[test] + fn replace_indices() { + check_assist( + assist, + r#" +fn main() { + let $0tup = (1,2,3); + let v1 = tup.0; + let v2 = tup.1; + let v3 = tup.2; +} + "#, + r#" +fn main() { + let ($0_0, _1, _2) = (1,2,3); + let v1 = _0; + let v2 = _1; + let v3 = _2; +} + "#, + ) + } + + #[test] + fn replace_usage_in_parentheses() { + check_assist( + assist, + r#" +fn main() { + let $0tup = (1,2,3); + let a = (tup).1; + let b = ((tup)).1; +} + "#, + r#" +fn main() { + let ($0_0, _1, _2) = (1,2,3); + let a = _1; + let b = _1; +} + "#, + ) + } + + #[test] + fn handle_function_call() { + check_assist( + assist, + r#" +fn main() { + let $0tup = (1,2); + let v = tup.into(); +} + "#, + r#" +fn main() { + let ($0_0, _1) = (1,2); + let v = /*tup*/.into(); +} + "#, + ) + } + + #[test] + fn handle_invalid_index() { + check_assist( + assist, + r#" +fn main() { + let $0tup = (1,2); + let v = tup.3; +} + "#, + r#" +fn main() { + let ($0_0, _1) = (1,2); + let v = /*tup*/.3; +} + "#, + ) + } + + #[test] + fn dont_replace_variable_with_same_name_as_tuple() { + check_assist( + assist, + r#" +fn main() { + let tup = (1,2); + let v = tup.1; + let $0tup = (1,2,3); + let v = tup.1; + let tup = (1,2,3); + let v = tup.1; +} + "#, + r#" +fn main() { + let tup = (1,2); + let v = tup.1; + let ($0_0, _1, _2) = (1,2,3); + let v = _1; + let tup = (1,2,3); + let v = tup.1; +} + "#, + ) + } + + #[test] + fn keep_function_call_in_tuple_item() { + check_assist( + assist, + r#" +fn main() { + let $0t = ("3.14", 0); + let pi: f32 = t.0.parse().unwrap_or(0.0); +} + "#, + r#" +fn main() { + let ($0_0, _1) = ("3.14", 0); + let pi: f32 = _0.parse().unwrap_or(0.0); +} + "#, + ) + } + + #[test] + fn keep_type() { + check_assist( + assist, + r#" +fn main() { + let $0t: (usize, i32) = (1,2); +} + "#, + r#" +fn main() { + let ($0_0, _1): (usize, i32) = (1,2); +} + "#, + ) + } + + #[test] + fn destructure_reference() { + check_assist( + assist, + r#" +fn main() { + let t = (1,2); + let $0t = &t; + let v = t.0; +} + "#, + r#" +fn main() { + let t = (1,2); + let ($0_0, _1) = &t; + let v = *_0; +} + "#, + ) + } + + #[test] + fn destructure_multiple_reference() { + check_assist( + assist, + r#" +fn main() { + let t = (1,2); + let $0t = &&t; + let v = t.0; +} + "#, + r#" +fn main() { + let t = (1,2); + let ($0_0, _1) = &&t; + let v = *_0; +} + "#, + ) + } + + #[test] + fn keep_reference() { + check_assist( + assist, + r#" +fn foo(t: &(usize, usize)) -> usize { + match t { + &$0t => t.0 + } +} + "#, + r#" +fn foo(t: &(usize, usize)) -> usize { + match t { + &($0_0, _1) => _0 + } +} + "#, + ) + } + + #[test] + fn with_ref() { + check_assist( + assist, + r#" +fn main() { + let ref $0t = (1,2); + let v = t.0; +} + "#, + r#" +fn main() { + let (ref $0_0, ref _1) = (1,2); + let v = *_0; +} + "#, + ) + } + + #[test] + fn with_mut() { + check_assist( + assist, + r#" +fn main() { + let mut $0t = (1,2); + t.0 = 42; + let v = t.0; +} + "#, + r#" +fn main() { + let (mut $0_0, mut _1) = (1,2); + _0 = 42; + let v = _0; +} + "#, + ) + } + + #[test] + fn with_ref_mut() { + check_assist( + assist, + r#" +fn main() { + let ref mut $0t = (1,2); + t.0 = 42; + let v = t.0; +} + "#, + r#" +fn main() { + let (ref mut $0_0, ref mut _1) = (1,2); + *_0 = 42; + let v = *_0; +} + "#, + ) + } + + #[test] + fn dont_trigger_for_non_tuple_reference() { + check_assist_not_applicable( + assist, + r#" +fn main() { + let v = 42; + let $0v = &42; +} + "#, + ) + } + + #[test] + fn dont_trigger_on_static_tuple() { + check_assist_not_applicable( + assist, + r#" +static $0TUP: (usize, usize) = (1,2); + "#, + ) + } + + #[test] + fn dont_trigger_on_wildcard() { + check_assist_not_applicable( + assist, + r#" +fn main() { + let $0_ = (1,2); +} + "#, + ) + } + + #[test] + fn dont_trigger_in_struct() { + check_assist_not_applicable( + assist, + r#" +struct S { + $0tup: (usize, usize), +} + "#, + ) + } + + #[test] + fn dont_trigger_in_struct_creation() { + check_assist_not_applicable( + assist, + r#" +struct S { + tup: (usize, usize), +} +fn main() { + let s = S { + $0tup: (1,2), + }; +} + "#, + ) + } + + #[test] + fn dont_trigger_on_tuple_struct() { + check_assist_not_applicable( + assist, + r#" +struct S(usize, usize); +fn main() { + let $0s = S(1,2); +} + "#, + ) + } + + #[test] + fn dont_trigger_when_subpattern_exists() { + // sub-pattern is only allowed with IdentPat (name), not other patterns (like TuplePat) + cov_mark::check!(destructure_tuple_subpattern); + check_assist_not_applicable( + assist, + r#" +fn sum(t: (usize, usize)) -> usize { + match t { + $0t @ (1..=3,1..=3) => t.0 + t.1, + _ => 0, + } +} + "#, + ) + } + + #[test] + fn in_subpattern() { + check_assist( + assist, + r#" +fn main() { + let t1 @ (_, $0t2) = (1, (2,3)); + let v = t1.0 + t2.0 + t2.1; +} + "#, + r#" +fn main() { + let t1 @ (_, ($0_0, _1)) = (1, (2,3)); + let v = t1.0 + _0 + _1; +} + "#, + ) + } + + #[test] + fn in_nested_tuple() { + check_assist( + assist, + r#" +fn main() { + let ($0tup, v) = ((1,2),3); +} + "#, + r#" +fn main() { + let (($0_0, _1), v) = ((1,2),3); +} + "#, + ) + } + + #[test] + fn in_closure() { + check_assist( + assist, + r#" +fn main() { + let $0tup = (1,2,3); + let f = |v| v + tup.1; +} + "#, + r#" +fn main() { + let ($0_0, _1, _2) = (1,2,3); + let f = |v| v + _1; +} + "#, + ) + } + + #[test] + fn in_closure_args() { + check_assist( + assist, + r#" +fn main() { + let f = |$0t| t.0 + t.1; + let v = f((1,2)); +} + "#, + r#" +fn main() { + let f = |($0_0, _1)| _0 + _1; + let v = f((1,2)); +} + "#, + ) + } + + #[test] + fn in_function_args() { + check_assist( + assist, + r#" +fn f($0t: (usize, usize)) { + let v = t.0; +} + "#, + r#" +fn f(($0_0, _1): (usize, usize)) { + let v = _0; +} + "#, + ) + } + + #[test] + fn in_if_let() { + check_assist( + assist, + r#" +fn f(t: (usize, usize)) { + if let $0t = t { + let v = t.0; + } +} + "#, + r#" +fn f(t: (usize, usize)) { + if let ($0_0, _1) = t { + let v = _0; + } +} + "#, + ) + } + #[test] + fn in_if_let_option() { + check_assist( + assist, + r#" +//- minicore: option +fn f(o: Option<(usize, usize)>) { + if let Some($0t) = o { + let v = t.0; + } +} + "#, + r#" +fn f(o: Option<(usize, usize)>) { + if let Some(($0_0, _1)) = o { + let v = _0; + } +} + "#, + ) + } + + #[test] + fn in_match() { + check_assist( + assist, + r#" +fn main() { + match (1,2) { + $0t => t.1, + }; +} + "#, + r#" +fn main() { + match (1,2) { + ($0_0, _1) => _1, + }; +} + "#, + ) + } + #[test] + fn in_match_option() { + check_assist( + assist, + r#" +//- minicore: option +fn main() { + match Some((1,2)) { + Some($0t) => t.1, + _ => 0, + }; +} + "#, + r#" +fn main() { + match Some((1,2)) { + Some(($0_0, _1)) => _1, + _ => 0, + }; +} + "#, + ) + } + #[test] + fn in_match_reference_option() { + check_assist( + assist, + r#" +//- minicore: option +fn main() { + let t = (1,2); + match Some(&t) { + Some($0t) => t.1, + _ => 0, + }; +} + "#, + r#" +fn main() { + let t = (1,2); + match Some(&t) { + Some(($0_0, _1)) => *_1, + _ => 0, + }; +} + "#, + ) + } + + #[test] + fn in_for() { + check_assist( + assist, + r#" +//- minicore: iterators +fn main() { + for $0t in core::iter::repeat((1,2)) { + let v = t.1; + } +} + "#, + r#" +fn main() { + for ($0_0, _1) in core::iter::repeat((1,2)) { + let v = _1; + } +} + "#, + ) + } + #[test] + fn in_for_nested() { + check_assist( + assist, + r#" +//- minicore: iterators +fn main() { + for (a, $0b) in core::iter::repeat((1,(2,3))) { + let v = b.1; + } +} + "#, + r#" +fn main() { + for (a, ($0_0, _1)) in core::iter::repeat((1,(2,3))) { + let v = _1; + } +} + "#, + ) + } + + #[test] + fn not_applicable_on_tuple_usage() { + //Improvement: might be reasonable to allow & implement + check_assist_not_applicable( + assist, + r#" +fn main() { + let t = (1,2); + let v = $0t.0; +} + "#, + ) + } + + #[test] + fn replace_all() { + check_assist( + assist, + r#" +fn main() { + let $0t = (1,2); + let v = t.1; + let s = (t.0 + t.1) / 2; + let f = |v| v + t.0; + let r = f(t.1); + let e = t == (9,0); + let m = + match t { + (_,2) if t.0 > 2 => 1, + _ => 0, + }; +} + "#, + r#" +fn main() { + let ($0_0, _1) = (1,2); + let v = _1; + let s = (_0 + _1) / 2; + let f = |v| v + _0; + let r = f(_1); + let e = /*t*/ == (9,0); + let m = + match /*t*/ { + (_,2) if _0 > 2 => 1, + _ => 0, + }; +} + "#, + ) + } + + #[test] + fn non_trivial_tuple_assignment() { + check_assist( + assist, + r#" +fn main { + let $0t = + if 1 > 2 { + (1,2) + } else { + (5,6) + }; + let v1 = t.0; + let v2 = + if t.0 > t.1 { + t.0 - t.1 + } else { + t.1 - t.0 + }; +} + "#, + r#" +fn main { + let ($0_0, _1) = + if 1 > 2 { + (1,2) + } else { + (5,6) + }; + let v1 = _0; + let v2 = + if _0 > _1 { + _0 - _1 + } else { + _1 - _0 + }; +} + "#, + ) + } + + mod assist { + use super::*; + use crate::tests::check_assist_by_label; + + fn assist(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + destructure_tuple_binding_impl(acc, ctx, true) + } + fn in_place_assist(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + destructure_tuple_binding_impl(acc, ctx, false) + } + + pub(crate) fn check_in_place_assist(ra_fixture_before: &str, ra_fixture_after: &str) { + check_assist_by_label( + in_place_assist, + ra_fixture_before, + ra_fixture_after, + // "Destructure tuple in place", + "Destructure tuple", + ); + } + + pub(crate) fn check_sub_pattern_assist(ra_fixture_before: &str, ra_fixture_after: &str) { + check_assist_by_label( + assist, + ra_fixture_before, + ra_fixture_after, + "Destructure tuple in sub-pattern", + ); + } + + pub(crate) fn check_both_assists( + ra_fixture_before: &str, + ra_fixture_after_in_place: &str, + ra_fixture_after_in_sub_pattern: &str, + ) { + check_in_place_assist(ra_fixture_before, ra_fixture_after_in_place); + check_sub_pattern_assist(ra_fixture_before, ra_fixture_after_in_sub_pattern); + } + } + + /// Tests for destructure of tuple in sub-pattern: + /// `let $0t = (1,2);` -> `let t @ (_0, _1) = (1,2);` + mod sub_pattern { + use super::assist::*; + use super::*; + use crate::tests::check_assist_by_label; + + #[test] + fn destructure_in_sub_pattern() { + check_sub_pattern_assist( + r#" +#![feature(bindings_after_at)] + +fn main() { + let $0t = (1,2); +} + "#, + r#" +#![feature(bindings_after_at)] + +fn main() { + let t @ ($0_0, _1) = (1,2); +} + "#, + ) + } + + #[test] + fn trigger_both_destructure_tuple_assists() { + fn assist(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + destructure_tuple_binding_impl(acc, ctx, true) + } + let text = r#" +fn main() { + let $0t = (1,2); +} + "#; + check_assist_by_label( + assist, + text, + r#" +fn main() { + let ($0_0, _1) = (1,2); +} + "#, + "Destructure tuple in place", + ); + check_assist_by_label( + assist, + text, + r#" +fn main() { + let t @ ($0_0, _1) = (1,2); +} + "#, + "Destructure tuple in sub-pattern", + ); + } + + #[test] + fn replace_indices() { + check_sub_pattern_assist( + r#" +fn main() { + let $0t = (1,2); + let v1 = t.0; + let v2 = t.1; +} + "#, + r#" +fn main() { + let t @ ($0_0, _1) = (1,2); + let v1 = _0; + let v2 = _1; +} + "#, + ) + } + + #[test] + fn keep_function_call() { + cov_mark::check!(destructure_tuple_call_with_subpattern); + check_sub_pattern_assist( + r#" +fn main() { + let $0t = (1,2); + let v = t.into(); +} + "#, + r#" +fn main() { + let t @ ($0_0, _1) = (1,2); + let v = t.into(); +} + "#, + ) + } + + #[test] + fn keep_type() { + check_sub_pattern_assist( + r#" +fn main() { + let $0t: (usize, i32) = (1,2); + let v = t.1; + let f = t.into(); +} + "#, + r#" +fn main() { + let t @ ($0_0, _1): (usize, i32) = (1,2); + let v = _1; + let f = t.into(); +} + "#, + ) + } + + #[test] + fn in_function_args() { + check_sub_pattern_assist( + r#" +fn f($0t: (usize, usize)) { + let v = t.0; + let f = t.into(); +} + "#, + r#" +fn f(t @ ($0_0, _1): (usize, usize)) { + let v = _0; + let f = t.into(); +} + "#, + ) + } + + #[test] + fn with_ref() { + check_sub_pattern_assist( + r#" +fn main() { + let ref $0t = (1,2); + let v = t.1; + let f = t.into(); +} + "#, + r#" +fn main() { + let ref t @ (ref $0_0, ref _1) = (1,2); + let v = *_1; + let f = t.into(); +} + "#, + ) + } + #[test] + fn with_mut() { + check_sub_pattern_assist( + r#" +fn main() { + let mut $0t = (1,2); + let v = t.1; + let f = t.into(); +} + "#, + r#" +fn main() { + let mut t @ (mut $0_0, mut _1) = (1,2); + let v = _1; + let f = t.into(); +} + "#, + ) + } + #[test] + fn with_ref_mut() { + check_sub_pattern_assist( + r#" +fn main() { + let ref mut $0t = (1,2); + let v = t.1; + let f = t.into(); +} + "#, + r#" +fn main() { + let ref mut t @ (ref mut $0_0, ref mut _1) = (1,2); + let v = *_1; + let f = t.into(); +} + "#, + ) + } + } + + /// Tests for tuple usage in macro call: + /// `println!("{}", t.0)` + mod in_macro_call { + use super::assist::*; + + #[test] + fn detect_macro_call() { + cov_mark::check!(destructure_tuple_macro_call); + check_in_place_assist( + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let $0t = (1,2); + m!(t.0); +} + "#, + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let ($0_0, _1) = (1,2); + m!(/*t*/.0); +} + "#, + ) + } + + #[test] + fn tuple_usage() { + check_both_assists( + // leading `"foo"` to ensure `$e` doesn't start at position `0` + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let $0t = (1,2); + m!(t); +} + "#, + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let ($0_0, _1) = (1,2); + m!(/*t*/); +} + "#, + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let t @ ($0_0, _1) = (1,2); + m!(t); +} + "#, + ) + } + + #[test] + fn tuple_function_usage() { + check_both_assists( + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let $0t = (1,2); + m!(t.into()); +} + "#, + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let ($0_0, _1) = (1,2); + m!(/*t*/.into()); +} + "#, + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let t @ ($0_0, _1) = (1,2); + m!(t.into()); +} + "#, + ) + } + + #[test] + fn tuple_index_usage() { + check_both_assists( + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let $0t = (1,2); + m!(t.0); +} + "#, + // FIXME: replace `t.0` with `_0` (cannot detect range of tuple index in macro call) + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let ($0_0, _1) = (1,2); + m!(/*t*/.0); +} + "#, + // FIXME: replace `t.0` with `_0` + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let t @ ($0_0, _1) = (1,2); + m!(t.0); +} + "#, + ) + } + + #[test] + fn tuple_in_parentheses_index_usage() { + check_both_assists( + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let $0t = (1,2); + m!((t).0); +} + "#, + // FIXME: replace `(t).0` with `_0` + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let ($0_0, _1) = (1,2); + m!((/*t*/).0); +} + "#, + // FIXME: replace `(t).0` with `_0` + r#" +macro_rules! m { + ($e:expr) => { "foo"; $e }; +} + +fn main() { + let t @ ($0_0, _1) = (1,2); + m!((t).0); +} + "#, + ) + } + + #[test] + fn empty_macro() { + check_in_place_assist( + r#" +macro_rules! m { + () => { "foo" }; + ($e:expr) => { $e; "foo" }; +} + +fn main() { + let $0t = (1,2); + m!(t); +} + "#, + // FIXME: macro allows no arg -> is valid. But assist should result in invalid code + r#" +macro_rules! m { + () => { "foo" }; + ($e:expr) => { $e; "foo" }; +} + +fn main() { + let ($0_0, _1) = (1,2); + m!(/*t*/); +} + "#, + ) + } + + #[test] + fn tuple_index_in_macro() { + check_both_assists( + r#" +macro_rules! m { + ($t:expr, $i:expr) => { $t.0 + $i }; +} + +fn main() { + let $0t = (1,2); + m!(t, t.0); +} + "#, + // FIXME: replace `t.0` in macro call (not IN macro) with `_0` + r#" +macro_rules! m { + ($t:expr, $i:expr) => { $t.0 + $i }; +} + +fn main() { + let ($0_0, _1) = (1,2); + m!(/*t*/, /*t*/.0); +} + "#, + // FIXME: replace `t.0` in macro call with `_0` + r#" +macro_rules! m { + ($t:expr, $i:expr) => { $t.0 + $i }; +} + +fn main() { + let t @ ($0_0, _1) = (1,2); + m!(t, t.0); +} + "#, + ) + } + } + + mod refs { + use super::assist::*; + + #[test] + fn no_ref() { + check_in_place_assist( + r#" +fn main() { + let $0t = &(1,2); + let v: i32 = t.0; +} + "#, + r#" +fn main() { + let ($0_0, _1) = &(1,2); + let v: i32 = *_0; +} + "#, + ) + } + #[test] + fn no_ref_with_parens() { + check_in_place_assist( + r#" +fn main() { + let $0t = &(1,2); + let v: i32 = (t.0); +} + "#, + r#" +fn main() { + let ($0_0, _1) = &(1,2); + let v: i32 = (*_0); +} + "#, + ) + } + #[test] + fn with_ref() { + check_in_place_assist( + r#" +fn main() { + let $0t = &(1,2); + let v: &i32 = &t.0; +} + "#, + r#" +fn main() { + let ($0_0, _1) = &(1,2); + let v: &i32 = _0; +} + "#, + ) + } + #[test] + fn with_ref_in_parens_ref() { + check_in_place_assist( + r#" +fn main() { + let $0t = &(1,2); + let v: &i32 = &(t.0); +} + "#, + r#" +fn main() { + let ($0_0, _1) = &(1,2); + let v: &i32 = _0; +} + "#, + ) + } + #[test] + fn with_ref_in_ref_parens() { + check_in_place_assist( + r#" +fn main() { + let $0t = &(1,2); + let v: &i32 = (&t.0); +} + "#, + r#" +fn main() { + let ($0_0, _1) = &(1,2); + let v: &i32 = _0; +} + "#, + ) + } + + #[test] + fn deref_and_parentheses() { + // Operator/Expressions with higher precedence than deref (`*`): + // https://doc.rust-lang.org/reference/expressions.html#expression-precedence + // * Path + // * Method call + // * Field expression + // * Function calls, array indexing + // * `?` + check_in_place_assist( + r#" +//- minicore: option +fn f1(v: i32) {} +fn f2(v: &i32) {} +trait T { + fn do_stuff(self) {} +} +impl T for i32 { + fn do_stuff(self) {} +} +impl T for &i32 { + fn do_stuff(self) {} +} +struct S4 { + value: i32, +} + +fn foo() -> Option<()> { + let $0t = &(0, (1,"1"), Some(2), [3;3], S4 { value: 4 }, &5); + let v: i32 = t.0; // deref, no parens + let v: &i32 = &t.0; // no deref, no parens, remove `&` + f1(t.0); // deref, no parens + f2(&t.0); // `&*` -> cancel out -> no deref, no parens + // https://github.com/rust-lang/rust-analyzer/issues/1109#issuecomment-658868639 + // let v: i32 = t.1.0; // no deref, no parens + let v: i32 = t.4.value; // no deref, no parens + t.0.do_stuff(); // deref, parens + let v: i32 = t.2?; // deref, parens + let v: i32 = t.3[0]; // no deref, no parens + (t.0).do_stuff(); // deref, no additional parens + let v: i32 = *t.5; // deref (-> 2), no parens + + None +} + "#, + r#" +fn f1(v: i32) {} +fn f2(v: &i32) {} +trait T { + fn do_stuff(self) {} +} +impl T for i32 { + fn do_stuff(self) {} +} +impl T for &i32 { + fn do_stuff(self) {} +} +struct S4 { + value: i32, +} + +fn foo() -> Option<()> { + let ($0_0, _1, _2, _3, _4, _5) = &(0, (1,"1"), Some(2), [3;3], S4 { value: 4 }, &5); + let v: i32 = *_0; // deref, no parens + let v: &i32 = _0; // no deref, no parens, remove `&` + f1(*_0); // deref, no parens + f2(_0); // `&*` -> cancel out -> no deref, no parens + // https://github.com/rust-lang/rust-analyzer/issues/1109#issuecomment-658868639 + // let v: i32 = t.1.0; // no deref, no parens + let v: i32 = _4.value; // no deref, no parens + (*_0).do_stuff(); // deref, parens + let v: i32 = (*_2)?; // deref, parens + let v: i32 = _3[0]; // no deref, no parens + (*_0).do_stuff(); // deref, no additional parens + let v: i32 = **_5; // deref (-> 2), no parens + + None +} + "#, + ) + } + + // --------- + // auto-ref/deref + + #[test] + fn self_auto_ref_doesnt_need_deref() { + check_in_place_assist( + r#" +#[derive(Clone, Copy)] +struct S; +impl S { + fn f(&self) {} +} + +fn main() { + let $0t = &(S,2); + let s = t.0.f(); +} + "#, + r#" +#[derive(Clone, Copy)] +struct S; +impl S { + fn f(&self) {} +} + +fn main() { + let ($0_0, _1) = &(S,2); + let s = _0.f(); +} + "#, + ) + } + + #[test] + fn self_owned_requires_deref() { + check_in_place_assist( + r#" +#[derive(Clone, Copy)] +struct S; +impl S { + fn f(self) {} +} + +fn main() { + let $0t = &(S,2); + let s = t.0.f(); +} + "#, + r#" +#[derive(Clone, Copy)] +struct S; +impl S { + fn f(self) {} +} + +fn main() { + let ($0_0, _1) = &(S,2); + let s = (*_0).f(); +} + "#, + ) + } + + #[test] + fn self_auto_ref_in_trait_call_doesnt_require_deref() { + check_in_place_assist( + r#" +trait T { + fn f(self); +} +#[derive(Clone, Copy)] +struct S; +impl T for &S { + fn f(self) {} +} + +fn main() { + let $0t = &(S,2); + let s = t.0.f(); +} + "#, + // FIXME: doesn't need deref * parens. But `ctx.sema.resolve_method_call` doesn't resolve trait implementations + r#" +trait T { + fn f(self); +} +#[derive(Clone, Copy)] +struct S; +impl T for &S { + fn f(self) {} +} + +fn main() { + let ($0_0, _1) = &(S,2); + let s = (*_0).f(); +} + "#, + ) + } + #[test] + fn no_auto_deref_because_of_owned_and_ref_trait_impl() { + check_in_place_assist( + r#" +trait T { + fn f(self); +} +#[derive(Clone, Copy)] +struct S; +impl T for S { + fn f(self) {} +} +impl T for &S { + fn f(self) {} +} + +fn main() { + let $0t = &(S,2); + let s = t.0.f(); +} + "#, + r#" +trait T { + fn f(self); +} +#[derive(Clone, Copy)] +struct S; +impl T for S { + fn f(self) {} +} +impl T for &S { + fn f(self) {} +} + +fn main() { + let ($0_0, _1) = &(S,2); + let s = (*_0).f(); +} + "#, + ) + } + + #[test] + fn no_outer_parens_when_ref_deref() { + check_in_place_assist( + r#" +#[derive(Clone, Copy)] +struct S; +impl S { + fn do_stuff(&self) -> i32 { 42 } +} +fn main() { + let $0t = &(S,&S); + let v = (&t.0).do_stuff(); +} + "#, + r#" +#[derive(Clone, Copy)] +struct S; +impl S { + fn do_stuff(&self) -> i32 { 42 } +} +fn main() { + let ($0_0, _1) = &(S,&S); + let v = _0.do_stuff(); +} + "#, + ) + } + + #[test] + fn auto_ref_deref() { + check_in_place_assist( + r#" +#[derive(Clone, Copy)] +struct S; +impl S { + fn do_stuff(&self) -> i32 { 42 } +} +fn main() { + let $0t = &(S,&S); + let v = (&t.0).do_stuff(); // no deref, remove parens + // `t.0` gets auto-refed -> no deref needed -> no parens + let v = t.0.do_stuff(); // no deref, no parens + let v = &t.0.do_stuff(); // `&` is for result -> no deref, no parens + // deref: `_1` is `&&S`, but method called is on `&S` -> there might be a method accepting `&&S` + let v = t.1.do_stuff(); // deref, parens +} + "#, + r#" +#[derive(Clone, Copy)] +struct S; +impl S { + fn do_stuff(&self) -> i32 { 42 } +} +fn main() { + let ($0_0, _1) = &(S,&S); + let v = _0.do_stuff(); // no deref, remove parens + // `t.0` gets auto-refed -> no deref needed -> no parens + let v = _0.do_stuff(); // no deref, no parens + let v = &_0.do_stuff(); // `&` is for result -> no deref, no parens + // deref: `_1` is `&&S`, but method called is on `&S` -> there might be a method accepting `&&S` + let v = (*_1).do_stuff(); // deref, parens +} + "#, + ) + } + + #[test] + fn mutable() { + check_in_place_assist( + r#" +fn f_owned(v: i32) {} +fn f(v: &i32) {} +fn f_mut(v: &mut i32) { *v = 42; } + +fn main() { + let $0t = &mut (1,2); + let v = t.0; + t.0 = 42; + f_owned(t.0); + f(&t.0); + f_mut(&mut t.0); +} + "#, + r#" +fn f_owned(v: i32) {} +fn f(v: &i32) {} +fn f_mut(v: &mut i32) { *v = 42; } + +fn main() { + let ($0_0, _1) = &mut (1,2); + let v = *_0; + *_0 = 42; + f_owned(*_0); + f(_0); + f_mut(_0); +} + "#, + ) + } + + #[test] + fn with_ref_keyword() { + check_in_place_assist( + r#" +fn f_owned(v: i32) {} +fn f(v: &i32) {} + +fn main() { + let ref $0t = (1,2); + let v = t.0; + f_owned(t.0); + f(&t.0); +} + "#, + r#" +fn f_owned(v: i32) {} +fn f(v: &i32) {} + +fn main() { + let (ref $0_0, ref _1) = (1,2); + let v = *_0; + f_owned(*_0); + f(_0); +} + "#, + ) + } + #[test] + fn with_ref_mut_keywords() { + check_in_place_assist( + r#" +fn f_owned(v: i32) {} +fn f(v: &i32) {} +fn f_mut(v: &mut i32) { *v = 42; } + +fn main() { + let ref mut $0t = (1,2); + let v = t.0; + t.0 = 42; + f_owned(t.0); + f(&t.0); + f_mut(&mut t.0); +} + "#, + r#" +fn f_owned(v: i32) {} +fn f(v: &i32) {} +fn f_mut(v: &mut i32) { *v = 42; } + +fn main() { + let (ref mut $0_0, ref mut _1) = (1,2); + let v = *_0; + *_0 = 42; + f_owned(*_0); + f(_0); + f_mut(_0); +} + "#, + ) + } + } +} |