2148 lines
45 KiB
Rust
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::{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_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 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);
|
|
}
|
|
"#,
|
|
)
|
|
}
|
|
}
|
|
}
|