diff options
Diffstat (limited to '')
44 files changed, 3044 insertions, 572 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml b/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml index e781c0a01..b9260473b 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml +++ b/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml @@ -14,6 +14,7 @@ cov-mark = "2.0.0-pre.1" itertools = "0.10.5" either = "1.7.0" +smallvec = "1.10.0" stdx = { path = "../stdx", version = "0.0.0" } syntax = { path = "../syntax", version = "0.0.0" } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_type.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_type.rs index b5f99726f..0057f439f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_type.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_type.rs @@ -47,7 +47,10 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> O // Don't enable the assist if there is a type ascription without any placeholders if let Some(ty) = &ascribed_ty { let mut contains_infer_ty = false; - walk_ty(ty, &mut |ty| contains_infer_ty |= matches!(ty, ast::Type::InferType(_))); + walk_ty(ty, &mut |ty| { + contains_infer_ty |= matches!(ty, ast::Type::InferType(_)); + false + }); if !contains_infer_ty { cov_mark::hit!(add_explicit_type_not_applicable_if_ty_already_specified); return None; 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 2b3793659..161bcc5c8 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 @@ -107,6 +107,14 @@ fn add_missing_impl_members_inner( ) -> Option<()> { let _p = profile::span("add_missing_impl_members_inner"); let impl_def = ctx.find_node_at_offset::<ast::Impl>()?; + + if ctx.token_at_offset().all(|t| { + t.parent_ancestors() + .any(|s| ast::BlockExpr::can_cast(s.kind()) || ast::ParamList::can_cast(s.kind())) + }) { + return None; + } + let target_scope = ctx.sema.scope(impl_def.syntax())?; let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; @@ -1343,4 +1351,95 @@ impl PartialEq for SomeStruct { "#, ); } + + #[test] + fn test_ignore_function_body() { + check_assist_not_applicable( + add_missing_default_members, + r#" +trait Trait { + type X; + fn foo(&self); + fn bar(&self) {} +} + +impl Trait for () { + type X = u8; + fn foo(&self) {$0 + let x = 5; + } +}"#, + ) + } + + #[test] + fn test_ignore_param_list() { + check_assist_not_applicable( + add_missing_impl_members, + r#" +trait Trait { + type X; + fn foo(&self); + fn bar(&self); +} + +impl Trait for () { + type X = u8; + fn foo(&self$0) { + let x = 5; + } +}"#, + ) + } + + #[test] + fn test_ignore_scope_inside_function() { + check_assist_not_applicable( + add_missing_impl_members, + r#" +trait Trait { + type X; + fn foo(&self); + fn bar(&self); +} + +impl Trait for () { + type X = u8; + fn foo(&self) { + let x = async {$0 5 }; + } +}"#, + ) + } + + #[test] + fn test_apply_outside_function() { + check_assist( + add_missing_default_members, + r#" +trait Trait { + type X; + fn foo(&self); + fn bar(&self) {} +} + +impl Trait for () { + type X = u8; + fn foo(&self)$0 {} +}"#, + r#" +trait Trait { + type X; + fn foo(&self); + fn bar(&self) {} +} + +impl Trait for () { + type X = u8; + fn foo(&self) {} + + $0fn bar(&self) {} +}"#, + ) + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs index 73f4db4e5..8e4ac69ae 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs @@ -326,7 +326,7 @@ impl ExtendedEnum { fn resolve_enum_def(sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr) -> Option<ExtendedEnum> { sema.type_of_expr(expr)?.adjusted().autoderef(sema.db).find_map(|ty| match ty.as_adt() { Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)), - _ => ty.is_bool().then(|| ExtendedEnum::Bool), + _ => ty.is_bool().then_some(ExtendedEnum::Bool), }) } @@ -344,7 +344,7 @@ fn resolve_tuple_of_enum_def( // For now we only handle expansion for a tuple of enums. Here // we map non-enum items to None and rely on `collect` to // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>. - _ => ty.is_bool().then(|| ExtendedEnum::Bool), + _ => ty.is_bool().then_some(ExtendedEnum::Bool), }) }) .collect() diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_return_type.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_return_type.rs index 89040a856..879c478ac 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_return_type.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_return_type.rs @@ -35,16 +35,16 @@ pub(crate) fn add_return_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt match builder_edit_pos { InsertOrReplace::Insert(insert_pos, needs_whitespace) => { let preceeding_whitespace = if needs_whitespace { " " } else { "" }; - builder.insert(insert_pos, &format!("{preceeding_whitespace}-> {ty} ")) + builder.insert(insert_pos, format!("{preceeding_whitespace}-> {ty} ")) } InsertOrReplace::Replace(text_range) => { - builder.replace(text_range, &format!("-> {ty}")) + builder.replace(text_range, format!("-> {ty}")) } } if let FnType::Closure { wrap_expr: true } = fn_type { cov_mark::hit!(wrap_closure_non_block_expr); // `|x| x` becomes `|x| -> T x` which is invalid, so wrap it in a block - builder.replace(tail_expr.syntax().text_range(), &format!("{{{tail_expr}}}")); + builder.replace(tail_expr.syntax().text_range(), format!("{{{tail_expr}}}")); } }, ) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs index a689270bc..698ad78cc 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs @@ -203,7 +203,7 @@ fn relevance_score( // get the distance between the imported path and the current module // (prefer items that are more local) Some((item_module, current_module)) => { - score -= module_distance_hueristic(db, ¤t_module, &item_module) as i32; + score -= module_distance_hueristic(db, current_module, &item_module) as i32; } // could not find relevant modules, so just use the length of the path as an estimate diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs index 80eecf4a0..f32ef2d59 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs @@ -216,7 +216,7 @@ fn validate_method_call_expr( let krate = module.krate(); let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?; - it_type.impls_trait(sema.db, iter_trait, &[]).then(|| (expr, receiver)) + it_type.impls_trait(sema.db, iter_trait, &[]).then_some((expr, receiver)) } #[cfg(test)] diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index 92e091fca..b0383291e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -168,7 +168,7 @@ fn edit_struct_references( let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?; edit.replace( - call_expr.syntax().text_range(), + ctx.sema.original_range(&node).range, ast::make::record_expr( path, ast::make::record_expr_field_list(arg_list.args().zip(names).map( @@ -249,6 +249,24 @@ mod tests { ); check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"struct Foo$0;"#); } + #[test] + fn convert_in_macro_args() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +macro_rules! foo {($i:expr) => {$i} } +struct T$0(u8); +fn test() { + foo!(T(1)); +}"#, + r#" +macro_rules! foo {($i:expr) => {$i} } +struct T { field1: u8 } +fn test() { + foo!(T { field1: 1 }); +}"#, + ); + } #[test] fn convert_simple_struct() { @@ -555,6 +573,29 @@ where } #[test] + fn convert_variant_in_macro_args() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +macro_rules! foo {($i:expr) => {$i} } +enum T { + V$0(u8) +} +fn test() { + foo!(T::V(1)); +}"#, + r#" +macro_rules! foo {($i:expr) => {$i} } +enum T { + V { field1: u8 } +} +fn test() { + foo!(T::V { field1: 1 }); +}"#, + ); + } + + #[test] fn convert_simple_variant() { check_assist( convert_tuple_struct_to_named_struct, 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 new file mode 100644 index 000000000..4f3b6e0c2 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs @@ -0,0 +1,322 @@ +use crate::{AssistContext, Assists}; +use ide_db::{ + assists::{AssistId, AssistKind}, + syntax_helpers::{ + format_string::is_format_string, + format_string_exprs::{parse_format_exprs, Arg}, + }, +}; +use itertools::Itertools; +use stdx::format_to; +use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange}; + +// Assist: extract_expressions_from_format_string +// +// 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)*))); +// } +// +// 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); +// } +// ``` + +pub(crate) fn extract_expressions_from_format_string( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + let fmt_string = ctx.find_token_at_offset::<ast::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()), + )?; + if !is_format_string(&expanded_t) { + return None; + } + + let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?; + if extracted_args.is_empty() { + return None; + } + + acc.add( + AssistId( + "extract_expressions_from_format_string", + // if there aren't any expressions, then make the assist a RefactorExtract + if extracted_args.iter().filter(|f| matches!(f, Arg::Expr(_))).count() == 0 { + AssistKind::RefactorExtract + } else { + AssistKind::QuickFix + }, + ), + "Extract format expressions", + tt.syntax().text_range(), + |edit| { + let fmt_range = fmt_string.syntax().text_range(); + + // Replace old format string with new format string whose arguments have been extracted + edit.replace(fmt_range, new_fmt); + + // Insert cursor at end of format string + edit.insert(fmt_range.end(), "$0"); + + // Extract existing arguments in macro + let tokens = + tt.token_trees_and_tokens().collect_vec(); + + let mut existing_args: Vec<String> = vec![]; + + let mut current_arg = String::new(); + if let [_opening_bracket, NodeOrToken::Token(format_string), _args_start_comma, tokens @ .., NodeOrToken::Token(end_bracket)] = + tokens.as_slice() + { + for t in tokens { + match t { + NodeOrToken::Node(n) => { + format_to!(current_arg, "{n}"); + }, + NodeOrToken::Token(t) if t.kind() == COMMA => { + existing_args.push(current_arg.trim().into()); + current_arg.clear(); + }, + NodeOrToken::Token(t) => { + current_arg.push_str(t.text()); + }, + } + } + existing_args.push(current_arg.trim().into()); + + // delete everything after the format string till end bracket + // we're going to insert the new arguments later + edit.delete(TextRange::new( + format_string.text_range().end(), + end_bracket.text_range().start(), + )); + } + + // Start building the new args + let mut existing_args = existing_args.into_iter(); + let mut args = String::new(); + + let mut placeholder_idx = 1; + + for extracted_args in extracted_args { + match extracted_args { + Arg::Expr(s)=> { + args.push_str(", "); + // insert arg + args.push_str(&s); + } + Arg::Placeholder => { + args.push_str(", "); + // try matching with existing argument + match existing_args.next() { + Some(ea) => { + args.push_str(&ea); + } + None => { + // insert placeholder + args.push_str(&format!("${placeholder_idx}")); + placeholder_idx += 1; + } + } + } + Arg::Ident(_s) => (), + } + } + + // Insert new args + edit.insert(fmt_range.end(), args); + }, + ); + + Some(()) +} + +#[cfg(test)] +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#" +fn main() { + print!("{} {x + 1:b} {}$0", y + 2, 2); +} +"#, + ), + &add_macro_decl( + r#" +fn main() { + print!("{} {:b} {}"$0, y + 2, x + 1, 2); +} +"#, + ), + ); + } + + #[test] + fn single_arg() { + check_assist( + extract_expressions_from_format_string, + &add_macro_decl( + r#" +fn main() { + print!("{obj.value:b}$0",); +} +"#, + ), + &add_macro_decl( + r#" +fn main() { + print!("{:b}"$0, obj.value); +} +"#, + ), + ); + } + + #[test] + fn multiple_middle_placeholders_arg() { + check_assist( + extract_expressions_from_format_string, + &add_macro_decl( + r#" +fn main() { + print!("{} {x + 1:b} {} {}$0", y + 2, 2); +} +"#, + ), + &add_macro_decl( + r#" +fn main() { + print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1); +} +"#, + ), + ); + } + + #[test] + fn multiple_trailing_args() { + check_assist( + extract_expressions_from_format_string, + &add_macro_decl( + r#" +fn main() { + print!("{:b} {x + 1:b} {Struct(1, 2)}$0", 1); +} +"#, + ), + &add_macro_decl( + r#" +fn main() { + print!("{:b} {:b} {}"$0, 1, x + 1, Struct(1, 2)); +} +"#, + ), + ); + } + + #[test] + fn improper_commas() { + check_assist( + extract_expressions_from_format_string, + &add_macro_decl( + r#" +fn main() { + print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,); +} +"#, + ), + &add_macro_decl( + r#" +fn main() { + print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2)); +} +"#, + ), + ); + } + + #[test] + fn nested_tt() { + check_assist( + extract_expressions_from_format_string, + &add_macro_decl( + r#" +fn main() { + print!("My name is {} {x$0 + x}", stringify!(Paperino)) +} +"#, + ), + &add_macro_decl( + r#" +fn main() { + print!("My name is {} {}"$0, stringify!(Paperino), x + x) +} +"#, + ), + ); + } + + #[test] + fn extract_only_expressions() { + check_assist( + extract_expressions_from_format_string, + &add_macro_decl( + r#" +fn main() { + let var = 1 + 1; + print!("foobar {var} {var:?} {x$0 + x}") +} +"#, + ), + &add_macro_decl( + 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 c1e2f19ab..e04a1dabb 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 @@ -11,7 +11,9 @@ use ide_db::{ helpers::mod_path_to_ast, imports::insert_use::{insert_use, ImportScope}, search::{FileReference, ReferenceCategory, SearchScope}, - syntax_helpers::node_ext::{preorder_expr, walk_expr, walk_pat, walk_patterns_in_expr}, + syntax_helpers::node_ext::{ + for_each_tail_expr, preorder_expr, walk_expr, walk_pat, walk_patterns_in_expr, + }, FxIndexSet, RootDatabase, }; use itertools::Itertools; @@ -78,7 +80,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op }; let body = extraction_target(&node, range)?; - let container_info = body.analyze_container(&ctx.sema)?; + let (container_info, contains_tail_expr) = body.analyze_container(&ctx.sema)?; let (locals_used, self_param) = body.analyze(&ctx.sema); @@ -119,6 +121,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op ret_ty, body, outliving_locals, + contains_tail_expr, mods: container_info, }; @@ -245,6 +248,8 @@ struct Function { ret_ty: RetType, body: FunctionBody, outliving_locals: Vec<OutlivedLocal>, + /// Whether at least one of the container's tail expr is contained in the range we're extracting. + contains_tail_expr: bool, mods: ContainerInfo, } @@ -265,7 +270,7 @@ enum ParamKind { MutRef, } -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug)] enum FunType { Unit, Single(hir::Type), @@ -294,7 +299,6 @@ struct ControlFlow { #[derive(Clone, Debug)] struct ContainerInfo { is_const: bool, - is_in_tail: bool, parent_loop: Option<SyntaxNode>, /// The function's return type, const's type etc. ret_type: Option<hir::Type>, @@ -584,7 +588,7 @@ impl FunctionBody { FunctionBody::Expr(expr) => Some(expr.clone()), FunctionBody::Span { parent, text_range } => { let tail_expr = parent.tail_expr()?; - text_range.contains_range(tail_expr.syntax().text_range()).then(|| tail_expr) + text_range.contains_range(tail_expr.syntax().text_range()).then_some(tail_expr) } } } @@ -743,7 +747,10 @@ impl FunctionBody { (res, self_param) } - fn analyze_container(&self, sema: &Semantics<'_, RootDatabase>) -> Option<ContainerInfo> { + fn analyze_container( + &self, + sema: &Semantics<'_, RootDatabase>, + ) -> Option<(ContainerInfo, bool)> { let mut ancestors = self.parent()?.ancestors(); let infer_expr_opt = |expr| sema.type_of_expr(&expr?).map(TypeInfo::adjusted); let mut parent_loop = None; @@ -815,28 +822,36 @@ impl FunctionBody { } }; }; - let container_tail = match expr? { - ast::Expr::BlockExpr(block) => block.tail_expr(), - expr => Some(expr), - }; - let is_in_tail = - container_tail.zip(self.tail_expr()).map_or(false, |(container_tail, body_tail)| { - container_tail.syntax().text_range().contains_range(body_tail.syntax().text_range()) + + let expr = expr?; + let contains_tail_expr = if let Some(body_tail) = self.tail_expr() { + let mut contains_tail_expr = false; + let tail_expr_range = body_tail.syntax().text_range(); + for_each_tail_expr(&expr, &mut |e| { + if tail_expr_range.contains_range(e.syntax().text_range()) { + contains_tail_expr = true; + } }); + contains_tail_expr + } else { + false + }; let parent = self.parent()?; let parents = generic_parents(&parent); let generic_param_lists = parents.iter().filter_map(|it| it.generic_param_list()).collect(); let where_clauses = parents.iter().filter_map(|it| it.where_clause()).collect(); - Some(ContainerInfo { - is_in_tail, - is_const, - parent_loop, - ret_type: ty, - generic_param_lists, - where_clauses, - }) + Some(( + ContainerInfo { + is_const, + parent_loop, + ret_type: ty, + generic_param_lists, + where_clauses, + }, + contains_tail_expr, + )) } fn return_ty(&self, ctx: &AssistContext<'_>) -> Option<RetType> { @@ -1368,7 +1383,7 @@ impl FlowHandler { None => FlowHandler::None, Some(flow_kind) => { let action = flow_kind.clone(); - if *ret_ty == FunType::Unit { + if let FunType::Unit = ret_ty { match flow_kind { FlowKind::Return(None) | FlowKind::Break(_, None) @@ -1633,7 +1648,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.mods.is_in_tail { + let handler = if self.contains_tail_expr { FlowHandler::None } else { FlowHandler::from_ret_ty(self, &fun_ty) @@ -1707,7 +1722,7 @@ fn make_body( fun: &Function, ) -> ast::BlockExpr { let ret_ty = fun.return_type(ctx); - let handler = if fun.mods.is_in_tail { + let handler = if fun.contains_tail_expr { FlowHandler::None } else { FlowHandler::from_ret_ty(fun, &ret_ty) @@ -1785,7 +1800,7 @@ fn make_body( .collect::<Vec<SyntaxElement>>(); let tail_expr = tail_expr.map(|expr| expr.dedent(old_indent).indent(body_indent)); - make::hacky_block_expr_with_comments(elements, tail_expr) + make::hacky_block_expr(elements, tail_expr) } }; @@ -1845,9 +1860,29 @@ fn with_default_tail_expr(block: ast::BlockExpr, tail_expr: ast::Expr) -> ast::B } fn with_tail_expr(block: ast::BlockExpr, tail_expr: ast::Expr) -> ast::BlockExpr { - let stmt_tail = block.tail_expr().map(|expr| make::expr_stmt(expr).into()); - let stmts = block.statements().chain(stmt_tail); - make::block_expr(stmts, Some(tail_expr)) + let stmt_tail_opt: Option<ast::Stmt> = + block.tail_expr().map(|expr| make::expr_stmt(expr).into()); + + let mut elements: Vec<SyntaxElement> = vec![]; + + block.statements().for_each(|stmt| { + elements.push(syntax::NodeOrToken::Node(stmt.syntax().clone())); + }); + + 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 Some(stmt_tail) = stmt_tail_opt { + elements.push(syntax::NodeOrToken::Node(stmt_tail.syntax().clone())); + } + + make::hacky_block_expr(elements, Some(tail_expr)) } fn format_type(ty: &hir::Type, ctx: &AssistContext<'_>, module: hir::Module) -> String { @@ -1946,7 +1981,7 @@ fn update_external_control_flow(handler: &FlowHandler, syntax: &SyntaxNode) { if nested_scope.is_none() { if let Some(expr) = ast::Expr::cast(e.clone()) { match expr { - ast::Expr::ReturnExpr(return_expr) if nested_scope.is_none() => { + ast::Expr::ReturnExpr(return_expr) => { let expr = return_expr.expr(); if let Some(replacement) = make_rewritten_flow(handler, expr) { ted::replace(return_expr.syntax(), replacement.syntax()) @@ -4944,9 +4979,8 @@ fn $0fun_name() { ); } - // FIXME: we do want to preserve whitespace #[test] - fn extract_function_does_not_preserve_whitespace() { + fn extract_function_does_preserve_whitespace() { check_assist( extract_function, r#" @@ -4965,6 +4999,7 @@ fn func() { fn $0fun_name() { let a = 0; + let x = 0; } "#, @@ -5585,4 +5620,191 @@ fn $0fun_name<T, V>(t: T, v: V) -> i32 where T: Into<i32> + Copy, V: Into<i32> { "#, ); } + + #[test] + fn non_tail_expr_of_tail_expr_loop() { + check_assist( + extract_function, + r#" +pub fn f() { + loop { + $0if true { + continue; + }$0 + + if false { + break; + } + } +} +"#, + r#" +pub fn f() { + loop { + if let ControlFlow::Break(_) = fun_name() { + continue; + } + + if false { + break; + } + } +} + +fn $0fun_name() -> ControlFlow<()> { + if true { + return ControlFlow::Break(()); + } + ControlFlow::Continue(()) +} +"#, + ); + } + + #[test] + fn non_tail_expr_of_tail_if_block() { + // FIXME: double semicolon + check_assist( + 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 { + Some(())? + } else { + () + }$0; + Some(a) + } else { + None + } +} +"#, + 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()?;; + Some(a) + } else { + None + } +} + +fn $0fun_name() -> Option<()> { + Some(if true { + Some(())? + } else { + () + }) +} +"#, + ); + } + + #[test] + fn tail_expr_of_tail_block_nested() { + check_assist( + 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{ + let a = if true { + Some(())? + } else { + () + }; + Some(a) + }$0 + } else { + None + } +} +"#, + 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()? + } else { + None + } +} + +fn $0fun_name() -> Option<()> { + let a = if true { + Some(())? + } else { + () + }; + Some(a) +} +"#, + ); + } + + #[test] + fn non_tail_expr_with_comment_of_tail_expr_loop() { + check_assist( + extract_function, + r#" +pub fn f() { + loop { + $0// A comment + if true { + continue; + }$0 + if false { + break; + } + } +} +"#, + r#" +pub fn f() { + loop { + if let ControlFlow::Break(_) = fun_name() { + continue; + } + if false { + break; + } + } +} + +fn $0fun_name() -> ControlFlow<()> { + // A comment + if true { + return ControlFlow::Break(()); + } + ControlFlow::Continue(()) +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs index 56834394a..0fa7bd558 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs @@ -10,6 +10,8 @@ use ide_db::{ defs::{Definition, NameClass, NameRefClass}, search::{FileReference, SearchScope}, }; +use itertools::Itertools; +use smallvec::SmallVec; use stdx::format_to; use syntax::{ algo::find_node_at_range, @@ -116,13 +118,13 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti let mut body_items: Vec<String> = Vec::new(); let mut items_to_be_processed: Vec<ast::Item> = module.body_items.clone(); - let mut new_item_indent = old_item_indent + 1; - if impl_parent.is_some() { - new_item_indent = old_item_indent + 2; + let new_item_indent = if impl_parent.is_some() { + old_item_indent + 2 } else { items_to_be_processed = [module.use_items.clone(), items_to_be_processed].concat(); - } + old_item_indent + 1 + }; for item in items_to_be_processed { let item = item.indent(IndentLevel(1)); @@ -657,28 +659,23 @@ impl Module { fn check_intersection_and_push( import_paths_to_be_removed: &mut Vec<TextRange>, - import_path: TextRange, + mut import_path: TextRange, ) { - if import_paths_to_be_removed.len() > 0 { - // Text ranges received here for imports are extended to the - // next/previous comma which can cause intersections among them - // and later deletion of these can cause panics similar - // to reported in #11766. So to mitigate it, we - // check for intersection between all current members - // and if it exists we combine both text ranges into - // one - let r = import_paths_to_be_removed - .into_iter() - .position(|it| it.intersect(import_path).is_some()); - match r { - Some(it) => { - import_paths_to_be_removed[it] = import_paths_to_be_removed[it].cover(import_path) - } - None => import_paths_to_be_removed.push(import_path), - } - } else { - import_paths_to_be_removed.push(import_path); + // Text ranges received here for imports are extended to the + // next/previous comma which can cause intersections among them + // and later deletion of these can cause panics similar + // to reported in #11766. So to mitigate it, we + // check for intersection between all current members + // and combine all such ranges into one. + let s: SmallVec<[_; 2]> = import_paths_to_be_removed + .into_iter() + .positions(|it| it.intersect(import_path).is_some()) + .collect(); + for pos in s.into_iter().rev() { + let intersecting_path = import_paths_to_be_removed.swap_remove(pos); + import_path = import_path.cover(intersecting_path); } + import_paths_to_be_removed.push(import_path); } fn does_source_exists_outside_sel_in_same_mod( @@ -1766,4 +1763,49 @@ mod modname { ", ) } + + #[test] + fn test_merge_multiple_intersections() { + check_assist( + extract_module, + r#" +mod dep { + pub struct A; + pub struct B; + pub struct C; +} + +use dep::{A, B, C}; + +$0struct S { + inner: A, + state: C, + condvar: B, +}$0 +"#, + r#" +mod dep { + pub struct A; + pub struct B; + pub struct C; +} + +use dep::{}; + +mod modname { + use super::dep::B; + + use super::dep::C; + + use super::dep::A; + + pub(crate) struct S { + pub(crate) inner: A, + pub(crate) state: C, + pub(crate) condvar: B, + } +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs index b4e10667b..49debafe1 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs @@ -178,7 +178,7 @@ fn extract_generic_params( .fold(false, |tagged, ty| tag_generics_in_variant(&ty, &mut generics) || tagged), }; - let generics = generics.into_iter().filter_map(|(param, tag)| tag.then(|| param)); + let generics = generics.into_iter().filter_map(|(param, tag)| tag.then_some(param)); tagged_one.then(|| make::generic_param_list(generics)) } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs index 3116935fc..0505f5784 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs @@ -108,76 +108,80 @@ fn collect_used_generics<'gp>( } let mut generics = Vec::new(); - walk_ty(ty, &mut |ty| match ty { - ast::Type::PathType(ty) => { - if let Some(path) = ty.path() { - if let Some(name_ref) = path.as_single_name_ref() { - if let Some(param) = known_generics.iter().find(|gp| { - match gp { - ast::GenericParam::ConstParam(cp) => cp.name(), - ast::GenericParam::TypeParam(tp) => tp.name(), - _ => None, + walk_ty(ty, &mut |ty| { + match ty { + ast::Type::PathType(ty) => { + if let Some(path) = ty.path() { + if let Some(name_ref) = path.as_single_name_ref() { + if let Some(param) = known_generics.iter().find(|gp| { + match gp { + ast::GenericParam::ConstParam(cp) => cp.name(), + ast::GenericParam::TypeParam(tp) => tp.name(), + _ => None, + } + .map_or(false, |n| n.text() == name_ref.text()) + }) { + generics.push(param); } - .map_or(false, |n| n.text() == name_ref.text()) - }) { - generics.push(param); } + generics.extend( + path.segments() + .filter_map(|seg| seg.generic_arg_list()) + .flat_map(|it| it.generic_args()) + .filter_map(|it| match it { + ast::GenericArg::LifetimeArg(lt) => { + let lt = lt.lifetime()?; + known_generics.iter().find(find_lifetime(<.text())) + } + _ => None, + }), + ); } - generics.extend( - path.segments() - .filter_map(|seg| seg.generic_arg_list()) - .flat_map(|it| it.generic_args()) - .filter_map(|it| match it { - ast::GenericArg::LifetimeArg(lt) => { - let lt = lt.lifetime()?; - known_generics.iter().find(find_lifetime(<.text())) - } - _ => None, - }), - ); } - } - ast::Type::ImplTraitType(impl_ty) => { - if let Some(it) = impl_ty.type_bound_list() { - generics.extend( - it.bounds() - .filter_map(|it| it.lifetime()) - .filter_map(|lt| known_generics.iter().find(find_lifetime(<.text()))), - ); + ast::Type::ImplTraitType(impl_ty) => { + if let Some(it) = impl_ty.type_bound_list() { + generics.extend( + it.bounds() + .filter_map(|it| it.lifetime()) + .filter_map(|lt| known_generics.iter().find(find_lifetime(<.text()))), + ); + } } - } - ast::Type::DynTraitType(dyn_ty) => { - if let Some(it) = dyn_ty.type_bound_list() { - generics.extend( - it.bounds() - .filter_map(|it| it.lifetime()) - .filter_map(|lt| known_generics.iter().find(find_lifetime(<.text()))), - ); + ast::Type::DynTraitType(dyn_ty) => { + if let Some(it) = dyn_ty.type_bound_list() { + generics.extend( + it.bounds() + .filter_map(|it| it.lifetime()) + .filter_map(|lt| known_generics.iter().find(find_lifetime(<.text()))), + ); + } } - } - ast::Type::RefType(ref_) => generics.extend( - ref_.lifetime().and_then(|lt| known_generics.iter().find(find_lifetime(<.text()))), - ), - ast::Type::ArrayType(ar) => { - if let Some(expr) = ar.expr() { - if let ast::Expr::PathExpr(p) = expr { - if let Some(path) = p.path() { - if let Some(name_ref) = path.as_single_name_ref() { - if let Some(param) = known_generics.iter().find(|gp| { - if let ast::GenericParam::ConstParam(cp) = gp { - cp.name().map_or(false, |n| n.text() == name_ref.text()) - } else { - false + ast::Type::RefType(ref_) => generics.extend( + ref_.lifetime() + .and_then(|lt| known_generics.iter().find(find_lifetime(<.text()))), + ), + ast::Type::ArrayType(ar) => { + if let Some(expr) = ar.expr() { + if let ast::Expr::PathExpr(p) = expr { + if let Some(path) = p.path() { + if let Some(name_ref) = path.as_single_name_ref() { + if let Some(param) = known_generics.iter().find(|gp| { + if let ast::GenericParam::ConstParam(cp) = gp { + cp.name().map_or(false, |n| n.text() == name_ref.text()) + } else { + false + } + }) { + generics.push(param); } - }) { - generics.push(param); } } } } } - } - _ => (), + _ => (), + }; + false }); // stable resort to lifetime, type, const generics.sort_by_key(|gp| match gp { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs index 49d9fd707..2d074a33e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs @@ -53,7 +53,7 @@ pub(crate) fn generate_default_from_new(acc: &mut Assists, ctx: &AssistContext<' return None; } - let impl_ = fn_node.syntax().ancestors().into_iter().find_map(ast::Impl::cast)?; + let impl_ = fn_node.syntax().ancestors().find_map(ast::Impl::cast)?; if is_default_implemented(ctx, &impl_) { cov_mark::hit!(default_block_is_already_present); cov_mark::hit!(struct_in_module_with_default); 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 ceae80755..c8d0493d0 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 @@ -81,7 +81,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' acc.add_group( &GroupLabel("Generate delegate methods…".to_owned()), AssistId("generate_delegate_methods", AssistKind::Generate), - format!("Generate delegate for `{}.{}()`", field_name, method.name(ctx.db())), + format!("Generate delegate for `{field_name}.{}()`", method.name(ctx.db())), target, |builder| { // Create the function @@ -104,9 +104,11 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' make::name_ref(&method_name.to_string()), arg_list, ); - let body = make::block_expr([], Some(tail_expr)); let ret_type = method_source.ret_type(); let is_async = method_source.async_token().is_some(); + let tail_expr_finished = + if is_async { make::expr_await(tail_expr) } else { tail_expr }; + let body = make::block_expr([], Some(tail_expr_finished)); let f = make::fn_(vis, name, type_params, params, body, ret_type, is_async) .indent(ast::edit::IndentLevel(1)) .clone_for_update(); @@ -306,7 +308,7 @@ struct Person<T> { impl<T> Person<T> { $0pub(crate) async fn age<J, 'a>(&'a mut self, ty: T, arg: J) -> T { - self.age.age(ty, arg) + self.age.age(ty, arg).await } }"#, ); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_deref.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_deref.rs index 55b7afb3d..b6958e291 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_deref.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_deref.rs @@ -85,8 +85,7 @@ fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<() let strukt = ctx.find_node_at_offset::<ast::Struct>()?; let field = ctx.find_node_at_offset::<ast::TupleField>()?; let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?; - let field_list_index = - field_list.syntax().children().into_iter().position(|s| &s == field.syntax())?; + let field_list_index = field_list.syntax().children().position(|s| &s == field.syntax())?; let deref_type_to_generate = match existing_deref_impl(&ctx.sema, &strukt) { None => DerefType::Deref, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs index c9aa41c84..ee643ce9a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs @@ -157,7 +157,7 @@ fn generate_enum_projection_method( assist_description, target, |builder| { - let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); + let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} ")); let field_type_syntax = field_type.syntax(); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs index 0bcb57283..cd037f749 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs @@ -180,7 +180,7 @@ fn make_tuple_field_list( ) -> Option<ast::FieldList> { let args = call_expr.arg_list()?.args(); let tuple_fields = args.map(|arg| { - let ty = expr_ty(ctx, arg, &scope).unwrap_or_else(make::ty_placeholder); + let ty = expr_ty(ctx, arg, scope).unwrap_or_else(make::ty_placeholder); make::tuple_field(None, ty) }); Some(make::tuple_field_list(tuple_fields).into()) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs index 7c81d2c6a..742f1f78c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs @@ -1,7 +1,9 @@ use ide_db::{famous_defs::FamousDefs, RootDatabase}; use syntax::ast::{self, AstNode, HasName}; -use crate::{utils::generate_trait_impl_text, AssistContext, AssistId, AssistKind, Assists}; +use crate::{ + utils::generate_trait_impl_text_intransitive, AssistContext, AssistId, AssistKind, Assists, +}; // Assist: generate_from_impl_for_enum // @@ -70,7 +72,7 @@ pub(crate) fn generate_from_impl_for_enum( }}"# ) }; - let from_impl = generate_trait_impl_text(&enum_, &from_trait, &impl_code); + let from_impl = generate_trait_impl_text_intransitive(&enum_, &from_trait, &impl_code); edit.insert(start_offset, from_impl); }, ) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs index 57f198748..da9b0cda5 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs @@ -514,7 +514,7 @@ fn fn_args( /// vec!["foo_1".into(), "foo_2".into(), "bar_1".into(), "baz".into(), "bar_2".into()]; /// assert_eq!(names, expected); /// ``` -fn deduplicate_arg_names(arg_names: &mut Vec<String>) { +fn deduplicate_arg_names(arg_names: &mut [String]) { let mut arg_name_counts = FxHashMap::default(); for name in arg_names.iter() { *arg_name_counts.entry(name).or_insert(0) += 1; diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs index 5e7191428..15641b448 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs @@ -176,7 +176,7 @@ pub(crate) fn generate_getter_impl( // for separating it from other assoc items, that needs // to be handled spearately let mut getter_buf = - generate_getter_from_info(ctx, &getter_info, &record_field_info); + generate_getter_from_info(ctx, &getter_info, record_field_info); // Insert `$0` only for last getter we generate if i == record_fields_count - 1 { @@ -235,7 +235,7 @@ fn generate_getter_from_info( ) -> String { let mut buf = String::with_capacity(512); - let vis = info.strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); + let vis = info.strukt.visibility().map_or(String::new(), |v| format!("{v} ")); let (ty, body) = if info.mutable { ( format!("&mut {}", record_field_info.field_ty), @@ -271,7 +271,7 @@ fn generate_getter_from_info( }}", vis, record_field_info.fn_name, - info.mutable.then(|| "mut ").unwrap_or_default(), + info.mutable.then_some("mut ").unwrap_or_default(), ty, body, ); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs index 9af26c04e..9ad14a819 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs @@ -1,14 +1,17 @@ use syntax::ast::{self, AstNode, HasName}; -use crate::{utils::generate_impl_text, AssistContext, AssistId, AssistKind, Assists}; +use crate::{ + utils::{generate_impl_text, generate_trait_impl_text_intransitive}, + AssistContext, AssistId, AssistKind, Assists, +}; // Assist: generate_impl // // Adds a new inherent impl for a type. // // ``` -// struct Ctx<T: Clone> { -// data: T,$0 +// struct Ctx$0<T: Clone> { +// data: T, // } // ``` // -> @@ -26,6 +29,10 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio let name = nominal.name()?; let target = nominal.syntax().text_range(); + if let Some(_) = ctx.find_node_at_offset::<ast::RecordFieldList>() { + return None; + } + acc.add( AssistId("generate_impl", AssistKind::Generate), format!("Generate impl for `{name}`"), @@ -46,145 +53,393 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio ) } +// Assist: generate_trait_impl +// +// Adds a new trait impl for a type. +// +// ``` +// struct $0Ctx<T: Clone> { +// data: T, +// } +// ``` +// -> +// ``` +// struct Ctx<T: Clone> { +// data: T, +// } +// +// impl<T: Clone> $0 for Ctx<T> { +// +// } +// ``` +pub(crate) fn generate_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let nominal = ctx.find_node_at_offset::<ast::Adt>()?; + let name = nominal.name()?; + let target = nominal.syntax().text_range(); + + if let Some(_) = ctx.find_node_at_offset::<ast::RecordFieldList>() { + return None; + } + + acc.add( + AssistId("generate_trait_impl", AssistKind::Generate), + format!("Generate trait impl for `{name}`"), + target, + |edit| { + let start_offset = nominal.syntax().text_range().end(); + match ctx.config.snippet_cap { + Some(cap) => { + let snippet = generate_trait_impl_text_intransitive(&nominal, "$0", ""); + edit.insert_snippet(cap, start_offset, snippet); + } + None => { + let text = generate_trait_impl_text_intransitive(&nominal, "", ""); + edit.insert(start_offset, text); + } + } + }, + ) +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_target}; use super::*; - // FIXME: break up into separate test fns #[test] fn test_add_impl() { check_assist( generate_impl, - "struct Foo {$0}\n", - "struct Foo {}\n\nimpl Foo {\n $0\n}\n", + r#" + struct Foo$0 {} + "#, + r#" + struct Foo {} + + impl Foo { + $0 + } + "#, ); + } + + #[test] + fn test_add_impl_with_generics() { check_assist( generate_impl, - "struct Foo<T: Clone> {$0}", - "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}", + r#" + struct Foo$0<T: Clone> {} + "#, + r#" + struct Foo<T: Clone> {} + + impl<T: Clone> Foo<T> { + $0 + } + "#, ); + } + + #[test] + fn test_add_impl_with_generics_and_lifetime_parameters() { check_assist( generate_impl, - "struct Foo<'a, T: Foo<'a>> {$0}", - "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}", + r#" + struct Foo<'a, T: Foo<'a>>$0 {} + "#, + r#" + struct Foo<'a, T: Foo<'a>> {} + + impl<'a, T: Foo<'a>> Foo<'a, T> { + $0 + } + "#, ); + } + + #[test] + fn test_add_impl_with_attributes() { check_assist( generate_impl, r#" - struct MyOwnArray<T, const S: usize> {}$0"#, + #[cfg(feature = "foo")] + struct Foo<'a, T: Foo$0<'a>> {} + "#, r#" - struct MyOwnArray<T, const S: usize> {} + #[cfg(feature = "foo")] + struct Foo<'a, T: Foo<'a>> {} - impl<T, const S: usize> MyOwnArray<T, S> { - $0 - }"#, + #[cfg(feature = "foo")] + impl<'a, T: Foo<'a>> Foo<'a, T> { + $0 + } + "#, ); + } + + #[test] + fn test_add_impl_with_default_generic() { check_assist( generate_impl, r#" - #[cfg(feature = "foo")] - struct Foo<'a, T: Foo<'a>> {$0}"#, + struct Defaulted$0<T = i32> {} + "#, r#" - #[cfg(feature = "foo")] - struct Foo<'a, T: Foo<'a>> {} + struct Defaulted<T = i32> {} - #[cfg(feature = "foo")] - impl<'a, T: Foo<'a>> Foo<'a, T> { - $0 - }"#, + impl<T> Defaulted<T> { + $0 + } + "#, ); + } + #[test] + fn test_add_impl_with_constrained_default_generic() { check_assist( generate_impl, r#" - #[cfg(not(feature = "foo"))] - struct Foo<'a, T: Foo<'a>> {$0}"#, + struct Defaulted$0<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {} + "#, r#" - #[cfg(not(feature = "foo"))] - struct Foo<'a, T: Foo<'a>> {} + struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {} - #[cfg(not(feature = "foo"))] - impl<'a, T: Foo<'a>> Foo<'a, T> { - $0 - }"#, + impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> Defaulted<'a, 'b, T, S> { + $0 + } + "#, ); + } + #[test] + fn test_add_impl_with_const_defaulted_generic() { check_assist( generate_impl, r#" - struct Defaulted<T = i32> {}$0"#, + struct Defaulted$0<const N: i32 = 0> {} + "#, r#" - struct Defaulted<T = i32> {} + struct Defaulted<const N: i32 = 0> {} - impl<T> Defaulted<T> { - $0 - }"#, + impl<const N: i32> Defaulted<N> { + $0 + } + "#, ); + } + #[test] + fn test_add_impl_with_trait_constraint() { check_assist( generate_impl, r#" - struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}$0"#, + pub trait Trait {} + struct Struct$0<T> + where + T: Trait, + { + inner: T, + } + "#, r#" - struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {} + pub trait Trait {} + struct Struct<T> + where + T: Trait, + { + inner: T, + } - impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> Defaulted<'a, 'b, T, S> { - $0 - }"#, + impl<T> Struct<T> + where + T: Trait, + { + $0 + } + "#, ); + } - check_assist( + #[test] + fn add_impl_target() { + check_assist_target( generate_impl, r#" - struct Defaulted<const N: i32 = 0> {}$0"#, + struct SomeThingIrrelevant; + /// Has a lifetime parameter + struct Foo$0<'a, T: Foo<'a>> {} + struct EvenMoreIrrelevant; + "#, + "/// Has a lifetime parameter\nstruct Foo<'a, T: Foo<'a>> {}", + ); + } + + #[test] + fn test_add_trait_impl() { + check_assist( + generate_trait_impl, + r#" + struct Foo$0 {} + "#, r#" - struct Defaulted<const N: i32 = 0> {} + struct Foo {} - impl<const N: i32> Defaulted<N> { - $0 - }"#, + impl $0 for Foo { + + } + "#, ); + } + #[test] + fn test_add_trait_impl_with_generics() { check_assist( - generate_impl, - r#"pub trait Trait {} -struct Struct<T>$0 -where - T: Trait, -{ - inner: T, -}"#, - r#"pub trait Trait {} -struct Struct<T> -where - T: Trait, -{ - inner: T, -} + generate_trait_impl, + r#" + struct Foo$0<T: Clone> {} + "#, + r#" + struct Foo<T: Clone> {} -impl<T> Struct<T> -where - T: Trait, -{ - $0 -}"#, + impl<T: Clone> $0 for Foo<T> { + + } + "#, ); } #[test] - fn add_impl_target() { + fn test_add_trait_impl_with_generics_and_lifetime_parameters() { + check_assist( + generate_trait_impl, + r#" + struct Foo<'a, T: Foo<'a>>$0 {} + "#, + r#" + struct Foo<'a, T: Foo<'a>> {} + + impl<'a, T: Foo<'a>> $0 for Foo<'a, T> { + + } + "#, + ); + } + + #[test] + fn test_add_trait_impl_with_attributes() { + check_assist( + generate_trait_impl, + r#" + #[cfg(feature = "foo")] + struct Foo<'a, T: Foo$0<'a>> {} + "#, + r#" + #[cfg(feature = "foo")] + struct Foo<'a, T: Foo<'a>> {} + + #[cfg(feature = "foo")] + impl<'a, T: Foo<'a>> $0 for Foo<'a, T> { + + } + "#, + ); + } + + #[test] + fn test_add_trait_impl_with_default_generic() { + check_assist( + generate_trait_impl, + r#" + struct Defaulted$0<T = i32> {} + "#, + r#" + struct Defaulted<T = i32> {} + + impl<T> $0 for Defaulted<T> { + + } + "#, + ); + } + + #[test] + fn test_add_trait_impl_with_constrained_default_generic() { + check_assist( + generate_trait_impl, + r#" + struct Defaulted$0<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {} + "#, + r#" + struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {} + + impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> $0 for Defaulted<'a, 'b, T, S> { + + } + "#, + ); + } + + #[test] + fn test_add_trait_impl_with_const_defaulted_generic() { + check_assist( + generate_trait_impl, + r#" + struct Defaulted$0<const N: i32 = 0> {} + "#, + r#" + struct Defaulted<const N: i32 = 0> {} + + impl<const N: i32> $0 for Defaulted<N> { + + } + "#, + ); + } + + #[test] + fn test_add_trait_impl_with_trait_constraint() { + check_assist( + generate_trait_impl, + r#" + pub trait Trait {} + struct Struct$0<T> + where + T: Trait, + { + inner: T, + } + "#, + r#" + pub trait Trait {} + struct Struct<T> + where + T: Trait, + { + inner: T, + } + + impl<T> $0 for Struct<T> + where + T: Trait, + { + + } + "#, + ); + } + + #[test] + fn add_trait_impl_target() { check_assist_target( - generate_impl, - " -struct SomeThingIrrelevant; -/// Has a lifetime parameter -struct Foo<'a, T: Foo<'a>> {$0} -struct EvenMoreIrrelevant; -", - "/// Has a lifetime parameter -struct Foo<'a, T: Foo<'a>> {}", + generate_trait_impl, + r#" + struct SomeThingIrrelevant; + /// Has a lifetime parameter + struct Foo$0<'a, T: Foo<'a>> {} + struct EvenMoreIrrelevant; + "#, + "/// Has a lifetime parameter\nstruct Foo<'a, T: Foo<'a>> {}", ); } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs index 17fadea0e..8d311262a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs @@ -70,7 +70,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option )?; let expr = use_trivial_constructor( - &ctx.sema.db, + ctx.sema.db, ide_db::helpers::mod_path_to_ast(&type_path), &ty, )?; diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs index 0c546ce5d..5ac18727c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs @@ -394,7 +394,7 @@ fn inline( // Inline parameter expressions or generate `let` statements depending on whether inlining works or not. for ((pat, param_ty, _), usages, expr) in izip!(params, param_use_nodes, arguments).rev() { // izip confuses RA due to our lack of hygiene info currently losing us type info causing incorrect errors - let usages: &[ast::PathExpr] = &*usages; + let usages: &[ast::PathExpr] = &usages; let expr: &ast::Expr = expr; let insert_let_stmt = || { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs new file mode 100644 index 000000000..9d03f03d2 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs @@ -0,0 +1,233 @@ +use syntax::ast::{self, AstNode}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: inline_macro +// +// Takes a macro and inlines it one step. +// +// ``` +// macro_rules! num { +// (+$($t:tt)+) => (1 + num!($($t )+)); +// (-$($t:tt)+) => (-1 + num!($($t )+)); +// (+) => (1); +// (-) => (-1); +// } +// +// fn main() { +// let number = num$0!(+ + + - + +); +// println!("{number}"); +// } +// ``` +// -> +// ``` +// macro_rules! num { +// (+$($t:tt)+) => (1 + num!($($t )+)); +// (-$($t:tt)+) => (-1 + num!($($t )+)); +// (+) => (1); +// (-) => (-1); +// } +// +// fn main() { +// let number = 1+num!(+ + - + +); +// println!("{number}"); +// } +// ``` +pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let unexpanded = ctx.find_node_at_offset::<ast::MacroCall>()?; + let expanded = ctx.sema.expand(&unexpanded)?.clone_for_update(); + + let text_range = unexpanded.syntax().text_range(); + + acc.add( + AssistId("inline_macro", AssistKind::RefactorRewrite), + format!("Inline macro"), + text_range, + |builder| builder.replace(text_range, expanded.to_string()), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; + + macro_rules! simple_macro { + () => { + r#" +macro_rules! foo { + (foo) => (true); + () => (false); +} +"# + }; + } + macro_rules! double_macro { + () => { + r#" +macro_rules! bar { + (bar) => (true); + ($($tt:tt)?) => (false); +} +macro_rules! foo { + (foo) => (true); + (bar) => (bar!(bar)); + ($($tt:tt)?) => (bar!($($tt)?)); +} +"# + }; + } + + macro_rules! complex_macro { + () => { + r#" +macro_rules! num { + (+$($t:tt)+) => (1 + num!($($t )+)); + (-$($t:tt)+) => (-1 + num!($($t )+)); + (+) => (1); + (-) => (-1); +} +"# + }; + } + #[test] + fn inline_macro_target() { + check_assist_target( + inline_macro, + concat!(simple_macro!(), r#"fn f() { let a = foo$0!(foo); }"#), + "foo!(foo)", + ); + } + + #[test] + fn inline_macro_target_start() { + check_assist_target( + inline_macro, + concat!(simple_macro!(), r#"fn f() { let a = $0foo!(foo); }"#), + "foo!(foo)", + ); + } + + #[test] + fn inline_macro_target_end() { + check_assist_target( + inline_macro, + concat!(simple_macro!(), r#"fn f() { let a = foo!(foo$0); }"#), + "foo!(foo)", + ); + } + + #[test] + fn inline_macro_simple_case1() { + check_assist( + inline_macro, + concat!(simple_macro!(), r#"fn f() { let result = foo$0!(foo); }"#), + concat!(simple_macro!(), r#"fn f() { let result = true; }"#), + ); + } + + #[test] + fn inline_macro_simple_case2() { + check_assist( + inline_macro, + concat!(simple_macro!(), r#"fn f() { let result = foo$0!(); }"#), + concat!(simple_macro!(), r#"fn f() { let result = false; }"#), + ); + } + + #[test] + fn inline_macro_simple_not_applicable() { + check_assist_not_applicable( + inline_macro, + concat!(simple_macro!(), r#"fn f() { let result$0 = foo!(foo); }"#), + ); + } + + #[test] + fn inline_macro_simple_not_applicable_broken_macro() { + // FIXME: This is a bug. The macro should not expand, but it's + // the same behaviour as the "Expand Macro Recursively" commmand + // so it's presumably OK for the time being. + check_assist( + inline_macro, + concat!(simple_macro!(), r#"fn f() { let result = foo$0!(asdfasdf); }"#), + concat!(simple_macro!(), r#"fn f() { let result = true; }"#), + ); + } + + #[test] + fn inline_macro_double_case1() { + check_assist( + inline_macro, + concat!(double_macro!(), r#"fn f() { let result = foo$0!(bar); }"#), + concat!(double_macro!(), r#"fn f() { let result = bar!(bar); }"#), + ); + } + + #[test] + fn inline_macro_double_case2() { + check_assist( + inline_macro, + concat!(double_macro!(), r#"fn f() { let result = foo$0!(asdf); }"#), + concat!(double_macro!(), r#"fn f() { let result = bar!(asdf); }"#), + ); + } + + #[test] + fn inline_macro_complex_case1() { + check_assist( + inline_macro, + concat!(complex_macro!(), r#"fn f() { let result = num!(+ +$0 + - +); }"#), + concat!(complex_macro!(), r#"fn f() { let result = 1+num!(+ + - +); }"#), + ); + } + + #[test] + fn inline_macro_complex_case2() { + check_assist( + inline_macro, + concat!(complex_macro!(), r#"fn f() { let result = n$0um!(- + + - +); }"#), + concat!(complex_macro!(), r#"fn f() { let result = -1+num!(+ + - +); }"#), + ); + } + + #[test] + fn inline_macro_recursive_macro() { + check_assist( + inline_macro, + r#" +macro_rules! foo { + () => {foo!()} +} +fn f() { let result = foo$0!(); } +"#, + r#" +macro_rules! foo { + () => {foo!()} +} +fn f() { let result = foo!(); } +"#, + ); + } + + #[test] + fn inline_macro_unknown_macro() { + check_assist_not_applicable( + inline_macro, + r#" +fn f() { let result = foo$0!(); } +"#, + ); + } + + #[test] + fn inline_macro_function_call_not_applicable() { + check_assist_not_applicable( + inline_macro, + r#" +fn f() { let result = foo$0(); } +"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs index 353d467ed..5982e9d61 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs @@ -138,7 +138,7 @@ pub(crate) fn inline_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> O replacement = Replacement::Plain; } _ => { - let alias = get_type_alias(&ctx, &alias_instance)?; + let alias = get_type_alias(ctx, &alias_instance)?; concrete_type = alias.ty()?; replacement = inline(&alias, &alias_instance)?; } @@ -158,7 +158,7 @@ impl Replacement { fn to_text(&self, concrete_type: &ast::Type) -> String { match self { Replacement::Generic { lifetime_map, const_and_type_map } => { - create_replacement(&lifetime_map, &const_and_type_map, &concrete_type) + create_replacement(lifetime_map, const_and_type_map, concrete_type) } Replacement::Plain => concrete_type.to_string(), } @@ -240,7 +240,7 @@ impl ConstAndTypeMap { ) -> Option<Self> { let mut inner = HashMap::new(); let instance_generics = generic_args_to_const_and_type_generics(instance_args); - let alias_generics = generic_param_list_to_const_and_type_generics(&alias_generics); + let alias_generics = generic_param_list_to_const_and_type_generics(alias_generics); if instance_generics.len() > alias_generics.len() { cov_mark::hit!(too_many_generic_args); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs new file mode 100644 index 000000000..0e3a1e652 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs @@ -0,0 +1,481 @@ +use hir::{AsAssocItem, AssocItemContainer, HasCrate, HasSource}; +use ide_db::{assists::AssistId, base_db::FileRange, defs::Definition, search::SearchScope}; +use syntax::{ + ast::{self, edit::IndentLevel, edit_in_place::Indent, AstNode}, + SyntaxKind, +}; + +use crate::{ + assist_context::{AssistContext, Assists}, + utils, +}; + +// NOTE: Code may break if the self type implements a trait that has associated const with the same +// name, but it's pretty expensive to check that (`hir::Impl::all_for_type()`) and we assume that's +// pretty rare case. + +// Assist: move_const_to_impl +// +// Move a local constant item in a method to impl's associated constant. All the references will be +// qualified with `Self::`. +// +// ``` +// struct S; +// impl S { +// fn foo() -> usize { +// /// The answer. +// const C$0: usize = 42; +// +// C * C +// } +// } +// ``` +// -> +// ``` +// struct S; +// impl S { +// /// The answer. +// const C: usize = 42; +// +// fn foo() -> usize { +// Self::C * Self::C +// } +// } +// ``` +pub(crate) fn move_const_to_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let db = ctx.db(); + let const_: ast::Const = ctx.find_node_at_offset()?; + // Don't show the assist when the cursor is at the const's body. + if let Some(body) = const_.body() { + if body.syntax().text_range().contains(ctx.offset()) { + return None; + } + } + + let parent_fn = const_.syntax().ancestors().find_map(ast::Fn::cast)?; + + // NOTE: We can technically provide this assist for default methods in trait definitions, but + // it's somewhat complex to handle it correctly when the const's name conflicts with + // supertrait's item. We may want to consider implementing it in the future. + let AssocItemContainer::Impl(impl_) = ctx.sema.to_def(&parent_fn)?.as_assoc_item(db)?.container(db) else { return None; }; + if impl_.trait_(db).is_some() { + return None; + } + + let def = ctx.sema.to_def(&const_)?; + let name = def.name(db)?; + let items = impl_.source(db)?.value.assoc_item_list()?; + + let ty = impl_.self_ty(db); + // If there exists another associated item with the same name, skip the assist. + if ty + .iterate_assoc_items(db, ty.krate(db), |assoc| { + // Type aliases wouldn't conflict due to different namespaces, but we're only checking + // the items in inherent impls, so we assume `assoc` is never type alias for the sake + // of brevity (inherent associated types exist in nightly Rust, but it's *very* + // unstable and we don't support them either). + assoc.name(db).filter(|it| it == &name) + }) + .is_some() + { + return None; + } + + let usages = + Definition::Const(def).usages(&ctx.sema).in_scope(SearchScope::file_range(FileRange { + file_id: ctx.file_id(), + range: parent_fn.syntax().text_range(), + })); + + acc.add( + AssistId("move_const_to_impl", crate::AssistKind::RefactorRewrite), + "Move const to impl block", + const_.syntax().text_range(), + |builder| { + let range_to_delete = match const_.syntax().next_sibling_or_token() { + Some(s) if matches!(s.kind(), SyntaxKind::WHITESPACE) => { + // Remove following whitespaces too. + const_.syntax().text_range().cover(s.text_range()) + } + _ => const_.syntax().text_range(), + }; + builder.delete(range_to_delete); + + let const_ref = format!("Self::{name}"); + for range in usages.all().file_ranges().map(|it| it.range) { + builder.replace(range, const_ref.clone()); + } + + // Heuristically inserting the extracted const after the consecutive existing consts + // from the beginning of assoc items. We assume there are no inherent assoc type as + // above. + let last_const = + items.assoc_items().take_while(|it| matches!(it, ast::AssocItem::Const(_))).last(); + let insert_offset = match &last_const { + Some(it) => it.syntax().text_range().end(), + None => match items.l_curly_token() { + Some(l_curly) => l_curly.text_range().end(), + // Not sure if this branch is ever reachable, but it wouldn't hurt to have a + // fallback. + None => items.syntax().text_range().start(), + }, + }; + + // If the moved const will be the first item of the impl, add a new line after that. + // + // We're assuming the code is formatted according to Rust's standard style guidelines + // (i.e. no empty lines between impl's `{` token and its first assoc item). + let fixup = if last_const.is_none() { "\n" } else { "" }; + let indent = IndentLevel::from_node(parent_fn.syntax()); + + let const_ = const_.clone_for_update(); + const_.reindent_to(indent); + let mut const_text = format!("\n{indent}{const_}{fixup}"); + utils::escape_non_snippet(&mut const_text); + builder.insert(insert_offset, const_text); + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn not_applicable_to_top_level_const() { + check_assist_not_applicable( + move_const_to_impl, + r#" +const C$0: () = (); +"#, + ); + } + + #[test] + fn not_applicable_to_free_fn() { + check_assist_not_applicable( + move_const_to_impl, + r#" +fn f() { + const C$0: () = (); +} +"#, + ); + } + + #[test] + fn not_applicable_when_at_const_body() { + check_assist_not_applicable( + move_const_to_impl, + r#" +struct S; +impl S { + fn f() { + const C: () = ($0); + } +} + "#, + ); + } + + #[test] + fn not_applicable_when_inside_const_body_block() { + check_assist_not_applicable( + move_const_to_impl, + r#" +struct S; +impl S { + fn f() { + const C: () = { + ($0) + }; + } +} + "#, + ); + } + + #[test] + fn not_applicable_to_trait_impl_fn() { + check_assist_not_applicable( + move_const_to_impl, + r#" +trait Trait { + fn f(); +} +impl Trait for () { + fn f() { + const C$0: () = (); + } +} +"#, + ); + } + + #[test] + fn not_applicable_to_non_assoc_fn_inside_impl() { + check_assist_not_applicable( + move_const_to_impl, + r#" +struct S; +impl S { + fn f() { + fn g() { + const C$0: () = (); + } + } +} +"#, + ); + } + + #[test] + fn not_applicable_when_const_with_same_name_exists() { + check_assist_not_applicable( + move_const_to_impl, + r#" +struct S; +impl S { + const C: usize = 42; + fn f() { + const C$0: () = (); + } +"#, + ); + + check_assist_not_applicable( + move_const_to_impl, + r#" +struct S; +impl S { + const C: usize = 42; +} +impl S { + fn f() { + const C$0: () = (); + } +"#, + ); + } + + #[test] + fn move_const_simple_body() { + check_assist( + move_const_to_impl, + r#" +struct S; +impl S { + fn f() -> usize { + /// doc comment + const C$0: usize = 42; + + C * C + } +} +"#, + r#" +struct S; +impl S { + /// doc comment + const C: usize = 42; + + fn f() -> usize { + Self::C * Self::C + } +} +"#, + ); + } + + #[test] + fn move_const_simple_body_existing_const() { + check_assist( + move_const_to_impl, + r#" +struct S; +impl S { + const X: () = (); + const Y: () = (); + + fn f() -> usize { + /// doc comment + const C$0: usize = 42; + + C * C + } +} +"#, + r#" +struct S; +impl S { + const X: () = (); + const Y: () = (); + /// doc comment + const C: usize = 42; + + fn f() -> usize { + Self::C * Self::C + } +} +"#, + ); + } + + #[test] + fn move_const_block_body() { + check_assist( + move_const_to_impl, + r#" +struct S; +impl S { + fn f() -> usize { + /// doc comment + const C$0: usize = { + let a = 3; + let b = 4; + a * b + }; + + C * C + } +} +"#, + r#" +struct S; +impl S { + /// doc comment + const C: usize = { + let a = 3; + let b = 4; + a * b + }; + + fn f() -> usize { + Self::C * Self::C + } +} +"#, + ); + } + + #[test] + fn correct_indent_when_nested() { + check_assist( + move_const_to_impl, + r#" +fn main() { + struct S; + impl S { + fn f() -> usize { + /// doc comment + const C$0: usize = 42; + + C * C + } + } +} +"#, + r#" +fn main() { + struct S; + impl S { + /// doc comment + const C: usize = 42; + + fn f() -> usize { + Self::C * Self::C + } + } +} +"#, + ) + } + + #[test] + fn move_const_in_nested_scope_with_same_name_in_other_scope() { + check_assist( + move_const_to_impl, + r#" +struct S; +impl S { + fn f() -> usize { + const C: &str = "outer"; + + let n = { + /// doc comment + const C$0: usize = 42; + + let m = { + const C: &str = "inner"; + C.len() + }; + + C * m + }; + + n + C.len() + } +} +"#, + r#" +struct S; +impl S { + /// doc comment + const C: usize = 42; + + fn f() -> usize { + const C: &str = "outer"; + + let n = { + let m = { + const C: &str = "inner"; + C.len() + }; + + Self::C * m + }; + + n + C.len() + } +} +"#, + ); + } + + #[test] + fn moved_const_body_is_escaped() { + // Note that the last argument is what *lsp clients would see* rather than + // what users would see. Unescaping happens thereafter. + check_assist( + move_const_to_impl, + r#" +struct S; +impl S { + fn f() -> usize { + /// doc comment + /// \\ + /// ${snippet} + const C$0: &str = "\ and $1"; + + C.len() + } +} +"#, + r#" +struct S; +impl S { + /// doc comment + /// \\\\ + /// \${snippet} + const C: &str = "\\ and \$1"; + + fn f() -> usize { + Self::C.len() + } +} +"#, + ) + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_format_string_arg.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_format_string_arg.rs deleted file mode 100644 index 11db6ae7f..000000000 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_format_string_arg.rs +++ /dev/null @@ -1,296 +0,0 @@ -use crate::{AssistContext, Assists}; -use ide_db::{ - assists::{AssistId, AssistKind}, - syntax_helpers::{ - format_string::is_format_string, - format_string_exprs::{parse_format_exprs, Arg}, - }, -}; -use itertools::Itertools; -use stdx::format_to; -use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange}; - -// Assist: move_format_string_arg -// -// 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)*))); -// } -// -// fn main() { -// print!("{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!("{}"$0, x + 1); -// } -// ``` - -pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let fmt_string = ctx.find_token_at_offset::<ast::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()), - )?; - if !is_format_string(&expanded_t) { - return None; - } - - let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?; - if extracted_args.is_empty() { - return None; - } - - acc.add( - AssistId( - "move_format_string_arg", - // if there aren't any expressions, then make the assist a RefactorExtract - if extracted_args.iter().filter(|f| matches!(f, Arg::Expr(_))).count() == 0 { - AssistKind::RefactorExtract - } else { - AssistKind::QuickFix - }, - ), - "Extract format args", - tt.syntax().text_range(), - |edit| { - let fmt_range = fmt_string.syntax().text_range(); - - // Replace old format string with new format string whose arguments have been extracted - edit.replace(fmt_range, new_fmt); - - // Insert cursor at end of format string - edit.insert(fmt_range.end(), "$0"); - - // Extract existing arguments in macro - let tokens = - tt.token_trees_and_tokens().collect_vec(); - - let mut existing_args: Vec<String> = vec![]; - - let mut current_arg = String::new(); - if let [_opening_bracket, NodeOrToken::Token(format_string), _args_start_comma, tokens @ .., NodeOrToken::Token(end_bracket)] = - tokens.as_slice() - { - for t in tokens { - match t { - NodeOrToken::Node(n) => { - format_to!(current_arg, "{n}"); - }, - NodeOrToken::Token(t) if t.kind() == COMMA => { - existing_args.push(current_arg.trim().into()); - current_arg.clear(); - }, - NodeOrToken::Token(t) => { - current_arg.push_str(t.text()); - }, - } - } - existing_args.push(current_arg.trim().into()); - - // delete everything after the format string till end bracket - // we're going to insert the new arguments later - edit.delete(TextRange::new( - format_string.text_range().end(), - end_bracket.text_range().start(), - )); - } - - // Start building the new args - let mut existing_args = existing_args.into_iter(); - let mut args = String::new(); - - let mut placeholder_idx = 1; - - for extracted_args in extracted_args { - // remove expr from format string - args.push_str(", "); - - match extracted_args { - Arg::Ident(s) | Arg::Expr(s) => { - // insert arg - args.push_str(&s); - } - Arg::Placeholder => { - // try matching with existing argument - match existing_args.next() { - Some(ea) => { - args.push_str(&ea); - } - None => { - // insert placeholder - args.push_str(&format!("${placeholder_idx}")); - placeholder_idx += 1; - } - } - } - } - } - - // Insert new args - edit.insert(fmt_range.end(), args); - }, - ); - - Some(()) -} - -#[cfg(test)] -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( - move_format_string_arg, - &add_macro_decl( - r#" -fn main() { - print!("{} {x + 1:b} {}$0", y + 2, 2); -} -"#, - ), - &add_macro_decl( - r#" -fn main() { - print!("{} {:b} {}"$0, y + 2, x + 1, 2); -} -"#, - ), - ); - } - - #[test] - fn single_arg() { - check_assist( - move_format_string_arg, - &add_macro_decl( - r#" -fn main() { - print!("{obj.value:b}$0",); -} -"#, - ), - &add_macro_decl( - r#" -fn main() { - print!("{:b}"$0, obj.value); -} -"#, - ), - ); - } - - #[test] - fn multiple_middle_placeholders_arg() { - check_assist( - move_format_string_arg, - &add_macro_decl( - r#" -fn main() { - print!("{} {x + 1:b} {} {}$0", y + 2, 2); -} -"#, - ), - &add_macro_decl( - r#" -fn main() { - print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1); -} -"#, - ), - ); - } - - #[test] - fn multiple_trailing_args() { - check_assist( - move_format_string_arg, - &add_macro_decl( - r#" -fn main() { - print!("{:b} {x + 1:b} {Struct(1, 2)}$0", 1); -} -"#, - ), - &add_macro_decl( - r#" -fn main() { - print!("{:b} {:b} {}"$0, 1, x + 1, Struct(1, 2)); -} -"#, - ), - ); - } - - #[test] - fn improper_commas() { - check_assist( - move_format_string_arg, - &add_macro_decl( - r#" -fn main() { - print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,); -} -"#, - ), - &add_macro_decl( - r#" -fn main() { - print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2)); -} -"#, - ), - ); - } - - #[test] - fn nested_tt() { - check_assist( - move_format_string_arg, - &add_macro_decl( - r#" -fn main() { - print!("My name is {} {x$0 + x}", stringify!(Paperino)) -} -"#, - ), - &add_macro_decl( - r#" -fn main() { - print!("My name is {} {}"$0, stringify!(Paperino), x + x) -} -"#, - ), - ); - } -} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs index 1ea87429c..e7014597a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs @@ -53,7 +53,7 @@ pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> let qualify_candidate = QualifyCandidate::ImplMethod(ctx.sema.db, call, resolved_call); acc.add( - AssistId("qualify_method_call", AssistKind::RefactorInline), + AssistId("qualify_method_call", AssistKind::RefactorRewrite), format!("Qualify `{ident}` method call"), range, |builder| { 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 99ae60e07..52dd670ec 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 @@ -64,7 +64,7 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str let input_expressions = mac_input.group_by(|tok| tok.kind() == T![,]); let input_expressions = input_expressions .into_iter() - .filter_map(|(is_sep, group)| (!is_sep).then(|| group)) + .filter_map(|(is_sep, group)| (!is_sep).then_some(group)) .map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join(""))) .collect::<Option<Vec<ast::Expr>>>()?; diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_parentheses.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_parentheses.rs new file mode 100644 index 000000000..e9c7c6bae --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_parentheses.rs @@ -0,0 +1,221 @@ +use syntax::{ast, AstNode}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: remove_parentheses +// +// Removes redundant parentheses. +// +// ``` +// fn main() { +// _ = $0(2) + 2; +// } +// ``` +// -> +// ``` +// fn main() { +// _ = 2 + 2; +// } +// ``` +pub(crate) fn remove_parentheses(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let parens = ctx.find_node_at_offset::<ast::ParenExpr>()?; + + let cursor_in_range = + parens.l_paren_token()?.text_range().contains_range(ctx.selection_trimmed()) + || parens.r_paren_token()?.text_range().contains_range(ctx.selection_trimmed()); + if !cursor_in_range { + return None; + } + + let expr = parens.expr()?; + + let parent = parens.syntax().parent()?; + if expr.needs_parens_in(parent) { + return None; + } + + let target = parens.syntax().text_range(); + acc.add( + AssistId("remove_parentheses", AssistKind::Refactor), + "Remove redundant parentheses", + target, + |builder| builder.replace_ast(parens.into(), expr), + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn remove_parens_simple() { + check_assist(remove_parentheses, r#"fn f() { $0(2) + 2; }"#, r#"fn f() { 2 + 2; }"#); + check_assist(remove_parentheses, r#"fn f() { ($02) + 2; }"#, r#"fn f() { 2 + 2; }"#); + check_assist(remove_parentheses, r#"fn f() { (2)$0 + 2; }"#, r#"fn f() { 2 + 2; }"#); + check_assist(remove_parentheses, r#"fn f() { (2$0) + 2; }"#, r#"fn f() { 2 + 2; }"#); + } + + #[test] + fn remove_parens_closure() { + check_assist(remove_parentheses, r#"fn f() { &$0(|| 42) }"#, r#"fn f() { &|| 42 }"#); + + check_assist_not_applicable(remove_parentheses, r#"fn f() { $0(|| 42).f() }"#); + } + + #[test] + fn remove_parens_if_let_chains() { + check_assist_not_applicable( + remove_parentheses, + r#"fn f() { if let true = $0(true && true) {} }"#, + ); + } + + #[test] + fn remove_parens_associativity() { + check_assist( + remove_parentheses, + r#"fn f() { $0(2 + 2) + 2; }"#, + r#"fn f() { 2 + 2 + 2; }"#, + ); + check_assist_not_applicable(remove_parentheses, r#"fn f() { 2 + $0(2 + 2); }"#); + } + + #[test] + fn remove_parens_precedence() { + check_assist( + remove_parentheses, + r#"fn f() { $0(2 * 3) + 1; }"#, + r#"fn f() { 2 * 3 + 1; }"#, + ); + check_assist(remove_parentheses, r#"fn f() { ( $0(2) ); }"#, r#"fn f() { ( 2 ); }"#); + check_assist(remove_parentheses, r#"fn f() { $0(2?)?; }"#, r#"fn f() { 2??; }"#); + check_assist(remove_parentheses, r#"fn f() { f(($02 + 2)); }"#, r#"fn f() { f(2 + 2); }"#); + check_assist( + remove_parentheses, + r#"fn f() { (1<2)&&$0(3>4); }"#, + r#"fn f() { (1<2)&&3>4; }"#, + ); + } + + #[test] + fn remove_parens_doesnt_apply_precedence() { + check_assist_not_applicable(remove_parentheses, r#"fn f() { $0(2 + 2) * 8; }"#); + check_assist_not_applicable(remove_parentheses, r#"fn f() { $0(2 + 2).f(); }"#); + check_assist_not_applicable(remove_parentheses, r#"fn f() { $0(2 + 2).await; }"#); + check_assist_not_applicable(remove_parentheses, r#"fn f() { $0!(2..2); }"#); + } + + #[test] + fn remove_parens_doesnt_apply_with_cursor_not_on_paren() { + check_assist_not_applicable(remove_parentheses, r#"fn f() { (2 +$0 2) }"#); + check_assist_not_applicable(remove_parentheses, r#"fn f() {$0 (2 + 2) }"#); + } + + #[test] + fn remove_parens_doesnt_apply_when_expr_would_be_turned_into_a_statement() { + check_assist_not_applicable(remove_parentheses, r#"fn x() -> u8 { $0({ 0 } + 1) }"#); + check_assist_not_applicable( + remove_parentheses, + r#"fn x() -> u8 { $0(if true { 0 } else { 1 } + 1) }"#, + ); + check_assist_not_applicable(remove_parentheses, r#"fn x() -> u8 { $0(loop {} + 1) }"#); + } + + #[test] + fn remove_parens_doesnt_apply_weird_syntax_and_adge_cases() { + // removing `()` would break code because {} would be counted as the loop/if body + check_assist_not_applicable(remove_parentheses, r#"fn f() { for _ in $0(0..{3}) {} }"#); + check_assist_not_applicable(remove_parentheses, r#"fn f() { for _ in $0(S {}) {} }"#); + check_assist_not_applicable(remove_parentheses, r#"fn f() { if $0(S {} == 2) {} }"#); + check_assist_not_applicable(remove_parentheses, r#"fn f() { if $0(return) {} }"#); + } + + #[test] + fn remove_parens_return_with_value_followed_by_block() { + check_assist( + remove_parentheses, + r#"fn f() { if $0(return ()) {} }"#, + r#"fn f() { if return () {} }"#, + ); + } + + #[test] + fn remove_exprs_let_else_restrictions() { + // `}` is not allowed before `else` here + check_assist_not_applicable( + remove_parentheses, + r#"fn f() { let _ = $0(S{}) else { return }; }"#, + ); + + // logic operators can't directly appear in the let-else + check_assist_not_applicable( + remove_parentheses, + r#"fn f() { let _ = $0(false || false) else { return }; }"#, + ); + check_assist_not_applicable( + remove_parentheses, + r#"fn f() { let _ = $0(true && true) else { return }; }"#, + ); + } + + #[test] + fn remove_parens_weird_places() { + check_assist( + remove_parentheses, + r#"fn f() { match () { _=>$0(()) } }"#, + r#"fn f() { match () { _=>() } }"#, + ); + + check_assist( + remove_parentheses, + r#"fn x() -> u8 { { [$0({ 0 } + 1)] } }"#, + r#"fn x() -> u8 { { [{ 0 } + 1] } }"#, + ); + } + + #[test] + fn remove_parens_return_dot_f() { + check_assist( + remove_parentheses, + r#"fn f() { $0(return).f() }"#, + r#"fn f() { return.f() }"#, + ); + } + + #[test] + fn remove_parens_prefix_then_return_something() { + check_assist( + remove_parentheses, + r#"fn f() { &$0(return ()) }"#, + r#"fn f() { &return () }"#, + ); + } + + #[test] + fn remove_parens_double_paren_stmt() { + check_assist( + remove_parentheses, + r#"fn x() -> u8 { $0(({ 0 } + 1)) }"#, + r#"fn x() -> u8 { ({ 0 } + 1) }"#, + ); + + check_assist( + remove_parentheses, + r#"fn x() -> u8 { (($0{ 0 } + 1)) }"#, + r#"fn x() -> u8 { ({ 0 } + 1) }"#, + ); + } + + #[test] + fn remove_parens_im_tired_of_naming_tests() { + check_assist( + remove_parentheses, + r#"fn f() { 2 + $0(return 2) }"#, + r#"fn f() { 2 + return 2 }"#, + ); + + check_assist_not_applicable(remove_parentheses, r#"fn f() { $0(return 2) + 2 }"#); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_arith_op.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_arith_op.rs new file mode 100644 index 000000000..f1ca35caf --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_arith_op.rs @@ -0,0 +1,226 @@ +use ide_db::assists::{AssistId, AssistKind, GroupLabel}; +use syntax::{ + ast::{self, ArithOp, BinaryOp}, + AstNode, TextRange, +}; + +use crate::assist_context::{AssistContext, Assists}; + +// Assist: replace_arith_with_checked +// +// Replaces arithmetic on integers with the `checked_*` equivalent. +// +// ``` +// fn main() { +// let x = 1 $0+ 2; +// } +// ``` +// -> +// ``` +// fn main() { +// let x = 1.checked_add(2); +// } +// ``` +pub(crate) fn replace_arith_with_checked(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + replace_arith(acc, ctx, ArithKind::Checked) +} + +// Assist: replace_arith_with_saturating +// +// Replaces arithmetic on integers with the `saturating_*` equivalent. +// +// ``` +// fn main() { +// let x = 1 $0+ 2; +// } +// ``` +// -> +// ``` +// fn main() { +// let x = 1.saturating_add(2); +// } +// ``` +pub(crate) fn replace_arith_with_saturating( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + replace_arith(acc, ctx, ArithKind::Saturating) +} + +// Assist: replace_arith_with_wrapping +// +// Replaces arithmetic on integers with the `wrapping_*` equivalent. +// +// ``` +// fn main() { +// let x = 1 $0+ 2; +// } +// ``` +// -> +// ``` +// fn main() { +// let x = 1.wrapping_add(2); +// } +// ``` +pub(crate) fn replace_arith_with_wrapping( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + replace_arith(acc, ctx, ArithKind::Wrapping) +} + +fn replace_arith(acc: &mut Assists, ctx: &AssistContext<'_>, kind: ArithKind) -> Option<()> { + let (lhs, op, rhs) = parse_binary_op(ctx)?; + + if !is_primitive_int(ctx, &lhs) || !is_primitive_int(ctx, &rhs) { + return None; + } + + let start = lhs.syntax().text_range().start(); + let end = rhs.syntax().text_range().end(); + let range = TextRange::new(start, end); + + acc.add_group( + &GroupLabel("replace_arith".into()), + kind.assist_id(), + kind.label(), + range, + |builder| { + let method_name = kind.method_name(op); + + builder.replace(range, format!("{lhs}.{method_name}({rhs})")) + }, + ) +} + +fn is_primitive_int(ctx: &AssistContext<'_>, expr: &ast::Expr) -> bool { + match ctx.sema.type_of_expr(expr) { + Some(ty) => ty.adjusted().is_int_or_uint(), + _ => false, + } +} + +/// Extract the operands of an arithmetic expression (e.g. `1 + 2` or `1.checked_add(2)`) +fn parse_binary_op(ctx: &AssistContext<'_>) -> Option<(ast::Expr, ArithOp, ast::Expr)> { + let expr = ctx.find_node_at_offset::<ast::BinExpr>()?; + + let op = match expr.op_kind() { + Some(BinaryOp::ArithOp(ArithOp::Add)) => ArithOp::Add, + Some(BinaryOp::ArithOp(ArithOp::Sub)) => ArithOp::Sub, + Some(BinaryOp::ArithOp(ArithOp::Mul)) => ArithOp::Mul, + Some(BinaryOp::ArithOp(ArithOp::Div)) => ArithOp::Div, + _ => return None, + }; + + let lhs = expr.lhs()?; + let rhs = expr.rhs()?; + + Some((lhs, op, rhs)) +} + +pub(crate) enum ArithKind { + Saturating, + Wrapping, + Checked, +} + +impl ArithKind { + fn assist_id(&self) -> AssistId { + let s = match self { + ArithKind::Saturating => "replace_arith_with_saturating", + ArithKind::Checked => "replace_arith_with_checked", + ArithKind::Wrapping => "replace_arith_with_wrapping", + }; + + AssistId(s, AssistKind::RefactorRewrite) + } + + fn label(&self) -> &'static str { + match self { + ArithKind::Saturating => "Replace arithmetic with call to saturating_*", + ArithKind::Checked => "Replace arithmetic with call to checked_*", + ArithKind::Wrapping => "Replace arithmetic with call to wrapping_*", + } + } + + fn method_name(&self, op: ArithOp) -> String { + let prefix = match self { + ArithKind::Checked => "checked_", + ArithKind::Wrapping => "wrapping_", + ArithKind::Saturating => "saturating_", + }; + + let suffix = match op { + ArithOp::Add => "add", + ArithOp::Sub => "sub", + ArithOp::Mul => "mul", + ArithOp::Div => "div", + _ => unreachable!("this function should only be called with +, -, / or *"), + }; + format!("{prefix}{suffix}") + } +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist; + + use super::*; + + #[test] + fn arith_kind_method_name() { + assert_eq!(ArithKind::Saturating.method_name(ArithOp::Add), "saturating_add"); + assert_eq!(ArithKind::Checked.method_name(ArithOp::Sub), "checked_sub"); + } + + #[test] + fn replace_arith_with_checked_add() { + check_assist( + replace_arith_with_checked, + r#" +fn main() { + let x = 1 $0+ 2; +} +"#, + r#" +fn main() { + let x = 1.checked_add(2); +} +"#, + ) + } + + #[test] + fn replace_arith_with_saturating_add() { + check_assist( + replace_arith_with_saturating, + r#" +fn main() { + let x = 1 $0+ 2; +} +"#, + r#" +fn main() { + let x = 1.saturating_add(2); +} +"#, + ) + } + + #[test] + fn replace_arith_with_wrapping_add() { + check_assist( + replace_arith_with_wrapping, + r#" +fn main() { + let x = 1 $0+ 2; +} +"#, + r#" +fn main() { + let x = 1.wrapping_add(2); +} +"#, + ) + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs index 6fa15b28e..a6693d7d7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -907,7 +907,34 @@ impl PartialEq for Foo { } #[test] - fn add_custom_impl_partial_eq_tuple_enum() { + fn add_custom_impl_partial_eq_single_variant_tuple_enum() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: eq, derive +#[derive(Partial$0Eq)] +enum Foo { + Bar(String), +} +"#, + r#" +enum Foo { + Bar(String), +} + +impl PartialEq for Foo { + $0fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Bar(l0), Self::Bar(r0)) => l0 == r0, + } + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_partial_eq_partial_tuple_enum() { check_assist( replace_derive_with_manual_impl, r#" @@ -937,6 +964,99 @@ impl PartialEq for Foo { } #[test] + fn add_custom_impl_partial_eq_tuple_enum() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: eq, derive +#[derive(Partial$0Eq)] +enum Foo { + Bar(String), + Baz(i32), +} +"#, + r#" +enum Foo { + Bar(String), + Baz(i32), +} + +impl PartialEq for Foo { + $0fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Bar(l0), Self::Bar(r0)) => l0 == r0, + (Self::Baz(l0), Self::Baz(r0)) => l0 == r0, + _ => false, + } + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_partial_eq_tuple_enum_generic() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: eq, derive +#[derive(Partial$0Eq)] +enum Either<T, U> { + Left(T), + Right(U), +} +"#, + r#" +enum Either<T, U> { + Left(T), + Right(U), +} + +impl<T: PartialEq, U: PartialEq> PartialEq for Either<T, U> { + $0fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Left(l0), Self::Left(r0)) => l0 == r0, + (Self::Right(l0), Self::Right(r0)) => l0 == r0, + _ => false, + } + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_partial_eq_tuple_enum_generic_existing_bounds() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: eq, derive +#[derive(Partial$0Eq)] +enum Either<T: PartialEq + Error, U: Clone> { + Left(T), + Right(U), +} +"#, + r#" +enum Either<T: PartialEq + Error, U: Clone> { + Left(T), + Right(U), +} + +impl<T: PartialEq + Error, U: Clone + PartialEq> PartialEq for Either<T, U> { + $0fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Left(l0), Self::Left(r0)) => l0 == r0, + (Self::Right(l0), Self::Right(r0)) => l0 == r0, + _ => false, + } + } +} +"#, + ) + } + + #[test] fn add_custom_impl_partial_eq_record_enum() { check_assist( replace_derive_with_manual_impl, @@ -1112,7 +1232,7 @@ struct Foo<T, U> { bar: U, } -impl<T, U> Default for Foo<T, U> { +impl<T: Default, U: Default> Default for Foo<T, U> { $0fn default() -> Self { Self { foo: Default::default(), bar: Default::default() } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_or_with_or_else.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_or_with_or_else.rs index 77382056c..f0ed3c4fe 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_or_with_or_else.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_or_with_or_else.rs @@ -75,7 +75,7 @@ fn into_closure(param: &Expr) -> Expr { (|| { if let ast::Expr::CallExpr(call) = param { if call.arg_list()?.args().count() == 0 { - Some(call.expr()?.clone()) + Some(call.expr()?) } else { None } @@ -151,7 +151,7 @@ fn into_call(param: &Expr) -> Expr { (|| { if let ast::Expr::ClosureExpr(closure) = param { if closure.param_list()?.params().count() == 0 { - Some(closure.body()?.clone()) + Some(closure.body()?) } else { None } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs index c177adc7a..6626ce079 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs @@ -42,7 +42,7 @@ pub(crate) fn replace_turbofish_with_explicit_type( let r_angle = generic_args.r_angle_token()?; let turbofish_range = TextRange::new(colon2.text_range().start(), r_angle.text_range().end()); - let turbofish_args: Vec<GenericArg> = generic_args.generic_args().into_iter().collect(); + let turbofish_args: Vec<GenericArg> = generic_args.generic_args().collect(); // Find type of ::<_> if turbofish_args.len() != 1 { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unnecessary_async.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unnecessary_async.rs index 043988322..7f612c2a1 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unnecessary_async.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unnecessary_async.rs @@ -107,7 +107,7 @@ fn find_all_references( /// If no await expression is found, returns None. fn find_await_expression(ctx: &AssistContext<'_>, nameref: &NameRef) -> Option<ast::AwaitExpr> { // From the nameref, walk up the tree to the await expression. - let await_expr = if let Some(path) = full_path_of_name_ref(&nameref) { + let await_expr = if let Some(path) = full_path_of_name_ref(nameref) { // Function calls. path.syntax() .parent() diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unqualify_method_call.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unqualify_method_call.rs new file mode 100644 index 000000000..e9d4e270c --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unqualify_method_call.rs @@ -0,0 +1,211 @@ +use syntax::{ + ast::{self, make, AstNode, HasArgList}, + TextRange, +}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: unqualify_method_call +// +// Transforms universal function call syntax into a method call. +// +// ``` +// fn main() { +// std::ops::Add::add$0(1, 2); +// } +// # mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } } +// ``` +// -> +// ``` +// fn main() { +// 1.add(2); +// } +// # mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } } +// ``` +pub(crate) fn unqualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let call = ctx.find_node_at_offset::<ast::CallExpr>()?; + let ast::Expr::PathExpr(path_expr) = call.expr()? else { return None }; + let path = path_expr.path()?; + + let cursor_in_range = path.syntax().text_range().contains_range(ctx.selection_trimmed()); + if !cursor_in_range { + return None; + } + + let args = call.arg_list()?; + let l_paren = args.l_paren_token()?; + let mut args_iter = args.args(); + let first_arg = args_iter.next()?; + let second_arg = args_iter.next(); + + _ = path.qualifier()?; + let method_name = path.segment()?.name_ref()?; + + let res = ctx.sema.resolve_path(&path)?; + let hir::PathResolution::Def(hir::ModuleDef::Function(fun)) = res else { return None }; + if !fun.has_self_param(ctx.sema.db) { + return None; + } + + // `core::ops::Add::add(` -> `` + let delete_path = + TextRange::new(path.syntax().text_range().start(), l_paren.text_range().end()); + + // Parens around `expr` if needed + let parens = needs_parens_as_receiver(&first_arg).then(|| { + let range = first_arg.syntax().text_range(); + (range.start(), range.end()) + }); + + // `, ` -> `.add(` + let replace_comma = TextRange::new( + first_arg.syntax().text_range().end(), + second_arg + .map(|a| a.syntax().text_range().start()) + .unwrap_or_else(|| first_arg.syntax().text_range().end()), + ); + + acc.add( + AssistId("unqualify_method_call", AssistKind::RefactorRewrite), + "Unqualify method call", + call.syntax().text_range(), + |edit| { + edit.delete(delete_path); + if let Some((open, close)) = parens { + edit.insert(open, "("); + edit.insert(close, ")"); + } + edit.replace(replace_comma, format!(".{method_name}(")); + }, + ) +} + +fn needs_parens_as_receiver(expr: &ast::Expr) -> bool { + // Make `(expr).dummy()` + let dummy_call = make::expr_method_call( + make::expr_paren(expr.clone()), + make::name_ref("dummy"), + make::arg_list([]), + ); + + // Get the `expr` clone with the right parent back + // (unreachable!s are fine since we've just constructed the expression) + let ast::Expr::MethodCallExpr(call) = &dummy_call else { unreachable!() }; + let Some(receiver) = call.receiver() else { unreachable!() }; + let ast::Expr::ParenExpr(parens) = receiver else { unreachable!() }; + let Some(expr) = parens.expr() else { unreachable!() }; + + expr.needs_parens_in(dummy_call.syntax().clone()) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn unqualify_method_call_simple() { + check_assist( + unqualify_method_call, + r#" +struct S; +impl S { fn f(self, S: S) {} } +fn f() { S::$0f(S, S); }"#, + r#" +struct S; +impl S { fn f(self, S: S) {} } +fn f() { S.f(S); }"#, + ); + } + + #[test] + fn unqualify_method_call_trait() { + check_assist( + unqualify_method_call, + r#" +//- minicore: add +fn f() { <u32 as core::ops::Add>::$0add(2, 2); }"#, + r#" +fn f() { 2.add(2); }"#, + ); + + check_assist( + unqualify_method_call, + r#" +//- minicore: add +fn f() { core::ops::Add::$0add(2, 2); }"#, + r#" +fn f() { 2.add(2); }"#, + ); + + check_assist( + unqualify_method_call, + r#" +//- minicore: add +use core::ops::Add; +fn f() { <_>::$0add(2, 2); }"#, + r#" +use core::ops::Add; +fn f() { 2.add(2); }"#, + ); + } + + #[test] + fn unqualify_method_call_single_arg() { + check_assist( + unqualify_method_call, + r#" + struct S; + impl S { fn f(self) {} } + fn f() { S::$0f(S); }"#, + r#" + struct S; + impl S { fn f(self) {} } + fn f() { S.f(); }"#, + ); + } + + #[test] + fn unqualify_method_call_parens() { + check_assist( + unqualify_method_call, + r#" +//- minicore: deref +struct S; +impl core::ops::Deref for S { + type Target = S; + fn deref(&self) -> &S { self } +} +fn f() { core::ops::Deref::$0deref(&S); }"#, + r#" +struct S; +impl core::ops::Deref for S { + type Target = S; + fn deref(&self) -> &S { self } +} +fn f() { (&S).deref(); }"#, + ); + } + + #[test] + fn unqualify_method_call_doesnt_apply_with_cursor_not_on_path() { + check_assist_not_applicable( + unqualify_method_call, + r#" +//- minicore: add +fn f() { core::ops::Add::add(2,$0 2); }"#, + ); + } + + #[test] + fn unqualify_method_call_doesnt_apply_with_no_self() { + check_assist_not_applicable( + unqualify_method_call, + r#" +struct S; +impl S { fn assoc(S: S, S: S) {} } +fn f() { S::assoc$0(S, S); }"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs index 7969a4918..53cdac03a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs @@ -37,7 +37,8 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))? } - if matches!(parent.kind(), SyntaxKind::STMT_LIST | SyntaxKind::EXPR_STMT) { + if matches!(parent.kind(), SyntaxKind::STMT_LIST | SyntaxKind::EXPR_STMT | SyntaxKind::LET_STMT) + { return acc.add(assist_id, assist_label, target, |builder| { builder.replace(block.syntax().text_range(), update_expr_string(block.to_string())); }); @@ -716,4 +717,48 @@ fn main() -> i32 { "#, ); } + + #[test] + fn unwrap_block_in_let_initializers() { + // https://github.com/rust-lang/rust-analyzer/issues/13679 + check_assist( + unwrap_block, + r#" +fn main() { + let x = {$0 + bar + }; +} +"#, + r#" +fn main() { + let x = bar; +} +"#, + ); + } + + #[test] + fn unwrap_if_in_let_initializers() { + // https://github.com/rust-lang/rust-analyzer/issues/13679 + check_assist( + unwrap_block, + r#" +fn main() { + let a = 1; + let x = if a - 1 == 0 {$0 + foo + } else { + bar + }; +} +"#, + r#" +fn main() { + let a = 1; + let x = foo; +} +"#, + ); + } } 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 387cc6314..7813c9f9c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs @@ -128,6 +128,7 @@ mod handlers { mod convert_while_to_loop; mod destructure_tuple_binding; mod expand_glob_import; + mod extract_expressions_from_format_string; mod extract_function; mod extract_module; mod extract_struct_from_enum_variant; @@ -138,7 +139,6 @@ mod handlers { mod flip_binexpr; mod flip_comma; mod flip_trait_bound; - mod move_format_string_arg; mod generate_constant; mod generate_default_from_enum_variant; mod generate_default_from_new; @@ -159,12 +159,14 @@ mod handlers { mod add_return_type; mod inline_call; mod inline_local_variable; + mod inline_macro; mod inline_type_alias; mod introduce_named_lifetime; mod invert_if; mod merge_imports; mod merge_match_arms; mod move_bounds; + mod move_const_to_impl; mod move_guard; mod move_module_to_file; mod move_to_mod_rs; @@ -178,12 +180,14 @@ mod handlers { mod remove_dbg; mod remove_mut; mod remove_unused_param; + mod remove_parentheses; mod reorder_fields; mod reorder_impl_items; mod replace_try_expr_with_match; mod replace_derive_with_manual_impl; mod replace_if_let_with_match; mod replace_or_with_or_else; + mod replace_arith_op; mod introduce_named_generic; mod replace_let_with_if_let; mod replace_qualified_name_with_use; @@ -198,6 +202,7 @@ mod handlers { mod unnecessary_async; mod unwrap_block; mod unwrap_result_return_type; + mod unqualify_method_call; mod wrap_return_type_in_result; pub(crate) fn all() -> &'static [Handler] { @@ -228,6 +233,7 @@ mod handlers { convert_while_to_loop::convert_while_to_loop, destructure_tuple_binding::destructure_tuple_binding, expand_glob_import::expand_glob_import, + extract_expressions_from_format_string::extract_expressions_from_format_string, extract_struct_from_enum_variant::extract_struct_from_enum_variant, extract_type_alias::extract_type_alias, fix_visibility::fix_visibility, @@ -247,6 +253,7 @@ mod handlers { generate_from_impl_for_enum::generate_from_impl_for_enum, generate_function::generate_function, generate_impl::generate_impl, + generate_impl::generate_trait_impl, generate_is_empty_from_len::generate_is_empty_from_len, generate_new::generate_new, inline_call::inline_call, @@ -254,13 +261,14 @@ mod handlers { inline_local_variable::inline_local_variable, inline_type_alias::inline_type_alias, inline_type_alias::inline_type_alias_uses, + inline_macro::inline_macro, introduce_named_generic::introduce_named_generic, introduce_named_lifetime::introduce_named_lifetime, invert_if::invert_if, merge_imports::merge_imports, merge_match_arms::merge_match_arms, move_bounds::move_bounds_to_where_clause, - move_format_string_arg::move_format_string_arg, + move_const_to_impl::move_const_to_impl, move_guard::move_arm_cond_to_match_guard, move_guard::move_guard_to_arm_body, move_module_to_file::move_module_to_file, @@ -277,6 +285,7 @@ mod handlers { remove_dbg::remove_dbg, remove_mut::remove_mut, remove_unused_param::remove_unused_param, + remove_parentheses::remove_parentheses, reorder_fields::reorder_fields, reorder_impl_items::reorder_impl_items, replace_try_expr_with_match::replace_try_expr_with_match, @@ -288,6 +297,9 @@ mod handlers { replace_or_with_or_else::replace_or_with_or_else, replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type, replace_qualified_name_with_use::replace_qualified_name_with_use, + replace_arith_op::replace_arith_with_wrapping, + replace_arith_op::replace_arith_with_checked, + replace_arith_op::replace_arith_with_saturating, sort_items::sort_items, split_import::split_import, toggle_ignore::toggle_ignore, @@ -297,6 +309,7 @@ mod handlers { unwrap_block::unwrap_block, unwrap_result_return_type::unwrap_result_return_type, unwrap_tuple::unwrap_tuple, + unqualify_method_call::unqualify_method_call, wrap_return_type_in_result::wrap_return_type_in_result, // These are manually sorted for better priorities. By default, // priority is determined by the size of the target range (smaller diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs index 92ced27c7..fca268a1f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs @@ -171,7 +171,7 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult<'_>, assist_la } FileSystemEdit::MoveDir { src, src_id, dst } => { // temporary placeholder for MoveDir since we are not using MoveDir in ide assists yet. - (dst, format!("{:?}\n{:?}", src_id, src)) + (dst, format!("{src_id:?}\n{src:?}")) } }; let sr = db.file_source_root(dst.anchor); 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 c09317572..006ae4b30 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 @@ -625,6 +625,37 @@ fn qux(bar: Bar, baz: Baz) {} } #[test] +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)*))); +} + +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); +} +"#####, + ) +} + +#[test] fn doctest_extract_function() { check_doc_test( "extract_function", @@ -1249,8 +1280,8 @@ fn doctest_generate_impl() { check_doc_test( "generate_impl", r#####" -struct Ctx<T: Clone> { - data: T,$0 +struct Ctx$0<T: Clone> { + data: T, } "#####, r#####" @@ -1342,6 +1373,27 @@ impl Person { } #[test] +fn doctest_generate_trait_impl() { + check_doc_test( + "generate_trait_impl", + r#####" +struct $0Ctx<T: Clone> { + data: T, +} +"#####, + r#####" +struct Ctx<T: Clone> { + data: T, +} + +impl<T: Clone> $0 for Ctx<T> { + +} +"#####, + ) +} + +#[test] fn doctest_inline_call() { check_doc_test( "inline_call", @@ -1418,6 +1470,39 @@ fn main() { } #[test] +fn doctest_inline_macro() { + check_doc_test( + "inline_macro", + r#####" +macro_rules! num { + (+$($t:tt)+) => (1 + num!($($t )+)); + (-$($t:tt)+) => (-1 + num!($($t )+)); + (+) => (1); + (-) => (-1); +} + +fn main() { + let number = num$0!(+ + + - + +); + println!("{number}"); +} +"#####, + r#####" +macro_rules! num { + (+$($t:tt)+) => (1 + num!($($t )+)); + (-$($t:tt)+) => (-1 + num!($($t )+)); + (+) => (1); + (-) => (-1); +} + +fn main() { + let number = 1+num!(+ + - + +); + println!("{number}"); +} +"#####, + ) +} + +#[test] fn doctest_inline_type_alias() { check_doc_test( "inline_type_alias", @@ -1654,31 +1739,29 @@ fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U { } #[test] -fn doctest_move_format_string_arg() { +fn doctest_move_const_to_impl() { check_doc_test( - "move_format_string_arg", + "move_const_to_impl", r#####" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} -macro_rules! print { - ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -} +struct S; +impl S { + fn foo() -> usize { + /// The answer. + const C$0: usize = 42; -fn main() { - print!("{x + 1}$0"); + C * C + } } "#####, r#####" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} -macro_rules! print { - ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -} +struct S; +impl S { + /// The answer. + const C: usize = 42; -fn main() { - print!("{}"$0, x + 1); + fn foo() -> usize { + Self::C * Self::C + } } "#####, ) @@ -1929,6 +2012,23 @@ impl Walrus { } #[test] +fn doctest_remove_parentheses() { + check_doc_test( + "remove_parentheses", + r#####" +fn main() { + _ = $0(2) + 2; +} +"#####, + r#####" +fn main() { + _ = 2 + 2; +} +"#####, + ) +} + +#[test] fn doctest_remove_unused_param() { check_doc_test( "remove_unused_param", @@ -2000,6 +2100,57 @@ impl Foo for Bar { } #[test] +fn doctest_replace_arith_with_checked() { + check_doc_test( + "replace_arith_with_checked", + r#####" +fn main() { + let x = 1 $0+ 2; +} +"#####, + r#####" +fn main() { + let x = 1.checked_add(2); +} +"#####, + ) +} + +#[test] +fn doctest_replace_arith_with_saturating() { + check_doc_test( + "replace_arith_with_saturating", + r#####" +fn main() { + let x = 1 $0+ 2; +} +"#####, + r#####" +fn main() { + let x = 1.saturating_add(2); +} +"#####, + ) +} + +#[test] +fn doctest_replace_arith_with_wrapping() { + check_doc_test( + "replace_arith_with_wrapping", + r#####" +fn main() { + let x = 1 $0+ 2; +} +"#####, + r#####" +fn main() { + let x = 1.wrapping_add(2); +} +"#####, + ) +} + +#[test] fn doctest_replace_char_with_string() { check_doc_test( "replace_char_with_string", @@ -2416,6 +2567,25 @@ pub async fn bar() { foo() } } #[test] +fn doctest_unqualify_method_call() { + check_doc_test( + "unqualify_method_call", + r#####" +fn main() { + std::ops::Add::add$0(1, 2); +} +mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } } +"#####, + r#####" +fn main() { + 1.add(2); +} +mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } } +"#####, + ) +} + +#[test] fn doctest_unwrap_block() { check_doc_test( "unwrap_block", diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/sourcegen.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/sourcegen.rs index 070b83d3c..b4f50c7fb 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/sourcegen.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/sourcegen.rs @@ -18,7 +18,7 @@ use super::check_doc_test; for assist in assists.iter() { for (idx, section) in assist.sections.iter().enumerate() { let test_id = - if idx == 0 { assist.id.clone() } else { format!("{}_{}", &assist.id, idx) }; + if idx == 0 { assist.id.clone() } else { format!("{}_{idx}", &assist.id) }; let test = format!( r######" #[test] @@ -95,8 +95,7 @@ impl Assist { let id = block.id; assert!( id.chars().all(|it| it.is_ascii_lowercase() || it == '_'), - "invalid assist id: {:?}", - id + "invalid assist id: {id:?}" ); let mut lines = block.contents.iter().peekable(); let location = sourcegen::Location { file: path.to_path_buf(), line: block.line }; @@ -175,7 +174,7 @@ impl fmt::Display for Assist { fn hide_hash_comments(text: &str) -> String { text.split('\n') // want final newline .filter(|&it| !(it.starts_with("# ") || it == "#")) - .map(|it| format!("{}\n", it)) + .map(|it| format!("{it}\n")) .collect() } @@ -190,6 +189,6 @@ fn reveal_hash_comments(text: &str) -> String { it } }) - .map(|it| format!("{}\n", it)) + .map(|it| format!("{it}\n")) .collect() } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs index 68c31b4f8..7add66064 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -208,6 +208,23 @@ pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor } } +/// Escapes text that should be rendered as-is, typically those that we're copy-pasting what the +/// users wrote. +/// +/// This function should only be used when the text doesn't contain snippet **AND** the text +/// wouldn't be included in a snippet. +pub(crate) fn escape_non_snippet(text: &mut String) { + // While we *can* escape `}`, we don't really have to in this specific case. We only need to + // escape it inside `${}` to disambiguate it from the ending token of the syntax, but after we + // escape every occurrence of `$`, we wouldn't have `${}` in the first place. + // + // This will break if the text contains snippet or it will be included in a snippet (hence doc + // comment). Compare `fn escape(buf)` in `render_snippet()` above, where the escaped text is + // included in a snippet. + stdx::replace(text, '\\', r"\\"); + stdx::replace(text, '$', r"\$"); +} + pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { node.children_with_tokens() .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) @@ -417,35 +434,67 @@ pub(crate) fn find_impl_block_end(impl_def: ast::Impl, buf: &mut String) -> Opti Some(end) } -// Generates the surrounding `impl Type { <code> }` including type and lifetime -// parameters +/// Generates the surrounding `impl Type { <code> }` including type and lifetime +/// parameters. pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String { - generate_impl_text_inner(adt, None, code) + generate_impl_text_inner(adt, None, true, code) } -// Generates the surrounding `impl <trait> for Type { <code> }` including type -// and lifetime parameters +/// Generates the surrounding `impl <trait> for Type { <code> }` including type +/// and lifetime parameters, with `<trait>` appended to `impl`'s generic parameters' bounds. +/// +/// This is useful for traits like `PartialEq`, since `impl<T> PartialEq for U<T>` often requires `T: PartialEq`. pub(crate) fn generate_trait_impl_text(adt: &ast::Adt, trait_text: &str, code: &str) -> String { - generate_impl_text_inner(adt, Some(trait_text), code) + generate_impl_text_inner(adt, Some(trait_text), true, code) } -fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str) -> String { +/// Generates the surrounding `impl <trait> for Type { <code> }` including type +/// and lifetime parameters, with `impl`'s generic parameters' bounds kept as-is. +/// +/// This is useful for traits like `From<T>`, since `impl<T> From<T> for U<T>` doesn't require `T: From<T>`. +pub(crate) fn generate_trait_impl_text_intransitive( + adt: &ast::Adt, + trait_text: &str, + code: &str, +) -> String { + generate_impl_text_inner(adt, Some(trait_text), false, code) +} + +fn generate_impl_text_inner( + adt: &ast::Adt, + trait_text: Option<&str>, + trait_is_transitive: bool, + code: &str, +) -> String { // Ensure lifetime params are before type & const params let generic_params = adt.generic_param_list().map(|generic_params| { let lifetime_params = generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam); - let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| { - // remove defaults since they can't be specified in impls + let ty_or_const_params = generic_params.type_or_const_params().map(|param| { match param { ast::TypeOrConstParam::Type(param) => { let param = param.clone_for_update(); + // remove defaults since they can't be specified in impls param.remove_default(); - Some(ast::GenericParam::TypeParam(param)) + let mut bounds = + param.type_bound_list().map_or_else(Vec::new, |it| it.bounds().collect()); + if let Some(trait_) = trait_text { + // Add the current trait to `bounds` if the trait is transitive, + // meaning `impl<T> Trait for U<T>` requires `T: Trait`. + if trait_is_transitive { + bounds.push(make::type_bound(trait_)); + } + }; + // `{ty_param}: {bounds}` + let param = + make::type_param(param.name().unwrap(), make::type_bound_list(bounds)); + ast::GenericParam::TypeParam(param) } ast::TypeOrConstParam::Const(param) => { let param = param.clone_for_update(); + // remove defaults since they can't be specified in impls param.remove_default(); - Some(ast::GenericParam::ConstParam(param)) + ast::GenericParam::ConstParam(param) } } }); @@ -596,7 +645,7 @@ pub(crate) fn convert_reference_type( } fn handle_copy(ty: &hir::Type, db: &dyn HirDatabase) -> Option<ReferenceConversionType> { - ty.is_copy(db).then(|| ReferenceConversionType::Copy) + ty.is_copy(db).then_some(ReferenceConversionType::Copy) } fn handle_as_ref_str( @@ -607,7 +656,7 @@ fn handle_as_ref_str( let str_type = hir::BuiltinType::str().ty(db); ty.impls_trait(db, famous_defs.core_convert_AsRef()?, &[str_type]) - .then(|| ReferenceConversionType::AsRefStr) + .then_some(ReferenceConversionType::AsRefStr) } fn handle_as_ref_slice( @@ -619,7 +668,7 @@ fn handle_as_ref_slice( let slice_type = hir::Type::new_slice(type_argument); ty.impls_trait(db, famous_defs.core_convert_AsRef()?, &[slice_type]) - .then(|| ReferenceConversionType::AsRefSlice) + .then_some(ReferenceConversionType::AsRefSlice) } fn handle_dereferenced( @@ -630,7 +679,7 @@ fn handle_dereferenced( let type_argument = ty.type_arguments().next()?; ty.impls_trait(db, famous_defs.core_convert_AsRef()?, &[type_argument]) - .then(|| ReferenceConversionType::Dereferenced) + .then_some(ReferenceConversionType::Dereferenced) } fn handle_option_as_ref( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs index 6c87e66c1..d4abb5125 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs @@ -419,7 +419,7 @@ fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { make::ext::path_from_idents(["Self", &variant.name()?.to_string()]) } - fn gen_tuple_field(field_name: &String) -> ast::Pat { + fn gen_tuple_field(field_name: &str) -> ast::Pat { ast::Pat::IdentPat(make::ident_pat(false, false, make::name(field_name))) } @@ -516,10 +516,18 @@ fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { let expr = match arms.len() { 0 => eq_check, - _ => { - if n_cases > arms.len() { + arms_len => { + // Generate the fallback arm when this enum has >1 variants. + // The fallback arm will be `_ => false,` if we've already gone through every case where the variants of self and other match, + // and `_ => std::mem::discriminant(self) == std::mem::discriminant(other),` otherwise. + if n_cases > 1 { let lhs = make::wildcard_pat().into(); - arms.push(make::match_arm(Some(lhs), None, eq_check)); + let rhs = if arms_len == n_cases { + make::expr_literal("false").into() + } else { + eq_check + }; + arms.push(make::match_arm(Some(lhs), None, rhs)); } let match_target = make::expr_tuple(vec![lhs_name, rhs_name]); |