internal: Migrate unwrap_return_type assist to use SyntaxEditor

Also changes `make::expr_empty_block()` to return `ast::BlockExpr` instead of `ast::Expr`
This commit is contained in:
Giga Bowser 2024-11-17 14:03:28 -05:00
parent 651b43e551
commit a5a79f5957
4 changed files with 75 additions and 57 deletions

View file

@ -1,11 +1,11 @@
use either::Either;
use ide_db::{ use ide_db::{
famous_defs::FamousDefs, famous_defs::FamousDefs,
syntax_helpers::node_ext::{for_each_tail_expr, walk_expr}, syntax_helpers::node_ext::{for_each_tail_expr, walk_expr},
}; };
use itertools::Itertools;
use syntax::{ use syntax::{
ast::{self, Expr, HasGenericArgs}, ast::{self, syntax_factory::SyntaxFactory, HasArgList, HasGenericArgs},
match_ast, AstNode, NodeOrToken, SyntaxKind, TextRange, match_ast, AstNode, NodeOrToken, SyntaxKind,
}; };
use crate::{AssistContext, AssistId, AssistKind, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
@ -39,11 +39,11 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
pub(crate) fn unwrap_return_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { pub(crate) fn unwrap_return_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let ret_type = ctx.find_node_at_offset::<ast::RetType>()?; let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
let parent = ret_type.syntax().parent()?; let parent = ret_type.syntax().parent()?;
let body = match_ast! { let body_expr = match_ast! {
match parent { match parent {
ast::Fn(func) => func.body()?, ast::Fn(func) => func.body()?.into(),
ast::ClosureExpr(closure) => match closure.body()? { ast::ClosureExpr(closure) => match closure.body()? {
Expr::BlockExpr(block) => block, ast::Expr::BlockExpr(block) => block.into(),
// closures require a block when a return type is specified // closures require a block when a return type is specified
_ => return None, _ => return None,
}, },
@ -65,72 +65,94 @@ pub(crate) fn unwrap_return_type(acc: &mut Assists, ctx: &AssistContext<'_>) ->
let happy_type = extract_wrapped_type(type_ref)?; let happy_type = extract_wrapped_type(type_ref)?;
acc.add(kind.assist_id(), kind.label(), type_ref.syntax().text_range(), |builder| { acc.add(kind.assist_id(), kind.label(), type_ref.syntax().text_range(), |builder| {
let body = ast::Expr::BlockExpr(body); let mut editor = builder.make_editor(&parent);
let make = SyntaxFactory::new();
let mut exprs_to_unwrap = Vec::new(); let mut exprs_to_unwrap = Vec::new();
let tail_cb = &mut |e: &_| tail_cb_impl(&mut exprs_to_unwrap, e); let tail_cb = &mut |e: &_| tail_cb_impl(&mut exprs_to_unwrap, e);
walk_expr(&body, &mut |expr| { walk_expr(&body_expr, &mut |expr| {
if let Expr::ReturnExpr(ret_expr) = expr { if let ast::Expr::ReturnExpr(ret_expr) = expr {
if let Some(ret_expr_arg) = &ret_expr.expr() { if let Some(ret_expr_arg) = &ret_expr.expr() {
for_each_tail_expr(ret_expr_arg, tail_cb); for_each_tail_expr(ret_expr_arg, tail_cb);
} }
} }
}); });
for_each_tail_expr(&body, tail_cb); for_each_tail_expr(&body_expr, tail_cb);
let is_unit_type = is_unit_type(&happy_type); let is_unit_type = is_unit_type(&happy_type);
if is_unit_type { if is_unit_type {
let mut text_range = ret_type.syntax().text_range();
if let Some(NodeOrToken::Token(token)) = ret_type.syntax().next_sibling_or_token() { if let Some(NodeOrToken::Token(token)) = ret_type.syntax().next_sibling_or_token() {
if token.kind() == SyntaxKind::WHITESPACE { if token.kind() == SyntaxKind::WHITESPACE {
text_range = TextRange::new(text_range.start(), token.text_range().end()); editor.delete(token);
} }
} }
builder.delete(text_range); editor.delete(ret_type.syntax());
} else { } else {
builder.replace(type_ref.syntax().text_range(), happy_type.syntax().text()); editor.replace(type_ref.syntax(), happy_type.syntax());
} }
for ret_expr_arg in exprs_to_unwrap { for tail_expr in exprs_to_unwrap {
let ret_expr_str = ret_expr_arg.to_string(); match &tail_expr {
ast::Expr::CallExpr(call_expr) => {
let needs_replacing = match kind { let ast::Expr::PathExpr(path_expr) = call_expr.expr().unwrap() else {
UnwrapperKind::Option => ret_expr_str.starts_with("Some("), continue;
UnwrapperKind::Result => {
ret_expr_str.starts_with("Ok(") || ret_expr_str.starts_with("Err(")
}
}; };
if needs_replacing { let path_str = path_expr.path().unwrap().to_string();
let arg_list = ret_expr_arg.syntax().children().find_map(ast::ArgList::cast); let needs_replacing = match kind {
if let Some(arg_list) = arg_list { UnwrapperKind::Option => path_str == "Some",
UnwrapperKind::Result => path_str == "Ok" || path_str == "Err",
};
if !needs_replacing {
continue;
}
let arg_list = call_expr.arg_list().unwrap();
if is_unit_type { if is_unit_type {
match ret_expr_arg.syntax().prev_sibling_or_token() { let tail_parent = tail_expr
// Useful to delete the entire line without leaving trailing whitespaces .syntax()
Some(whitespace) => { .parent()
let new_range = TextRange::new( .and_then(Either::<ast::ReturnExpr, ast::StmtList>::cast)
whitespace.text_range().start(), .unwrap();
ret_expr_arg.syntax().text_range().end(), match tail_parent {
); Either::Left(ret_expr) => {
builder.delete(new_range); editor.replace(ret_expr.syntax(), make.expr_return(None).syntax())
}
None => {
builder.delete(ret_expr_arg.syntax().text_range());
}
} }
Either::Right(stmt_list) => {
let new_block = if stmt_list.statements().next().is_none() {
make.expr_empty_block()
} else { } else {
builder.replace( make.block_expr(stmt_list.statements(), None)
ret_expr_arg.syntax().text_range(), };
arg_list.args().join(", "), editor.replace(
stmt_list.syntax(),
new_block.stmt_list().unwrap().syntax(),
); );
} }
} }
} else if matches!(kind, UnwrapperKind::Option if ret_expr_str == "None") { } else if let Some(first_arg) = arg_list.args().next() {
builder.replace(ret_expr_arg.syntax().text_range(), "()"); editor.replace(tail_expr.syntax(), first_arg.syntax());
} }
} }
ast::Expr::PathExpr(path_expr) => {
let UnwrapperKind::Option = kind else {
continue;
};
if path_expr.path().unwrap().to_string() != "None" {
continue;
}
editor.replace(path_expr.syntax(), make.expr_unit().syntax());
}
_ => (),
}
}
editor.add_mappings(make.finish_with_mappings());
builder.add_file_edits(ctx.file_id(), editor);
}) })
} }
@ -168,12 +190,12 @@ impl UnwrapperKind {
fn tail_cb_impl(acc: &mut Vec<ast::Expr>, e: &ast::Expr) { fn tail_cb_impl(acc: &mut Vec<ast::Expr>, e: &ast::Expr) {
match e { match e {
Expr::BreakExpr(break_expr) => { ast::Expr::BreakExpr(break_expr) => {
if let Some(break_expr_arg) = break_expr.expr() { if let Some(break_expr_arg) = break_expr.expr() {
for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(acc, e)) for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(acc, e))
} }
} }
Expr::ReturnExpr(_) => { ast::Expr::ReturnExpr(_) => {
// all return expressions have already been handled by the walk loop // all return expressions have already been handled by the walk loop
} }
e => acc.push(e.clone()), e => acc.push(e.clone()),
@ -238,8 +260,7 @@ fn foo() -> Option<()$0> {
} }
"#, "#,
r#" r#"
fn foo() { fn foo() {}
}
"#, "#,
"Unwrap Option return type", "Unwrap Option return type",
); );
@ -254,8 +275,7 @@ fn foo() -> Option<()$0>{
} }
"#, "#,
r#" r#"
fn foo() { fn foo() {}
}
"#, "#,
"Unwrap Option return type", "Unwrap Option return type",
); );
@ -1262,8 +1282,7 @@ fn foo() -> Result<(), Box<dyn Error$0>> {
} }
"#, "#,
r#" r#"
fn foo() { fn foo() {}
}
"#, "#,
"Unwrap Result return type", "Unwrap Result return type",
); );
@ -1278,8 +1297,7 @@ fn foo() -> Result<(), Box<dyn Error$0>>{
} }
"#, "#,
r#" r#"
fn foo() { fn foo() {}
}
"#, "#,
"Unwrap Result return type", "Unwrap Result return type",
); );

View file

@ -599,7 +599,7 @@ fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef>)
let variant_name = let variant_name =
make::path_pat(make::ext::path_from_idents(["core", "cmp", "Ordering", "Equal"])?); make::path_pat(make::ext::path_from_idents(["core", "cmp", "Ordering", "Equal"])?);
let lhs = make::tuple_struct_pat(make::ext::path_from_idents(["Some"])?, [variant_name]); let lhs = make::tuple_struct_pat(make::ext::path_from_idents(["Some"])?, [variant_name]);
arms.push(make::match_arm(lhs.into(), None, make::expr_empty_block())); arms.push(make::match_arm(lhs.into(), None, make::expr_empty_block().into()));
arms.push(make::match_arm( arms.push(make::match_arm(
make::ident_pat(false, false, make::name("ord")).into(), make::ident_pat(false, false, make::name("ord")).into(),

View file

@ -558,8 +558,8 @@ pub fn expr_const_value(text: &str) -> ast::ConstArg {
ast_from_text(&format!("trait Foo<const N: usize = {text}> {{}}")) ast_from_text(&format!("trait Foo<const N: usize = {text}> {{}}"))
} }
pub fn expr_empty_block() -> ast::Expr { pub fn expr_empty_block() -> ast::BlockExpr {
expr_from_text("{}") ast_from_text("const C: () = {};")
} }
pub fn expr_path(path: ast::Path) -> ast::Expr { pub fn expr_path(path: ast::Path) -> ast::Expr {
expr_from_text(&path.to_string()) expr_from_text(&path.to_string())

View file

@ -211,7 +211,7 @@ impl SyntaxFactory {
} }
pub fn expr_empty_block(&self) -> ast::BlockExpr { pub fn expr_empty_block(&self) -> ast::BlockExpr {
ast::BlockExpr { syntax: make::expr_empty_block().syntax().clone_for_update() } make::expr_empty_block().clone_for_update()
} }
pub fn expr_tuple(&self, fields: impl IntoIterator<Item = ast::Expr>) -> ast::TupleExpr { pub fn expr_tuple(&self, fields: impl IntoIterator<Item = ast::Expr>) -> ast::TupleExpr {