summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide-assists
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-assists')
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/Cargo.toml4
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs31
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs83
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs140
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs334
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs261
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/bool_to_enum.rs1675
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs17
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs7
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs76
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs889
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs241
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs14
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs35
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs161
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_binexpr.rs31
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_constant.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs216
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_deref.rs16
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs13
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs202
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs58
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs25
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/into_to_qualified_from.rs74
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs7
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_parentheses.rs33
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs17
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs172
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_ignore.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_use.rs40
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/unnecessary_async.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/unqualify_method_call.rs52
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_result_return_type.rs24
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs24
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/lib.rs11
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests.rs7
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs136
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils.rs14
50 files changed, 4880 insertions, 302 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml b/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml
index 447e38f91..a622ec1a9 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml
@@ -14,8 +14,8 @@ doctest = false
[dependencies]
cov-mark = "2.0.0-pre.1"
-itertools = "0.10.5"
-either = "1.7.0"
+itertools.workspace = true
+either.workspace = true
smallvec.workspace = true
# local deps
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs b/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs
index b273ebc85..fbe17dbfd 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs
@@ -14,5 +14,6 @@ pub struct AssistConfig {
pub allowed: Option<Vec<AssistKind>>,
pub insert_use: InsertUseConfig,
pub prefer_no_std: bool,
+ pub prefer_prelude: bool,
pub assist_emit_must_use: bool,
}
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 c0e5429a2..410c62310 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
@@ -2249,4 +2249,35 @@ impl b::LocalTrait for B {
"#,
)
}
+
+ #[test]
+ fn doc_hidden_nondefault_member() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+//- /lib.rs crate:b new_source_root:local
+trait LocalTrait {
+ #[doc(hidden)]
+ fn no_skip_non_default() -> Option<()>;
+
+ #[doc(hidden)]
+ fn skip_default() -> Option<()> {
+ todo!()
+ }
+}
+
+//- /main.rs crate:a deps:b
+struct B;
+impl b::Loc$0alTrait for B {}
+ "#,
+ r#"
+struct B;
+impl b::LocalTrait for B {
+ fn no_skip_non_default() -> Option<()> {
+ ${0:todo!()}
+ }
+}
+ "#,
+ )
+ }
}
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 3b162d7c4..2374da9a3 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
@@ -88,7 +88,13 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
.into_iter()
.filter_map(|variant| {
Some((
- build_pat(ctx.db(), module, variant, ctx.config.prefer_no_std)?,
+ build_pat(
+ ctx.db(),
+ module,
+ variant,
+ ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
+ )?,
variant.should_be_hidden(ctx.db(), module.krate()),
))
})
@@ -140,7 +146,13 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
.iter()
.any(|variant| variant.should_be_hidden(ctx.db(), module.krate()));
let patterns = variants.into_iter().filter_map(|variant| {
- build_pat(ctx.db(), module, variant, ctx.config.prefer_no_std)
+ build_pat(
+ ctx.db(),
+ module,
+ variant,
+ ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
+ )
});
(ast::Pat::from(make::tuple_pat(patterns)), is_hidden)
@@ -173,7 +185,13 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
.iter()
.any(|variant| variant.should_be_hidden(ctx.db(), module.krate()));
let patterns = variants.into_iter().filter_map(|variant| {
- build_pat(ctx.db(), module, variant.clone(), ctx.config.prefer_no_std)
+ build_pat(
+ ctx.db(),
+ module,
+ variant.clone(),
+ ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
+ )
});
(ast::Pat::from(make::slice_pat(patterns)), is_hidden)
})
@@ -273,9 +291,10 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
syntax::SyntaxElement::Token(it) => {
// Don't have a way to make tokens mut, so instead make the parent mut
// and find the token again
- let parent = edit.make_syntax_mut(it.parent().unwrap());
+ let parent =
+ edit.make_syntax_mut(it.parent().expect("Token must have a parent."));
let mut_token =
- parent.covering_element(it.text_range()).into_token().unwrap();
+ parent.covering_element(it.text_range()).into_token().expect("Covering element cannot be found. Range may be beyond the current node's range");
syntax::SyntaxElement::from(mut_token)
}
@@ -439,28 +458,35 @@ fn build_pat(
module: hir::Module,
var: ExtendedVariant,
prefer_no_std: bool,
+ prefer_prelude: bool,
) -> Option<ast::Pat> {
match var {
ExtendedVariant::Variant(var) => {
- let path =
- mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var), prefer_no_std)?);
+ let path = mod_path_to_ast(&module.find_use_path(
+ db,
+ ModuleDef::from(var),
+ prefer_no_std,
+ prefer_prelude,
+ )?);
// FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
- let pat: ast::Pat = match var.source(db)?.value.kind() {
+ Some(match var.source(db)?.value.kind() {
ast::StructKind::Tuple(field_list) => {
let pats =
iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count());
make::tuple_struct_pat(path, pats).into()
}
ast::StructKind::Record(field_list) => {
- let pats = field_list
- .fields()
- .map(|f| make::ext::simple_ident_pat(f.name().unwrap()).into());
+ let pats = field_list.fields().map(|f| {
+ make::ext::simple_ident_pat(
+ f.name().expect("Record field must have a name"),
+ )
+ .into()
+ });
make::record_pat(path, pats).into()
}
ast::StructKind::Unit => make::path_pat(path),
- };
- Some(pat)
+ })
}
ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))),
ExtendedVariant::False => Some(ast::Pat::from(make::literal_pat("false"))),
@@ -1941,4 +1967,35 @@ fn main() {
"#,
);
}
+
+ /// See [`discussion`](https://github.com/rust-lang/rust-analyzer/pull/15594#discussion_r1322960614)
+ #[test]
+ fn missing_field_name() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A {
+ A,
+ Missing { a: u32, : u32, c: u32 }
+}
+
+fn a() {
+ let b = A::A;
+ match b$0 {}
+}"#,
+ r#"
+enum A {
+ A,
+ Missing { a: u32, : u32, c: u32 }
+}
+
+fn a() {
+ let b = A::A;
+ match b {
+ $0A::A => todo!(),
+ A::Missing { a, u32, c } => todo!(),
+ }
+}"#,
+ )
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs
index 36f68d176..88fd0b1b7 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs
@@ -1,6 +1,9 @@
+use either::Either;
use ide_db::defs::{Definition, NameRefClass};
-use itertools::Itertools;
-use syntax::{ast, AstNode, SyntaxKind, T};
+use syntax::{
+ ast::{self, make, HasArgList},
+ ted, AstNode,
+};
use crate::{
assist_context::{AssistContext, Assists},
@@ -25,21 +28,45 @@ use crate::{
// }
// ```
pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
- let ident = ctx.find_token_syntax_at_offset(SyntaxKind::IDENT).or_else(|| {
- let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?;
- if arg_list.args().next().is_some() {
- return None;
- }
- cov_mark::hit!(add_turbo_fish_after_call);
- cov_mark::hit!(add_type_ascription_after_call);
- arg_list.l_paren_token()?.prev_token().filter(|it| it.kind() == SyntaxKind::IDENT)
- })?;
- let next_token = ident.next_token()?;
- if next_token.kind() == T![::] {
+ let turbofish_target =
+ ctx.find_node_at_offset::<ast::PathSegment>().map(Either::Left).or_else(|| {
+ let callable_expr = ctx.find_node_at_offset::<ast::CallableExpr>()?;
+
+ if callable_expr.arg_list()?.args().next().is_some() {
+ return None;
+ }
+
+ cov_mark::hit!(add_turbo_fish_after_call);
+ cov_mark::hit!(add_type_ascription_after_call);
+
+ match callable_expr {
+ ast::CallableExpr::Call(it) => {
+ let ast::Expr::PathExpr(path) = it.expr()? else {
+ return None;
+ };
+
+ Some(Either::Left(path.path()?.segment()?))
+ }
+ ast::CallableExpr::MethodCall(it) => Some(Either::Right(it)),
+ }
+ })?;
+
+ let already_has_turbofish = match &turbofish_target {
+ Either::Left(path_segment) => path_segment.generic_arg_list().is_some(),
+ Either::Right(method_call) => method_call.generic_arg_list().is_some(),
+ };
+
+ if already_has_turbofish {
cov_mark::hit!(add_turbo_fish_one_fish_is_enough);
return None;
}
- let name_ref = ast::NameRef::cast(ident.parent()?)?;
+
+ let name_ref = match &turbofish_target {
+ Either::Left(path_segment) => path_segment.name_ref()?,
+ Either::Right(method_call) => method_call.name_ref()?,
+ };
+ let ident = name_ref.ident_token()?;
+
let def = match NameRefClass::classify(&ctx.sema, &name_ref)? {
NameRefClass::Definition(def) => def,
NameRefClass::FieldShorthand { .. } | NameRefClass::ExternCrateShorthand { .. } => {
@@ -58,20 +85,27 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
if let Some(let_stmt) = ctx.find_node_at_offset::<ast::LetStmt>() {
if let_stmt.colon_token().is_none() {
- let type_pos = let_stmt.pat()?.syntax().last_token()?.text_range().end();
- let semi_pos = let_stmt.syntax().last_token()?.text_range().end();
+ if let_stmt.pat().is_none() {
+ return None;
+ }
acc.add(
AssistId("add_type_ascription", AssistKind::RefactorRewrite),
"Add `: _` before assignment operator",
ident.text_range(),
- |builder| {
+ |edit| {
+ let let_stmt = edit.make_mut(let_stmt);
+
if let_stmt.semicolon_token().is_none() {
- builder.insert(semi_pos, ";");
+ ted::append_child(let_stmt.syntax(), make::tokens::semicolon());
}
- match ctx.config.snippet_cap {
- Some(cap) => builder.insert_snippet(cap, type_pos, ": ${0:_}"),
- None => builder.insert(type_pos, ": _"),
+
+ let placeholder_ty = make::ty_placeholder().clone_for_update();
+
+ let_stmt.set_ty(Some(placeholder_ty.clone()));
+
+ if let Some(cap) = ctx.config.snippet_cap {
+ edit.add_placeholder_snippet(cap, placeholder_ty);
}
},
)?
@@ -91,38 +125,46 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
"Add `::<>`",
ident.text_range(),
- |builder| {
- builder.trigger_signature_help();
- match ctx.config.snippet_cap {
- Some(cap) => {
- let fish_head = get_snippet_fish_head(number_of_arguments);
- let snip = format!("::<{fish_head}>");
- builder.insert_snippet(cap, ident.text_range().end(), snip)
+ |edit| {
+ edit.trigger_signature_help();
+
+ let new_arg_list = match turbofish_target {
+ Either::Left(path_segment) => {
+ edit.make_mut(path_segment).get_or_create_generic_arg_list()
+ }
+ Either::Right(method_call) => {
+ edit.make_mut(method_call).get_or_create_generic_arg_list()
}
- None => {
- let fish_head = std::iter::repeat("_").take(number_of_arguments).format(", ");
- let snip = format!("::<{fish_head}>");
- builder.insert(ident.text_range().end(), snip);
+ };
+
+ let fish_head = get_fish_head(number_of_arguments).clone_for_update();
+
+ // Note: we need to replace the `new_arg_list` instead of being able to use something like
+ // `GenericArgList::add_generic_arg` as `PathSegment::get_or_create_generic_arg_list`
+ // always creates a non-turbofish form generic arg list.
+ ted::replace(new_arg_list.syntax(), fish_head.syntax());
+
+ if let Some(cap) = ctx.config.snippet_cap {
+ for arg in fish_head.generic_args() {
+ edit.add_placeholder_snippet(cap, arg)
}
}
},
)
}
-/// This will create a snippet string with tabstops marked
-fn get_snippet_fish_head(number_of_arguments: usize) -> String {
- let mut fish_head = (1..number_of_arguments)
- .format_with("", |i, f| f(&format_args!("${{{i}:_}}, ")))
- .to_string();
-
- // tabstop 0 is a special case and always the last one
- fish_head.push_str("${0:_}");
- fish_head
+/// This will create a turbofish generic arg list corresponding to the number of arguments
+fn get_fish_head(number_of_arguments: usize) -> ast::GenericArgList {
+ let args = (0..number_of_arguments).map(|_| make::type_arg(make::ty_placeholder()).into());
+ make::turbofish_generic_arg_list(args)
}
#[cfg(test)]
mod tests {
- use crate::tests::{check_assist, check_assist_by_label, check_assist_not_applicable};
+ use crate::tests::{
+ check_assist, check_assist_by_label, check_assist_not_applicable,
+ check_assist_not_applicable_by_label,
+ };
use super::*;
@@ -364,6 +406,20 @@ fn main() {
}
#[test]
+ fn add_type_ascription_missing_pattern() {
+ check_assist_not_applicable_by_label(
+ add_turbo_fish,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let = make$0()
+}
+"#,
+ "Add `: _` before assignment operator",
+ );
+ }
+
+ #[test]
fn add_turbo_fish_function_lifetime_parameter() {
check_assist(
add_turbo_fish,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs
index 66bc2f6da..2d41243c2 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs
@@ -1,7 +1,13 @@
use std::collections::VecDeque;
+use ide_db::{
+ assists::GroupLabel,
+ famous_defs::FamousDefs,
+ source_change::SourceChangeBuilder,
+ syntax_helpers::node_ext::{for_each_tail_expr, walk_expr},
+};
use syntax::{
- ast::{self, AstNode, Expr::BinExpr},
+ ast::{self, make, AstNode, Expr::BinExpr, HasArgList},
ted::{self, Position},
SyntaxKind,
};
@@ -89,7 +95,8 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
let dm_lhs = demorganed.lhs()?;
- acc.add(
+ acc.add_group(
+ &GroupLabel("Apply De Morgan's law".to_string()),
AssistId("apply_demorgan", AssistKind::RefactorRewrite),
"Apply De Morgan's law",
op_range,
@@ -143,6 +150,127 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
)
}
+// Assist: apply_demorgan_iterator
+//
+// Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law] to
+// `Iterator::all` and `Iterator::any`.
+//
+// This transforms expressions of the form `!iter.any(|x| predicate(x))` into
+// `iter.all(|x| !predicate(x))` and vice versa. This also works the other way for
+// `Iterator::all` into `Iterator::any`.
+//
+// ```
+// # //- minicore: iterator
+// fn main() {
+// let arr = [1, 2, 3];
+// if !arr.into_iter().$0any(|num| num == 4) {
+// println!("foo");
+// }
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// let arr = [1, 2, 3];
+// if arr.into_iter().all(|num| num != 4) {
+// println!("foo");
+// }
+// }
+// ```
+pub(crate) fn apply_demorgan_iterator(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
+ let (name, arg_expr) = validate_method_call_expr(ctx, &method_call)?;
+
+ let ast::Expr::ClosureExpr(closure_expr) = arg_expr else { return None };
+ let closure_body = closure_expr.body()?;
+
+ let op_range = method_call.syntax().text_range();
+ let label = format!("Apply De Morgan's law to `Iterator::{}`", name.text().as_str());
+ acc.add_group(
+ &GroupLabel("Apply De Morgan's law".to_string()),
+ AssistId("apply_demorgan_iterator", AssistKind::RefactorRewrite),
+ label,
+ op_range,
+ |edit| {
+ // replace the method name
+ let new_name = match name.text().as_str() {
+ "all" => make::name_ref("any"),
+ "any" => make::name_ref("all"),
+ _ => unreachable!(),
+ }
+ .clone_for_update();
+ edit.replace_ast(name, new_name);
+
+ // negate all tail expressions in the closure body
+ let tail_cb = &mut |e: &_| tail_cb_impl(edit, e);
+ walk_expr(&closure_body, &mut |expr| {
+ if let ast::Expr::ReturnExpr(ret_expr) = expr {
+ if let Some(ret_expr_arg) = &ret_expr.expr() {
+ for_each_tail_expr(ret_expr_arg, tail_cb);
+ }
+ }
+ });
+ for_each_tail_expr(&closure_body, tail_cb);
+
+ // negate the whole method call
+ if let Some(prefix_expr) = method_call
+ .syntax()
+ .parent()
+ .and_then(ast::PrefixExpr::cast)
+ .filter(|prefix_expr| matches!(prefix_expr.op_kind(), Some(ast::UnaryOp::Not)))
+ {
+ edit.delete(
+ prefix_expr
+ .op_token()
+ .expect("prefix expression always has an operator")
+ .text_range(),
+ );
+ } else {
+ edit.insert(method_call.syntax().text_range().start(), "!");
+ }
+ },
+ )
+}
+
+/// Ensures that the method call is to `Iterator::all` or `Iterator::any`.
+fn validate_method_call_expr(
+ ctx: &AssistContext<'_>,
+ method_call: &ast::MethodCallExpr,
+) -> Option<(ast::NameRef, ast::Expr)> {
+ let name_ref = method_call.name_ref()?;
+ if name_ref.text() != "all" && name_ref.text() != "any" {
+ return None;
+ }
+ let arg_expr = method_call.arg_list()?.args().next()?;
+
+ let sema = &ctx.sema;
+
+ let receiver = method_call.receiver()?;
+ let it_type = sema.type_of_expr(&receiver)?.adjusted();
+ let module = sema.scope(receiver.syntax())?.module();
+ let krate = module.krate();
+
+ let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?;
+ it_type.impls_trait(sema.db, iter_trait, &[]).then_some((name_ref, arg_expr))
+}
+
+fn tail_cb_impl(edit: &mut SourceChangeBuilder, e: &ast::Expr) {
+ match e {
+ ast::Expr::BreakExpr(break_expr) => {
+ if let Some(break_expr_arg) = break_expr.expr() {
+ for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(edit, e))
+ }
+ }
+ ast::Expr::ReturnExpr(_) => {
+ // all return expressions have already been handled by the walk loop
+ }
+ e => {
+ let inverted_body = invert_boolean_expression(e.clone());
+ edit.replace(e.syntax().text_range(), inverted_body.syntax().text());
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -255,4 +383,206 @@ fn f() { !(S <= S || S < S) }
"fn() { let x = a && b && c; }",
)
}
+
+ #[test]
+ fn demorgan_iterator_any_all_reverse() {
+ check_assist(
+ apply_demorgan_iterator,
+ r#"
+//- minicore: iterator
+fn main() {
+ let arr = [1, 2, 3];
+ if arr.into_iter().all(|num| num $0!= 4) {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+fn main() {
+ let arr = [1, 2, 3];
+ if !arr.into_iter().any(|num| num == 4) {
+ println!("foo");
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn demorgan_iterator_all_any() {
+ check_assist(
+ apply_demorgan_iterator,
+ r#"
+//- minicore: iterator
+fn main() {
+ let arr = [1, 2, 3];
+ if !arr.into_iter().$0all(|num| num > 3) {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+fn main() {
+ let arr = [1, 2, 3];
+ if arr.into_iter().any(|num| num <= 3) {
+ println!("foo");
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn demorgan_iterator_multiple_terms() {
+ check_assist(
+ apply_demorgan_iterator,
+ r#"
+//- minicore: iterator
+fn main() {
+ let arr = [1, 2, 3];
+ if !arr.into_iter().$0any(|num| num > 3 && num == 23 && num <= 30) {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+fn main() {
+ let arr = [1, 2, 3];
+ if arr.into_iter().all(|num| !(num > 3 && num == 23 && num <= 30)) {
+ println!("foo");
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn demorgan_iterator_double_negation() {
+ check_assist(
+ apply_demorgan_iterator,
+ r#"
+//- minicore: iterator
+fn main() {
+ let arr = [1, 2, 3];
+ if !arr.into_iter().$0all(|num| !(num > 3)) {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+fn main() {
+ let arr = [1, 2, 3];
+ if arr.into_iter().any(|num| num > 3) {
+ println!("foo");
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn demorgan_iterator_double_parens() {
+ check_assist(
+ apply_demorgan_iterator,
+ r#"
+//- minicore: iterator
+fn main() {
+ let arr = [1, 2, 3];
+ if !arr.into_iter().$0any(|num| (num > 3 && (num == 1 || num == 2))) {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+fn main() {
+ let arr = [1, 2, 3];
+ if arr.into_iter().all(|num| !(num > 3 && (num == 1 || num == 2))) {
+ println!("foo");
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn demorgan_iterator_multiline() {
+ check_assist(
+ apply_demorgan_iterator,
+ r#"
+//- minicore: iterator
+fn main() {
+ let arr = [1, 2, 3];
+ if arr
+ .into_iter()
+ .all$0(|num| !num.is_negative())
+ {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+fn main() {
+ let arr = [1, 2, 3];
+ if !arr
+ .into_iter()
+ .any(|num| num.is_negative())
+ {
+ println!("foo");
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn demorgan_iterator_block_closure() {
+ check_assist(
+ apply_demorgan_iterator,
+ r#"
+//- minicore: iterator
+fn main() {
+ let arr = [-1, 1, 2, 3];
+ if arr.into_iter().all(|num: i32| {
+ $0if num.is_positive() {
+ num <= 3
+ } else {
+ num >= -1
+ }
+ }) {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+fn main() {
+ let arr = [-1, 1, 2, 3];
+ if !arr.into_iter().any(|num: i32| {
+ if num.is_positive() {
+ num > 3
+ } else {
+ num < -1
+ }
+ }) {
+ println!("foo");
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn demorgan_iterator_wrong_method() {
+ check_assist_not_applicable(
+ apply_demorgan_iterator,
+ r#"
+//- minicore: iterator
+fn main() {
+ let arr = [1, 2, 3];
+ if !arr.into_iter().$0map(|num| num > 3) {
+ println!("foo");
+ }
+}
+"#,
+ );
+ }
}
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 7acf2ea0a..f508c42c5 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
@@ -5,7 +5,7 @@ use ide_db::{
helpers::mod_path_to_ast,
imports::{
import_assets::{ImportAssets, ImportCandidate, LocatedImport},
- insert_use::{insert_use, ImportScope},
+ insert_use::{insert_use, insert_use_as_alias, ImportScope},
},
};
use syntax::{ast, AstNode, NodeOrToken, SyntaxElement};
@@ -93,6 +93,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
&ctx.sema,
ctx.config.insert_use.prefix_kind,
ctx.config.prefer_no_std,
+ ctx.config.prefer_no_std,
);
if proposed_imports.is_empty() {
return None;
@@ -129,10 +130,12 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
for import in proposed_imports {
let import_path = import.import_path;
+ let (assist_id, import_name) =
+ (AssistId("auto_import", AssistKind::QuickFix), import_path.display(ctx.db()));
acc.add_group(
&group_label,
- AssistId("auto_import", AssistKind::QuickFix),
- format!("Import `{}`", import_path.display(ctx.db())),
+ assist_id,
+ format!("Import `{}`", import_name),
range,
|builder| {
let scope = match scope.clone() {
@@ -143,6 +146,38 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
insert_use(&scope, mod_path_to_ast(&import_path), &ctx.config.insert_use);
},
);
+
+ match import_assets.import_candidate() {
+ ImportCandidate::TraitAssocItem(name) | ImportCandidate::TraitMethod(name) => {
+ let is_method =
+ matches!(import_assets.import_candidate(), ImportCandidate::TraitMethod(_));
+ let type_ = if is_method { "method" } else { "item" };
+ let group_label = GroupLabel(format!(
+ "Import a trait for {} {} by alias",
+ type_,
+ name.assoc_item_name.text()
+ ));
+ acc.add_group(
+ &group_label,
+ assist_id,
+ format!("Import `{} as _`", import_name),
+ range,
+ |builder| {
+ let scope = match scope.clone() {
+ ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
+ ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
+ ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
+ };
+ insert_use_as_alias(
+ &scope,
+ mod_path_to_ast(&import_path),
+ &ctx.config.insert_use,
+ );
+ },
+ );
+ }
+ _ => {}
+ }
}
Some(())
}
@@ -253,7 +288,8 @@ mod tests {
};
use crate::tests::{
- check_assist, check_assist_not_applicable, check_assist_target, TEST_CONFIG,
+ check_assist, check_assist_by_label, check_assist_not_applicable, check_assist_target,
+ TEST_CONFIG,
};
fn check_auto_import_order(before: &str, order: &[&str]) {
@@ -705,7 +741,7 @@ fn main() {
#[test]
fn associated_trait_function() {
- check_assist(
+ check_assist_by_label(
auto_import,
r"
mod test_mod {
@@ -739,6 +775,44 @@ fn main() {
test_mod::TestStruct::test_function
}
",
+ "Import `test_mod::TestTrait`",
+ );
+
+ check_assist_by_label(
+ auto_import,
+ r"
+ mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_function() {}
+ }
+ }
+
+ fn main() {
+ test_mod::TestStruct::test_function$0
+ }
+ ",
+ r"
+ use test_mod::TestTrait as _;
+
+ mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_function() {}
+ }
+ }
+
+ fn main() {
+ test_mod::TestStruct::test_function
+ }
+ ",
+ "Import `test_mod::TestTrait as _`",
);
}
@@ -776,7 +850,44 @@ fn main() {
#[test]
fn associated_trait_const() {
- check_assist(
+ check_assist_by_label(
+ auto_import,
+ r"
+ mod test_mod {
+ pub trait TestTrait {
+ const TEST_CONST: u8;
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const TEST_CONST: u8 = 42;
+ }
+ }
+
+ fn main() {
+ test_mod::TestStruct::TEST_CONST$0
+ }
+ ",
+ r"
+ use test_mod::TestTrait as _;
+
+ mod test_mod {
+ pub trait TestTrait {
+ const TEST_CONST: u8;
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const TEST_CONST: u8 = 42;
+ }
+ }
+
+ fn main() {
+ test_mod::TestStruct::TEST_CONST
+ }
+ ",
+ "Import `test_mod::TestTrait as _`",
+ );
+
+ check_assist_by_label(
auto_import,
r"
mod test_mod {
@@ -810,6 +921,7 @@ fn main() {
test_mod::TestStruct::TEST_CONST
}
",
+ "Import `test_mod::TestTrait`",
);
}
@@ -847,7 +959,46 @@ fn main() {
#[test]
fn trait_method() {
- check_assist(
+ check_assist_by_label(
+ auto_import,
+ r"
+ mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+ }
+
+ fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od()
+ }
+ ",
+ r"
+ use test_mod::TestTrait as _;
+
+ mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+ }
+
+ fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_method()
+ }
+ ",
+ "Import `test_mod::TestTrait as _`",
+ );
+
+ check_assist_by_label(
auto_import,
r"
mod test_mod {
@@ -883,12 +1034,43 @@ fn main() {
test_struct.test_method()
}
",
+ "Import `test_mod::TestTrait`",
);
}
#[test]
fn trait_method_cross_crate() {
- check_assist(
+ check_assist_by_label(
+ auto_import,
+ r"
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.test_meth$0od()
+ }
+ //- /dep.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+ }
+ ",
+ r"
+ use dep::test_mod::TestTrait as _;
+
+ fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.test_method()
+ }
+ ",
+ "Import `dep::test_mod::TestTrait as _`",
+ );
+
+ check_assist_by_label(
auto_import,
r"
//- /main.rs crate:main deps:dep
@@ -915,12 +1097,41 @@ fn main() {
test_struct.test_method()
}
",
+ "Import `dep::test_mod::TestTrait`",
);
}
#[test]
fn assoc_fn_cross_crate() {
- check_assist(
+ check_assist_by_label(
+ auto_import,
+ r"
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ dep::test_mod::TestStruct::test_func$0tion
+ }
+ //- /dep.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_function() {}
+ }
+ }
+ ",
+ r"
+ use dep::test_mod::TestTrait as _;
+
+ fn main() {
+ dep::test_mod::TestStruct::test_function
+ }
+ ",
+ "Import `dep::test_mod::TestTrait as _`",
+ );
+
+ check_assist_by_label(
auto_import,
r"
//- /main.rs crate:main deps:dep
@@ -945,12 +1156,41 @@ fn main() {
dep::test_mod::TestStruct::test_function
}
",
+ "Import `dep::test_mod::TestTrait`",
);
}
#[test]
fn assoc_const_cross_crate() {
- check_assist(
+ check_assist_by_label(
+ auto_import,
+ r"
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ dep::test_mod::TestStruct::CONST$0
+ }
+ //- /dep.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ const CONST: bool;
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const CONST: bool = true;
+ }
+ }
+ ",
+ r"
+ use dep::test_mod::TestTrait as _;
+
+ fn main() {
+ dep::test_mod::TestStruct::CONST
+ }
+ ",
+ "Import `dep::test_mod::TestTrait as _`",
+ );
+
+ check_assist_by_label(
auto_import,
r"
//- /main.rs crate:main deps:dep
@@ -975,6 +1215,7 @@ fn main() {
dep::test_mod::TestStruct::CONST
}
",
+ "Import `dep::test_mod::TestTrait`",
);
}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bool_to_enum.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bool_to_enum.rs
new file mode 100644
index 000000000..0f2d1057c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bool_to_enum.rs
@@ -0,0 +1,1675 @@
+use hir::ModuleDef;
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ defs::Definition,
+ helpers::mod_path_to_ast,
+ imports::insert_use::{insert_use, ImportScope},
+ search::{FileReference, UsageSearchResult},
+ source_change::SourceChangeBuilder,
+ FxHashSet,
+};
+use itertools::Itertools;
+use syntax::{
+ ast::{
+ self,
+ edit::IndentLevel,
+ edit_in_place::{AttrsOwnerEdit, Indent},
+ make, HasName,
+ },
+ ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T,
+};
+use text_edit::TextRange;
+
+use crate::assist_context::{AssistContext, Assists};
+
+// Assist: bool_to_enum
+//
+// This converts boolean local variables, fields, constants, and statics into a new
+// enum with two variants `Bool::True` and `Bool::False`, as well as replacing
+// all assignments with the variants and replacing all usages with `== Bool::True` or
+// `== Bool::False`.
+//
+// ```
+// fn main() {
+// let $0bool = true;
+//
+// if bool {
+// println!("foo");
+// }
+// }
+// ```
+// ->
+// ```
+// #[derive(PartialEq, Eq)]
+// enum Bool { True, False }
+//
+// fn main() {
+// let bool = Bool::True;
+//
+// if bool == Bool::True {
+// println!("foo");
+// }
+// }
+// ```
+pub(crate) fn bool_to_enum(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let BoolNodeData { target_node, name, ty_annotation, initializer, definition } =
+ find_bool_node(ctx)?;
+ let target_module = ctx.sema.scope(&target_node)?.module().nearest_non_block_module(ctx.db());
+
+ let target = name.syntax().text_range();
+ acc.add(
+ AssistId("bool_to_enum", AssistKind::RefactorRewrite),
+ "Convert boolean to enum",
+ target,
+ |edit| {
+ if let Some(ty) = &ty_annotation {
+ cov_mark::hit!(replaces_ty_annotation);
+ edit.replace(ty.syntax().text_range(), "Bool");
+ }
+
+ if let Some(initializer) = initializer {
+ replace_bool_expr(edit, initializer);
+ }
+
+ let usages = definition.usages(&ctx.sema).all();
+ add_enum_def(edit, ctx, &usages, target_node, &target_module);
+ replace_usages(edit, ctx, &usages, definition, &target_module);
+ },
+ )
+}
+
+struct BoolNodeData {
+ target_node: SyntaxNode,
+ name: ast::Name,
+ ty_annotation: Option<ast::Type>,
+ initializer: Option<ast::Expr>,
+ definition: Definition,
+}
+
+/// Attempts to find an appropriate node to apply the action to.
+fn find_bool_node(ctx: &AssistContext<'_>) -> Option<BoolNodeData> {
+ let name: ast::Name = ctx.find_node_at_offset()?;
+
+ if let Some(let_stmt) = name.syntax().ancestors().find_map(ast::LetStmt::cast) {
+ let bind_pat = match let_stmt.pat()? {
+ ast::Pat::IdentPat(pat) => pat,
+ _ => {
+ cov_mark::hit!(not_applicable_in_non_ident_pat);
+ return None;
+ }
+ };
+ let def = ctx.sema.to_def(&bind_pat)?;
+ if !def.ty(ctx.db()).is_bool() {
+ cov_mark::hit!(not_applicable_non_bool_local);
+ return None;
+ }
+
+ Some(BoolNodeData {
+ target_node: let_stmt.syntax().clone(),
+ name,
+ ty_annotation: let_stmt.ty(),
+ initializer: let_stmt.initializer(),
+ definition: Definition::Local(def),
+ })
+ } else if let Some(const_) = name.syntax().parent().and_then(ast::Const::cast) {
+ let def = ctx.sema.to_def(&const_)?;
+ if !def.ty(ctx.db()).is_bool() {
+ cov_mark::hit!(not_applicable_non_bool_const);
+ return None;
+ }
+
+ Some(BoolNodeData {
+ target_node: const_.syntax().clone(),
+ name,
+ ty_annotation: const_.ty(),
+ initializer: const_.body(),
+ definition: Definition::Const(def),
+ })
+ } else if let Some(static_) = name.syntax().parent().and_then(ast::Static::cast) {
+ let def = ctx.sema.to_def(&static_)?;
+ if !def.ty(ctx.db()).is_bool() {
+ cov_mark::hit!(not_applicable_non_bool_static);
+ return None;
+ }
+
+ Some(BoolNodeData {
+ target_node: static_.syntax().clone(),
+ name,
+ ty_annotation: static_.ty(),
+ initializer: static_.body(),
+ definition: Definition::Static(def),
+ })
+ } else {
+ let field = name.syntax().parent().and_then(ast::RecordField::cast)?;
+ if field.name()? != name {
+ return None;
+ }
+
+ let adt = field.syntax().ancestors().find_map(ast::Adt::cast)?;
+ let def = ctx.sema.to_def(&field)?;
+ if !def.ty(ctx.db()).is_bool() {
+ cov_mark::hit!(not_applicable_non_bool_field);
+ return None;
+ }
+ Some(BoolNodeData {
+ target_node: adt.syntax().clone(),
+ name,
+ ty_annotation: field.ty(),
+ initializer: None,
+ definition: Definition::Field(def),
+ })
+ }
+}
+
+fn replace_bool_expr(edit: &mut SourceChangeBuilder, expr: ast::Expr) {
+ let expr_range = expr.syntax().text_range();
+ let enum_expr = bool_expr_to_enum_expr(expr);
+ edit.replace(expr_range, enum_expr.syntax().text())
+}
+
+/// Converts an expression of type `bool` to one of the new enum type.
+fn bool_expr_to_enum_expr(expr: ast::Expr) -> ast::Expr {
+ let true_expr = make::expr_path(make::path_from_text("Bool::True")).clone_for_update();
+ let false_expr = make::expr_path(make::path_from_text("Bool::False")).clone_for_update();
+
+ if let ast::Expr::Literal(literal) = &expr {
+ match literal.kind() {
+ ast::LiteralKind::Bool(true) => true_expr,
+ ast::LiteralKind::Bool(false) => false_expr,
+ _ => expr,
+ }
+ } else {
+ make::expr_if(
+ expr,
+ make::tail_only_block_expr(true_expr),
+ Some(ast::ElseBranch::Block(make::tail_only_block_expr(false_expr))),
+ )
+ .clone_for_update()
+ }
+}
+
+/// Replaces all usages of the target identifier, both when read and written to.
+fn replace_usages(
+ edit: &mut SourceChangeBuilder,
+ ctx: &AssistContext<'_>,
+ usages: &UsageSearchResult,
+ target_definition: Definition,
+ target_module: &hir::Module,
+) {
+ for (file_id, references) in usages.iter() {
+ edit.edit_file(*file_id);
+
+ let refs_with_imports =
+ augment_references_with_imports(edit, ctx, references, target_module);
+
+ refs_with_imports.into_iter().rev().for_each(
+ |FileReferenceWithImport { range, old_name, new_name, import_data }| {
+ // replace the usages in patterns and expressions
+ if let Some(ident_pat) = old_name.syntax().ancestors().find_map(ast::IdentPat::cast)
+ {
+ cov_mark::hit!(replaces_record_pat_shorthand);
+
+ let definition = ctx.sema.to_def(&ident_pat).map(Definition::Local);
+ if let Some(def) = definition {
+ replace_usages(
+ edit,
+ ctx,
+ &def.usages(&ctx.sema).all(),
+ target_definition,
+ target_module,
+ )
+ }
+ } else if let Some(initializer) = find_assignment_usage(&new_name) {
+ cov_mark::hit!(replaces_assignment);
+
+ replace_bool_expr(edit, initializer);
+ } else if let Some((prefix_expr, inner_expr)) = find_negated_usage(&new_name) {
+ cov_mark::hit!(replaces_negation);
+
+ edit.replace(
+ prefix_expr.syntax().text_range(),
+ format!("{} == Bool::False", inner_expr),
+ );
+ } else if let Some((record_field, initializer)) = old_name
+ .as_name_ref()
+ .and_then(ast::RecordExprField::for_field_name)
+ .and_then(|record_field| ctx.sema.resolve_record_field(&record_field))
+ .and_then(|(got_field, _, _)| {
+ find_record_expr_usage(&new_name, got_field, target_definition)
+ })
+ {
+ cov_mark::hit!(replaces_record_expr);
+
+ let record_field = edit.make_mut(record_field);
+ let enum_expr = bool_expr_to_enum_expr(initializer);
+ record_field.replace_expr(enum_expr);
+ } else if let Some(pat) = find_record_pat_field_usage(&old_name) {
+ match pat {
+ ast::Pat::IdentPat(ident_pat) => {
+ cov_mark::hit!(replaces_record_pat);
+
+ let definition = ctx.sema.to_def(&ident_pat).map(Definition::Local);
+ if let Some(def) = definition {
+ replace_usages(
+ edit,
+ ctx,
+ &def.usages(&ctx.sema).all(),
+ target_definition,
+ target_module,
+ )
+ }
+ }
+ ast::Pat::LiteralPat(literal_pat) => {
+ cov_mark::hit!(replaces_literal_pat);
+
+ if let Some(expr) = literal_pat.literal().and_then(|literal| {
+ literal.syntax().ancestors().find_map(ast::Expr::cast)
+ }) {
+ replace_bool_expr(edit, expr);
+ }
+ }
+ _ => (),
+ }
+ } else if let Some((ty_annotation, initializer)) = find_assoc_const_usage(&new_name)
+ {
+ edit.replace(ty_annotation.syntax().text_range(), "Bool");
+ replace_bool_expr(edit, initializer);
+ } else if let Some(receiver) = find_method_call_expr_usage(&new_name) {
+ edit.replace(
+ receiver.syntax().text_range(),
+ format!("({} == Bool::True)", receiver),
+ );
+ } else if new_name.syntax().ancestors().find_map(ast::UseTree::cast).is_none() {
+ // for any other usage in an expression, replace it with a check that it is the true variant
+ if let Some((record_field, expr)) = new_name
+ .as_name_ref()
+ .and_then(ast::RecordExprField::for_field_name)
+ .and_then(|record_field| {
+ record_field.expr().map(|expr| (record_field, expr))
+ })
+ {
+ record_field.replace_expr(
+ make::expr_bin_op(
+ expr,
+ ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: false }),
+ make::expr_path(make::path_from_text("Bool::True")),
+ )
+ .clone_for_update(),
+ );
+ } else {
+ edit.replace(range, format!("{} == Bool::True", new_name.text()));
+ }
+ }
+
+ // add imports across modules where needed
+ if let Some((import_scope, path)) = import_data {
+ insert_use(&import_scope, path, &ctx.config.insert_use);
+ }
+ },
+ )
+ }
+}
+
+struct FileReferenceWithImport {
+ range: TextRange,
+ old_name: ast::NameLike,
+ new_name: ast::NameLike,
+ import_data: Option<(ImportScope, ast::Path)>,
+}
+
+fn augment_references_with_imports(
+ edit: &mut SourceChangeBuilder,
+ ctx: &AssistContext<'_>,
+ references: &[FileReference],
+ target_module: &hir::Module,
+) -> Vec<FileReferenceWithImport> {
+ let mut visited_modules = FxHashSet::default();
+
+ references
+ .iter()
+ .filter_map(|FileReference { range, name, .. }| {
+ let name = name.clone().into_name_like()?;
+ ctx.sema.scope(name.syntax()).map(|scope| (*range, name, scope.module()))
+ })
+ .map(|(range, name, ref_module)| {
+ let old_name = name.clone();
+ let new_name = edit.make_mut(name.clone());
+
+ // if the referenced module is not the same as the target one and has not been seen before, add an import
+ let import_data = if ref_module.nearest_non_block_module(ctx.db()) != *target_module
+ && !visited_modules.contains(&ref_module)
+ {
+ visited_modules.insert(ref_module);
+
+ let import_scope =
+ ImportScope::find_insert_use_container(new_name.syntax(), &ctx.sema);
+ let path = ref_module
+ .find_use_path_prefixed(
+ ctx.sema.db,
+ ModuleDef::Module(*target_module),
+ ctx.config.insert_use.prefix_kind,
+ ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
+ )
+ .map(|mod_path| {
+ make::path_concat(mod_path_to_ast(&mod_path), make::path_from_text("Bool"))
+ });
+
+ import_scope.zip(path)
+ } else {
+ None
+ };
+
+ FileReferenceWithImport { range, old_name, new_name, import_data }
+ })
+ .collect()
+}
+
+fn find_assignment_usage(name: &ast::NameLike) -> Option<ast::Expr> {
+ let bin_expr = name.syntax().ancestors().find_map(ast::BinExpr::cast)?;
+
+ if !bin_expr.lhs()?.syntax().descendants().contains(name.syntax()) {
+ cov_mark::hit!(dont_assign_incorrect_ref);
+ return None;
+ }
+
+ if let Some(ast::BinaryOp::Assignment { op: None }) = bin_expr.op_kind() {
+ bin_expr.rhs()
+ } else {
+ None
+ }
+}
+
+fn find_negated_usage(name: &ast::NameLike) -> Option<(ast::PrefixExpr, ast::Expr)> {
+ let prefix_expr = name.syntax().ancestors().find_map(ast::PrefixExpr::cast)?;
+
+ if !matches!(prefix_expr.expr()?, ast::Expr::PathExpr(_) | ast::Expr::FieldExpr(_)) {
+ cov_mark::hit!(dont_overwrite_expression_inside_negation);
+ return None;
+ }
+
+ if let Some(ast::UnaryOp::Not) = prefix_expr.op_kind() {
+ let inner_expr = prefix_expr.expr()?;
+ Some((prefix_expr, inner_expr))
+ } else {
+ None
+ }
+}
+
+fn find_record_expr_usage(
+ name: &ast::NameLike,
+ got_field: hir::Field,
+ target_definition: Definition,
+) -> Option<(ast::RecordExprField, ast::Expr)> {
+ let name_ref = name.as_name_ref()?;
+ let record_field = ast::RecordExprField::for_field_name(name_ref)?;
+ let initializer = record_field.expr()?;
+
+ if let Definition::Field(expected_field) = target_definition {
+ if got_field != expected_field {
+ return None;
+ }
+ }
+
+ Some((record_field, initializer))
+}
+
+fn find_record_pat_field_usage(name: &ast::NameLike) -> Option<ast::Pat> {
+ let record_pat_field = name.syntax().parent().and_then(ast::RecordPatField::cast)?;
+ let pat = record_pat_field.pat()?;
+
+ match pat {
+ ast::Pat::IdentPat(_) | ast::Pat::LiteralPat(_) | ast::Pat::WildcardPat(_) => Some(pat),
+ _ => None,
+ }
+}
+
+fn find_assoc_const_usage(name: &ast::NameLike) -> Option<(ast::Type, ast::Expr)> {
+ let const_ = name.syntax().parent().and_then(ast::Const::cast)?;
+ if const_.syntax().parent().and_then(ast::AssocItemList::cast).is_none() {
+ return None;
+ }
+
+ Some((const_.ty()?, const_.body()?))
+}
+
+fn find_method_call_expr_usage(name: &ast::NameLike) -> Option<ast::Expr> {
+ let method_call = name.syntax().ancestors().find_map(ast::MethodCallExpr::cast)?;
+ let receiver = method_call.receiver()?;
+
+ if !receiver.syntax().descendants().contains(name.syntax()) {
+ return None;
+ }
+
+ Some(receiver)
+}
+
+/// Adds the definition of the new enum before the target node.
+fn add_enum_def(
+ edit: &mut SourceChangeBuilder,
+ ctx: &AssistContext<'_>,
+ usages: &UsageSearchResult,
+ target_node: SyntaxNode,
+ target_module: &hir::Module,
+) {
+ let make_enum_pub = usages
+ .iter()
+ .flat_map(|(_, refs)| refs)
+ .filter_map(|FileReference { name, .. }| {
+ let name = name.clone().into_name_like()?;
+ ctx.sema.scope(name.syntax()).map(|scope| scope.module())
+ })
+ .any(|module| module.nearest_non_block_module(ctx.db()) != *target_module);
+ let enum_def = make_bool_enum(make_enum_pub);
+
+ let insert_before = node_to_insert_before(target_node);
+ let indent = IndentLevel::from_node(&insert_before);
+ enum_def.reindent_to(indent);
+
+ ted::insert_all(
+ ted::Position::before(&edit.make_syntax_mut(insert_before)),
+ vec![
+ enum_def.syntax().clone().into(),
+ make::tokens::whitespace(&format!("\n\n{indent}")).into(),
+ ],
+ );
+}
+
+/// Finds where to put the new enum definition.
+/// Tries to find the ast node at the nearest module or at top-level, otherwise just
+/// returns the input node.
+fn node_to_insert_before(target_node: SyntaxNode) -> SyntaxNode {
+ target_node
+ .ancestors()
+ .take_while(|it| !matches!(it.kind(), SyntaxKind::MODULE | SyntaxKind::SOURCE_FILE))
+ .filter(|it| ast::Item::can_cast(it.kind()))
+ .last()
+ .unwrap_or(target_node)
+}
+
+fn make_bool_enum(make_pub: bool) -> ast::Enum {
+ let enum_def = make::enum_(
+ if make_pub { Some(make::visibility_pub()) } else { None },
+ make::name("Bool"),
+ make::variant_list(vec![
+ make::variant(make::name("True"), None),
+ make::variant(make::name("False"), None),
+ ]),
+ )
+ .clone_for_update();
+
+ let derive_eq = make::attr_outer(make::meta_token_tree(
+ make::ext::ident_path("derive"),
+ make::token_tree(
+ T!['('],
+ vec![
+ NodeOrToken::Token(make::tokens::ident("PartialEq")),
+ NodeOrToken::Token(make::token(T![,])),
+ NodeOrToken::Token(make::tokens::single_space()),
+ NodeOrToken::Token(make::tokens::ident("Eq")),
+ ],
+ ),
+ ))
+ .clone_for_update();
+ enum_def.add_attr(derive_eq);
+
+ enum_def
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ #[test]
+ fn local_variable_with_usage() {
+ check_assist(
+ bool_to_enum,
+ r#"
+fn main() {
+ let $0foo = true;
+
+ if foo {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+fn main() {
+ let foo = Bool::True;
+
+ if foo == Bool::True {
+ println!("foo");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn local_variable_with_usage_negated() {
+ cov_mark::check!(replaces_negation);
+ check_assist(
+ bool_to_enum,
+ r#"
+fn main() {
+ let $0foo = true;
+
+ if !foo {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+fn main() {
+ let foo = Bool::True;
+
+ if foo == Bool::False {
+ println!("foo");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn local_variable_with_type_annotation() {
+ cov_mark::check!(replaces_ty_annotation);
+ check_assist(
+ bool_to_enum,
+ r#"
+fn main() {
+ let $0foo: bool = false;
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+fn main() {
+ let foo: Bool = Bool::False;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn local_variable_with_non_literal_initializer() {
+ check_assist(
+ bool_to_enum,
+ r#"
+fn main() {
+ let $0foo = 1 == 2;
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+fn main() {
+ let foo = if 1 == 2 { Bool::True } else { Bool::False };
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn local_variable_binexpr_usage() {
+ check_assist(
+ bool_to_enum,
+ r#"
+fn main() {
+ let $0foo = false;
+ let bar = true;
+
+ if !foo && bar {
+ println!("foobar");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+fn main() {
+ let foo = Bool::False;
+ let bar = true;
+
+ if foo == Bool::False && bar {
+ println!("foobar");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn local_variable_unop_usage() {
+ check_assist(
+ bool_to_enum,
+ r#"
+fn main() {
+ let $0foo = true;
+
+ if *&foo {
+ println!("foobar");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+fn main() {
+ let foo = Bool::True;
+
+ if *&foo == Bool::True {
+ println!("foobar");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn local_variable_assigned_later() {
+ cov_mark::check!(replaces_assignment);
+ check_assist(
+ bool_to_enum,
+ r#"
+fn main() {
+ let $0foo: bool;
+ foo = true;
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+fn main() {
+ let foo: Bool;
+ foo = Bool::True;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn local_variable_does_not_apply_recursively() {
+ check_assist(
+ bool_to_enum,
+ r#"
+fn main() {
+ let $0foo = true;
+ let bar = !foo;
+
+ if bar {
+ println!("bar");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+fn main() {
+ let foo = Bool::True;
+ let bar = foo == Bool::False;
+
+ if bar {
+ println!("bar");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn local_variable_nested_in_negation() {
+ cov_mark::check!(dont_overwrite_expression_inside_negation);
+ check_assist(
+ bool_to_enum,
+ r#"
+fn main() {
+ if !"foo".chars().any(|c| {
+ let $0foo = true;
+ foo
+ }) {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+fn main() {
+ if !"foo".chars().any(|c| {
+ let foo = Bool::True;
+ foo == Bool::True
+ }) {
+ println!("foo");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn local_variable_non_bool() {
+ cov_mark::check!(not_applicable_non_bool_local);
+ check_assist_not_applicable(
+ bool_to_enum,
+ r#"
+fn main() {
+ let $0foo = 1;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn local_variable_cursor_not_on_ident() {
+ check_assist_not_applicable(
+ bool_to_enum,
+ r#"
+fn main() {
+ let foo = $0true;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn local_variable_non_ident_pat() {
+ cov_mark::check!(not_applicable_in_non_ident_pat);
+ check_assist_not_applicable(
+ bool_to_enum,
+ r#"
+fn main() {
+ let ($0foo, bar) = (true, false);
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_struct_basic() {
+ cov_mark::check!(replaces_record_expr);
+ check_assist(
+ bool_to_enum,
+ r#"
+struct Foo {
+ $0bar: bool,
+ baz: bool,
+}
+
+fn main() {
+ let foo = Foo { bar: true, baz: false };
+
+ if foo.bar {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+struct Foo {
+ bar: Bool,
+ baz: bool,
+}
+
+fn main() {
+ let foo = Foo { bar: Bool::True, baz: false };
+
+ if foo.bar == Bool::True {
+ println!("foo");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_enum_basic() {
+ cov_mark::check!(replaces_record_pat);
+ check_assist(
+ bool_to_enum,
+ r#"
+enum Foo {
+ Foo,
+ Bar { $0bar: bool },
+}
+
+fn main() {
+ let foo = Foo::Bar { bar: true };
+
+ if let Foo::Bar { bar: baz } = foo {
+ if baz {
+ println!("foo");
+ }
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+enum Foo {
+ Foo,
+ Bar { bar: Bool },
+}
+
+fn main() {
+ let foo = Foo::Bar { bar: Bool::True };
+
+ if let Foo::Bar { bar: baz } = foo {
+ if baz == Bool::True {
+ println!("foo");
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_enum_cross_file() {
+ check_assist(
+ bool_to_enum,
+ r#"
+//- /foo.rs
+pub enum Foo {
+ Foo,
+ Bar { $0bar: bool },
+}
+
+fn foo() {
+ let foo = Foo::Bar { bar: true };
+}
+
+//- /main.rs
+use foo::Foo;
+
+mod foo;
+
+fn main() {
+ let foo = Foo::Bar { bar: false };
+}
+"#,
+ r#"
+//- /foo.rs
+#[derive(PartialEq, Eq)]
+pub enum Bool { True, False }
+
+pub enum Foo {
+ Foo,
+ Bar { bar: Bool },
+}
+
+fn foo() {
+ let foo = Foo::Bar { bar: Bool::True };
+}
+
+//- /main.rs
+use foo::{Foo, Bool};
+
+mod foo;
+
+fn main() {
+ let foo = Foo::Bar { bar: Bool::False };
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_enum_shorthand() {
+ cov_mark::check!(replaces_record_pat_shorthand);
+ check_assist(
+ bool_to_enum,
+ r#"
+enum Foo {
+ Foo,
+ Bar { $0bar: bool },
+}
+
+fn main() {
+ let foo = Foo::Bar { bar: true };
+
+ match foo {
+ Foo::Bar { bar } => {
+ if bar {
+ println!("foo");
+ }
+ }
+ _ => (),
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+enum Foo {
+ Foo,
+ Bar { bar: Bool },
+}
+
+fn main() {
+ let foo = Foo::Bar { bar: Bool::True };
+
+ match foo {
+ Foo::Bar { bar } => {
+ if bar == Bool::True {
+ println!("foo");
+ }
+ }
+ _ => (),
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_enum_replaces_literal_patterns() {
+ cov_mark::check!(replaces_literal_pat);
+ check_assist(
+ bool_to_enum,
+ r#"
+enum Foo {
+ Foo,
+ Bar { $0bar: bool },
+}
+
+fn main() {
+ let foo = Foo::Bar { bar: true };
+
+ if let Foo::Bar { bar: true } = foo {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+enum Foo {
+ Foo,
+ Bar { bar: Bool },
+}
+
+fn main() {
+ let foo = Foo::Bar { bar: Bool::True };
+
+ if let Foo::Bar { bar: Bool::True } = foo {
+ println!("foo");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_enum_keeps_wildcard_patterns() {
+ check_assist(
+ bool_to_enum,
+ r#"
+enum Foo {
+ Foo,
+ Bar { $0bar: bool },
+}
+
+fn main() {
+ let foo = Foo::Bar { bar: true };
+
+ if let Foo::Bar { bar: _ } = foo {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+enum Foo {
+ Foo,
+ Bar { bar: Bool },
+}
+
+fn main() {
+ let foo = Foo::Bar { bar: Bool::True };
+
+ if let Foo::Bar { bar: _ } = foo {
+ println!("foo");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_union_basic() {
+ check_assist(
+ bool_to_enum,
+ r#"
+union Foo {
+ $0foo: bool,
+ bar: usize,
+}
+
+fn main() {
+ let foo = Foo { foo: true };
+
+ if unsafe { foo.foo } {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+union Foo {
+ foo: Bool,
+ bar: usize,
+}
+
+fn main() {
+ let foo = Foo { foo: Bool::True };
+
+ if unsafe { foo.foo == Bool::True } {
+ println!("foo");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_negated() {
+ check_assist(
+ bool_to_enum,
+ r#"
+struct Foo {
+ $0bar: bool,
+}
+
+fn main() {
+ let foo = Foo { bar: false };
+
+ if !foo.bar {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+struct Foo {
+ bar: Bool,
+}
+
+fn main() {
+ let foo = Foo { bar: Bool::False };
+
+ if foo.bar == Bool::False {
+ println!("foo");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_in_mod_properly_indented() {
+ check_assist(
+ bool_to_enum,
+ r#"
+mod foo {
+ struct Bar {
+ $0baz: bool,
+ }
+
+ impl Bar {
+ fn new(baz: bool) -> Self {
+ Self { baz }
+ }
+ }
+}
+"#,
+ r#"
+mod foo {
+ #[derive(PartialEq, Eq)]
+ enum Bool { True, False }
+
+ struct Bar {
+ baz: Bool,
+ }
+
+ impl Bar {
+ fn new(baz: bool) -> Self {
+ Self { baz: if baz { Bool::True } else { Bool::False } }
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_multiple_initializations() {
+ check_assist(
+ bool_to_enum,
+ r#"
+struct Foo {
+ $0bar: bool,
+ baz: bool,
+}
+
+fn main() {
+ let foo1 = Foo { bar: true, baz: false };
+ let foo2 = Foo { bar: false, baz: false };
+
+ if foo1.bar && foo2.bar {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+struct Foo {
+ bar: Bool,
+ baz: bool,
+}
+
+fn main() {
+ let foo1 = Foo { bar: Bool::True, baz: false };
+ let foo2 = Foo { bar: Bool::False, baz: false };
+
+ if foo1.bar == Bool::True && foo2.bar == Bool::True {
+ println!("foo");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_assigned_to_another() {
+ cov_mark::check!(dont_assign_incorrect_ref);
+ check_assist(
+ bool_to_enum,
+ r#"
+struct Foo {
+ $0foo: bool,
+}
+
+struct Bar {
+ bar: bool,
+}
+
+fn main() {
+ let foo = Foo { foo: true };
+ let mut bar = Bar { bar: true };
+
+ bar.bar = foo.foo;
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+struct Foo {
+ foo: Bool,
+}
+
+struct Bar {
+ bar: bool,
+}
+
+fn main() {
+ let foo = Foo { foo: Bool::True };
+ let mut bar = Bar { bar: true };
+
+ bar.bar = foo.foo == Bool::True;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_initialized_with_other() {
+ check_assist(
+ bool_to_enum,
+ r#"
+struct Foo {
+ $0foo: bool,
+}
+
+struct Bar {
+ bar: bool,
+}
+
+fn main() {
+ let foo = Foo { foo: true };
+ let bar = Bar { bar: foo.foo };
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+struct Foo {
+ foo: Bool,
+}
+
+struct Bar {
+ bar: bool,
+}
+
+fn main() {
+ let foo = Foo { foo: Bool::True };
+ let bar = Bar { bar: foo.foo == Bool::True };
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_method_chain_usage() {
+ check_assist(
+ bool_to_enum,
+ r#"
+struct Foo {
+ $0bool: bool,
+}
+
+fn main() {
+ let foo = Foo { bool: true };
+
+ foo.bool.then(|| 2);
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+struct Foo {
+ bool: Bool,
+}
+
+fn main() {
+ let foo = Foo { bool: Bool::True };
+
+ (foo.bool == Bool::True).then(|| 2);
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_non_bool() {
+ cov_mark::check!(not_applicable_non_bool_field);
+ check_assist_not_applicable(
+ bool_to_enum,
+ r#"
+struct Foo {
+ $0bar: usize,
+}
+
+fn main() {
+ let foo = Foo { bar: 1 };
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn const_basic() {
+ check_assist(
+ bool_to_enum,
+ r#"
+const $0FOO: bool = false;
+
+fn main() {
+ if FOO {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+const FOO: Bool = Bool::False;
+
+fn main() {
+ if FOO == Bool::True {
+ println!("foo");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn const_in_module() {
+ check_assist(
+ bool_to_enum,
+ r#"
+fn main() {
+ if foo::FOO {
+ println!("foo");
+ }
+}
+
+mod foo {
+ pub const $0FOO: bool = true;
+}
+"#,
+ r#"
+use foo::Bool;
+
+fn main() {
+ if foo::FOO == Bool::True {
+ println!("foo");
+ }
+}
+
+mod foo {
+ #[derive(PartialEq, Eq)]
+ pub enum Bool { True, False }
+
+ pub const FOO: Bool = Bool::True;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn const_in_module_with_import() {
+ check_assist(
+ bool_to_enum,
+ r#"
+fn main() {
+ use foo::FOO;
+
+ if FOO {
+ println!("foo");
+ }
+}
+
+mod foo {
+ pub const $0FOO: bool = true;
+}
+"#,
+ r#"
+use crate::foo::Bool;
+
+fn main() {
+ use foo::FOO;
+
+ if FOO == Bool::True {
+ println!("foo");
+ }
+}
+
+mod foo {
+ #[derive(PartialEq, Eq)]
+ pub enum Bool { True, False }
+
+ pub const FOO: Bool = Bool::True;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn const_cross_file() {
+ check_assist(
+ bool_to_enum,
+ r#"
+//- /main.rs
+mod foo;
+
+fn main() {
+ if foo::FOO {
+ println!("foo");
+ }
+}
+
+//- /foo.rs
+pub const $0FOO: bool = true;
+"#,
+ r#"
+//- /main.rs
+use foo::Bool;
+
+mod foo;
+
+fn main() {
+ if foo::FOO == Bool::True {
+ println!("foo");
+ }
+}
+
+//- /foo.rs
+#[derive(PartialEq, Eq)]
+pub enum Bool { True, False }
+
+pub const FOO: Bool = Bool::True;
+"#,
+ )
+ }
+
+ #[test]
+ fn const_cross_file_and_module() {
+ check_assist(
+ bool_to_enum,
+ r#"
+//- /main.rs
+mod foo;
+
+fn main() {
+ use foo::bar;
+
+ if bar::BAR {
+ println!("foo");
+ }
+}
+
+//- /foo.rs
+pub mod bar {
+ pub const $0BAR: bool = false;
+}
+"#,
+ r#"
+//- /main.rs
+use crate::foo::bar::Bool;
+
+mod foo;
+
+fn main() {
+ use foo::bar;
+
+ if bar::BAR == Bool::True {
+ println!("foo");
+ }
+}
+
+//- /foo.rs
+pub mod bar {
+ #[derive(PartialEq, Eq)]
+ pub enum Bool { True, False }
+
+ pub const BAR: Bool = Bool::False;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn const_in_impl_cross_file() {
+ check_assist(
+ bool_to_enum,
+ r#"
+//- /main.rs
+mod foo;
+
+struct Foo;
+
+impl Foo {
+ pub const $0BOOL: bool = true;
+}
+
+//- /foo.rs
+use crate::Foo;
+
+fn foo() -> bool {
+ Foo::BOOL
+}
+"#,
+ r#"
+//- /main.rs
+mod foo;
+
+struct Foo;
+
+#[derive(PartialEq, Eq)]
+pub enum Bool { True, False }
+
+impl Foo {
+ pub const BOOL: Bool = Bool::True;
+}
+
+//- /foo.rs
+use crate::{Foo, Bool};
+
+fn foo() -> bool {
+ Foo::BOOL == Bool::True
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn const_in_trait() {
+ check_assist(
+ bool_to_enum,
+ r#"
+trait Foo {
+ const $0BOOL: bool;
+}
+
+impl Foo for usize {
+ const BOOL: bool = true;
+}
+
+fn main() {
+ if <usize as Foo>::BOOL {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+trait Foo {
+ const BOOL: Bool;
+}
+
+impl Foo for usize {
+ const BOOL: Bool = Bool::True;
+}
+
+fn main() {
+ if <usize as Foo>::BOOL == Bool::True {
+ println!("foo");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn const_non_bool() {
+ cov_mark::check!(not_applicable_non_bool_const);
+ check_assist_not_applicable(
+ bool_to_enum,
+ r#"
+const $0FOO: &str = "foo";
+
+fn main() {
+ println!("{FOO}");
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn static_basic() {
+ check_assist(
+ bool_to_enum,
+ r#"
+static mut $0BOOL: bool = true;
+
+fn main() {
+ unsafe { BOOL = false };
+ if unsafe { BOOL } {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+static mut BOOL: Bool = Bool::True;
+
+fn main() {
+ unsafe { BOOL = Bool::False };
+ if unsafe { BOOL == Bool::True } {
+ println!("foo");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn static_non_bool() {
+ cov_mark::check!(not_applicable_non_bool_static);
+ check_assist_not_applicable(
+ bool_to_enum,
+ r#"
+static mut $0FOO: usize = 0;
+
+fn main() {
+ if unsafe { FOO } == 0 {
+ println!("foo");
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_to_other_names() {
+ check_assist_not_applicable(bool_to_enum, "fn $0main() {}")
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs
index 1acd5ee97..3f478ee7d 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs
@@ -25,9 +25,7 @@ pub(crate) fn convert_comment_block(acc: &mut Assists, ctx: &AssistContext<'_>)
let comment = ctx.find_token_at_offset::<ast::Comment>()?;
// Only allow comments which are alone on their line
if let Some(prev) = comment.syntax().prev_token() {
- if Whitespace::cast(prev).filter(|w| w.text().contains('\n')).is_none() {
- return None;
- }
+ Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?;
}
match comment.kind().shape {
@@ -78,7 +76,7 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
// Establish the target of our edit based on the comments we found
let target = TextRange::new(
comments[0].syntax().text_range().start(),
- comments.last().unwrap().syntax().text_range().end(),
+ comments.last()?.syntax().text_range().end(),
);
acc.add(
@@ -91,8 +89,12 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
// contents of each line comment when they're put into the block comment.
let indentation = IndentLevel::from_token(comment.syntax());
- let block_comment_body =
- comments.into_iter().map(|c| line_comment_text(indentation, c)).join("\n");
+ let block_comment_body = comments
+ .into_iter()
+ .map(|c| line_comment_text(indentation, c))
+ .collect::<Vec<String>>()
+ .into_iter()
+ .join("\n");
let block_prefix =
CommentKind { shape: CommentShape::Block, ..comment.kind() }.prefix();
@@ -160,7 +162,8 @@ pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> {
//
// But since such comments aren't idiomatic we're okay with this.
pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String {
- let contents_without_prefix = comm.text().strip_prefix(comm.prefix()).unwrap();
+ let text = comm.text();
+ let contents_without_prefix = text.strip_prefix(comm.prefix()).unwrap_or(text);
let contents = contents_without_prefix.strip_prefix(' ').unwrap_or(contents_without_prefix);
// Don't add the indentation if the line is empty
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs
index 872b52c98..d649f13d6 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs
@@ -50,7 +50,12 @@ pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext<'_>) -
_ => return None,
};
- mod_path_to_ast(&module.find_use_path(ctx.db(), src_type_def, ctx.config.prefer_no_std)?)
+ mod_path_to_ast(&module.find_use_path(
+ ctx.db(),
+ src_type_def,
+ ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
+ )?)
};
let dest_type = match &ast_trait {
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs
index 7d0e42476..73ba3f5c4 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs
@@ -51,22 +51,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<'
// Check if there is an IfLet that we can handle.
let (if_let_pat, cond_expr) = if is_pattern_cond(cond.clone()) {
let let_ = single_let(cond)?;
- match let_.pat() {
- Some(ast::Pat::TupleStructPat(pat)) if pat.fields().count() == 1 => {
- let path = pat.path()?;
- if path.qualifier().is_some() {
- return None;
- }
-
- let bound_ident = pat.fields().next()?;
- if !ast::IdentPat::can_cast(bound_ident.syntax().kind()) {
- return None;
- }
-
- (Some((path, bound_ident)), let_.expr()?)
- }
- _ => return None, // Unsupported IfLet.
- }
+ (Some(let_.pat()?), let_.expr()?)
} else {
(None, cond)
};
@@ -136,11 +121,10 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<'
};
new_expr.syntax().clone_for_update()
}
- Some((path, bound_ident)) => {
+ Some(pat) => {
// If-let.
- let pat = make::tuple_struct_pat(path, once(bound_ident));
let let_else_stmt = make::let_else_stmt(
- pat.into(),
+ pat,
None,
cond_expr,
ast::make::tail_only_block_expr(early_expression),
@@ -443,6 +427,60 @@ fn main() {
}
#[test]
+ fn convert_arbitrary_if_let_patterns() {
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ $0if let None = Some(92) {
+ foo();
+ }
+}
+"#,
+ r#"
+fn main() {
+ let None = Some(92) else { return };
+ foo();
+}
+"#,
+ );
+
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ $0if let [1, x] = [1, 92] {
+ foo(x);
+ }
+}
+"#,
+ r#"
+fn main() {
+ let [1, x] = [1, 92] else { return };
+ foo(x);
+}
+"#,
+ );
+
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ $0if let (Some(x), None) = (Some(92), None) {
+ foo(x);
+ }
+}
+"#,
+ r#"
+fn main() {
+ let (Some(x), None) = (Some(92), None) else { return };
+ foo(x);
+}
+"#,
+ );
+ }
+
+ #[test]
fn ignore_already_converted_if() {
check_assist_not_applicable(
convert_to_guarded_return,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs
new file mode 100644
index 000000000..79b46d661
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs
@@ -0,0 +1,889 @@
+use either::Either;
+use hir::ModuleDef;
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ defs::Definition,
+ helpers::mod_path_to_ast,
+ imports::insert_use::{insert_use, ImportScope},
+ search::{FileReference, UsageSearchResult},
+ source_change::SourceChangeBuilder,
+ syntax_helpers::node_ext::{for_each_tail_expr, walk_expr},
+ FxHashSet,
+};
+use syntax::{
+ ast::{self, edit::IndentLevel, edit_in_place::Indent, make, HasName},
+ match_ast, ted, AstNode, SyntaxNode,
+};
+
+use crate::assist_context::{AssistContext, Assists};
+
+// Assist: convert_tuple_return_type_to_struct
+//
+// This converts the return type of a function from a tuple type
+// into a tuple struct and updates the body accordingly.
+//
+// ```
+// fn bar() {
+// let (a, b, c) = foo();
+// }
+//
+// fn foo() -> ($0u32, u32, u32) {
+// (1, 2, 3)
+// }
+// ```
+// ->
+// ```
+// fn bar() {
+// let FooResult(a, b, c) = foo();
+// }
+//
+// struct FooResult(u32, u32, u32);
+//
+// fn foo() -> FooResult {
+// FooResult(1, 2, 3)
+// }
+// ```
+pub(crate) fn convert_tuple_return_type_to_struct(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
+ let type_ref = ret_type.ty()?;
+
+ let ast::Type::TupleType(tuple_ty) = &type_ref else { return None };
+ if tuple_ty.fields().any(|field| matches!(field, ast::Type::ImplTraitType(_))) {
+ return None;
+ }
+
+ let fn_ = ret_type.syntax().parent().and_then(ast::Fn::cast)?;
+ let fn_def = ctx.sema.to_def(&fn_)?;
+ let fn_name = fn_.name()?;
+ let target_module = ctx.sema.scope(fn_.syntax())?.module().nearest_non_block_module(ctx.db());
+
+ let target = type_ref.syntax().text_range();
+ acc.add(
+ AssistId("convert_tuple_return_type_to_struct", AssistKind::RefactorRewrite),
+ "Convert tuple return type to tuple struct",
+ target,
+ move |edit| {
+ let ret_type = edit.make_mut(ret_type);
+ let fn_ = edit.make_mut(fn_);
+
+ let usages = Definition::Function(fn_def).usages(&ctx.sema).all();
+ let struct_name = format!("{}Result", stdx::to_camel_case(&fn_name.to_string()));
+ let parent = fn_.syntax().ancestors().find_map(<Either<ast::Impl, ast::Trait>>::cast);
+ add_tuple_struct_def(
+ edit,
+ ctx,
+ &usages,
+ parent.as_ref().map(|it| it.syntax()).unwrap_or(fn_.syntax()),
+ tuple_ty,
+ &struct_name,
+ &target_module,
+ );
+
+ ted::replace(
+ ret_type.syntax(),
+ make::ret_type(make::ty(&struct_name)).syntax().clone_for_update(),
+ );
+
+ if let Some(fn_body) = fn_.body() {
+ replace_body_return_values(ast::Expr::BlockExpr(fn_body), &struct_name);
+ }
+
+ replace_usages(edit, ctx, &usages, &struct_name, &target_module);
+ },
+ )
+}
+
+/// Replaces tuple usages with the corresponding tuple struct pattern.
+fn replace_usages(
+ edit: &mut SourceChangeBuilder,
+ ctx: &AssistContext<'_>,
+ usages: &UsageSearchResult,
+ struct_name: &str,
+ target_module: &hir::Module,
+) {
+ for (file_id, references) in usages.iter() {
+ edit.edit_file(*file_id);
+
+ let refs_with_imports =
+ augment_references_with_imports(edit, ctx, references, struct_name, target_module);
+
+ refs_with_imports.into_iter().rev().for_each(|(name, import_data)| {
+ if let Some(fn_) = name.syntax().parent().and_then(ast::Fn::cast) {
+ cov_mark::hit!(replace_trait_impl_fns);
+
+ if let Some(ret_type) = fn_.ret_type() {
+ ted::replace(
+ ret_type.syntax(),
+ make::ret_type(make::ty(struct_name)).syntax().clone_for_update(),
+ );
+ }
+
+ if let Some(fn_body) = fn_.body() {
+ replace_body_return_values(ast::Expr::BlockExpr(fn_body), struct_name);
+ }
+ } else {
+ // replace tuple patterns
+ let pats = name
+ .syntax()
+ .ancestors()
+ .find(|node| {
+ ast::CallExpr::can_cast(node.kind())
+ || ast::MethodCallExpr::can_cast(node.kind())
+ })
+ .and_then(|node| node.parent())
+ .and_then(node_to_pats)
+ .unwrap_or(Vec::new());
+
+ let tuple_pats = pats.iter().filter_map(|pat| match pat {
+ ast::Pat::TuplePat(tuple_pat) => Some(tuple_pat),
+ _ => None,
+ });
+ for tuple_pat in tuple_pats {
+ ted::replace(
+ tuple_pat.syntax(),
+ make::tuple_struct_pat(
+ make::path_from_text(struct_name),
+ tuple_pat.fields(),
+ )
+ .clone_for_update()
+ .syntax(),
+ );
+ }
+ }
+ // add imports across modules where needed
+ if let Some((import_scope, path)) = import_data {
+ insert_use(&import_scope, path, &ctx.config.insert_use);
+ }
+ })
+ }
+}
+
+fn node_to_pats(node: SyntaxNode) -> Option<Vec<ast::Pat>> {
+ match_ast! {
+ match node {
+ ast::LetStmt(it) => it.pat().map(|pat| vec![pat]),
+ ast::LetExpr(it) => it.pat().map(|pat| vec![pat]),
+ ast::MatchExpr(it) => it.match_arm_list().map(|arm_list| {
+ arm_list.arms().filter_map(|arm| arm.pat()).collect()
+ }),
+ _ => None,
+ }
+ }
+}
+
+fn augment_references_with_imports(
+ edit: &mut SourceChangeBuilder,
+ ctx: &AssistContext<'_>,
+ references: &[FileReference],
+ struct_name: &str,
+ target_module: &hir::Module,
+) -> Vec<(ast::NameLike, Option<(ImportScope, ast::Path)>)> {
+ let mut visited_modules = FxHashSet::default();
+
+ references
+ .iter()
+ .filter_map(|FileReference { name, .. }| {
+ let name = name.clone().into_name_like()?;
+ ctx.sema.scope(name.syntax()).map(|scope| (name, scope.module()))
+ })
+ .map(|(name, ref_module)| {
+ let new_name = edit.make_mut(name.clone());
+
+ // if the referenced module is not the same as the target one and has not been seen before, add an import
+ let import_data = if ref_module.nearest_non_block_module(ctx.db()) != *target_module
+ && !visited_modules.contains(&ref_module)
+ {
+ visited_modules.insert(ref_module);
+
+ let import_scope =
+ ImportScope::find_insert_use_container(new_name.syntax(), &ctx.sema);
+ let path = ref_module
+ .find_use_path_prefixed(
+ ctx.sema.db,
+ ModuleDef::Module(*target_module),
+ ctx.config.insert_use.prefix_kind,
+ ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
+ )
+ .map(|mod_path| {
+ make::path_concat(
+ mod_path_to_ast(&mod_path),
+ make::path_from_text(struct_name),
+ )
+ });
+
+ import_scope.zip(path)
+ } else {
+ None
+ };
+
+ (new_name, import_data)
+ })
+ .collect()
+}
+
+// Adds the definition of the tuple struct before the parent function.
+fn add_tuple_struct_def(
+ edit: &mut SourceChangeBuilder,
+ ctx: &AssistContext<'_>,
+ usages: &UsageSearchResult,
+ parent: &SyntaxNode,
+ tuple_ty: &ast::TupleType,
+ struct_name: &str,
+ target_module: &hir::Module,
+) {
+ let make_struct_pub = usages
+ .iter()
+ .flat_map(|(_, refs)| refs)
+ .filter_map(|FileReference { name, .. }| {
+ let name = name.clone().into_name_like()?;
+ ctx.sema.scope(name.syntax()).map(|scope| scope.module())
+ })
+ .any(|module| module.nearest_non_block_module(ctx.db()) != *target_module);
+ let visibility = if make_struct_pub { Some(make::visibility_pub()) } else { None };
+
+ let field_list = ast::FieldList::TupleFieldList(make::tuple_field_list(
+ tuple_ty.fields().map(|ty| make::tuple_field(visibility.clone(), ty)),
+ ));
+ let struct_name = make::name(struct_name);
+ let struct_def = make::struct_(visibility, struct_name, None, field_list).clone_for_update();
+
+ let indent = IndentLevel::from_node(parent);
+ struct_def.reindent_to(indent);
+
+ edit.insert(parent.text_range().start(), format!("{struct_def}\n\n{indent}"));
+}
+
+/// Replaces each returned tuple in `body` with the constructor of the tuple struct named `struct_name`.
+fn replace_body_return_values(body: ast::Expr, struct_name: &str) {
+ let mut exprs_to_wrap = Vec::new();
+
+ let tail_cb = &mut |e: &_| tail_cb_impl(&mut exprs_to_wrap, e);
+ walk_expr(&body, &mut |expr| {
+ if let ast::Expr::ReturnExpr(ret_expr) = expr {
+ if let Some(ret_expr_arg) = &ret_expr.expr() {
+ for_each_tail_expr(ret_expr_arg, tail_cb);
+ }
+ }
+ });
+ for_each_tail_expr(&body, tail_cb);
+
+ for ret_expr in exprs_to_wrap {
+ if let ast::Expr::TupleExpr(tuple_expr) = &ret_expr {
+ let struct_constructor = make::expr_call(
+ make::expr_path(make::ext::ident_path(struct_name)),
+ make::arg_list(tuple_expr.fields()),
+ )
+ .clone_for_update();
+ ted::replace(ret_expr.syntax(), struct_constructor.syntax());
+ }
+ }
+}
+
+fn tail_cb_impl(acc: &mut Vec<ast::Expr>, e: &ast::Expr) {
+ match e {
+ ast::Expr::BreakExpr(break_expr) => {
+ if let Some(break_expr_arg) = break_expr.expr() {
+ for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(acc, e))
+ }
+ }
+ ast::Expr::ReturnExpr(_) => {
+ // all return expressions have already been handled by the walk loop
+ }
+ e => acc.push(e.clone()),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ #[test]
+ fn function_basic() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+fn bar() -> $0(&'static str, bool) {
+ ("bar", true)
+}
+"#,
+ r#"
+struct BarResult(&'static str, bool);
+
+fn bar() -> BarResult {
+ BarResult("bar", true)
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn struct_and_usages_indented() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+mod foo {
+ pub(crate) fn foo() {
+ let (bar, baz) = bar();
+ println!("{bar} {baz}");
+ }
+
+ pub(crate) fn bar() -> $0(usize, bool) {
+ (42, true)
+ }
+}
+"#,
+ r#"
+mod foo {
+ pub(crate) fn foo() {
+ let BarResult(bar, baz) = bar();
+ println!("{bar} {baz}");
+ }
+
+ struct BarResult(usize, bool);
+
+ pub(crate) fn bar() -> BarResult {
+ BarResult(42, true)
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn field_usage() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+fn bar() -> $0(usize, bool) {
+ (42, true)
+}
+
+fn main() {
+ let bar_result = bar();
+ println!("{} {}", bar_result.1, bar().0);
+}
+"#,
+ r#"
+struct BarResult(usize, bool);
+
+fn bar() -> BarResult {
+ BarResult(42, true)
+}
+
+fn main() {
+ let bar_result = bar();
+ println!("{} {}", bar_result.1, bar().0);
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn method_usage() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+struct Foo;
+
+impl Foo {
+ fn foo(&self, x: usize) -> $0(usize, usize) {
+ (x, x)
+ }
+}
+
+fn main() {
+ let foo = Foo {};
+ let (x, y) = foo.foo(2);
+}
+"#,
+ r#"
+struct Foo;
+
+struct FooResult(usize, usize);
+
+impl Foo {
+ fn foo(&self, x: usize) -> FooResult {
+ FooResult(x, x)
+ }
+}
+
+fn main() {
+ let foo = Foo {};
+ let FooResult(x, y) = foo.foo(2);
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn method_usage_within_same_impl() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+struct Foo;
+
+impl Foo {
+ fn new() -> $0(usize, usize) {
+ (0, 0)
+ }
+
+ fn foo() {
+ let (mut foo1, mut foo2) = Self::new();
+ }
+}
+"#,
+ r#"
+struct Foo;
+
+struct NewResult(usize, usize);
+
+impl Foo {
+ fn new() -> NewResult {
+ NewResult(0, 0)
+ }
+
+ fn foo() {
+ let NewResult(mut foo1, mut foo2) = Self::new();
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn multiple_usages() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+fn bar() -> $0(usize, usize) {
+ (42, 24)
+}
+
+fn main() {
+ let bar_result = bar();
+ let (foo, b) = bar();
+ let (b, baz) = bar();
+
+ if foo == b && b == baz {
+ println!("{} {}", bar_result.1, bar().0);
+ }
+}
+"#,
+ r#"
+struct BarResult(usize, usize);
+
+fn bar() -> BarResult {
+ BarResult(42, 24)
+}
+
+fn main() {
+ let bar_result = bar();
+ let BarResult(foo, b) = bar();
+ let BarResult(b, baz) = bar();
+
+ if foo == b && b == baz {
+ println!("{} {}", bar_result.1, bar().0);
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn usage_match_tuple_pat() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+fn bar() -> $0(usize, bool) {
+ (42, true)
+}
+
+fn main() {
+ match bar() {
+ x if x.0 == 0 => println!("0"),
+ (x, false) => println!("{x}"),
+ (42, true) => println!("bar"),
+ _ => println!("foo"),
+ }
+}
+"#,
+ r#"
+struct BarResult(usize, bool);
+
+fn bar() -> BarResult {
+ BarResult(42, true)
+}
+
+fn main() {
+ match bar() {
+ x if x.0 == 0 => println!("0"),
+ BarResult(x, false) => println!("{x}"),
+ BarResult(42, true) => println!("bar"),
+ _ => println!("foo"),
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn usage_if_let_tuple_pat() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+fn bar() -> $0(usize, bool) {
+ (42, true)
+}
+
+fn main() {
+ if let (42, true) = bar() {
+ println!("bar")
+ }
+}
+"#,
+ r#"
+struct BarResult(usize, bool);
+
+fn bar() -> BarResult {
+ BarResult(42, true)
+}
+
+fn main() {
+ if let BarResult(42, true) = bar() {
+ println!("bar")
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn function_nested_outer() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+fn bar() -> $0(usize, bool) {
+ fn foo() -> (usize, bool) {
+ (42, true)
+ }
+
+ foo()
+}
+"#,
+ r#"
+struct BarResult(usize, bool);
+
+fn bar() -> BarResult {
+ fn foo() -> (usize, bool) {
+ (42, true)
+ }
+
+ foo()
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn function_nested_inner() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+fn bar() -> (usize, bool) {
+ fn foo() -> $0(usize, bool) {
+ (42, true)
+ }
+
+ foo()
+}
+"#,
+ r#"
+fn bar() -> (usize, bool) {
+ struct FooResult(usize, bool);
+
+ fn foo() -> FooResult {
+ FooResult(42, true)
+ }
+
+ foo()
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn trait_impl_and_usage() {
+ cov_mark::check!(replace_trait_impl_fns);
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+struct Struct;
+
+trait Foo {
+ fn foo(&self) -> $0(usize, bool);
+}
+
+impl Foo for Struct {
+ fn foo(&self) -> (usize, bool) {
+ (0, true)
+ }
+}
+
+fn main() {
+ let s = Struct {};
+ let (foo, bar) = s.foo();
+ let (foo, bar) = Struct::foo(&s);
+ println!("{foo} {bar}");
+}
+"#,
+ r#"
+struct Struct;
+
+struct FooResult(usize, bool);
+
+trait Foo {
+ fn foo(&self) -> FooResult;
+}
+
+impl Foo for Struct {
+ fn foo(&self) -> FooResult {
+ FooResult(0, true)
+ }
+}
+
+fn main() {
+ let s = Struct {};
+ let FooResult(foo, bar) = s.foo();
+ let FooResult(foo, bar) = Struct::foo(&s);
+ println!("{foo} {bar}");
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn body_wraps_nested() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+fn foo() -> $0(u8, usize, u32) {
+ if true {
+ match 3 {
+ 0 => (1, 2, 3),
+ _ => return (4, 5, 6),
+ }
+ } else {
+ (2, 1, 3)
+ }
+}
+"#,
+ r#"
+struct FooResult(u8, usize, u32);
+
+fn foo() -> FooResult {
+ if true {
+ match 3 {
+ 0 => FooResult(1, 2, 3),
+ _ => return FooResult(4, 5, 6),
+ }
+ } else {
+ FooResult(2, 1, 3)
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn body_wraps_break_and_return() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+fn foo(mut i: isize) -> (usize, $0u32, u8) {
+ if i < 0 {
+ return (0, 0, 0);
+ }
+
+ loop {
+ if i == 2 {
+ println!("foo");
+ break (1, 2, 3);
+ }
+ i += 1;
+ }
+}
+"#,
+ r#"
+struct FooResult(usize, u32, u8);
+
+fn foo(mut i: isize) -> FooResult {
+ if i < 0 {
+ return FooResult(0, 0, 0);
+ }
+
+ loop {
+ if i == 2 {
+ println!("foo");
+ break FooResult(1, 2, 3);
+ }
+ i += 1;
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn body_doesnt_wrap_identifier() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+fn foo() -> $0(u8, usize, u32) {
+ let tuple = (1, 2, 3);
+ tuple
+}
+"#,
+ r#"
+struct FooResult(u8, usize, u32);
+
+fn foo() -> FooResult {
+ let tuple = (1, 2, 3);
+ tuple
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn body_doesnt_wrap_other_exprs() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+fn bar(num: usize) -> (u8, usize, u32) {
+ (1, num, 3)
+}
+
+fn foo() -> $0(u8, usize, u32) {
+ bar(2)
+}
+"#,
+ r#"
+fn bar(num: usize) -> (u8, usize, u32) {
+ (1, num, 3)
+}
+
+struct FooResult(u8, usize, u32);
+
+fn foo() -> FooResult {
+ bar(2)
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn cross_file_and_module() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+//- /main.rs
+mod foo;
+
+fn main() {
+ use foo::bar;
+
+ let (bar, baz) = bar::bar();
+ println!("{}", bar == baz);
+}
+
+//- /foo.rs
+pub mod bar {
+ pub fn bar() -> $0(usize, usize) {
+ (1, 3)
+ }
+}
+"#,
+ r#"
+//- /main.rs
+use crate::foo::bar::BarResult;
+
+mod foo;
+
+fn main() {
+ use foo::bar;
+
+ let BarResult(bar, baz) = bar::bar();
+ println!("{}", bar == baz);
+}
+
+//- /foo.rs
+pub mod bar {
+ pub struct BarResult(pub usize, pub usize);
+
+ pub fn bar() -> BarResult {
+ BarResult(1, 3)
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn does_not_replace_nested_usage() {
+ check_assist(
+ convert_tuple_return_type_to_struct,
+ r#"
+fn bar() -> $0(usize, bool) {
+ (42, true)
+}
+
+fn main() {
+ let ((bar1, bar2), foo) = (bar(), 3);
+ println!("{bar1} {bar2} {foo}");
+}
+"#,
+ r#"
+struct BarResult(usize, bool);
+
+fn bar() -> BarResult {
+ BarResult(42, true)
+}
+
+fn main() {
+ let ((bar1, bar2), foo) = (bar(), 3);
+ println!("{bar1} {bar2} {foo}");
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn function_with_non_tuple_return_type() {
+ check_assist_not_applicable(
+ convert_tuple_return_type_to_struct,
+ r#"
+fn bar() -> $0usize {
+ 0
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn function_with_impl_type() {
+ check_assist_not_applicable(
+ convert_tuple_return_type_to_struct,
+ r#"
+fn bar() -> $0(impl Clone, usize) {
+ ("bar", 0)
+}
+"#,
+ )
+ }
+}
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 017853a4a..435d7c4a5 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
@@ -216,7 +216,7 @@ fn edit_field_references(
edit.edit_file(file_id);
for r in refs {
if let Some(name_ref) = r.name.as_name_ref() {
- edit.replace(name_ref.syntax().text_range(), name.text());
+ edit.replace(ctx.sema.original_range(name_ref.syntax()).range, name.text());
}
}
}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
index f30ca2552..65b497e83 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
@@ -3,10 +3,12 @@ use ide_db::{
defs::Definition,
search::{FileReference, SearchScope, UsageSearchResult},
};
+use itertools::Itertools;
use syntax::{
- ast::{self, AstNode, FieldExpr, HasName, IdentPat, MethodCallExpr},
- TextRange,
+ ast::{self, make, AstNode, FieldExpr, HasName, IdentPat, MethodCallExpr},
+ ted, T,
};
+use text_edit::TextRange;
use crate::assist_context::{AssistContext, Assists, SourceChangeBuilder};
@@ -61,27 +63,36 @@ pub(crate) fn destructure_tuple_binding_impl(
acc.add(
AssistId("destructure_tuple_binding_in_sub_pattern", AssistKind::RefactorRewrite),
"Destructure tuple in sub-pattern",
- data.range,
- |builder| {
- edit_tuple_assignment(ctx, builder, &data, true);
- edit_tuple_usages(&data, builder, ctx, true);
- },
+ data.ident_pat.syntax().text_range(),
+ |edit| destructure_tuple_edit_impl(ctx, edit, &data, true),
);
}
acc.add(
AssistId("destructure_tuple_binding", AssistKind::RefactorRewrite),
if with_sub_pattern { "Destructure tuple in place" } else { "Destructure tuple" },
- data.range,
- |builder| {
- edit_tuple_assignment(ctx, builder, &data, false);
- edit_tuple_usages(&data, builder, ctx, false);
- },
+ data.ident_pat.syntax().text_range(),
+ |edit| destructure_tuple_edit_impl(ctx, edit, &data, false),
);
Some(())
}
+fn destructure_tuple_edit_impl(
+ ctx: &AssistContext<'_>,
+ edit: &mut SourceChangeBuilder,
+ data: &TupleData,
+ in_sub_pattern: bool,
+) {
+ let assignment_edit = edit_tuple_assignment(ctx, edit, &data, in_sub_pattern);
+ let current_file_usages_edit = edit_tuple_usages(&data, edit, ctx, in_sub_pattern);
+
+ assignment_edit.apply();
+ if let Some(usages_edit) = current_file_usages_edit {
+ usages_edit.into_iter().for_each(|usage_edit| usage_edit.apply(edit))
+ }
+}
+
fn collect_data(ident_pat: IdentPat, ctx: &AssistContext<'_>) -> Option<TupleData> {
if ident_pat.at_token().is_some() {
// Cannot destructure pattern with sub-pattern:
@@ -109,7 +120,6 @@ fn collect_data(ident_pat: IdentPat, ctx: &AssistContext<'_>) -> Option<TupleDat
}
let name = ident_pat.name()?.to_string();
- let range = ident_pat.syntax().text_range();
let usages = ctx.sema.to_def(&ident_pat).map(|def| {
Definition::Local(def)
@@ -122,7 +132,7 @@ fn collect_data(ident_pat: IdentPat, ctx: &AssistContext<'_>) -> Option<TupleDat
.map(|i| generate_name(ctx, i, &name, &ident_pat, &usages))
.collect::<Vec<_>>();
- Some(TupleData { ident_pat, range, ref_type, field_names, usages })
+ Some(TupleData { ident_pat, ref_type, field_names, usages })
}
fn generate_name(
@@ -142,72 +152,100 @@ enum RefType {
}
struct TupleData {
ident_pat: IdentPat,
- // name: String,
- range: TextRange,
ref_type: Option<RefType>,
field_names: Vec<String>,
- // field_types: Vec<Type>,
usages: Option<UsageSearchResult>,
}
fn edit_tuple_assignment(
ctx: &AssistContext<'_>,
- builder: &mut SourceChangeBuilder,
+ edit: &mut SourceChangeBuilder,
data: &TupleData,
in_sub_pattern: bool,
-) {
+) -> AssignmentEdit {
+ let ident_pat = edit.make_mut(data.ident_pat.clone());
+
let tuple_pat = {
let original = &data.ident_pat;
let is_ref = original.ref_token().is_some();
let is_mut = original.mut_token().is_some();
- let fields = data.field_names.iter().map(|name| {
- ast::Pat::from(ast::make::ident_pat(is_ref, is_mut, ast::make::name(name)))
- });
- ast::make::tuple_pat(fields)
+ let fields = data
+ .field_names
+ .iter()
+ .map(|name| ast::Pat::from(make::ident_pat(is_ref, is_mut, make::name(name))));
+ make::tuple_pat(fields).clone_for_update()
};
- let add_cursor = |text: &str| {
- // place cursor on first tuple item
- let first_tuple = &data.field_names[0];
- text.replacen(first_tuple, &format!("$0{first_tuple}"), 1)
- };
+ if let Some(cap) = ctx.config.snippet_cap {
+ // place cursor on first tuple name
+ if let Some(ast::Pat::IdentPat(first_pat)) = tuple_pat.fields().next() {
+ edit.add_tabstop_before(
+ cap,
+ first_pat.name().expect("first ident pattern should have a name"),
+ )
+ }
+ }
- // with sub_pattern: keep original tuple and add subpattern: `tup @ (_0, _1)`
- if in_sub_pattern {
- let text = format!(" @ {tuple_pat}");
- match ctx.config.snippet_cap {
- Some(cap) => {
- let snip = add_cursor(&text);
- builder.insert_snippet(cap, data.range.end(), snip);
- }
- None => builder.insert(data.range.end(), text),
- };
- } else {
- let text = tuple_pat.to_string();
- match ctx.config.snippet_cap {
- Some(cap) => {
- let snip = add_cursor(&text);
- builder.replace_snippet(cap, data.range, snip);
- }
- None => builder.replace(data.range, text),
- };
+ AssignmentEdit { ident_pat, tuple_pat, in_sub_pattern }
+}
+struct AssignmentEdit {
+ ident_pat: ast::IdentPat,
+ tuple_pat: ast::TuplePat,
+ in_sub_pattern: bool,
+}
+
+impl AssignmentEdit {
+ fn apply(self) {
+ // with sub_pattern: keep original tuple and add subpattern: `tup @ (_0, _1)`
+ if self.in_sub_pattern {
+ self.ident_pat.set_pat(Some(self.tuple_pat.into()))
+ } else {
+ ted::replace(self.ident_pat.syntax(), self.tuple_pat.syntax())
+ }
}
}
fn edit_tuple_usages(
data: &TupleData,
- builder: &mut SourceChangeBuilder,
+ edit: &mut SourceChangeBuilder,
ctx: &AssistContext<'_>,
in_sub_pattern: bool,
-) {
+) -> Option<Vec<EditTupleUsage>> {
+ let mut current_file_usages = None;
+
if let Some(usages) = data.usages.as_ref() {
- for (file_id, refs) in usages.iter() {
- builder.edit_file(*file_id);
+ // We need to collect edits first before actually applying them
+ // as mapping nodes to their mutable node versions requires an
+ // unmodified syntax tree.
+ //
+ // We also defer editing usages in the current file first since
+ // tree mutation in the same file breaks when `builder.edit_file`
+ // is called
+
+ if let Some((_, refs)) = usages.iter().find(|(file_id, _)| **file_id == ctx.file_id()) {
+ current_file_usages = Some(
+ refs.iter()
+ .filter_map(|r| edit_tuple_usage(ctx, edit, r, data, in_sub_pattern))
+ .collect_vec(),
+ );
+ }
- for r in refs {
- edit_tuple_usage(ctx, builder, r, data, in_sub_pattern);
+ for (file_id, refs) in usages.iter() {
+ if *file_id == ctx.file_id() {
+ continue;
}
+
+ edit.edit_file(*file_id);
+
+ let tuple_edits = refs
+ .iter()
+ .filter_map(|r| edit_tuple_usage(ctx, edit, r, data, in_sub_pattern))
+ .collect_vec();
+
+ tuple_edits.into_iter().for_each(|tuple_edit| tuple_edit.apply(edit))
}
}
+
+ current_file_usages
}
fn edit_tuple_usage(
ctx: &AssistContext<'_>,
@@ -215,25 +253,14 @@ fn edit_tuple_usage(
usage: &FileReference,
data: &TupleData,
in_sub_pattern: bool,
-) {
+) -> Option<EditTupleUsage> {
match detect_tuple_index(usage, data) {
- Some(index) => edit_tuple_field_usage(ctx, builder, data, index),
- None => {
- if in_sub_pattern {
- cov_mark::hit!(destructure_tuple_call_with_subpattern);
- return;
- }
-
- // no index access -> make invalid -> requires handling by user
- // -> put usage in block comment
- //
- // Note: For macro invocations this might result in still valid code:
- // When a macro accepts the tuple as argument, as well as no arguments at all,
- // uncommenting the tuple still leaves the macro call working (see `tests::in_macro_call::empty_macro`).
- // But this is an unlikely case. Usually the resulting macro call will become erroneous.
- builder.insert(usage.range.start(), "/*");
- builder.insert(usage.range.end(), "*/");
+ Some(index) => Some(edit_tuple_field_usage(ctx, builder, data, index)),
+ None if in_sub_pattern => {
+ cov_mark::hit!(destructure_tuple_call_with_subpattern);
+ return None;
}
+ None => Some(EditTupleUsage::NoIndex(usage.range)),
}
}
@@ -242,19 +269,47 @@ fn edit_tuple_field_usage(
builder: &mut SourceChangeBuilder,
data: &TupleData,
index: TupleIndex,
-) {
+) -> EditTupleUsage {
let field_name = &data.field_names[index.index];
+ let field_name = make::expr_path(make::ext::ident_path(field_name));
if data.ref_type.is_some() {
- let ref_data = handle_ref_field_usage(ctx, &index.field_expr);
- builder.replace(ref_data.range, ref_data.format(field_name));
+ let (replace_expr, ref_data) = handle_ref_field_usage(ctx, &index.field_expr);
+ let replace_expr = builder.make_mut(replace_expr);
+ EditTupleUsage::ReplaceExpr(replace_expr, ref_data.wrap_expr(field_name))
} else {
- builder.replace(index.range, field_name);
+ let field_expr = builder.make_mut(index.field_expr);
+ EditTupleUsage::ReplaceExpr(field_expr.into(), field_name)
+ }
+}
+enum EditTupleUsage {
+ /// no index access -> make invalid -> requires handling by user
+ /// -> put usage in block comment
+ ///
+ /// Note: For macro invocations this might result in still valid code:
+ /// When a macro accepts the tuple as argument, as well as no arguments at all,
+ /// uncommenting the tuple still leaves the macro call working (see `tests::in_macro_call::empty_macro`).
+ /// But this is an unlikely case. Usually the resulting macro call will become erroneous.
+ NoIndex(TextRange),
+ ReplaceExpr(ast::Expr, ast::Expr),
+}
+
+impl EditTupleUsage {
+ fn apply(self, edit: &mut SourceChangeBuilder) {
+ match self {
+ EditTupleUsage::NoIndex(range) => {
+ edit.insert(range.start(), "/*");
+ edit.insert(range.end(), "*/");
+ }
+ EditTupleUsage::ReplaceExpr(target_expr, replace_with) => {
+ ted::replace(target_expr.syntax(), replace_with.clone_for_update().syntax())
+ }
+ }
}
}
+
struct TupleIndex {
index: usize,
- range: TextRange,
field_expr: FieldExpr,
}
fn detect_tuple_index(usage: &FileReference, data: &TupleData) -> Option<TupleIndex> {
@@ -296,7 +351,7 @@ fn detect_tuple_index(usage: &FileReference, data: &TupleData) -> Option<TupleIn
return None;
}
- Some(TupleIndex { index: idx, range: field_expr.syntax().text_range(), field_expr })
+ Some(TupleIndex { index: idx, field_expr })
} else {
// tuple index out of range
None
@@ -307,32 +362,34 @@ fn detect_tuple_index(usage: &FileReference, data: &TupleData) -> Option<TupleIn
}
struct RefData {
- range: TextRange,
needs_deref: bool,
needs_parentheses: bool,
}
impl RefData {
- fn format(&self, field_name: &str) -> String {
- match (self.needs_deref, self.needs_parentheses) {
- (true, true) => format!("(*{field_name})"),
- (true, false) => format!("*{field_name}"),
- (false, true) => format!("({field_name})"),
- (false, false) => field_name.to_string(),
+ fn wrap_expr(&self, mut expr: ast::Expr) -> ast::Expr {
+ if self.needs_deref {
+ expr = make::expr_prefix(T![*], expr);
}
+
+ if self.needs_parentheses {
+ expr = make::expr_paren(expr);
+ }
+
+ return expr;
}
}
-fn handle_ref_field_usage(ctx: &AssistContext<'_>, field_expr: &FieldExpr) -> RefData {
+fn handle_ref_field_usage(ctx: &AssistContext<'_>, field_expr: &FieldExpr) -> (ast::Expr, RefData) {
let s = field_expr.syntax();
- let mut ref_data =
- RefData { range: s.text_range(), needs_deref: true, needs_parentheses: true };
+ let mut ref_data = RefData { needs_deref: true, needs_parentheses: true };
+ let mut target_node = field_expr.clone().into();
let parent = match s.parent().map(ast::Expr::cast) {
Some(Some(parent)) => parent,
Some(None) => {
ref_data.needs_parentheses = false;
- return ref_data;
+ return (target_node, ref_data);
}
- None => return ref_data,
+ None => return (target_node, ref_data),
};
match parent {
@@ -342,7 +399,7 @@ fn handle_ref_field_usage(ctx: &AssistContext<'_>, field_expr: &FieldExpr) -> Re
// there might be a ref outside: `&(t.0)` -> can be removed
if let Some(it) = it.syntax().parent().and_then(ast::RefExpr::cast) {
ref_data.needs_deref = false;
- ref_data.range = it.syntax().text_range();
+ target_node = it.into();
}
}
ast::Expr::RefExpr(it) => {
@@ -351,8 +408,8 @@ fn handle_ref_field_usage(ctx: &AssistContext<'_>, field_expr: &FieldExpr) -> Re
ref_data.needs_parentheses = false;
// might be surrounded by parens -> can be removed too
match it.syntax().parent().and_then(ast::ParenExpr::cast) {
- Some(parent) => ref_data.range = parent.syntax().text_range(),
- None => ref_data.range = it.syntax().text_range(),
+ Some(parent) => target_node = parent.into(),
+ None => target_node = it.into(),
};
}
// higher precedence than deref `*`
@@ -414,7 +471,7 @@ fn handle_ref_field_usage(ctx: &AssistContext<'_>, field_expr: &FieldExpr) -> Re
}
};
- ref_data
+ (target_node, ref_data)
}
#[cfg(test)]
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs
index ddc8a50ed..c859e9852 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs
@@ -33,9 +33,7 @@ pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) ->
// Only allow comments which are alone on their line
if let Some(prev) = comment.syntax().prev_token() {
- if Whitespace::cast(prev).filter(|w| w.text().contains('\n')).is_none() {
- return None;
- }
+ Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?;
}
let indentation = IndentLevel::from_token(comment.syntax()).to_string();
@@ -50,7 +48,7 @@ pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) ->
(
TextRange::new(
comments[0].syntax().text_range().start(),
- comments.last().unwrap().syntax().text_range().end(),
+ comments.last()?.syntax().text_range().end(),
),
Either::Right(comments),
)
@@ -71,9 +69,11 @@ pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) ->
.map(|l| l.strip_prefix(&indentation).unwrap_or(l))
.join("\n")
}
- Either::Right(comments) => {
- comments.into_iter().map(|c| line_comment_text(IndentLevel(0), c)).join("\n")
- }
+ Either::Right(comments) => comments
+ .into_iter()
+ .map(|cm| line_comment_text(IndentLevel(0), cm))
+ .collect::<Vec<_>>()
+ .join("\n"),
};
let hashes = "#".repeat(required_hashes(&text));
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
index 31a1ff496..9d72d3af0 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
@@ -1,4 +1,5 @@
use crate::{AssistContext, Assists};
+use hir::DescendPreference;
use ide_db::{
assists::{AssistId, AssistKind},
syntax_helpers::{
@@ -35,7 +36,8 @@ pub(crate) fn extract_expressions_from_format_string(
let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?;
let expanded_t = ast::String::cast(
- ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone(), 0.into()),
+ ctx.sema
+ .descend_into_macros_single(DescendPreference::SameKind, fmt_string.syntax().clone()),
)?;
if !is_format_string(&expanded_t) {
return None;
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 de591cfde..347a3e9ba 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
@@ -3,8 +3,8 @@ use std::iter;
use ast::make;
use either::Either;
use hir::{
- HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef, PathResolution, Semantics,
- TypeInfo, TypeParam,
+ DescendPreference, HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef,
+ PathResolution, Semantics, TypeInfo, TypeParam,
};
use ide_db::{
defs::{Definition, NameRefClass},
@@ -147,7 +147,12 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
_ => format_function(ctx, module, &fun, old_indent, new_indent),
};
- if fn_def.contains("ControlFlow") {
+ // There are external control flows
+ if fun
+ .control_flow
+ .kind
+ .is_some_and(|kind| matches!(kind, FlowKind::Break(_, _) | FlowKind::Continue(_)))
+ {
let scope = match scope {
ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
@@ -163,6 +168,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
ModuleDef::from(control_flow_enum),
ctx.config.insert_use.prefix_kind,
ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
);
if let Some(mod_path) = mod_path {
@@ -750,7 +756,7 @@ impl FunctionBody {
.descendants_with_tokens()
.filter_map(SyntaxElement::into_token)
.filter(|it| matches!(it.kind(), SyntaxKind::IDENT | T![self]))
- .flat_map(|t| sema.descend_into_macros(t, 0.into()))
+ .flat_map(|t| sema.descend_into_macros(DescendPreference::None, t))
.for_each(|t| add_name_if_local(t.parent().and_then(ast::NameRef::cast)));
}
}
@@ -4970,6 +4976,27 @@ fn $0fun_name(arg: &mut Foo) {
"#,
);
}
+ #[test]
+ fn does_not_import_control_flow() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: try
+fn func() {
+ $0let cf = "I'm ControlFlow";$0
+}
+"#,
+ r#"
+fn func() {
+ fun_name();
+}
+
+fn $0fun_name() {
+ let cf = "I'm ControlFlow";
+}
+"#,
+ );
+ }
#[test]
fn extract_function_copies_comment_at_start() {
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 6839c5820..4b9fedc7e 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
@@ -3,7 +3,7 @@ use std::{
iter,
};
-use hir::{HasSource, ModuleSource};
+use hir::{HasSource, HirFileIdExt, ModuleSource};
use ide_db::{
assists::{AssistId, AssistKind},
base_db::FileId,
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 e4f64ccc7..37db27a8f 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
@@ -384,6 +384,7 @@ fn process_references(
*enum_module_def,
ctx.config.insert_use.prefix_kind,
ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
);
if let Some(mut mod_path) = mod_path {
mod_path.pop_segment();
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs
index 014c23197..e7c884dcb 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs
@@ -29,22 +29,31 @@ use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists};
// }
// ```
pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
- if ctx.has_empty_selection() {
- return None;
- }
-
- let node = match ctx.covering_element() {
- NodeOrToken::Node(it) => it,
- NodeOrToken::Token(it) if it.kind() == COMMENT => {
- cov_mark::hit!(extract_var_in_comment_is_not_applicable);
+ let node = if ctx.has_empty_selection() {
+ if let Some(expr_stmt) = ctx.find_node_at_offset::<ast::ExprStmt>() {
+ expr_stmt.syntax().clone()
+ } else if let Some(expr) = ctx.find_node_at_offset::<ast::Expr>() {
+ expr.syntax().ancestors().find_map(valid_target_expr)?.syntax().clone()
+ } else {
return None;
}
- NodeOrToken::Token(it) => it.parent()?,
+ } else {
+ match ctx.covering_element() {
+ NodeOrToken::Node(it) => it,
+ NodeOrToken::Token(it) if it.kind() == COMMENT => {
+ cov_mark::hit!(extract_var_in_comment_is_not_applicable);
+ return None;
+ }
+ NodeOrToken::Token(it) => it.parent()?,
+ }
};
+
let node = node.ancestors().take_while(|anc| anc.text_range() == node.text_range()).last()?;
+ let range = node.text_range();
+
let to_extract = node
.descendants()
- .take_while(|it| ctx.selection_trimmed().contains_range(it.text_range()))
+ .take_while(|it| range.contains_range(it.text_range()))
.find_map(valid_target_expr)?;
let ty = ctx.sema.type_of_expr(&to_extract).map(TypeInfo::adjusted);
@@ -236,6 +245,138 @@ mod tests {
use super::*;
#[test]
+ fn test_extract_var_simple_without_select() {
+ check_assist(
+ extract_variable,
+ r#"
+fn main() -> i32 {
+ if true {
+ 1
+ } else {
+ 2
+ }$0
+}
+"#,
+ r#"
+fn main() -> i32 {
+ let $0var_name = if true {
+ 1
+ } else {
+ 2
+ };
+ var_name
+}
+"#,
+ );
+
+ check_assist(
+ extract_variable,
+ r#"
+fn foo() -> i32 { 1 }
+fn main() {
+ foo();$0
+}
+"#,
+ r#"
+fn foo() -> i32 { 1 }
+fn main() {
+ let $0foo = foo();
+}
+"#,
+ );
+
+ check_assist(
+ extract_variable,
+ r#"
+fn main() {
+ let a = Some(2);
+ a.is_some();$0
+}
+"#,
+ r#"
+fn main() {
+ let a = Some(2);
+ let $0is_some = a.is_some();
+}
+"#,
+ );
+
+ check_assist(
+ extract_variable,
+ r#"
+fn main() {
+ "hello"$0;
+}
+"#,
+ r#"
+fn main() {
+ let $0var_name = "hello";
+}
+"#,
+ );
+
+ check_assist(
+ extract_variable,
+ r#"
+fn main() {
+ 1 + 2$0;
+}
+"#,
+ r#"
+fn main() {
+ let $0var_name = 1 + 2;
+}
+"#,
+ );
+
+ check_assist(
+ extract_variable,
+ r#"
+fn main() {
+ match () {
+ () if true => 1,
+ _ => 2,
+ };$0
+}
+"#,
+ r#"
+fn main() {
+ let $0var_name = match () {
+ () if true => 1,
+ _ => 2,
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_var_unit_expr_without_select_not_applicable() {
+ check_assist_not_applicable(
+ extract_variable,
+ r#"
+fn foo() {}
+fn main() {
+ foo()$0;
+}
+"#,
+ );
+
+ check_assist_not_applicable(
+ extract_variable,
+ r#"
+fn foo() {
+ let mut i = 3;
+ if i >= 0 {
+ i += 1;
+ } else {
+ i -= 1;
+ }$0
+}"#,
+ );
+ }
+
+ #[test]
fn test_extract_var_simple() {
check_assist(
extract_variable,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs
index c9f272474..204e796fa 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs
@@ -1,4 +1,6 @@
-use hir::{db::HirDatabase, HasSource, HasVisibility, ModuleDef, PathResolution, ScopeDef};
+use hir::{
+ db::HirDatabase, HasSource, HasVisibility, HirFileIdExt, ModuleDef, PathResolution, ScopeDef,
+};
use ide_db::base_db::FileId;
use syntax::{
ast::{self, edit_in_place::HasVisibilityEdit, make, HasVisibility as _},
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_binexpr.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_binexpr.rs
index 2ea6f58fa..8b46a23f9 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_binexpr.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_binexpr.rs
@@ -19,8 +19,19 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
// ```
pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let expr = ctx.find_node_at_offset::<BinExpr>()?;
- let lhs = expr.lhs()?.syntax().clone();
let rhs = expr.rhs()?.syntax().clone();
+ let lhs = expr.lhs()?.syntax().clone();
+
+ let lhs = if let Some(bin_expr) = BinExpr::cast(lhs.clone()) {
+ if bin_expr.op_kind() == expr.op_kind() {
+ bin_expr.rhs()?.syntax().clone()
+ } else {
+ lhs
+ }
+ } else {
+ lhs
+ };
+
let op_range = expr.op_token()?.text_range();
// The assist should be applied only if the cursor is on the operator
let cursor_in_range = op_range.contains_range(ctx.selection_trimmed());
@@ -115,6 +126,24 @@ mod tests {
}
#[test]
+ fn flip_binexpr_works_for_lhs_arith() {
+ check_assist(
+ flip_binexpr,
+ r"fn f() { let res = 1 + (2 - 3) +$0 4 + 5; }",
+ r"fn f() { let res = 1 + 4 + (2 - 3) + 5; }",
+ )
+ }
+
+ #[test]
+ fn flip_binexpr_works_for_lhs_cmp() {
+ check_assist(
+ flip_binexpr,
+ r"fn f() { let res = 1 + (2 - 3) >$0 4 + 5; }",
+ r"fn f() { let res = 4 + 5 < 1 + (2 - 3); }",
+ )
+ }
+
+ #[test]
fn flip_binexpr_works_inside_match() {
check_assist(
flip_binexpr,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_constant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_constant.rs
index eccd7675f..a4e8e7388 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_constant.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_constant.rs
@@ -1,5 +1,5 @@
use crate::assist_context::{AssistContext, Assists};
-use hir::{HasVisibility, HirDisplay, Module};
+use hir::{HasVisibility, HirDisplay, HirFileIdExt, Module};
use ide_db::{
assists::{AssistId, AssistKind},
base_db::{FileId, Upcast},
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 bbac0a26e..db1e0ceae 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
@@ -1,6 +1,7 @@
use std::collections::HashSet;
-use hir::{self, HasCrate, HasSource, HasVisibility};
+use hir::{self, HasCrate, HasVisibility};
+use ide_db::path_transform::PathTransform;
use syntax::{
ast::{
self, edit_in_place::Indent, make, AstNode, HasGenericParams, HasName, HasVisibility as _,
@@ -105,7 +106,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
target,
|edit| {
// Create the function
- let method_source = match method.source(ctx.db()) {
+ let method_source = match ctx.sema.source(method) {
Some(source) => source.value,
None => return,
};
@@ -130,7 +131,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
vis,
fn_name,
type_params,
- None,
+ method_source.where_clause(),
params,
body,
ret_type,
@@ -183,6 +184,12 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
let assoc_items = impl_def.get_or_create_assoc_item_list();
assoc_items.add_item(f.clone().into());
+ if let Some((target, source)) =
+ ctx.sema.scope(strukt.syntax()).zip(ctx.sema.scope(method_source.syntax()))
+ {
+ PathTransform::generic_transformation(&target, &source).apply(f.syntax());
+ }
+
if let Some(cap) = ctx.config.snippet_cap {
edit.add_tabstop_before(cap, f)
}
@@ -455,6 +462,209 @@ impl Person {
}
#[test]
+ fn test_preserve_where_clause() {
+ check_assist(
+ generate_delegate_methods,
+ r#"
+struct Inner<T>(T);
+impl<T> Inner<T> {
+ fn get(&self) -> T
+ where
+ T: Copy,
+ T: PartialEq,
+ {
+ self.0
+ }
+}
+
+struct Struct<T> {
+ $0field: Inner<T>,
+}
+"#,
+ r#"
+struct Inner<T>(T);
+impl<T> Inner<T> {
+ fn get(&self) -> T
+ where
+ T: Copy,
+ T: PartialEq,
+ {
+ self.0
+ }
+}
+
+struct Struct<T> {
+ field: Inner<T>,
+}
+
+impl<T> Struct<T> {
+ $0fn get(&self) -> T where
+ T: Copy,
+ T: PartialEq, {
+ self.field.get()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fixes_basic_self_references() {
+ check_assist(
+ generate_delegate_methods,
+ r#"
+struct Foo {
+ field: $0Bar,
+}
+
+struct Bar;
+
+impl Bar {
+ fn bar(&self, other: Self) -> Self {
+ other
+ }
+}
+"#,
+ r#"
+struct Foo {
+ field: Bar,
+}
+
+impl Foo {
+ $0fn bar(&self, other: Bar) -> Bar {
+ self.field.bar(other)
+ }
+}
+
+struct Bar;
+
+impl Bar {
+ fn bar(&self, other: Self) -> Self {
+ other
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fixes_nested_self_references() {
+ check_assist(
+ generate_delegate_methods,
+ r#"
+struct Foo {
+ field: $0Bar,
+}
+
+struct Bar;
+
+impl Bar {
+ fn bar(&mut self, a: (Self, [Self; 4]), b: Vec<Self>) {}
+}
+"#,
+ r#"
+struct Foo {
+ field: Bar,
+}
+
+impl Foo {
+ $0fn bar(&mut self, a: (Bar, [Bar; 4]), b: Vec<Bar>) {
+ self.field.bar(a, b)
+ }
+}
+
+struct Bar;
+
+impl Bar {
+ fn bar(&mut self, a: (Self, [Self; 4]), b: Vec<Self>) {}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fixes_self_references_with_lifetimes_and_generics() {
+ check_assist(
+ generate_delegate_methods,
+ r#"
+struct Foo<'a, T> {
+ $0field: Bar<'a, T>,
+}
+
+struct Bar<'a, T>(&'a T);
+
+impl<'a, T> Bar<'a, T> {
+ fn bar(self, mut b: Vec<&'a Self>) -> &'a Self {
+ b.pop().unwrap()
+ }
+}
+"#,
+ r#"
+struct Foo<'a, T> {
+ field: Bar<'a, T>,
+}
+
+impl<'a, T> Foo<'a, T> {
+ $0fn bar(self, mut b: Vec<&'a Bar<'_, T>>) -> &'a Bar<'_, T> {
+ self.field.bar(b)
+ }
+}
+
+struct Bar<'a, T>(&'a T);
+
+impl<'a, T> Bar<'a, T> {
+ fn bar(self, mut b: Vec<&'a Self>) -> &'a Self {
+ b.pop().unwrap()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fixes_self_references_across_macros() {
+ check_assist(
+ generate_delegate_methods,
+ r#"
+//- /bar.rs
+macro_rules! test_method {
+ () => {
+ pub fn test(self, b: Bar) -> Self {
+ self
+ }
+ };
+}
+
+pub struct Bar;
+
+impl Bar {
+ test_method!();
+}
+
+//- /main.rs
+mod bar;
+
+struct Foo {
+ $0bar: bar::Bar,
+}
+"#,
+ r#"
+mod bar;
+
+struct Foo {
+ bar: bar::Bar,
+}
+
+impl Foo {
+ $0pub fn test(self,b:bar::Bar) ->bar::Bar {
+ self.bar.test(b)
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
fn test_generate_delegate_visibility() {
check_assist_not_applicable(
generate_delegate_methods,
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 815453961..473c699b5 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
@@ -58,8 +58,12 @@ fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
let module = ctx.sema.to_def(&strukt)?.module(ctx.db());
let trait_ = deref_type_to_generate.to_trait(&ctx.sema, module.krate())?;
- let trait_path =
- module.find_use_path(ctx.db(), ModuleDef::Trait(trait_), ctx.config.prefer_no_std)?;
+ let trait_path = module.find_use_path(
+ ctx.db(),
+ ModuleDef::Trait(trait_),
+ ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
+ )?;
let field_type = field.ty()?;
let field_name = field.name()?;
@@ -99,8 +103,12 @@ fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()
let module = ctx.sema.to_def(&strukt)?.module(ctx.db());
let trait_ = deref_type_to_generate.to_trait(&ctx.sema, module.krate())?;
- let trait_path =
- module.find_use_path(ctx.db(), ModuleDef::Trait(trait_), ctx.config.prefer_no_std)?;
+ let trait_path = module.find_use_path(
+ ctx.db(),
+ ModuleDef::Trait(trait_),
+ ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
+ )?;
let field_type = field.ty()?;
let target = field.syntax().text_range();
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 184f523e0..1a1e992e2 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
@@ -1,4 +1,4 @@
-use hir::{HasSource, HirDisplay, InFile};
+use hir::{HasSource, HirDisplay, InRealFile};
use ide_db::assists::{AssistId, AssistKind};
use syntax::{
ast::{self, make, HasArgList},
@@ -114,14 +114,14 @@ fn add_variant_to_accumulator(
parent: PathParent,
) -> Option<()> {
let db = ctx.db();
- let InFile { file_id, value: enum_node } = adt.source(db)?.original_ast_node(db)?;
+ let InRealFile { file_id, value: enum_node } = adt.source(db)?.original_ast_node(db)?;
acc.add(
AssistId("generate_enum_variant", AssistKind::Generate),
"Generate variant",
target,
|builder| {
- builder.edit_file(file_id.original_file(db));
+ builder.edit_file(file_id);
let node = builder.make_mut(enum_node);
let variant = make_variant(ctx, name_ref, parent);
node.variant_list().map(|it| it.add_variant(variant.clone_for_update()));
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 5b13e01b1..a113c817f 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
@@ -1,5 +1,6 @@
use hir::{
- Adt, AsAssocItem, HasSource, HirDisplay, Module, PathResolution, Semantics, Type, TypeInfo,
+ Adt, AsAssocItem, HasSource, HirDisplay, HirFileIdExt, Module, PathResolution, Semantics, Type,
+ TypeInfo,
};
use ide_db::{
base_db::FileId,
@@ -404,7 +405,11 @@ impl FunctionBuilder {
leading_ws,
ret_type: fn_def.ret_type(),
// PANIC: we guarantee we always create a function body with a tail expr
- tail_expr: fn_def.body().unwrap().tail_expr().unwrap(),
+ tail_expr: fn_def
+ .body()
+ .expect("generated function should have a body")
+ .tail_expr()
+ .expect("function body should have a tail expression"),
should_focus_return_type: self.should_focus_return_type,
fn_def,
trailing_ws,
@@ -506,7 +511,7 @@ fn assoc_fn_target_info(
}
fn get_insert_offset(target: &GeneratedFunctionTarget) -> TextSize {
- match &target {
+ match target {
GeneratedFunctionTarget::BehindItem(it) => it.text_range().end(),
GeneratedFunctionTarget::InEmptyItemList(it) => it.text_range().start() + TextSize::of('{'),
}
@@ -683,7 +688,7 @@ where
{
// This function should be only called with `Impl`, `Trait`, or `Function`, for which it's
// infallible to get source ast.
- let node = ctx.sema.source(def).unwrap().value;
+ let node = ctx.sema.source(def).expect("definition's source couldn't be found").value;
let generic_params = node.generic_param_list().into_iter().flat_map(|it| it.generic_params());
let where_clauses = node.where_clause().into_iter().flat_map(|it| it.predicates());
(generic_params, where_clauses)
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs
new file mode 100644
index 000000000..cb8ef3956
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs
@@ -0,0 +1,202 @@
+use ide_db::famous_defs::FamousDefs;
+use syntax::{
+ ast::{self, make},
+ ted, AstNode,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// FIXME: Generate proper `index_mut` method body refer to `index` method body may impossible due to the unpredicable case [#15581].
+// Here just leave the `index_mut` method body be same as `index` method body, user can modify it manually to meet their need.
+
+// Assist: generate_mut_trait_impl
+//
+// Adds a IndexMut impl from the `Index` trait.
+//
+// ```
+// # //- minicore: index
+// pub enum Axis { X = 0, Y = 1, Z = 2 }
+//
+// impl<T> core::ops::Index$0<Axis> for [T; 3] {
+// type Output = T;
+//
+// fn index(&self, index: Axis) -> &Self::Output {
+// &self[index as usize]
+// }
+// }
+// ```
+// ->
+// ```
+// pub enum Axis { X = 0, Y = 1, Z = 2 }
+//
+// $0impl<T> core::ops::IndexMut<Axis> for [T; 3] {
+// fn index_mut(&mut self, index: Axis) -> &mut Self::Output {
+// &self[index as usize]
+// }
+// }
+//
+// impl<T> core::ops::Index<Axis> for [T; 3] {
+// type Output = T;
+//
+// fn index(&self, index: Axis) -> &Self::Output {
+// &self[index as usize]
+// }
+// }
+// ```
+pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let impl_def = ctx.find_node_at_offset::<ast::Impl>()?.clone_for_update();
+
+ let trait_ = impl_def.trait_()?;
+ if let ast::Type::PathType(trait_path) = trait_.clone() {
+ let trait_type = ctx.sema.resolve_trait(&trait_path.path()?)?;
+ let scope = ctx.sema.scope(trait_path.syntax())?;
+ if trait_type != FamousDefs(&ctx.sema, scope.krate()).core_convert_Index()? {
+ return None;
+ }
+ }
+
+ // Index -> IndexMut
+ let index_trait = impl_def
+ .syntax()
+ .descendants()
+ .filter_map(ast::NameRef::cast)
+ .find(|it| it.text() == "Index")?;
+ ted::replace(
+ index_trait.syntax(),
+ make::path_segment(make::name_ref("IndexMut")).clone_for_update().syntax(),
+ );
+
+ // index -> index_mut
+ let trait_method_name = impl_def
+ .syntax()
+ .descendants()
+ .filter_map(ast::Name::cast)
+ .find(|it| it.text() == "index")?;
+ ted::replace(trait_method_name.syntax(), make::name("index_mut").clone_for_update().syntax());
+
+ let type_alias = impl_def.syntax().descendants().find_map(ast::TypeAlias::cast)?;
+ ted::remove(type_alias.syntax());
+
+ // &self -> &mut self
+ let mut_self_param = make::mut_self_param();
+ let self_param: ast::SelfParam =
+ impl_def.syntax().descendants().find_map(ast::SelfParam::cast)?;
+ ted::replace(self_param.syntax(), mut_self_param.clone_for_update().syntax());
+
+ // &Self::Output -> &mut Self::Output
+ let ret_type = impl_def.syntax().descendants().find_map(ast::RetType::cast)?;
+ ted::replace(
+ ret_type.syntax(),
+ make::ret_type(make::ty("&mut Self::Output")).clone_for_update().syntax(),
+ );
+
+ let fn_ = impl_def.assoc_item_list()?.assoc_items().find_map(|it| match it {
+ ast::AssocItem::Fn(f) => Some(f),
+ _ => None,
+ })?;
+
+ let assoc_list = make::assoc_item_list().clone_for_update();
+ assoc_list.add_item(syntax::ast::AssocItem::Fn(fn_));
+ ted::replace(impl_def.assoc_item_list()?.syntax(), assoc_list.syntax());
+
+ let target = impl_def.syntax().text_range();
+ acc.add(
+ AssistId("generate_mut_trait_impl", AssistKind::Generate),
+ "Generate `IndexMut` impl from this `Index` trait",
+ target,
+ |edit| {
+ edit.insert(target.start(), format!("$0{}\n\n", impl_def.to_string()));
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_generate_mut_trait_impl() {
+ check_assist(
+ generate_mut_trait_impl,
+ r#"
+//- minicore: index
+pub enum Axis { X = 0, Y = 1, Z = 2 }
+
+impl<T> core::ops::Index$0<Axis> for [T; 3] {
+ type Output = T;
+
+ fn index(&self, index: Axis) -> &Self::Output {
+ &self[index as usize]
+ }
+}
+"#,
+ r#"
+pub enum Axis { X = 0, Y = 1, Z = 2 }
+
+$0impl<T> core::ops::IndexMut<Axis> for [T; 3] {
+ fn index_mut(&mut self, index: Axis) -> &mut Self::Output {
+ &self[index as usize]
+ }
+}
+
+impl<T> core::ops::Index<Axis> for [T; 3] {
+ type Output = T;
+
+ fn index(&self, index: Axis) -> &Self::Output {
+ &self[index as usize]
+ }
+}
+"#,
+ );
+
+ check_assist(
+ generate_mut_trait_impl,
+ r#"
+//- minicore: index
+pub enum Axis { X = 0, Y = 1, Z = 2 }
+
+impl<T> core::ops::Index$0<Axis> for [T; 3] where T: Copy {
+ type Output = T;
+
+ fn index(&self, index: Axis) -> &Self::Output {
+ let var_name = &self[index as usize];
+ var_name
+ }
+}
+"#,
+ r#"
+pub enum Axis { X = 0, Y = 1, Z = 2 }
+
+$0impl<T> core::ops::IndexMut<Axis> for [T; 3] where T: Copy {
+ fn index_mut(&mut self, index: Axis) -> &mut Self::Output {
+ let var_name = &self[index as usize];
+ var_name
+ }
+}
+
+impl<T> core::ops::Index<Axis> for [T; 3] where T: Copy {
+ type Output = T;
+
+ fn index(&self, index: Axis) -> &Self::Output {
+ let var_name = &self[index as usize];
+ var_name
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_mut_trait_impl_not_applicable() {
+ check_assist_not_applicable(
+ generate_mut_trait_impl,
+ r#"
+pub trait Index<Idx: ?Sized> {}
+
+impl<T> Index$0<i32> for [T; 3] {}
+"#,
+ );
+ }
+}
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 824255e4f..7bfd59966 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
@@ -67,6 +67,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
ctx.sema.db,
item_for_path_search(ctx.sema.db, item_in_ns)?,
ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
)?;
let expr = use_trivial_constructor(
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 ffab58509..5b9cc5f66 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
@@ -8,7 +8,7 @@ use ide_db::{
defs::Definition,
imports::insert_use::remove_path_if_in_use_stmt,
path_transform::PathTransform,
- search::{FileReference, SearchScope},
+ search::{FileReference, FileReferenceNode, SearchScope},
source_change::SourceChangeBuilder,
syntax_helpers::{insert_whitespace_into_node::insert_ws_into, node_ext::expr_as_name_ref},
RootDatabase,
@@ -148,7 +148,7 @@ pub(super) fn split_refs_and_uses<T: ast::AstNode>(
) -> (Vec<T>, Vec<ast::Path>) {
iter.into_iter()
.filter_map(|file_ref| match file_ref.name {
- ast::NameLike::NameRef(name_ref) => Some(name_ref),
+ FileReferenceNode::NameRef(name_ref) => Some(name_ref),
_ => None,
})
.filter_map(|name_ref| match name_ref.syntax().ancestors().find_map(ast::UseTree::cast) {
@@ -224,7 +224,6 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
syntax.text_range(),
|builder| {
let replacement = inline(&ctx.sema, file_id, function, &fn_body, &params, &call_info);
-
builder.replace_ast(
match call_info.node {
ast::CallableExpr::Call(it) => ast::Expr::CallExpr(it),
@@ -347,7 +346,7 @@ fn inline(
match param.as_local(sema.db) {
Some(l) => usages_for_locals(l)
.map(|FileReference { name, range, .. }| match name {
- ast::NameLike::NameRef(_) => body
+ FileReferenceNode::NameRef(_) => body
.syntax()
.covering_element(range)
.ancestors()
@@ -363,16 +362,22 @@ fn inline(
.collect();
if function.self_param(sema.db).is_some() {
- let this = || make::name_ref("this").syntax().clone_for_update().first_token().unwrap();
+ let this = || {
+ make::name_ref("this")
+ .syntax()
+ .clone_for_update()
+ .first_token()
+ .expect("NameRef should have had a token.")
+ };
if let Some(self_local) = params[0].2.as_local(sema.db) {
usages_for_locals(self_local)
.filter_map(|FileReference { name, range, .. }| match name {
- ast::NameLike::NameRef(_) => Some(body.syntax().covering_element(range)),
+ FileReferenceNode::NameRef(_) => Some(body.syntax().covering_element(range)),
_ => None,
})
- .for_each(|it| {
- ted::replace(it, &this());
- })
+ .for_each(|usage| {
+ ted::replace(usage, &this());
+ });
}
}
@@ -470,7 +475,9 @@ fn inline(
}
} else if let Some(stmt_list) = body.stmt_list() {
ted::insert_all(
- ted::Position::after(stmt_list.l_curly_token().unwrap()),
+ ted::Position::after(
+ stmt_list.l_curly_token().expect("L_CURLY for StatementList is missing."),
+ ),
let_stmts.into_iter().map(|stmt| stmt.syntax().clone().into()).collect(),
);
}
@@ -481,8 +488,12 @@ fn inline(
};
body.reindent_to(original_indentation);
+ let no_stmts = body.statements().next().is_none();
match body.tail_expr() {
- Some(expr) if !is_async_fn && body.statements().next().is_none() => expr,
+ Some(expr) if matches!(expr, ast::Expr::ClosureExpr(_)) && no_stmts => {
+ make::expr_paren(expr).clone_for_update()
+ }
+ Some(expr) if !is_async_fn && no_stmts => expr,
_ => match node
.syntax()
.parent()
@@ -1474,4 +1485,29 @@ fn main() {
"#,
);
}
+
+ #[test]
+ fn inline_call_closure_body() {
+ check_assist(
+ inline_call,
+ r#"
+fn f() -> impl Fn() -> i32 {
+ || 2
+}
+
+fn main() {
+ let _ = $0f()();
+}
+"#,
+ r#"
+fn f() -> impl Fn() -> i32 {
+ || 2
+}
+
+fn main() {
+ let _ = (|| 2)();
+}
+"#,
+ );
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs
index e69d1a296..5d8ba43ec 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs
@@ -2,7 +2,7 @@ use hir::{PathResolution, Semantics};
use ide_db::{
base_db::FileId,
defs::Definition,
- search::{FileReference, UsageSearchResult},
+ search::{FileReference, FileReferenceNode, UsageSearchResult},
RootDatabase,
};
use syntax::{
@@ -63,7 +63,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>)
let wrap_in_parens = references
.into_iter()
.filter_map(|FileReference { range, name, .. }| match name {
- ast::NameLike::NameRef(name) => Some((range, name)),
+ FileReferenceNode::NameRef(name) => Some((range, name)),
_ => None,
})
.map(|(range, name_ref)| {
@@ -96,8 +96,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>)
);
let parent = matches!(
usage_parent,
- ast::Expr::CallExpr(_)
- | ast::Expr::TupleExpr(_)
+ ast::Expr::TupleExpr(_)
| ast::Expr::ArrayExpr(_)
| ast::Expr::ParenExpr(_)
| ast::Expr::ForExpr(_)
@@ -952,4 +951,22 @@ fn f() {
"#,
);
}
+
+ #[test]
+ fn test_inline_closure() {
+ check_assist(
+ inline_local_variable,
+ r#"
+fn main() {
+ let $0f = || 2;
+ let _ = f();
+}
+"#,
+ r#"
+fn main() {
+ let _ = (|| 2)();
+}
+"#,
+ );
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/into_to_qualified_from.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/into_to_qualified_from.rs
index 663df266b..965e4aa78 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/into_to_qualified_from.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/into_to_qualified_from.rs
@@ -52,9 +52,13 @@ pub(crate) fn into_to_qualified_from(acc: &mut Assists, ctx: &AssistContext<'_>)
== FamousDefs(sema, scope.krate()).core_convert_Into()?
{
let type_call = sema.type_of_expr(&method_call.clone().into())?;
- let type_call_disp =
- type_call.adjusted().display_source_code(db, scope.module().into(), true).ok()?;
+ let adjusted_tc = type_call.adjusted();
+ if adjusted_tc.contains_unknown() {
+ return None;
+ }
+
+ let sc = adjusted_tc.display_source_code(db, scope.module().into(), true).ok()?;
acc.add(
AssistId("into_to_qualified_from", AssistKind::Generate),
"Convert `into` to fully qualified `from`",
@@ -62,7 +66,11 @@ pub(crate) fn into_to_qualified_from(acc: &mut Assists, ctx: &AssistContext<'_>)
|edit| {
edit.replace(
method_call.syntax().text_range(),
- format!("{}::from({})", type_call_disp, receiver),
+ if sc.chars().all(|c| c.is_alphanumeric() || c == ':') {
+ format!("{}::from({})", sc, receiver)
+ } else {
+ format!("<{}>::from({})", sc, receiver)
+ },
);
},
);
@@ -202,4 +210,64 @@ fn main() -> () {
}"#,
)
}
+
+ #[test]
+ fn preceding_type_qualifier() {
+ check_assist(
+ into_to_qualified_from,
+ r#"
+//- minicore: from
+impl From<(i32,i32)> for [i32;2] {
+ fn from(value: (i32,i32)) -> Self {
+ [value.0, value.1]
+ }
+}
+
+fn tuple_to_array() -> [i32; 2] {
+ (0,1).in$0to()
+}"#,
+ r#"
+impl From<(i32,i32)> for [i32;2] {
+ fn from(value: (i32,i32)) -> Self {
+ [value.0, value.1]
+ }
+}
+
+fn tuple_to_array() -> [i32; 2] {
+ <[i32; 2]>::from((0,1))
+}"#,
+ )
+ }
+
+ #[test]
+ fn type_with_gens() {
+ check_assist(
+ into_to_qualified_from,
+ r#"
+//- minicore: from
+struct StructA<Gen>(Gen);
+
+impl From<i32> for StructA<i32> {
+ fn from(value: i32) -> Self {
+ StructA(value + 1)
+ }
+}
+
+fn main() -> () {
+ let a: StructA<i32> = 3.in$0to();
+}"#,
+ r#"
+struct StructA<Gen>(Gen);
+
+impl From<i32> for StructA<i32> {
+ fn from(value: i32) -> Self {
+ StructA(value + 1)
+ }
+}
+
+fn main() -> () {
+ let a: StructA<i32> = <StructA<i32>>::from(3);
+}"#,
+ )
+ }
}
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 4bf974a56..ff65aac82 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
@@ -48,6 +48,7 @@ pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) ->
ctx.sema.db,
item_for_path_search(ctx.sema.db, item_in_ns)?,
ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
)?;
let qualify_candidate = QualifyCandidate::ImplMethod(ctx.sema.db, call, resolved_call);
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs
index 239149dc4..fde46db30 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs
@@ -37,8 +37,11 @@ use crate::{
// ```
pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
- let mut proposed_imports =
- import_assets.search_for_relative_paths(&ctx.sema, ctx.config.prefer_no_std);
+ let mut proposed_imports = import_assets.search_for_relative_paths(
+ &ctx.sema,
+ ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
+ );
if proposed_imports.is_empty() {
return None;
}
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
index ffc32f804..0281b29cd 100644
--- 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
@@ -1,4 +1,4 @@
-use syntax::{ast, AstNode};
+use syntax::{ast, AstNode, SyntaxKind, T};
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -39,7 +39,19 @@ pub(crate) fn remove_parentheses(acc: &mut Assists, ctx: &AssistContext<'_>) ->
AssistId("remove_parentheses", AssistKind::Refactor),
"Remove redundant parentheses",
target,
- |builder| builder.replace_ast(parens.into(), expr),
+ |builder| {
+ let prev_token = parens.syntax().first_token().and_then(|it| it.prev_token());
+ let need_to_add_ws = match prev_token {
+ Some(it) => {
+ let tokens = vec![T![&], T![!], T!['('], T!['['], T!['{']];
+ it.kind() != SyntaxKind::WHITESPACE && !tokens.contains(&it.kind())
+ }
+ None => false,
+ };
+ let expr = if need_to_add_ws { format!(" {}", expr) } else { expr.to_string() };
+
+ builder.replace(parens.syntax().text_range(), expr)
+ },
)
}
@@ -50,6 +62,15 @@ mod tests {
use super::*;
#[test]
+ fn remove_parens_space() {
+ check_assist(
+ remove_parentheses,
+ r#"fn f() { match$0(true) {} }"#,
+ r#"fn f() { match true {} }"#,
+ );
+ }
+
+ #[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; }"#);
@@ -94,8 +115,8 @@ mod tests {
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; }"#,
+ r#"fn f() { (1<2) &&$0(3>4); }"#,
+ r#"fn f() { (1<2) && 3>4; }"#,
);
}
@@ -164,8 +185,8 @@ mod tests {
fn remove_parens_weird_places() {
check_assist(
remove_parentheses,
- r#"fn f() { match () { _=>$0(()) } }"#,
- r#"fn f() { match () { _=>() } }"#,
+ r#"fn f() { match () { _ =>$0(()) } }"#,
+ r#"fn f() { match () { _ => () } }"#,
);
check_assist(
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs
index 5fcab8c02..ee44064e7 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs
@@ -1,6 +1,6 @@
use std::collections::{hash_map::Entry, HashMap};
-use hir::{InFile, Module, ModuleSource};
+use hir::{HirFileIdExt, InFile, InRealFile, Module, ModuleSource};
use ide_db::{
base_db::FileRange,
defs::Definition,
@@ -167,7 +167,7 @@ fn used_once_in_scope(ctx: &AssistContext<'_>, def: Definition, scopes: &Vec<Sea
fn module_search_scope(db: &RootDatabase, module: hir::Module) -> Vec<SearchScope> {
let (file_id, range) = {
let InFile { file_id, value } = module.definition_source(db);
- if let Some((file_id, call_source)) = file_id.original_call_node(db) {
+ if let Some(InRealFile { file_id, value: call_source }) = file_id.original_call_node(db) {
(file_id, Some(call_source.text_range()))
} else {
(
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 ac45581b7..b54e4204e 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
@@ -1,4 +1,4 @@
-use hir::{InFile, ModuleDef};
+use hir::{InFile, MacroFileIdExt, ModuleDef};
use ide_db::{helpers::mod_path_to_ast, imports::import_assets::NameToImport, items_locator};
use itertools::Itertools;
use syntax::{
@@ -43,12 +43,12 @@ pub(crate) fn replace_derive_with_manual_impl(
) -> Option<()> {
let attr = ctx.find_node_at_offset_with_descend::<ast::Attr>()?;
let path = attr.path()?;
- let hir_file = ctx.sema.hir_file_for(attr.syntax());
- if !hir_file.is_derive_attr_pseudo_expansion(ctx.db()) {
+ let macro_file = ctx.sema.hir_file_for(attr.syntax()).macro_file()?;
+ if !macro_file.is_derive_attr_pseudo_expansion(ctx.db()) {
return None;
}
- let InFile { file_id, value } = hir_file.call_node(ctx.db())?;
+ let InFile { file_id, value } = macro_file.call_node(ctx.db());
if file_id.is_macro() {
// FIXME: make this work in macro files
return None;
@@ -56,7 +56,7 @@ pub(crate) fn replace_derive_with_manual_impl(
// collect the derive paths from the #[derive] expansion
let current_derives = ctx
.sema
- .parse_or_expand(hir_file)
+ .parse_or_expand(macro_file.into())
.descendants()
.filter_map(ast::Attr::cast)
.filter_map(|attr| attr.path())
@@ -82,7 +82,12 @@ pub(crate) fn replace_derive_with_manual_impl(
})
.flat_map(|trait_| {
current_module
- .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_), ctx.config.prefer_no_std)
+ .find_use_path(
+ ctx.sema.db,
+ hir::ModuleDef::Trait(trait_),
+ ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
+ )
.as_ref()
.map(mod_path_to_ast)
.zip(Some(trait_))
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs
new file mode 100644
index 000000000..b1daaea1e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs
@@ -0,0 +1,172 @@
+use syntax::ast::{self, AstNode};
+
+use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: replace_is_some_with_if_let_some
+//
+// Replace `if x.is_some()` with `if let Some(_tmp) = x` or `if x.is_ok()` with `if let Ok(_tmp) = x`.
+//
+// ```
+// fn main() {
+// let x = Some(1);
+// if x.is_som$0e() {}
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// let x = Some(1);
+// if let Some(${0:x}) = x {}
+// }
+// ```
+pub(crate) fn replace_is_method_with_if_let_method(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let if_expr = ctx.find_node_at_offset::<ast::IfExpr>()?;
+
+ let cond = if_expr.condition()?;
+ let call_expr = match cond {
+ ast::Expr::MethodCallExpr(call) => call,
+ _ => return None,
+ };
+
+ let name_ref = call_expr.name_ref()?;
+ match name_ref.text().as_str() {
+ "is_some" | "is_ok" => {
+ let receiver = call_expr.receiver()?;
+
+ let var_name = if let ast::Expr::PathExpr(path_expr) = receiver.clone() {
+ path_expr.path()?.to_string()
+ } else {
+ suggest_name::for_variable(&receiver, &ctx.sema)
+ };
+
+ let target = call_expr.syntax().text_range();
+
+ let (assist_id, message, text) = if name_ref.text() == "is_some" {
+ ("replace_is_some_with_if_let_some", "Replace `is_some` with `if let Some`", "Some")
+ } else {
+ ("replace_is_ok_with_if_let_ok", "Replace `is_ok` with `if let Ok`", "Ok")
+ };
+
+ acc.add(AssistId(assist_id, AssistKind::RefactorRewrite), message, target, |edit| {
+ let var_name = format!("${{0:{}}}", var_name);
+ let replacement = format!("let {}({}) = {}", text, var_name, receiver);
+ edit.replace(target, replacement);
+ })
+ }
+ _ => return None,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::replace_is_method_with_if_let_method;
+
+ #[test]
+ fn replace_is_some_with_if_let_some_works() {
+ check_assist(
+ replace_is_method_with_if_let_method,
+ r#"
+fn main() {
+ let x = Some(1);
+ if x.is_som$0e() {}
+}
+"#,
+ r#"
+fn main() {
+ let x = Some(1);
+ if let Some(${0:x}) = x {}
+}
+"#,
+ );
+
+ check_assist(
+ replace_is_method_with_if_let_method,
+ r#"
+fn test() -> Option<i32> {
+ Some(1)
+}
+fn main() {
+ if test().is_som$0e() {}
+}
+"#,
+ r#"
+fn test() -> Option<i32> {
+ Some(1)
+}
+fn main() {
+ if let Some(${0:test}) = test() {}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_is_some_with_if_let_some_not_applicable() {
+ check_assist_not_applicable(
+ replace_is_method_with_if_let_method,
+ r#"
+fn main() {
+ let x = Some(1);
+ if x.is_non$0e() {}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_is_ok_with_if_let_ok_works() {
+ check_assist(
+ replace_is_method_with_if_let_method,
+ r#"
+fn main() {
+ let x = Ok(1);
+ if x.is_o$0k() {}
+}
+"#,
+ r#"
+fn main() {
+ let x = Ok(1);
+ if let Ok(${0:x}) = x {}
+}
+"#,
+ );
+
+ check_assist(
+ replace_is_method_with_if_let_method,
+ r#"
+fn test() -> Result<i32> {
+ Ok(1)
+}
+fn main() {
+ if test().is_o$0k() {}
+}
+"#,
+ r#"
+fn test() -> Result<i32> {
+ Ok(1)
+}
+fn main() {
+ if let Ok(${0:test}) = test() {}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_is_ok_with_if_let_ok_not_applicable() {
+ check_assist_not_applicable(
+ replace_is_method_with_if_let_method,
+ r#"
+fn main() {
+ let x = Ok(1);
+ if x.is_e$0rr() {}
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
index c7c0be4c7..e61ce4817 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
@@ -59,7 +59,10 @@ pub(crate) fn replace_named_generic_with_impl(
let mut path_types_to_replace = Vec::new();
for (_a, refs) in usage_refs.iter() {
for usage_ref in refs {
- let param_node = find_path_type(&ctx.sema, &type_param_name, &usage_ref.name)?;
+ let Some(name_like) = usage_ref.name.clone().into_name_like() else {
+ continue;
+ };
+ let param_node = find_path_type(&ctx.sema, &type_param_name, &name_like)?;
path_types_to_replace.push(param_node);
}
}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs
index dbbc56958..f03eb6118 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs
@@ -68,6 +68,7 @@ pub(crate) fn replace_qualified_name_with_use(
module,
ctx.config.insert_use.prefix_kind,
ctx.config.prefer_no_std,
+ ctx.config.prefer_prelude,
)
})
.flatten();
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_ignore.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_ignore.rs
index b7d57f02b..f864ee50c 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_ignore.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_ignore.rs
@@ -55,7 +55,7 @@ pub(crate) fn toggle_ignore(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio
}
fn has_ignore_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
- fn_def.attrs().find(|attr| attr.path().map(|it| it.syntax().text() == "ignore") == Some(true))
+ fn_def.attrs().find(|attr| attr.path().is_some_and(|it| it.syntax().text() == "ignore"))
}
#[cfg(test)]
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_use.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_use.rs
index dac216b69..52df30d96 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_use.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_use.rs
@@ -36,29 +36,25 @@ pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
let old_parent_range = use_.syntax().parent()?.text_range();
let new_parent = use_.syntax().parent()?;
+ // If possible, explain what is going to be done.
+ let label = match tree.path().and_then(|path| path.first_segment()) {
+ Some(name) => format!("Unmerge use of `{name}`"),
+ None => "Unmerge use".into(),
+ };
+
let target = tree.syntax().text_range();
- acc.add(
- AssistId("unmerge_use", AssistKind::RefactorRewrite),
- "Unmerge use",
- target,
- |builder| {
- let new_use = make::use_(
- use_.visibility(),
- make::use_tree(
- path,
- tree.use_tree_list(),
- tree.rename(),
- tree.star_token().is_some(),
- ),
- )
- .clone_for_update();
-
- tree.remove();
- ted::insert(Position::after(use_.syntax()), new_use.syntax());
-
- builder.replace(old_parent_range, new_parent.to_string());
- },
- )
+ acc.add(AssistId("unmerge_use", AssistKind::RefactorRewrite), label, target, |builder| {
+ let new_use = make::use_(
+ use_.visibility(),
+ make::use_tree(path, tree.use_tree_list(), tree.rename(), tree.star_token().is_some()),
+ )
+ .clone_for_update();
+
+ tree.remove();
+ ted::insert(Position::after(use_.syntax()), new_use.syntax());
+
+ builder.replace(old_parent_range, new_parent.to_string());
+ })
}
fn resolve_full_path(tree: &ast::UseTree) -> Option<ast::Path> {
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 7f612c2a1..1cfa291a2 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
@@ -2,11 +2,11 @@ use ide_db::{
assists::{AssistId, AssistKind},
base_db::FileId,
defs::Definition,
- search::FileReference,
+ search::{FileReference, FileReferenceNode},
syntax_helpers::node_ext::full_path_of_name_ref,
};
use syntax::{
- ast::{self, NameLike, NameRef},
+ ast::{self, NameRef},
AstNode, SyntaxKind, TextRange,
};
@@ -76,7 +76,7 @@ pub(crate) fn unnecessary_async(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
for await_expr in find_all_references(ctx, &Definition::Function(fn_def))
// Keep only references that correspond NameRefs.
.filter_map(|(_, reference)| match reference.name {
- NameLike::NameRef(nameref) => Some(nameref),
+ FileReferenceNode::NameRef(nameref) => Some(nameref),
_ => None,
})
// Keep only references that correspond to await expressions
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
index e9d4e270c..0876246e9 100644
--- 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
@@ -1,3 +1,4 @@
+use ide_db::imports::insert_use::ImportScope;
use syntax::{
ast::{self, make, AstNode, HasArgList},
TextRange,
@@ -17,6 +18,8 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
// ```
// ->
// ```
+// use std::ops::Add;
+//
// fn main() {
// 1.add(2);
// }
@@ -38,7 +41,7 @@ pub(crate) fn unqualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>)
let first_arg = args_iter.next()?;
let second_arg = args_iter.next();
- _ = path.qualifier()?;
+ let qualifier = path.qualifier()?;
let method_name = path.segment()?.name_ref()?;
let res = ctx.sema.resolve_path(&path)?;
@@ -76,10 +79,51 @@ pub(crate) fn unqualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>)
edit.insert(close, ")");
}
edit.replace(replace_comma, format!(".{method_name}("));
+ add_import(qualifier, ctx, edit);
},
)
}
+fn add_import(
+ qualifier: ast::Path,
+ ctx: &AssistContext<'_>,
+ edit: &mut ide_db::source_change::SourceChangeBuilder,
+) {
+ if let Some(path_segment) = qualifier.segment() {
+ // for `<i32 as std::ops::Add>`
+ let path_type = path_segment.qualifying_trait();
+ let import = match path_type {
+ Some(it) => {
+ if let Some(path) = it.path() {
+ path
+ } else {
+ return;
+ }
+ }
+ None => qualifier,
+ };
+
+ // in case for `<_>`
+ if import.coloncolon_token().is_none() {
+ return;
+ }
+
+ let scope = ide_db::imports::insert_use::ImportScope::find_insert_use_container(
+ import.syntax(),
+ &ctx.sema,
+ );
+
+ if let Some(scope) = scope {
+ let scope = match scope {
+ ImportScope::File(it) => ImportScope::File(edit.make_mut(it)),
+ ImportScope::Module(it) => ImportScope::Module(edit.make_mut(it)),
+ ImportScope::Block(it) => ImportScope::Block(edit.make_mut(it)),
+ };
+ ide_db::imports::insert_use::insert_use(&scope, import, &ctx.config.insert_use);
+ }
+ }
+}
+
fn needs_parens_as_receiver(expr: &ast::Expr) -> bool {
// Make `(expr).dummy()`
let dummy_call = make::expr_method_call(
@@ -127,6 +171,8 @@ fn f() { S.f(S); }"#,
//- minicore: add
fn f() { <u32 as core::ops::Add>::$0add(2, 2); }"#,
r#"
+use core::ops::Add;
+
fn f() { 2.add(2); }"#,
);
@@ -136,6 +182,8 @@ fn f() { 2.add(2); }"#,
//- minicore: add
fn f() { core::ops::Add::$0add(2, 2); }"#,
r#"
+use core::ops::Add;
+
fn f() { 2.add(2); }"#,
);
@@ -179,6 +227,8 @@ impl core::ops::Deref for S {
}
fn f() { core::ops::Deref::$0deref(&S); }"#,
r#"
+use core::ops::Deref;
+
struct S;
impl core::ops::Deref for S {
type Target = S;
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_result_return_type.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_result_return_type.rs
index f235b554e..03e6dfebe 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_result_return_type.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_result_return_type.rs
@@ -123,10 +123,8 @@ fn tail_cb_impl(acc: &mut Vec<ast::Expr>, e: &ast::Expr) {
for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(acc, e))
}
}
- Expr::ReturnExpr(ret_expr) => {
- if let Some(ret_expr_arg) = &ret_expr.expr() {
- for_each_tail_expr(ret_expr_arg, &mut |e| tail_cb_impl(acc, e));
- }
+ Expr::ReturnExpr(_) => {
+ // all return expressions have already been handled by the walk loop
}
e => acc.push(e.clone()),
}
@@ -801,6 +799,24 @@ fn foo() -> i32 {
}
#[test]
+ fn wrap_return_in_tail_position() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo(num: i32) -> $0Result<i32, String> {
+ return Ok(num)
+}
+"#,
+ r#"
+fn foo(num: i32) -> i32 {
+ return num
+}
+"#,
+ );
+ }
+
+ #[test]
fn unwrap_result_return_type_simple_with_closure() {
check_assist(
unwrap_result_return_type,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs
index 61e9bcdcc..b68ed00f7 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs
@@ -98,10 +98,8 @@ fn tail_cb_impl(acc: &mut Vec<ast::Expr>, e: &ast::Expr) {
for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(acc, e))
}
}
- Expr::ReturnExpr(ret_expr) => {
- if let Some(ret_expr_arg) = &ret_expr.expr() {
- for_each_tail_expr(ret_expr_arg, &mut |e| tail_cb_impl(acc, e));
- }
+ Expr::ReturnExpr(_) => {
+ // all return expressions have already been handled by the walk loop
}
e => acc.push(e.clone()),
}
@@ -733,6 +731,24 @@ fn foo() -> Result<i32, ${0:_}> {
}
#[test]
+ fn wrap_return_in_tail_position() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo(num: i32) -> $0i32 {
+ return num
+}
+"#,
+ r#"
+fn foo(num: i32) -> Result<i32, ${0:_}> {
+ return Ok(num)
+}
+"#,
+ );
+ }
+
+ #[test]
fn wrap_return_type_in_result_simple_with_closure() {
check_assist(
wrap_return_type_in_result,
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 6f973ab53..1e4d1c94f 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
@@ -58,7 +58,7 @@
//! See also this post:
//! <https://rust-analyzer.github.io/blog/2020/09/28/how-to-make-a-light-bulb.html>
-#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+#![warn(rust_2018_idioms, unused_lifetimes)]
#[allow(unused)]
macro_rules! eprintln {
@@ -115,6 +115,7 @@ mod handlers {
mod apply_demorgan;
mod auto_import;
mod bind_unused_param;
+ mod bool_to_enum;
mod change_visibility;
mod convert_bool_then;
mod convert_comment_block;
@@ -124,6 +125,7 @@ mod handlers {
mod convert_let_else_to_match;
mod convert_match_to_let_else;
mod convert_nested_function_to_closure;
+ mod convert_tuple_return_type_to_struct;
mod convert_tuple_struct_to_named_struct;
mod convert_named_struct_to_tuple_struct;
mod convert_to_guarded_return;
@@ -158,6 +160,7 @@ mod handlers {
mod generate_getter_or_setter;
mod generate_impl;
mod generate_is_empty_from_len;
+ mod generate_mut_trait_impl;
mod generate_new;
mod generate_delegate_methods;
mod generate_trait_from_impl;
@@ -193,6 +196,7 @@ mod handlers {
mod replace_try_expr_with_match;
mod replace_derive_with_manual_impl;
mod replace_if_let_with_match;
+ mod replace_is_method_with_if_let_method;
mod replace_method_eager_lazy;
mod replace_arith_op;
mod introduce_named_generic;
@@ -225,8 +229,10 @@ mod handlers {
add_return_type::add_return_type,
add_turbo_fish::add_turbo_fish,
apply_demorgan::apply_demorgan,
+ apply_demorgan::apply_demorgan_iterator,
auto_import::auto_import,
bind_unused_param::bind_unused_param,
+ bool_to_enum::bool_to_enum,
change_visibility::change_visibility,
convert_bool_then::convert_bool_then_to_if,
convert_bool_then::convert_if_to_bool_then,
@@ -237,6 +243,7 @@ mod handlers {
convert_iter_for_each_to_for::convert_for_loop_with_for_each,
convert_let_else_to_match::convert_let_else_to_match,
convert_match_to_let_else::convert_match_to_let_else,
+ convert_tuple_return_type_to_struct::convert_tuple_return_type_to_struct,
convert_named_struct_to_tuple_struct::convert_named_struct_to_tuple_struct,
convert_nested_function_to_closure::convert_nested_function_to_closure,
convert_to_guarded_return::convert_to_guarded_return,
@@ -268,6 +275,7 @@ mod handlers {
generate_function::generate_function,
generate_impl::generate_impl,
generate_impl::generate_trait_impl,
+ generate_mut_trait_impl::generate_mut_trait_impl,
generate_is_empty_from_len::generate_is_empty_from_len,
generate_new::generate_new,
generate_trait_from_impl::generate_trait_from_impl,
@@ -308,6 +316,7 @@ mod handlers {
replace_derive_with_manual_impl::replace_derive_with_manual_impl,
replace_if_let_with_match::replace_if_let_with_match,
replace_if_let_with_match::replace_match_with_if_let,
+ replace_is_method_with_if_let_method::replace_is_method_with_if_let_method,
replace_let_with_if_let::replace_let_with_if_let,
replace_method_eager_lazy::replace_with_eager_method,
replace_method_eager_lazy::replace_with_lazy_method,
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 cc3e251a8..25b3d6d9d 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs
@@ -30,6 +30,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
skip_glob_imports: true,
},
prefer_no_std: false,
+ prefer_prelude: true,
assist_emit_must_use: false,
};
@@ -44,6 +45,7 @@ pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig {
skip_glob_imports: true,
},
prefer_no_std: false,
+ prefer_prelude: true,
assist_emit_must_use: false,
};
@@ -98,6 +100,11 @@ pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
check(assist, ra_fixture, ExpectedResult::NotApplicable, None);
}
+#[track_caller]
+pub(crate) fn check_assist_not_applicable_by_label(assist: Handler, ra_fixture: &str, label: &str) {
+ check(assist, ra_fixture, ExpectedResult::NotApplicable, Some(label));
+}
+
/// Check assist in unresolved state. Useful to check assists for lazy computation.
#[track_caller]
pub(crate) fn check_assist_unresolved(assist: Handler, ra_fixture: &str) {
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 dfaa53449..da5822bba 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
@@ -245,6 +245,30 @@ fn main() {
}
#[test]
+fn doctest_apply_demorgan_iterator() {
+ check_doc_test(
+ "apply_demorgan_iterator",
+ r#####"
+//- minicore: iterator
+fn main() {
+ let arr = [1, 2, 3];
+ if !arr.into_iter().$0any(|num| num == 4) {
+ println!("foo");
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ let arr = [1, 2, 3];
+ if arr.into_iter().all(|num| num != 4) {
+ println!("foo");
+ }
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_auto_import() {
check_doc_test(
"auto_import",
@@ -281,6 +305,34 @@ fn some_function(x: i32) {
}
#[test]
+fn doctest_bool_to_enum() {
+ check_doc_test(
+ "bool_to_enum",
+ r#####"
+fn main() {
+ let $0bool = true;
+
+ if bool {
+ println!("foo");
+ }
+}
+"#####,
+ r#####"
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+fn main() {
+ let bool = Bool::True;
+
+ if bool == Bool::True {
+ println!("foo");
+ }
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_change_visibility() {
check_doc_test(
"change_visibility",
@@ -559,6 +611,33 @@ fn main() {
}
#[test]
+fn doctest_convert_tuple_return_type_to_struct() {
+ check_doc_test(
+ "convert_tuple_return_type_to_struct",
+ r#####"
+fn bar() {
+ let (a, b, c) = foo();
+}
+
+fn foo() -> ($0u32, u32, u32) {
+ (1, 2, 3)
+}
+"#####,
+ r#####"
+fn bar() {
+ let FooResult(a, b, c) = foo();
+}
+
+struct FooResult(u32, u32, u32);
+
+fn foo() -> FooResult {
+ FooResult(1, 2, 3)
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_convert_tuple_struct_to_named_struct() {
check_doc_test(
"convert_tuple_struct_to_named_struct",
@@ -1460,6 +1539,42 @@ impl MyStruct {
}
#[test]
+fn doctest_generate_mut_trait_impl() {
+ check_doc_test(
+ "generate_mut_trait_impl",
+ r#####"
+//- minicore: index
+pub enum Axis { X = 0, Y = 1, Z = 2 }
+
+impl<T> core::ops::Index$0<Axis> for [T; 3] {
+ type Output = T;
+
+ fn index(&self, index: Axis) -> &Self::Output {
+ &self[index as usize]
+ }
+}
+"#####,
+ r#####"
+pub enum Axis { X = 0, Y = 1, Z = 2 }
+
+$0impl<T> core::ops::IndexMut<Axis> for [T; 3] {
+ fn index_mut(&mut self, index: Axis) -> &mut Self::Output {
+ &self[index as usize]
+ }
+}
+
+impl<T> core::ops::Index<Axis> for [T; 3] {
+ type Output = T;
+
+ fn index(&self, index: Axis) -> &Self::Output {
+ &self[index as usize]
+ }
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_generate_new() {
check_doc_test(
"generate_new",
@@ -2480,6 +2595,25 @@ fn handle(action: Action) {
}
#[test]
+fn doctest_replace_is_some_with_if_let_some() {
+ check_doc_test(
+ "replace_is_some_with_if_let_some",
+ r#####"
+fn main() {
+ let x = Some(1);
+ if x.is_som$0e() {}
+}
+"#####,
+ r#####"
+fn main() {
+ let x = Some(1);
+ if let Some(${0:x}) = x {}
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_replace_let_with_if_let() {
check_doc_test(
"replace_let_with_if_let",
@@ -2850,6 +2984,8 @@ fn main() {
mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } }
"#####,
r#####"
+use std::ops::Add;
+
fn main() {
1.add(2);
}
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 a262570d9..f51e99a91 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
@@ -106,8 +106,18 @@ pub fn filter_assoc_items(
.iter()
.copied()
.filter(|assoc_item| {
- !(ignore_items == IgnoreAssocItems::DocHiddenAttrPresent
- && assoc_item.attrs(sema.db).has_doc_hidden())
+ if ignore_items == IgnoreAssocItems::DocHiddenAttrPresent
+ && assoc_item.attrs(sema.db).has_doc_hidden()
+ {
+ if let hir::AssocItem::Function(f) = assoc_item {
+ if !f.has_body(sema.db) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return true;
})
// Note: This throws away items with no source.
.filter_map(|assoc_item| {