summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide-assists
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:18:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:18:58 +0000
commita4b7ed7a42c716ab9f05e351f003d589124fd55d (patch)
treeb620cd3f223850b28716e474e80c58059dca5dd4 /src/tools/rust-analyzer/crates/ide-assists
parentAdding upstream version 1.67.1+dfsg1. (diff)
downloadrustc-a4b7ed7a42c716ab9f05e351f003d589124fd55d.tar.xz
rustc-a4b7ed7a42c716ab9f05e351f003d589124fd55d.zip
Adding upstream version 1.68.2+dfsg1.upstream/1.68.2+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-assists')
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/Cargo.toml1
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_type.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs99
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_return_type.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs43
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs (renamed from src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_format_string_arg.rs)58
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs284
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs90
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs120
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs8
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_deref.rs3
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs411
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs233
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs481
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_parentheses.rs221
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_arith_op.rs226
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs124
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_or_with_or_else.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/unnecessary_async.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/unqualify_method_call.rs211
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs47
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/lib.rs17
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs210
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests/sourcegen.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils.rs79
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs16
43 files changed, 2764 insertions, 292 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, &current_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/move_format_string_arg.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
index 11db6ae7f..4f3b6e0c2 100644
--- 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/extract_expressions_from_format_string.rs
@@ -10,7 +10,7 @@ use itertools::Itertools;
use stdx::format_to;
use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange};
-// Assist: move_format_string_arg
+// Assist: extract_expressions_from_format_string
//
// Move an expression out of a format string.
//
@@ -23,7 +23,7 @@ use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange};
// }
//
// fn main() {
-// print!("{x + 1}$0");
+// print!("{var} {x + 1}$0");
// }
// ```
// ->
@@ -36,11 +36,14 @@ use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange};
// }
//
// fn main() {
-// print!("{}"$0, x + 1);
+// print!("{var} {}"$0, x + 1);
// }
// ```
-pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+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)?;
@@ -58,7 +61,7 @@ pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>)
acc.add(
AssistId(
- "move_format_string_arg",
+ "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
@@ -66,7 +69,7 @@ pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>)
AssistKind::QuickFix
},
),
- "Extract format args",
+ "Extract format expressions",
tt.syntax().text_range(),
|edit| {
let fmt_range = fmt_string.syntax().text_range();
@@ -118,15 +121,14 @@ pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>)
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) => {
+ 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) => {
@@ -139,6 +141,7 @@ pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>)
}
}
}
+ Arg::Ident(_s) => (),
}
}
@@ -171,7 +174,7 @@ macro_rules! print {
#[test]
fn multiple_middle_arg() {
check_assist(
- move_format_string_arg,
+ extract_expressions_from_format_string,
&add_macro_decl(
r#"
fn main() {
@@ -192,7 +195,7 @@ fn main() {
#[test]
fn single_arg() {
check_assist(
- move_format_string_arg,
+ extract_expressions_from_format_string,
&add_macro_decl(
r#"
fn main() {
@@ -213,7 +216,7 @@ fn main() {
#[test]
fn multiple_middle_placeholders_arg() {
check_assist(
- move_format_string_arg,
+ extract_expressions_from_format_string,
&add_macro_decl(
r#"
fn main() {
@@ -234,7 +237,7 @@ fn main() {
#[test]
fn multiple_trailing_args() {
check_assist(
- move_format_string_arg,
+ extract_expressions_from_format_string,
&add_macro_decl(
r#"
fn main() {
@@ -255,7 +258,7 @@ fn main() {
#[test]
fn improper_commas() {
check_assist(
- move_format_string_arg,
+ extract_expressions_from_format_string,
&add_macro_decl(
r#"
fn main() {
@@ -276,7 +279,7 @@ fn main() {
#[test]
fn nested_tt() {
check_assist(
- move_format_string_arg,
+ extract_expressions_from_format_string,
&add_macro_decl(
r#"
fn main() {
@@ -293,4 +296,27 @@ fn main() {
),
);
}
+
+ #[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(&lt.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(&lt.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(&lt.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(&lt.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(&lt.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(&lt.text()))),
+ );
+ }
}
- }
- ast::Type::RefType(ref_) => generics.extend(
- ref_.lifetime().and_then(|lt| known_generics.iter().find(find_lifetime(&lt.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(&lt.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/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]);