diff options
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-assists')
18 files changed, 819 insertions, 309 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs index 6aca716bb..c0e5429a2 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -422,7 +422,7 @@ impl<'x, 'y, T, V, U: Default> Trait<'x, 'y, T, V, U> for () { check_assist( add_missing_default_members, r#" -struct Bar<const: N: bool> { +struct Bar<const N: usize> { bar: [i32, N] } @@ -439,7 +439,7 @@ impl<const X: usize, Y, Z> Foo<X, Z> for S<Y> { $0 }"#, r#" -struct Bar<const: N: bool> { +struct Bar<const N: usize> { bar: [i32, N] } @@ -484,6 +484,107 @@ impl<X> Foo<42, {20 + 22}, X> for () { } #[test] + fn test_const_substitution_with_defaults() { + check_assist( + add_missing_default_members, + r#" +trait Foo<T, const N: usize = 42, const M: bool = false, const P: char = 'a'> { + fn get_n(&self) -> usize { N } + fn get_m(&self) -> bool { M } + fn get_p(&self) -> char { P } + fn get_array(&self, arg: &T) -> [bool; N] { [M; N] } +} + +impl<X> Foo<X> for () { + $0 +}"#, + r#" +trait Foo<T, const N: usize = 42, const M: bool = false, const P: char = 'a'> { + fn get_n(&self) -> usize { N } + fn get_m(&self) -> bool { M } + fn get_p(&self) -> char { P } + fn get_array(&self, arg: &T) -> [bool; N] { [M; N] } +} + +impl<X> Foo<X> for () { + $0fn get_n(&self) -> usize { 42 } + + fn get_m(&self) -> bool { false } + + fn get_p(&self) -> char { 'a' } + + fn get_array(&self, arg: &X) -> [bool; 42] { [false; 42] } +}"#, + ); + } + + #[test] + fn test_const_substitution_with_defaults_2() { + check_assist( + add_missing_impl_members, + r#" +mod m { + pub const LEN: usize = 42; + pub trait Foo<const M: usize = LEN, const N: usize = M, T = [bool; N]> { + fn get_t(&self) -> T; + } +} + +impl m::Foo for () { + $0 +}"#, + r#" +mod m { + pub const LEN: usize = 42; + pub trait Foo<const M: usize = LEN, const N: usize = M, T = [bool; N]> { + fn get_t(&self) -> T; + } +} + +impl m::Foo for () { + fn get_t(&self) -> [bool; m::LEN] { + ${0:todo!()} + } +}"#, + ) + } + + #[test] + fn test_const_substitution_with_defaults_3() { + check_assist( + add_missing_default_members, + r#" +mod m { + pub const VAL: usize = 0; + + pub trait Foo<const N: usize = {40 + 2}, const M: usize = {VAL + 1}> { + fn get_n(&self) -> usize { N } + fn get_m(&self) -> usize { M } + } +} + +impl m::Foo for () { + $0 +}"#, + r#" +mod m { + pub const VAL: usize = 0; + + pub trait Foo<const N: usize = {40 + 2}, const M: usize = {VAL + 1}> { + fn get_n(&self) -> usize { N } + fn get_m(&self) -> usize { M } + } +} + +impl m::Foo for () { + $0fn get_n(&self) -> usize { {40 + 2} } + + fn get_m(&self) -> usize { {m::VAL + 1} } +}"#, + ) + } + + #[test] fn test_cursor_after_empty_impl_def() { check_assist( add_missing_impl_members, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs index 57cfa17cc..66bc2f6da 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs @@ -1,6 +1,10 @@ use std::collections::VecDeque; -use syntax::ast::{self, AstNode}; +use syntax::{ + ast::{self, AstNode, Expr::BinExpr}, + ted::{self, Position}, + SyntaxKind, +}; use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; @@ -23,121 +27,117 @@ use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKin // } // ``` pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let expr = ctx.find_node_at_offset::<ast::BinExpr>()?; - let op = expr.op_kind()?; - let op_range = expr.op_token()?.text_range(); + let mut bin_expr = ctx.find_node_at_offset::<ast::BinExpr>()?; + let op = bin_expr.op_kind()?; + let op_range = bin_expr.op_token()?.text_range(); - let opposite_op = match op { - ast::BinaryOp::LogicOp(ast::LogicOp::And) => "||", - ast::BinaryOp::LogicOp(ast::LogicOp::Or) => "&&", - _ => return None, - }; - - let cursor_in_range = op_range.contains_range(ctx.selection_trimmed()); - if !cursor_in_range { + // Is the cursor on the expression's logical operator? + if !op_range.contains_range(ctx.selection_trimmed()) { return None; } - let mut expr = expr; - // Walk up the tree while we have the same binary operator - while let Some(parent_expr) = expr.syntax().parent().and_then(ast::BinExpr::cast) { - match expr.op_kind() { + while let Some(parent_expr) = bin_expr.syntax().parent().and_then(ast::BinExpr::cast) { + match parent_expr.op_kind() { Some(parent_op) if parent_op == op => { - expr = parent_expr; + bin_expr = parent_expr; } _ => break, } } - let mut expr_stack = vec![expr.clone()]; - let mut terms = Vec::new(); - let mut op_ranges = Vec::new(); - - // Find all the children with the same binary operator - while let Some(expr) = expr_stack.pop() { - let mut traverse_bin_expr_arm = |expr| { - if let ast::Expr::BinExpr(bin_expr) = expr { - if let Some(expr_op) = bin_expr.op_kind() { - if expr_op == op { - expr_stack.push(bin_expr); - } else { - terms.push(ast::Expr::BinExpr(bin_expr)); - } + let op = bin_expr.op_kind()?; + let inv_token = match op { + ast::BinaryOp::LogicOp(ast::LogicOp::And) => SyntaxKind::PIPE2, + ast::BinaryOp::LogicOp(ast::LogicOp::Or) => SyntaxKind::AMP2, + _ => return None, + }; + + let demorganed = bin_expr.clone_subtree().clone_for_update(); + + ted::replace(demorganed.op_token()?, ast::make::token(inv_token)); + let mut exprs = VecDeque::from(vec![ + (bin_expr.lhs()?, demorganed.lhs()?), + (bin_expr.rhs()?, demorganed.rhs()?), + ]); + + while let Some((expr, dm)) = exprs.pop_front() { + if let BinExpr(bin_expr) = &expr { + if let BinExpr(cbin_expr) = &dm { + if op == bin_expr.op_kind()? { + ted::replace(cbin_expr.op_token()?, ast::make::token(inv_token)); + exprs.push_back((bin_expr.lhs()?, cbin_expr.lhs()?)); + exprs.push_back((bin_expr.rhs()?, cbin_expr.rhs()?)); } else { - terms.push(ast::Expr::BinExpr(bin_expr)); + let mut inv = invert_boolean_expression(expr); + if inv.needs_parens_in(dm.syntax().parent()?) { + inv = ast::make::expr_paren(inv).clone_for_update(); + } + ted::replace(dm.syntax(), inv.syntax()); } } else { - terms.push(expr); + return None; } - }; - - op_ranges.extend(expr.op_token().map(|t| t.text_range())); - traverse_bin_expr_arm(expr.lhs()?); - traverse_bin_expr_arm(expr.rhs()?); + } else { + let mut inv = invert_boolean_expression(dm.clone_subtree()).clone_for_update(); + if inv.needs_parens_in(dm.syntax().parent()?) { + inv = ast::make::expr_paren(inv).clone_for_update(); + } + ted::replace(dm.syntax(), inv.syntax()); + } } + let dm_lhs = demorganed.lhs()?; + acc.add( AssistId("apply_demorgan", AssistKind::RefactorRewrite), "Apply De Morgan's law", op_range, |edit| { - terms.sort_by_key(|t| t.syntax().text_range().start()); - let mut terms = VecDeque::from(terms); - - let paren_expr = expr.syntax().parent().and_then(ast::ParenExpr::cast); - + let paren_expr = bin_expr.syntax().parent().and_then(ast::ParenExpr::cast); let neg_expr = paren_expr .clone() .and_then(|paren_expr| paren_expr.syntax().parent()) .and_then(ast::PrefixExpr::cast) .and_then(|prefix_expr| { - if prefix_expr.op_kind().unwrap() == ast::UnaryOp::Not { + if prefix_expr.op_kind()? == ast::UnaryOp::Not { Some(prefix_expr) } else { None } }); - for op_range in op_ranges { - edit.replace(op_range, opposite_op); - } - if let Some(paren_expr) = paren_expr { - for term in terms { - let range = term.syntax().text_range(); - let not_term = invert_boolean_expression(term); - - edit.replace(range, not_term.syntax().text()); - } - if let Some(neg_expr) = neg_expr { cov_mark::hit!(demorgan_double_negation); - edit.replace(neg_expr.op_token().unwrap().text_range(), ""); + edit.replace_ast(ast::Expr::PrefixExpr(neg_expr), demorganed.into()); } else { cov_mark::hit!(demorgan_double_parens); - edit.replace(paren_expr.l_paren_token().unwrap().text_range(), "!("); + ted::insert_all_raw( + Position::before(dm_lhs.syntax()), + vec![ + syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::BANG)), + syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::L_PAREN)), + ], + ); + + ted::append_child_raw( + demorganed.syntax(), + syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::R_PAREN)), + ); + + edit.replace_ast(ast::Expr::ParenExpr(paren_expr), demorganed.into()); } } else { - if let Some(lhs) = terms.pop_front() { - let lhs_range = lhs.syntax().text_range(); - let not_lhs = invert_boolean_expression(lhs); - - edit.replace(lhs_range, format!("!({not_lhs}")); - } - - if let Some(rhs) = terms.pop_back() { - let rhs_range = rhs.syntax().text_range(); - let not_rhs = invert_boolean_expression(rhs); - - edit.replace(rhs_range, format!("{not_rhs})")); - } - - for term in terms { - let term_range = term.syntax().text_range(); - let not_term = invert_boolean_expression(term); - edit.replace(term_range, not_term.to_string()); - } + ted::insert_all_raw( + Position::before(dm_lhs.syntax()), + vec![ + syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::BANG)), + syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::L_PAREN)), + ], + ); + ted::append_child_raw(demorganed.syntax(), ast::make::token(SyntaxKind::R_PAREN)); + edit.replace_ast(bin_expr, demorganed); } }, ) @@ -145,9 +145,8 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti #[cfg(test)] mod tests { - use crate::tests::{check_assist, check_assist_not_applicable}; - use super::*; + use crate::tests::{check_assist, check_assist_not_applicable}; #[test] fn demorgan_handles_leq() { @@ -213,7 +212,7 @@ fn f() { !(S <= S || S < S) } #[test] fn demorgan_doesnt_double_negation() { cov_mark::check!(demorgan_double_negation); - check_assist(apply_demorgan, "fn f() { !(x ||$0 x) }", "fn f() { (!x && !x) }") + check_assist(apply_demorgan, "fn f() { !(x ||$0 x) }", "fn f() { !x && !x }") } #[test] @@ -222,13 +221,38 @@ fn f() { !(S <= S || S < S) } check_assist(apply_demorgan, "fn f() { (x ||$0 x) }", "fn f() { !(!x && !x) }") } - // https://github.com/rust-lang/rust-analyzer/issues/10963 + // FIXME : This needs to go. + // // https://github.com/rust-lang/rust-analyzer/issues/10963 + // #[test] + // fn demorgan_doesnt_hang() { + // check_assist( + // apply_demorgan, + // "fn f() { 1 || 3 &&$0 4 || 5 }", + // "fn f() { !(!1 || !3 || !4) || 5 }", + // ) + // } + + #[test] + fn demorgan_keep_pars_for_op_precedence() { + check_assist( + apply_demorgan, + "fn main() { + let _ = !(!a ||$0 !(b || c)); +} +", + "fn main() { + let _ = a && (b || c); +} +", + ); + } + #[test] - fn demorgan_doesnt_hang() { + fn demorgan_removes_pars_in_eq_precedence() { check_assist( apply_demorgan, - "fn f() { 1 || 3 &&$0 4 || 5 }", - "fn f() { !(!1 || !3 || !4) || 5 }", + "fn() { let x = a && !(!b |$0| !c); }", + "fn() { let x = a && b && c; }", ) } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bind_unused_param.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bind_unused_param.rs new file mode 100644 index 000000000..45c1f0cca --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bind_unused_param.rs @@ -0,0 +1,159 @@ +use crate::assist_context::{AssistContext, Assists}; +use ide_db::{ + assists::{AssistId, AssistKind}, + defs::Definition, + LineIndexDatabase, +}; +use syntax::{ + ast::{self, edit_in_place::Indent}, + AstNode, +}; + +// Assist: bind_unused_param +// +// Binds unused function parameter to an underscore. +// +// ``` +// fn some_function(x: i32$0) {} +// ``` +// -> +// ``` +// fn some_function(x: i32) { +// let _ = x; +// } +// ``` +pub(crate) fn bind_unused_param(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let param: ast::Param = ctx.find_node_at_offset()?; + + let Some(ast::Pat::IdentPat(ident_pat)) = param.pat() else { return None }; + + let param_def = { + let local = ctx.sema.to_def(&ident_pat)?; + Definition::Local(local) + }; + if param_def.usages(&ctx.sema).at_least_one() { + cov_mark::hit!(keep_used); + return None; + } + + let func = param.syntax().ancestors().find_map(ast::Fn::cast)?; + let stmt_list = func.body()?.stmt_list()?; + let l_curly_range = stmt_list.l_curly_token()?.text_range(); + let r_curly_range = stmt_list.r_curly_token()?.text_range(); + + acc.add( + AssistId("bind_unused_param", AssistKind::QuickFix), + &format!("Bind as `let _ = {};`", &ident_pat), + param.syntax().text_range(), + |builder| { + let line_index = ctx.db().line_index(ctx.file_id()); + + let indent = func.indent_level(); + let text_indent = indent + 1; + let mut text = format!("\n{text_indent}let _ = {ident_pat};"); + + let left_line = line_index.line_col(l_curly_range.end()).line; + let right_line = line_index.line_col(r_curly_range.start()).line; + + if left_line == right_line { + cov_mark::hit!(single_line); + text.push_str(&format!("\n{indent}")); + } + + builder.insert(l_curly_range.end(), text); + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn bind_unused_empty_block() { + cov_mark::check!(single_line); + check_assist( + bind_unused_param, + r#" +fn foo($0y: i32) {} +"#, + r#" +fn foo(y: i32) { + let _ = y; +} +"#, + ); + } + + #[test] + fn bind_unused_empty_block_with_newline() { + check_assist( + bind_unused_param, + r#" +fn foo($0y: i32) { +} +"#, + r#" +fn foo(y: i32) { + let _ = y; +} +"#, + ); + } + + #[test] + fn bind_unused_generic() { + check_assist( + bind_unused_param, + r#" +fn foo<T>($0y: T) +where T : Default { +} +"#, + r#" +fn foo<T>(y: T) +where T : Default { + let _ = y; +} +"#, + ); + } + + #[test] + fn trait_impl() { + check_assist( + bind_unused_param, + r#" +trait Trait { + fn foo(x: i32); +} +impl Trait for () { + fn foo($0x: i32) {} +} +"#, + r#" +trait Trait { + fn foo(x: i32); +} +impl Trait for () { + fn foo(x: i32) { + let _ = x; + } +} +"#, + ); + } + + #[test] + fn keep_used() { + cov_mark::check!(keep_used); + check_assist_not_applicable( + bind_unused_param, + r#" +fn foo(x: i32, $0y: i32) { y; } +"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs index 1af52c592..d231708c5 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs @@ -103,7 +103,6 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_> cond, ast::Expr::BinExpr(_) | ast::Expr::BlockExpr(_) - | ast::Expr::BoxExpr(_) | ast::Expr::BreakExpr(_) | ast::Expr::CastExpr(_) | ast::Expr::ClosureExpr(_) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs index fe1cb6fce..76f021ed9 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -161,9 +161,9 @@ fn process_struct_name_reference( let path_segment = name_ref.syntax().parent().and_then(ast::PathSegment::cast)?; // A `PathSegment` always belongs to a `Path`, so there's at least one `Path` at this point. let full_path = - path_segment.syntax().parent()?.ancestors().map_while(ast::Path::cast).last().unwrap(); + path_segment.syntax().parent()?.ancestors().map_while(ast::Path::cast).last()?; - if full_path.segment().unwrap().name_ref()? != *name_ref { + if full_path.segment()?.name_ref()? != *name_ref { // `name_ref` isn't the last segment of the path, so `full_path` doesn't point to the // struct we want to edit. return None; diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs index dcb96ab8a..7d0e42476 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs @@ -58,7 +58,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<' return None; } - let bound_ident = pat.fields().next().unwrap(); + let bound_ident = pat.fields().next()?; if !ast::IdentPat::can_cast(bound_ident.syntax().kind()) { return None; } @@ -108,6 +108,15 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<' then_block.syntax().last_child_or_token().filter(|t| t.kind() == T!['}'])?; + let then_block_items = then_block.dedent(IndentLevel(1)).clone_for_update(); + + let end_of_then = then_block_items.syntax().last_child_or_token()?; + let end_of_then = if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { + end_of_then.prev_sibling_or_token()? + } else { + end_of_then + }; + let target = if_expr.syntax().text_range(); acc.add( AssistId("convert_to_guarded_return", AssistKind::RefactorRewrite), @@ -141,16 +150,6 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<' } }; - let then_block_items = then_block.dedent(IndentLevel(1)).clone_for_update(); - - let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); - let end_of_then = - if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { - end_of_then.prev_sibling_or_token().unwrap() - } else { - end_of_then - }; - let then_statements = replacement .children_with_tokens() .chain( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs index 4f3b6e0c2..31a1ff496 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs @@ -15,26 +15,13 @@ use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange}; // Move an expression out of a format string. // // ``` -// macro_rules! format_args { -// ($lit:literal $(tt:tt)*) => { 0 }, -// } -// macro_rules! print { -// ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -// } -// +// # //- minicore: fmt // fn main() { // print!("{var} {x + 1}$0"); // } // ``` // -> // ``` -// macro_rules! format_args { -// ($lit:literal $(tt:tt)*) => { 0 }, -// } -// macro_rules! print { -// ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -// } -// // fn main() { // print!("{var} {}"$0, x + 1); // } @@ -48,7 +35,7 @@ pub(crate) fn extract_expressions_from_format_string( let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?; let expanded_t = ast::String::cast( - ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone()), + ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone(), 0.into()), )?; if !is_format_string(&expanded_t) { return None; @@ -158,37 +145,21 @@ mod tests { use super::*; use crate::tests::check_assist; - const MACRO_DECL: &'static str = r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} -macro_rules! print { - ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -} -"#; - - fn add_macro_decl(s: &'static str) -> String { - MACRO_DECL.to_string() + s - } - #[test] fn multiple_middle_arg() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{} {x + 1:b} {}$0", y + 2, 2); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{} {:b} {}"$0, y + 2, x + 1, 2); } "#, - ), ); } @@ -196,20 +167,17 @@ fn main() { fn single_arg() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{obj.value:b}$0",); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{:b}"$0, obj.value); } "#, - ), ); } @@ -217,20 +185,17 @@ fn main() { fn multiple_middle_placeholders_arg() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{} {x + 1:b} {} {}$0", y + 2, 2); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1); } "#, - ), ); } @@ -238,20 +203,17 @@ fn main() { fn multiple_trailing_args() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{:b} {x + 1:b} {Struct(1, 2)}$0", 1); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{:b} {:b} {}"$0, 1, x + 1, Struct(1, 2)); } "#, - ), ); } @@ -259,20 +221,17 @@ fn main() { fn improper_commas() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2)); } "#, - ), ); } @@ -280,20 +239,17 @@ fn main() { fn nested_tt() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("My name is {} {x$0 + x}", stringify!(Paperino)) } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("My name is {} {}"$0, stringify!(Paperino), x + x) } "#, - ), ); } @@ -301,22 +257,19 @@ fn main() { fn extract_only_expressions() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { let var = 1 + 1; print!("foobar {var} {var:?} {x$0 + x}") } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { let var = 1 + 1; print!("foobar {var} {var:?} {}"$0, x + x) } "#, - ), ); } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs index b8b781ea4..de591cfde 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs @@ -531,7 +531,7 @@ impl FunctionBody { fn extracted_from_trait_impl(&self) -> bool { match self.node().ancestors().find_map(ast::Impl::cast) { - Some(c) => return c.trait_().is_some(), + Some(c) => c.trait_().is_some(), None => false, } } @@ -750,7 +750,7 @@ impl FunctionBody { .descendants_with_tokens() .filter_map(SyntaxElement::into_token) .filter(|it| matches!(it.kind(), SyntaxKind::IDENT | T![self])) - .flat_map(|t| sema.descend_into_macros(t)) + .flat_map(|t| sema.descend_into_macros(t, 0.into())) .for_each(|t| add_name_if_local(t.parent().and_then(ast::NameRef::cast))); } } @@ -810,7 +810,7 @@ impl FunctionBody { (true, konst.body(), Some(sema.to_def(&konst)?.ty(sema.db))) }, ast::ConstParam(cp) => { - (true, cp.default_val(), Some(sema.to_def(&cp)?.ty(sema.db))) + (true, cp.default_val()?.expr(), Some(sema.to_def(&cp)?.ty(sema.db))) }, ast::ConstBlockPat(cbp) => { let expr = cbp.block_expr().map(ast::Expr::BlockExpr); @@ -1048,23 +1048,17 @@ impl GenericParent { fn generic_parents(parent: &SyntaxNode) -> Vec<GenericParent> { let mut list = Vec::new(); if let Some(parent_item) = parent.ancestors().find_map(ast::Item::cast) { - match parent_item { - ast::Item::Fn(ref fn_) => { - if let Some(parent_parent) = parent_item - .syntax() - .parent() - .and_then(|it| it.parent()) - .and_then(ast::Item::cast) - { - match parent_parent { - ast::Item::Impl(impl_) => list.push(GenericParent::Impl(impl_)), - ast::Item::Trait(trait_) => list.push(GenericParent::Trait(trait_)), - _ => (), - } + if let ast::Item::Fn(ref fn_) = parent_item { + if let Some(parent_parent) = + parent_item.syntax().parent().and_then(|it| it.parent()).and_then(ast::Item::cast) + { + match parent_parent { + ast::Item::Impl(impl_) => list.push(GenericParent::Impl(impl_)), + ast::Item::Trait(trait_) => list.push(GenericParent::Trait(trait_)), + _ => (), } - list.push(GenericParent::Fn(fn_.clone())); } - _ => (), + list.push(GenericParent::Fn(fn_.clone())); } } list @@ -1385,31 +1379,30 @@ enum FlowHandler { impl FlowHandler { fn from_ret_ty(fun: &Function, ret_ty: &FunType) -> FlowHandler { - match &fun.control_flow.kind { - None => FlowHandler::None, - Some(flow_kind) => { - let action = flow_kind.clone(); - if let FunType::Unit = ret_ty { - match flow_kind { - FlowKind::Return(None) - | FlowKind::Break(_, None) - | FlowKind::Continue(_) => FlowHandler::If { action }, - FlowKind::Return(_) | FlowKind::Break(_, _) => { - FlowHandler::IfOption { action } - } - FlowKind::Try { kind } => FlowHandler::Try { kind: kind.clone() }, - } - } else { - match flow_kind { - FlowKind::Return(None) - | FlowKind::Break(_, None) - | FlowKind::Continue(_) => FlowHandler::MatchOption { none: action }, - FlowKind::Return(_) | FlowKind::Break(_, _) => { - FlowHandler::MatchResult { err: action } - } - FlowKind::Try { kind } => FlowHandler::Try { kind: kind.clone() }, - } + if fun.contains_tail_expr { + return FlowHandler::None; + } + let Some(action) = fun.control_flow.kind.clone() else { + return FlowHandler::None; + }; + + if let FunType::Unit = ret_ty { + match action { + FlowKind::Return(None) | FlowKind::Break(_, None) | FlowKind::Continue(_) => { + FlowHandler::If { action } + } + FlowKind::Return(_) | FlowKind::Break(_, _) => FlowHandler::IfOption { action }, + FlowKind::Try { kind } => FlowHandler::Try { kind }, + } + } else { + match action { + FlowKind::Return(None) | FlowKind::Break(_, None) | FlowKind::Continue(_) => { + FlowHandler::MatchOption { none: action } + } + FlowKind::Return(_) | FlowKind::Break(_, _) => { + FlowHandler::MatchResult { err: action } } + FlowKind::Try { kind } => FlowHandler::Try { kind }, } } } @@ -1654,11 +1647,7 @@ impl Function { fn make_ret_ty(&self, ctx: &AssistContext<'_>, module: hir::Module) -> Option<ast::RetType> { let fun_ty = self.return_type(ctx); - let handler = if self.contains_tail_expr { - FlowHandler::None - } else { - FlowHandler::from_ret_ty(self, &fun_ty) - }; + let handler = FlowHandler::from_ret_ty(self, &fun_ty); let ret_ty = match &handler { FlowHandler::None => { if matches!(fun_ty, FunType::Unit) { @@ -1728,16 +1717,12 @@ fn make_body( fun: &Function, ) -> ast::BlockExpr { let ret_ty = fun.return_type(ctx); - let handler = if fun.contains_tail_expr { - FlowHandler::None - } else { - FlowHandler::from_ret_ty(fun, &ret_ty) - }; + let handler = FlowHandler::from_ret_ty(fun, &ret_ty); let block = match &fun.body { FunctionBody::Expr(expr) => { let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax()); - let expr = ast::Expr::cast(expr).unwrap(); + let expr = ast::Expr::cast(expr).expect("Body segment should be an expr"); match expr { ast::Expr::BlockExpr(block) => { // If the extracted expression is itself a block, there is no need to wrap it inside another block. @@ -1877,9 +1862,8 @@ fn with_tail_expr(block: ast::BlockExpr, tail_expr: ast::Expr) -> ast::BlockExpr if let Some(stmt_list) = block.stmt_list() { stmt_list.syntax().children_with_tokens().for_each(|node_or_token| { - match &node_or_token { - syntax::NodeOrToken::Token(_) => elements.push(node_or_token), - _ => (), + if let syntax::NodeOrToken::Token(_) = &node_or_token { + elements.push(node_or_token) }; }); } @@ -1943,12 +1927,18 @@ fn fix_param_usages(ctx: &AssistContext<'_>, params: &[Param], syntax: &SyntaxNo Some(ast::Expr::RefExpr(node)) if param.kind() == ParamKind::MutRef && node.mut_token().is_some() => { - ted::replace(node.syntax(), node.expr().unwrap().syntax()); + ted::replace( + node.syntax(), + node.expr().expect("RefExpr::expr() cannot be None").syntax(), + ); } Some(ast::Expr::RefExpr(node)) if param.kind() == ParamKind::SharedRef && node.mut_token().is_none() => { - ted::replace(node.syntax(), node.expr().unwrap().syntax()); + ted::replace( + node.syntax(), + node.expr().expect("RefExpr::expr() cannot be None").syntax(), + ); } Some(_) | None => { let p = &make::expr_prefix(T![*], usage.clone()).clone_for_update(); @@ -4471,7 +4461,7 @@ async fn foo() -> Result<(), ()> { "#, r#" async fn foo() -> Result<(), ()> { - fun_name().await? + fun_name().await } async fn $0fun_name() -> Result<(), ()> { @@ -4690,7 +4680,7 @@ fn $0fun_name() { check_assist( extract_function, r#" -//- minicore: result +//- minicore: result, try fn foo() -> Result<(), i64> { $0Result::<i32, i64>::Ok(0)?; Ok(())$0 @@ -4698,7 +4688,7 @@ fn foo() -> Result<(), i64> { "#, r#" fn foo() -> Result<(), i64> { - fun_name()? + fun_name() } fn $0fun_name() -> Result<(), i64> { @@ -5754,6 +5744,34 @@ fn $0fun_name<T, V>(t: T, v: V) -> i32 where T: Into<i32> + Copy, V: Into<i32> { } #[test] + fn tail_expr_no_extra_control_flow() { + check_assist( + extract_function, + r#" +//- minicore: result +fn fallible() -> Result<(), ()> { + $0if true { + return Err(()); + } + Ok(())$0 +} +"#, + r#" +fn fallible() -> Result<(), ()> { + fun_name() +} + +fn $0fun_name() -> Result<(), ()> { + if true { + return Err(()); + } + Ok(()) +} +"#, + ); + } + + #[test] fn non_tail_expr_of_tail_expr_loop() { check_assist( extract_function, @@ -5800,12 +5818,6 @@ fn $0fun_name() -> ControlFlow<()> { extract_function, r#" //- minicore: option, try -impl<T> core::ops::Try for Option<T> { - type Output = T; - type Residual = Option<!>; -} -impl<T> core::ops::FromResidual for Option<T> {} - fn f() -> Option<()> { if true { let a = $0if true { @@ -5820,12 +5832,6 @@ fn f() -> Option<()> { } "#, r#" -impl<T> core::ops::Try for Option<T> { - type Output = T; - type Residual = Option<!>; -} -impl<T> core::ops::FromResidual for Option<T> {} - fn f() -> Option<()> { if true { let a = fun_name()?;; @@ -5852,12 +5858,6 @@ fn $0fun_name() -> Option<()> { extract_function, r#" //- minicore: option, try -impl<T> core::ops::Try for Option<T> { - type Output = T; - type Residual = Option<!>; -} -impl<T> core::ops::FromResidual for Option<T> {} - fn f() -> Option<()> { if true { $0{ @@ -5874,15 +5874,9 @@ fn f() -> Option<()> { } "#, r#" -impl<T> core::ops::Try for Option<T> { - type Output = T; - type Residual = Option<!>; -} -impl<T> core::ops::FromResidual for Option<T> {} - fn f() -> Option<()> { if true { - fun_name()? + fun_name() } else { None } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs index 31fc69562..bbac0a26e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs @@ -95,6 +95,9 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' let Some(impl_def) = find_struct_impl(ctx, &adt, std::slice::from_ref(&name)) else { continue; }; + + let field = make::ext::field_from_idents(["self", &field_name])?; + acc.add_group( &GroupLabel("Generate delegate methods…".to_owned()), AssistId("generate_delegate_methods", AssistKind::Generate), @@ -115,11 +118,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' Some(list) => convert_param_list_to_arg_list(list), None => make::arg_list([]), }; - let tail_expr = make::expr_method_call( - make::ext::field_from_idents(["self", &field_name]).unwrap(), // This unwrap is ok because we have at least 1 arg in the list - make::name_ref(&name), - arg_list, - ); + let tail_expr = make::expr_method_call(field, make::name_ref(&name), arg_list); let ret_type = method_source.ret_type(); let is_async = method_source.async_token().is_some(); let is_const = method_source.const_token().is_some(); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs index 747f70f9f..53ba144ba 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs @@ -27,13 +27,19 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt let cap = ctx.config.snippet_cap?; let nominal = ctx.find_node_at_offset::<ast::Adt>()?; let target = nominal.syntax().text_range(); + let derive_attr = nominal + .attrs() + .filter_map(|x| x.as_simple_call()) + .filter(|(name, _arg)| name == "derive") + .map(|(_name, arg)| arg) + .next(); + + let delimiter = match &derive_attr { + None => None, + Some(tt) => Some(tt.right_delimiter_token()?), + }; + acc.add(AssistId("generate_derive", AssistKind::Generate), "Add `#[derive]`", target, |edit| { - let derive_attr = nominal - .attrs() - .filter_map(|x| x.as_simple_call()) - .filter(|(name, _arg)| name == "derive") - .map(|(_name, arg)| arg) - .next(); match derive_attr { None => { let derive = make::attr_outer(make::meta_token_tree( @@ -45,16 +51,23 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt let nominal = edit.make_mut(nominal); nominal.add_attr(derive.clone()); + let delimiter = derive + .meta() + .expect("make::attr_outer was expected to have Meta") + .token_tree() + .expect("failed to get token tree out of Meta") + .r_paren_token() + .expect("make::attr_outer was expected to have a R_PAREN"); + + edit.add_tabstop_before_token(cap, delimiter); + } + Some(_) => { + // Just move the cursor. edit.add_tabstop_before_token( cap, - derive.meta().unwrap().token_tree().unwrap().r_paren_token().unwrap(), + delimiter.expect("Right delim token could not be found."), ); } - Some(tt) => { - // Just move the cursor. - let tt = edit.make_mut(tt); - edit.add_tabstop_before_token(cap, tt.right_delimiter_token().unwrap()); - } }; }) } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/into_to_qualified_from.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/into_to_qualified_from.rs new file mode 100644 index 000000000..663df266b --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/into_to_qualified_from.rs @@ -0,0 +1,205 @@ +use hir::{AsAssocItem, HirDisplay}; +use ide_db::{ + assists::{AssistId, AssistKind}, + famous_defs::FamousDefs, +}; +use syntax::{ast, AstNode}; + +use crate::assist_context::{AssistContext, Assists}; + +// Assist: into_to_qualified_from +// +// Convert an `into` method call to a fully qualified `from` call. +// +// ``` +// //- minicore: from +// struct B; +// impl From<i32> for B { +// fn from(a: i32) -> Self { +// B +// } +// } +// +// fn main() -> () { +// let a = 3; +// let b: B = a.in$0to(); +// } +// ``` +// -> +// ``` +// struct B; +// impl From<i32> for B { +// fn from(a: i32) -> Self { +// B +// } +// } +// +// fn main() -> () { +// let a = 3; +// let b: B = B::from(a); +// } +// ``` +pub(crate) fn into_to_qualified_from(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?; + let nameref = method_call.name_ref()?; + let receiver = method_call.receiver()?; + let db = ctx.db(); + let sema = &ctx.sema; + let fnc = sema.resolve_method_call(&method_call)?; + let scope = sema.scope(method_call.syntax())?; + // Check if the method call refers to Into trait. + if fnc.as_assoc_item(db)?.containing_trait_impl(db)? + == FamousDefs(sema, scope.krate()).core_convert_Into()? + { + let type_call = sema.type_of_expr(&method_call.clone().into())?; + let type_call_disp = + type_call.adjusted().display_source_code(db, scope.module().into(), true).ok()?; + + acc.add( + AssistId("into_to_qualified_from", AssistKind::Generate), + "Convert `into` to fully qualified `from`", + nameref.syntax().text_range(), + |edit| { + edit.replace( + method_call.syntax().text_range(), + format!("{}::from({})", type_call_disp, receiver), + ); + }, + ); + } + + Some(()) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist; + + use super::into_to_qualified_from; + + #[test] + fn two_types_in_same_mod() { + check_assist( + into_to_qualified_from, + r#" +//- minicore: from +struct A; +struct B; +impl From<A> for B { + fn from(a: A) -> Self { + B + } +} + +fn main() -> () { + let a: A = A; + let b: B = a.in$0to(); +}"#, + r#" +struct A; +struct B; +impl From<A> for B { + fn from(a: A) -> Self { + B + } +} + +fn main() -> () { + let a: A = A; + let b: B = B::from(a); +}"#, + ) + } + + #[test] + fn fromed_in_child_mod_imported() { + check_assist( + into_to_qualified_from, + r#" +//- minicore: from +use C::B; + +struct A; + +mod C { + use crate::A; + + pub(super) struct B; + impl From<A> for B { + fn from(a: A) -> Self { + B + } + } +} + +fn main() -> () { + let a: A = A; + let b: B = a.in$0to(); +}"#, + r#" +use C::B; + +struct A; + +mod C { + use crate::A; + + pub(super) struct B; + impl From<A> for B { + fn from(a: A) -> Self { + B + } + } +} + +fn main() -> () { + let a: A = A; + let b: B = B::from(a); +}"#, + ) + } + + #[test] + fn fromed_in_child_mod_not_imported() { + check_assist( + into_to_qualified_from, + r#" +//- minicore: from +struct A; + +mod C { + use crate::A; + + pub(super) struct B; + impl From<A> for B { + fn from(a: A) -> Self { + B + } + } +} + +fn main() -> () { + let a: A = A; + let b: C::B = a.in$0to(); +}"#, + r#" +struct A; + +mod C { + use crate::A; + + pub(super) struct B; + impl From<A> for B { + fn from(a: A) -> Self { + B + } + } +} + +fn main() -> () { + let a: A = A; + let b: C::B = C::B::from(a); +}"#, + ) + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs index 5cc110cf1..6ed9bd85f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs @@ -76,12 +76,19 @@ pub(crate) fn promote_local_to_const(acc: &mut Assists, ctx: &AssistContext<'_>) let name = to_upper_snake_case(&name.to_string()); let usages = Definition::Local(local).usages(&ctx.sema).all(); if let Some(usages) = usages.references.get(&ctx.file_id()) { - let name = make::name_ref(&name); + let name_ref = make::name_ref(&name); for usage in usages { let Some(usage) = usage.name.as_name_ref().cloned() else { continue }; - let usage = edit.make_mut(usage); - ted::replace(usage.syntax(), name.clone_for_update().syntax()); + if let Some(record_field) = ast::RecordExprField::for_name_ref(&usage) { + let record_field = edit.make_mut(record_field); + let name_expr = + make::expr_path(make::path_from_text(&name)).clone_for_update(); + record_field.replace_expr(name_expr); + } else { + let usage = edit.make_mut(usage); + ted::replace(usage.syntax(), name_ref.clone_for_update().syntax()); + } } } @@ -120,8 +127,7 @@ fn is_body_const(sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr) -> bool { is_const &= sema.resolve_method_call(&call).map(|it| it.is_const(sema.db)).unwrap_or(true) } - ast::Expr::BoxExpr(_) - | ast::Expr::ForExpr(_) + ast::Expr::ForExpr(_) | ast::Expr::ReturnExpr(_) | ast::Expr::TryExpr(_) | ast::Expr::YieldExpr(_) @@ -180,6 +186,33 @@ fn foo() { } #[test] + fn usage_in_field_shorthand() { + check_assist( + promote_local_to_const, + r" +struct Foo { + bar: usize, +} + +fn main() { + let $0bar = 0; + let foo = Foo { bar }; +} +", + r" +struct Foo { + bar: usize, +} + +fn main() { + const $0BAR: usize = 0; + let foo = Foo { bar: BAR }; +} +", + ) + } + + #[test] fn not_applicable_non_const_meth_call() { cov_mark::check!(promote_local_non_const); check_assist_not_applicable( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs index a403d5bc6..cffa3f55c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs @@ -39,14 +39,11 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<( let replacements = macro_calls.into_iter().filter_map(compute_dbg_replacement).collect::<Vec<_>>(); - if replacements.is_empty() { - return None; - } acc.add( AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", - replacements.iter().map(|&(range, _)| range).reduce(|acc, range| acc.cover(range)).unwrap(), + replacements.iter().map(|&(range, _)| range).reduce(|acc, range| acc.cover(range))?, |builder| { for (range, expr) in replacements { if let Some(expr) = expr { @@ -116,10 +113,7 @@ fn compute_dbg_replacement(macro_expr: ast::MacroExpr) -> Option<(TextRange, Opt Some(parent) => match (expr, parent) { (ast::Expr::CastExpr(_), ast::Expr::CastExpr(_)) => false, ( - ast::Expr::BoxExpr(_) - | ast::Expr::PrefixExpr(_) - | ast::Expr::RefExpr(_) - | ast::Expr::MacroExpr(_), + ast::Expr::PrefixExpr(_) | ast::Expr::RefExpr(_) | ast::Expr::MacroExpr(_), ast::Expr::AwaitExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::CastExpr(_) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs index dd4839351..5fcab8c02 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs @@ -67,7 +67,7 @@ pub(crate) fn remove_unused_imports(acc: &mut Assists, ctx: &AssistContext<'_>) // This case maps to the situation where the * token is braced. // In this case, the parent use tree's path is the one we should use to resolve the glob. match u.syntax().ancestors().skip(1).find_map(ast::UseTree::cast) { - Some(parent_u) if parent_u.path().is_some() => parent_u.path().unwrap(), + Some(parent_u) if parent_u.path().is_some() => parent_u.path()?, _ => return None, } } else { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs index 24c338745..61e9bcdcc 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs @@ -48,6 +48,11 @@ pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext< return None; } + let new_result_ty = + make::ext::ty_result(type_ref.clone(), make::ty_placeholder()).clone_for_update(); + let generic_args = new_result_ty.syntax().descendants().find_map(ast::GenericArgList::cast)?; + let last_genarg = generic_args.generic_args().last()?; + acc.add( AssistId("wrap_return_type_in_result", AssistKind::RefactorRewrite), "Wrap return type in Result", @@ -75,19 +80,12 @@ pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext< ted::replace(ret_expr_arg.syntax(), ok_wrapped.syntax()); } - let new_result_ty = - make::ext::ty_result(type_ref.clone(), make::ty_placeholder()).clone_for_update(); let old_result_ty = edit.make_mut(type_ref.clone()); ted::replace(old_result_ty.syntax(), new_result_ty.syntax()); if let Some(cap) = ctx.config.snippet_cap { - let generic_args = new_result_ty - .syntax() - .descendants() - .find_map(ast::GenericArgList::cast) - .unwrap(); - edit.add_placeholder_snippet(cap, generic_args.generic_args().last().unwrap()); + edit.add_placeholder_snippet(cap, last_genarg); } }, ) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs index 2ebb5ef9b..6f973ab53 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs @@ -114,6 +114,7 @@ mod handlers { mod add_turbo_fish; mod apply_demorgan; mod auto_import; + mod bind_unused_param; mod change_visibility; mod convert_bool_then; mod convert_comment_block; @@ -211,6 +212,7 @@ mod handlers { mod unwrap_result_return_type; mod unqualify_method_call; mod wrap_return_type_in_result; + mod into_to_qualified_from; pub(crate) fn all() -> &'static [Handler] { &[ @@ -224,6 +226,7 @@ mod handlers { add_turbo_fish::add_turbo_fish, apply_demorgan::apply_demorgan, auto_import::auto_import, + bind_unused_param::bind_unused_param, change_visibility::change_visibility, convert_bool_then::convert_bool_then_to_if, convert_bool_then::convert_if_to_bool_then, @@ -274,6 +277,7 @@ mod handlers { inline_local_variable::inline_local_variable, inline_type_alias::inline_type_alias, inline_type_alias::inline_type_alias_uses, + into_to_qualified_from::into_to_qualified_from, introduce_named_generic::introduce_named_generic, introduce_named_lifetime::introduce_named_lifetime, invert_if::invert_if, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs index 6eadc3dbc..dfaa53449 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs @@ -266,6 +266,21 @@ pub mod std { pub mod collections { pub struct HashMap { } } } } #[test] +fn doctest_bind_unused_param() { + check_doc_test( + "bind_unused_param", + r#####" +fn some_function(x: i32$0) {} +"#####, + r#####" +fn some_function(x: i32) { + let _ = x; +} +"#####, + ) +} + +#[test] fn doctest_change_visibility() { check_doc_test( "change_visibility", @@ -694,25 +709,12 @@ fn doctest_extract_expressions_from_format_string() { check_doc_test( "extract_expressions_from_format_string", r#####" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} -macro_rules! print { - ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -} - +//- minicore: fmt fn main() { print!("{var} {x + 1}$0"); } "#####, r#####" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} -macro_rules! print { - ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -} - fn main() { print!("{var} {}"$0, x + 1); } @@ -1755,6 +1757,40 @@ fn foo() { } #[test] +fn doctest_into_to_qualified_from() { + check_doc_test( + "into_to_qualified_from", + r#####" +//- minicore: from +struct B; +impl From<i32> for B { + fn from(a: i32) -> Self { + B + } +} + +fn main() -> () { + let a = 3; + let b: B = a.in$0to(); +} +"#####, + r#####" +struct B; +impl From<i32> for B { + fn from(a: i32) -> Self { + B + } +} + +fn main() -> () { + let a = 3; + let b: B = B::from(a); +} +"#####, + ) +} + +#[test] fn doctest_introduce_named_generic() { check_doc_test( "introduce_named_generic", diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs index f74ebfae0..16704d598 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs @@ -103,7 +103,6 @@ pub(crate) fn for_variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) match expr { ast::Expr::RefExpr(inner) => next_expr = inner.expr(), - ast::Expr::BoxExpr(inner) => next_expr = inner.expr(), ast::Expr::AwaitExpr(inner) => next_expr = inner.expr(), // ast::Expr::BlockExpr(block) => expr = block.tail_expr(), ast::Expr::CastExpr(inner) => next_expr = inner.expr(), |