rust/crates/ide_assists/src/handlers/destructure_tuple_binding.rs
Florian Diebold 6fb5abbc03 Refactor autoderef and method resolution
- don't return the receiver type from method resolution; instead just
 return the autorefs/autoderefs that happened and repeat them. This
 ensures all the effects like trait obligations and whatever we learned
 about type variables from derefing them are actually applied. Also, it
 allows us to get rid of `decanonicalize_ty`, which was just wrong in
 principle.

 - Autoderef itself now directly works with an inference table. Sadly
 this has the effect of making it harder to use as an iterator, often
 requiring manual `while let` loops. (rustc works around this by using
 inner mutability in the inference context, so that things like unifying
 types don't require a unique reference.)

 - We now record the adjustments (autoref/deref) for method receivers
 and index expressions, which we didn't before.

 - Removed the redundant crate parameter from method resolution, since
 the trait_env contains the crate as well.

 - in the HIR API, the methods now take a scope to determine the trait env.
 `Type` carries a trait env, but I think that's probably a bad decision
 because it's easy to create it with the wrong env, e.g. by using
 `Adt::ty`. This mostly didn't matter so far because
 `iterate_method_candidates` took a crate parameter and ignored
 `self.krate`, but the trait env would still have been wrong in those
 cases, which I think would give some wrong results in some edge cases.

Fixes #10058.
2022-02-25 11:47:14 +01:00

2148 lines
45 KiB
Rust

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-analyzer/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-analyzer/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);
}
"#,
)
}
}
}