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::()?; 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 { 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::>(); 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, ) -> 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, field_names: Vec, // field_types: Vec, usages: Option, } 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 { // 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 { 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); } "#, ) } } }