use syntax::T;
use syntax::ast::RangeItem;
use syntax::ast::edit::AstNodeEdit;
use syntax::ast::syntax_factory::SyntaxFactory;
use syntax::ast::{self, AstNode, HasName, LetStmt, Pat};
use crate::{AssistContext, AssistId, Assists};
// Assist: convert_let_else_to_match
//
// Converts let-else statement to let statement and match expression.
//
// ```
// fn main() {
// let Ok(mut x) = f() else$0 { return };
// }
// ```
// ->
// ```
// fn main() {
// let mut x = match f() {
// Ok(x) => x,
// _ => return,
// };
// }
// ```
pub(crate) fn convert_let_else_to_match(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
// Should focus on the `else` token to trigger
let let_stmt = ctx
.find_token_syntax_at_offset(T![else])
.and_then(|it| it.parent()?.parent())
.or_else(|| ctx.find_token_syntax_at_offset(T![let])?.parent())?;
let let_stmt = LetStmt::cast(let_stmt)?;
let else_block = let_stmt.let_else()?.block_expr()?;
let else_expr = if else_block.statements().next().is_none() {
else_block.tail_expr()?
} else {
else_block.into()
};
let init = let_stmt.initializer()?;
// Ignore let stmt with type annotation
if let_stmt.ty().is_some() {
return None;
}
let pat = let_stmt.pat()?;
let make = SyntaxFactory::with_mappings();
let mut idents = Vec::default();
let pat_without_mut = remove_mut_and_collect_idents(&make, &pat, &mut idents)?;
let bindings = idents
.into_iter()
.filter_map(|ref pat| {
// Identifiers which resolve to constants are not bindings
if ctx.sema.resolve_bind_pat_to_const(pat).is_none() {
Some((pat.name()?, pat.ref_token().is_none() && pat.mut_token().is_some()))
} else {
None
}
})
.collect::<Vec<_>>();
acc.add(
AssistId::refactor_rewrite("convert_let_else_to_match"),
if bindings.is_empty() {
"Convert let-else to match"
} else {
"Convert let-else to let and match"
},
let_stmt.syntax().text_range(),
|builder| {
let mut editor = builder.make_editor(let_stmt.syntax());
let binding_paths = bindings
.iter()
.map(|(name, _)| make.expr_path(make.ident_path(&name.to_string())))
.collect::<Vec<_>>();
let binding_arm = make.match_arm(
pat_without_mut,
None,
// There are three possible cases:
//
// - No bindings: `None => {}`
// - Single binding: `Some(it) => it`
// - Multiple bindings: `Foo::Bar { a, b, .. } => (a, b)`
match binding_paths.len() {
0 => make.expr_empty_block().into(),
1 => binding_paths[0].clone(),
_ => make.expr_tuple(binding_paths).into(),
},
);
let else_arm = make.match_arm(make.wildcard_pat().into(), None, else_expr);
let match_ = make.expr_match(init, make.match_arm_list([binding_arm, else_arm]));
let match_ = match_.reset_indent();
let match_ = match_.indent(let_stmt.indent_level());
if bindings.is_empty() {
editor.replace(let_stmt.syntax(), match_.syntax());
} else {
let ident_pats = bindings
.into_iter()
.map(|(name, is_mut)| make.ident_pat(false, is_mut, name).into())
.collect::<Vec<Pat>>();
let new_let_stmt = make.let_stmt(
if ident_pats.len() == 1 {
ident_pats[0].clone()
} else {
make.tuple_pat(ident_pats).into()
},
None,
Some(match_.into()),
);
editor.replace(let_stmt.syntax(), new_let_stmt.syntax());
}
editor.add_mappings(make.finish_with_mappings());
builder.add_file_edits(ctx.vfs_file_id(), editor);
},
)
}
fn remove_mut_and_collect_idents(
make: &SyntaxFactory,
pat: &ast::Pat,
acc: &mut Vec<ast::IdentPat>,
) -> Option<ast::Pat> {
Some(match pat {
ast::Pat::IdentPat(p) => {
acc.push(p.clone());
let non_mut_pat = make.ident_pat(
p.ref_token().is_some(),
p.ref_token().is_some() && p.mut_token().is_some(),
p.name()?,
);
if let Some(inner) = p.pat() {
non_mut_pat.set_pat(remove_mut_and_collect_idents(make, &inner, acc));
}
non_mut_pat.into()
}
ast::Pat::BoxPat(p) => {
make.box_pat(remove_mut_and_collect_idents(make, &p.pat()?, acc)?).into()
}
ast::Pat::OrPat(p) => make
.or_pat(
p.pats()
.map(|pat| remove_mut_and_collect_idents(make, &pat, acc))
.collect::<Option<Vec<_>>>()?,
p.leading_pipe().is_some(),
)
.into(),
ast::Pat::ParenPat(p) => {
make.paren_pat(remove_mut_and_collect_idents(make, &p.pat()?, acc)?).into()
}
ast::Pat::RangePat(p) => make
.range_pat(
if let Some(start) = p.start() {
Some(remove_mut_and_collect_idents(make, &start, acc)?)
} else {
None
},
if let Some(end) = p.end() {
Some(remove_mut_and_collect_idents(make, &end, acc)?)
} else {
None
},
)
.into(),
ast::Pat::RecordPat(p) => make
.record_pat_with_fields(
p.path()?,
make.record_pat_field_list(
p.record_pat_field_list()?
.fields()
.map(|field| {
remove_mut_and_collect_idents(make, &field.pat()?, acc).map(|pat| {
if let Some(name_ref) = field.name_ref() {
make.record_pat_field(name_ref, pat)
} else {
make.record_pat_field_shorthand(pat)
}
})
})
.collect::<Option<Vec<_>>>()?,
p.record_pat_field_list()?.rest_pat(),
),
)
.into(),
ast::Pat::RefPat(p) => {
let inner = p.pat()?;
if let ast::Pat::IdentPat(ident) = inner {
acc.push(ident);
p.clone_for_update().into()
} else {
make.ref_pat(remove_mut_and_collect_idents(make, &inner, acc)?).into()
}
}
ast::Pat::SlicePat(p) => make
.slice_pat(
p.pats()
.map(|pat| remove_mut_and_collect_idents(make, &pat, acc))
.collect::<Option<Vec<_>>>()?,
)
.into(),
ast::Pat::TuplePat(p) => make
.tuple_pat(
p.fields()
.map(|field| remove_mut_and_collect_idents(make, &field, acc))
.collect::<Option<Vec<_>>>()?,
)
.into(),
ast::Pat::TupleStructPat(p) => make
.tuple_struct_pat(
p.path()?,
p.fields()
.map(|field| remove_mut_and_collect_idents(make, &field, acc))
.collect::<Option<Vec<_>>>()?,
)
.into(),
ast::Pat::RestPat(_)
| ast::Pat::LiteralPat(_)
| ast::Pat::PathPat(_)
| ast::Pat::WildcardPat(_)
| ast::Pat::ConstBlockPat(_) => pat.clone(),
// don't support macro pat yet
ast::Pat::MacroPat(_) => return None,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
#[test]
fn convert_let_else_to_match_no_type_let() {
check_assist_not_applicable(
convert_let_else_to_match,
r#"
fn main() {
let 1: u32 = v.iter().sum() else$0 { return };
}"#,
);
}
#[test]
fn convert_let_else_to_match_on_else() {
check_assist_not_applicable(
convert_let_else_to_match,
r#"
fn main() {
let Ok(x) = f() else {$0 return };
}
"#,
);
}
#[test]
fn convert_let_else_to_match_no_macropat() {
check_assist_not_applicable(
convert_let_else_to_match,
r#"
fn main() {
let m!() = g() else$0 { return };
}
"#,
);
}
#[test]
fn convert_let_else_to_match_target() {
check_assist_target(
convert_let_else_to_match,
r"
fn main() {
let Ok(x) = f() else$0 { continue };
}",
"let Ok(x) = f() else { continue };",
);
}
#[test]
fn convert_let_else_to_match_basic() {
check_assist(
convert_let_else_to_match,
r"
fn main() {
let Ok(x) = f() else$0 { continue };
}",
r"
fn main() {
let x = match f() {
Ok(x) => x,
_ => continue,
};
}",
);
}
#[test]
fn convert_let_else_to_match_const_ref() {
check_assist(
convert_let_else_to_match,
r"
enum Option<T> {
Some(T),
None,
}
use Option::*;
fn main() {
let None = f() el$0se { continue };
}",
r"
enum Option<T> {
Some(T),
None,
}
use Option::*;
fn main() {
match f() {
None => {}
_ => continue,
}
}",
);
}
#[test]
fn convert_let_else_to_match_const_ref_const() {
check_assist(
convert_let_else_to_match,
r"
const NEG1: i32 = -1;
fn main() {
let NEG1 = f() el$0se { continue };
}",
r"
const NEG1: i32 = -1;
fn main() {
match f() {
NEG1 => {}
_ => continue,
}
}",
);
}
#[test]
fn convert_let_else_to_match_mut() {
check_assist(
convert_let_else_to_match,
r"
fn main() {
let Ok(mut x) = f() el$0se { continue };
}",
r"
fn main() {
let mut x = match f() {
Ok(x) => x,
_ => continue,
};
}",
);
}
#[test]
fn convert_let_else_to_match_multi_binders() {
check_assist(
convert_let_else_to_match,
r#"
fn main() {
let ControlFlow::Break((x, "tag", y, ..)) = f() else$0 { g(); return };
}"#,
r#"
fn main() {
let (x, y) = match f() {
ControlFlow::Break((x, "tag", y, ..)) => (x, y),
_ => { g(); return }
};
}"#,
);
}
#[test]
fn convert_let_else_to_match_slice() {
check_assist(
convert_let_else_to_match,
r#"
fn main() {
let [one, 1001, other] = f() else$0 { break };
}"#,
r#"
fn main() {
let (one, other) = match f() {
[one, 1001, other] => (one, other),
_ => break,
};
}"#,
);
}
#[test]
fn convert_let_else_to_match_struct() {
check_assist(
convert_let_else_to_match,
r#"
fn main() {
let [Struct { inner: Some(it) }, 1001, other] = f() else$0 { break };
}"#,
r#"
fn main() {
let (it, other) = match f() {
[Struct { inner: Some(it) }, 1001, other] => (it, other),
_ => break,
};
}"#,
);
}
#[test]
fn convert_let_else_to_match_struct_ident_pat() {
check_assist(
convert_let_else_to_match,
r#"
fn main() {
let [Struct { inner }, 1001, other] = f() else$0 { break };
}"#,
r#"
fn main() {
let (inner, other) = match f() {
[Struct { inner }, 1001, other] => (inner, other),
_ => break,
};
}"#,
);
}
#[test]
fn convert_let_else_to_match_no_binder() {
check_assist(
convert_let_else_to_match,
r#"
fn main() {
let (8 | 9) = f() else$0 { panic!() };
}"#,
r#"
fn main() {
match f() {
(8 | 9) => {}
_ => panic!(),
}
}"#,
);
}
#[test]
fn convert_let_else_to_match_range() {
check_assist(
convert_let_else_to_match,
r#"
fn main() {
let 1.. = f() e$0lse { return };
}"#,
r#"
fn main() {
match f() {
1.. => {}
_ => return,
}
}"#,
);
}
#[test]
fn convert_let_else_to_match_refpat() {
check_assist(
convert_let_else_to_match,
r#"
fn main() {
let Ok(&mut x) = f(&mut 0) else$0 { return };
}"#,
r#"
fn main() {
let x = match f(&mut 0) {
Ok(&mut x) => x,
_ => return,
};
}"#,
);
}
#[test]
fn convert_let_else_to_match_refmut() {
check_assist(
convert_let_else_to_match,
r#"
fn main() {
let Ok(ref mut x) = f() else$0 { return };
}"#,
r#"
fn main() {
let x = match f() {
Ok(ref mut x) => x,
_ => return,
};
}"#,
);
}
#[test]
fn convert_let_else_to_match_atpat() {
check_assist(
convert_let_else_to_match,
r#"
fn main() {
let out @ Ok(ins) = f() else$0 { return };
}"#,
r#"
fn main() {
let (out, ins) = match f() {
out @ Ok(ins) => (out, ins),
_ => return,
};
}"#,
);
}
#[test]
fn convert_let_else_to_match_complex_init() {
check_assist(
convert_let_else_to_match,
r#"
fn main() {
let v = vec![1, 2, 3];
let &[mut x, y, ..] = &v.iter().collect::<Vec<_>>()[..] else$0 { return };
}"#,
r#"
fn main() {
let v = vec![1, 2, 3];
let (mut x, y) = match &v.iter().collect::<Vec<_>>()[..] {
&[x, y, ..] => (x, y),
_ => return,
};
}"#,
);
}
}