| 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::{AssistContext, Assists, SourceChangeBuilder}; |
| |
| // 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_binding_in_pat(&ident_pat)?; |
| 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 SourceChangeBuilder, |
| 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 SourceChangeBuilder, |
| 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 SourceChangeBuilder, |
| 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 SourceChangeBuilder, |
| 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); |
| } |
| "#, |
| ) |
| } |
| } |
| } |