summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide-assists
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:11:38 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:12:43 +0000
commitcf94bdc0742c13e2a0cac864c478b8626b266e1b (patch)
tree044670aa50cc5e2b4229aa0b6b3df6676730c0a6 /src/tools/rust-analyzer/crates/ide-assists
parentAdding debian version 1.65.0+dfsg1-2. (diff)
downloadrustc-cf94bdc0742c13e2a0cac864c478b8626b266e1b.tar.xz
rustc-cf94bdc0742c13e2a0cac864c478b8626b266e1b.zip
Merging upstream version 1.66.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-assists')
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/Cargo.toml2
-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_match_arms.rs17
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs23
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs822
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs35
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_constant.rs12
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs61
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs8
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_deref.rs18
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs52
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs18
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs39
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs19
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs141
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs325
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs15
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs23
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_setter.rs21
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_format_string_arg.rs296
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs7
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs3
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs2
-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/unwrap_tuple.rs159
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/lib.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests.rs14
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs91
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils.rs88
32 files changed, 1968 insertions, 359 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml b/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml
index fca09d384..57a41f3d9 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml
@@ -12,7 +12,7 @@ doctest = false
[dependencies]
cov-mark = "2.0.0-pre.1"
-itertools = "0.10.3"
+itertools = "0.10.5"
either = "1.7.0"
stdx = { path = "../stdx", version = "0.0.0" }
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 d4d148c77..60d1588a4 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
@@ -13,4 +13,5 @@ pub struct AssistConfig {
pub snippet_cap: Option<SnippetCap>,
pub allowed: Option<Vec<AssistKind>>,
pub insert_use: InsertUseConfig,
+ pub prefer_no_std: bool,
}
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 1a7919a5a..73f4db4e5 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
@@ -87,7 +87,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
.into_iter()
.filter_map(|variant| {
Some((
- build_pat(ctx.db(), module, variant)?,
+ build_pat(ctx.db(), module, variant, ctx.config.prefer_no_std)?,
variant.should_be_hidden(ctx.db(), module.krate()),
))
})
@@ -132,8 +132,9 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
let is_hidden = variants
.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));
+ let patterns = variants.into_iter().filter_map(|variant| {
+ build_pat(ctx.db(), module, variant, ctx.config.prefer_no_std)
+ });
(ast::Pat::from(make::tuple_pat(patterns)), is_hidden)
})
@@ -349,10 +350,16 @@ fn resolve_tuple_of_enum_def(
.collect()
}
-fn build_pat(db: &RootDatabase, module: hir::Module, var: ExtendedVariant) -> Option<ast::Pat> {
+fn build_pat(
+ db: &RootDatabase,
+ module: hir::Module,
+ var: ExtendedVariant,
+ prefer_no_std: bool,
+) -> Option<ast::Pat> {
match var {
ExtendedVariant::Variant(var) => {
- let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?);
+ let path =
+ mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var), prefer_no_std)?);
// 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() {
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 949cf3167..678dc877d 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
@@ -89,8 +89,11 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
// ```
pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
- let mut proposed_imports =
- import_assets.search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind);
+ let mut proposed_imports = import_assets.search_for_imports(
+ &ctx.sema,
+ ctx.config.insert_use.prefix_kind,
+ ctx.config.prefer_no_std,
+ );
if proposed_imports.is_empty() {
return None;
}
@@ -153,6 +156,8 @@ pub(super) fn find_importable_node(
{
ImportAssets::for_method_call(&method_under_caret, &ctx.sema)
.zip(Some(method_under_caret.syntax().clone().into()))
+ } else if let Some(_) = ctx.find_node_at_offset_with_descend::<ast::Param>() {
+ None
} else if let Some(pat) = ctx
.find_node_at_offset_with_descend::<ast::IdentPat>()
.filter(ast::IdentPat::is_simple_ident)
@@ -266,6 +271,20 @@ mod tests {
}
#[test]
+ fn ignore_parameter_name() {
+ check_assist_not_applicable(
+ auto_import,
+ r"
+ mod foo {
+ pub mod bar {}
+ }
+
+ fn foo(bar$0: &str) {}
+ ",
+ );
+ }
+
+ #[test]
fn prefer_shorter_paths() {
let before = r"
//- /main.rs crate:main deps:foo,bar
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 30f6dd41a..95d11abe8 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,7 @@ 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)?)
+ mod_path_to_ast(&module.find_use_path(ctx.db(), src_type_def, ctx.config.prefer_no_std)?)
};
let dest_type = match &ast_trait {
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs
new file mode 100644
index 000000000..8d11e0bac
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs
@@ -0,0 +1,822 @@
+use either::Either;
+use ide_db::defs::Definition;
+use itertools::Itertools;
+use syntax::{
+ ast::{self, AstNode, HasGenericParams, HasVisibility},
+ match_ast, SyntaxKind, SyntaxNode,
+};
+
+use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: convert_named_struct_to_tuple_struct
+//
+// Converts struct with named fields to tuple struct, and analogously for enum variants with named
+// fields.
+//
+// ```
+// struct Point$0 { x: f32, y: f32 }
+//
+// impl Point {
+// pub fn new(x: f32, y: f32) -> Self {
+// Point { x, y }
+// }
+//
+// pub fn x(&self) -> f32 {
+// self.x
+// }
+//
+// pub fn y(&self) -> f32 {
+// self.y
+// }
+// }
+// ```
+// ->
+// ```
+// struct Point(f32, f32);
+//
+// impl Point {
+// pub fn new(x: f32, y: f32) -> Self {
+// Point(x, y)
+// }
+//
+// pub fn x(&self) -> f32 {
+// self.0
+// }
+//
+// pub fn y(&self) -> f32 {
+// self.1
+// }
+// }
+// ```
+pub(crate) fn convert_named_struct_to_tuple_struct(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let strukt = ctx
+ .find_node_at_offset::<ast::Struct>()
+ .map(Either::Left)
+ .or_else(|| ctx.find_node_at_offset::<ast::Variant>().map(Either::Right))?;
+ let field_list = strukt.as_ref().either(|s| s.field_list(), |v| v.field_list())?;
+ let record_fields = match field_list {
+ ast::FieldList::RecordFieldList(it) => it,
+ ast::FieldList::TupleFieldList(_) => return None,
+ };
+ let strukt_def = match &strukt {
+ Either::Left(s) => Either::Left(ctx.sema.to_def(s)?),
+ Either::Right(v) => Either::Right(ctx.sema.to_def(v)?),
+ };
+ let target = strukt.as_ref().either(|s| s.syntax(), |v| v.syntax()).text_range();
+
+ acc.add(
+ AssistId("convert_named_struct_to_tuple_struct", AssistKind::RefactorRewrite),
+ "Convert to tuple struct",
+ target,
+ |edit| {
+ edit_field_references(ctx, edit, record_fields.fields());
+ edit_struct_references(ctx, edit, strukt_def);
+ edit_struct_def(ctx, edit, &strukt, record_fields);
+ },
+ )
+}
+
+fn edit_struct_def(
+ ctx: &AssistContext<'_>,
+ edit: &mut SourceChangeBuilder,
+ strukt: &Either<ast::Struct, ast::Variant>,
+ record_fields: ast::RecordFieldList,
+) {
+ let tuple_fields = record_fields
+ .fields()
+ .filter_map(|f| Some(ast::make::tuple_field(f.visibility(), f.ty()?)));
+ let tuple_fields = ast::make::tuple_field_list(tuple_fields);
+ let record_fields_text_range = record_fields.syntax().text_range();
+
+ edit.edit_file(ctx.file_id());
+ edit.replace(record_fields_text_range, tuple_fields.syntax().text());
+
+ if let Either::Left(strukt) = strukt {
+ if let Some(w) = strukt.where_clause() {
+ let mut where_clause = w.to_string();
+ if where_clause.ends_with(',') {
+ where_clause.pop();
+ }
+ where_clause.push(';');
+
+ edit.delete(w.syntax().text_range());
+ edit.insert(record_fields_text_range.end(), ast::make::tokens::single_newline().text());
+ edit.insert(record_fields_text_range.end(), where_clause);
+ edit.insert(record_fields_text_range.end(), ast::make::tokens::single_newline().text());
+
+ if let Some(tok) = strukt
+ .generic_param_list()
+ .and_then(|l| l.r_angle_token())
+ .and_then(|tok| tok.next_token())
+ .filter(|tok| tok.kind() == SyntaxKind::WHITESPACE)
+ {
+ edit.delete(tok.text_range());
+ }
+ } else {
+ edit.insert(record_fields_text_range.end(), ";");
+ }
+ }
+
+ if let Some(tok) = record_fields
+ .l_curly_token()
+ .and_then(|tok| tok.prev_token())
+ .filter(|tok| tok.kind() == SyntaxKind::WHITESPACE)
+ {
+ edit.delete(tok.text_range())
+ }
+}
+
+fn edit_struct_references(
+ ctx: &AssistContext<'_>,
+ edit: &mut SourceChangeBuilder,
+ strukt: Either<hir::Struct, hir::Variant>,
+) {
+ let strukt_def = match strukt {
+ Either::Left(s) => Definition::Adt(hir::Adt::Struct(s)),
+ Either::Right(v) => Definition::Variant(v),
+ };
+ let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
+
+ let edit_node = |edit: &mut SourceChangeBuilder, node: SyntaxNode| -> Option<()> {
+ match_ast! {
+ match node {
+ ast::RecordPat(record_struct_pat) => {
+ edit.replace(
+ record_struct_pat.syntax().text_range(),
+ ast::make::tuple_struct_pat(
+ record_struct_pat.path()?,
+ record_struct_pat
+ .record_pat_field_list()?
+ .fields()
+ .filter_map(|pat| pat.pat())
+ )
+ .to_string()
+ );
+ },
+ ast::RecordExpr(record_expr) => {
+ let path = record_expr.path()?;
+ let args = record_expr
+ .record_expr_field_list()?
+ .fields()
+ .filter_map(|f| f.expr())
+ .join(", ");
+
+ edit.replace(record_expr.syntax().text_range(), format!("{path}({args})"));
+ },
+ _ => return None,
+ }
+ }
+ Some(())
+ };
+
+ for (file_id, refs) in usages {
+ edit.edit_file(file_id);
+ for r in refs {
+ for node in r.name.syntax().ancestors() {
+ if edit_node(edit, node).is_some() {
+ break;
+ }
+ }
+ }
+ }
+}
+
+fn edit_field_references(
+ ctx: &AssistContext<'_>,
+ edit: &mut SourceChangeBuilder,
+ fields: impl Iterator<Item = ast::RecordField>,
+) {
+ for (index, field) in fields.enumerate() {
+ let field = match ctx.sema.to_def(&field) {
+ Some(it) => it,
+ None => continue,
+ };
+ let def = Definition::Field(field);
+ let usages = def.usages(&ctx.sema).all();
+ for (file_id, refs) in usages {
+ edit.edit_file(file_id);
+ for r in refs {
+ if let Some(name_ref) = r.name.as_name_ref() {
+ // Only edit the field reference if it's part of a `.field` access
+ if name_ref.syntax().parent().and_then(ast::FieldExpr::cast).is_some() {
+ edit.replace(name_ref.syntax().text_range(), index.to_string());
+ }
+ }
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn not_applicable_other_than_record_struct() {
+ check_assist_not_applicable(convert_named_struct_to_tuple_struct, r#"struct Foo$0(u32)"#);
+ check_assist_not_applicable(convert_named_struct_to_tuple_struct, r#"struct Foo$0;"#);
+ }
+
+ #[test]
+ fn convert_simple_struct() {
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+struct Inner;
+struct A$0 { inner: Inner }
+
+impl A {
+ fn new(inner: Inner) -> A {
+ A { inner }
+ }
+
+ fn new_with_default() -> A {
+ A::new(Inner)
+ }
+
+ fn into_inner(self) -> Inner {
+ self.inner
+ }
+}"#,
+ r#"
+struct Inner;
+struct A(Inner);
+
+impl A {
+ fn new(inner: Inner) -> A {
+ A(inner)
+ }
+
+ fn new_with_default() -> A {
+ A::new(Inner)
+ }
+
+ fn into_inner(self) -> Inner {
+ self.0
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_struct_referenced_via_self_kw() {
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+struct Inner;
+struct A$0 { inner: Inner }
+
+impl A {
+ fn new(inner: Inner) -> Self {
+ Self { inner }
+ }
+
+ fn new_with_default() -> Self {
+ Self::new(Inner)
+ }
+
+ fn into_inner(self) -> Inner {
+ self.inner
+ }
+}"#,
+ r#"
+struct Inner;
+struct A(Inner);
+
+impl A {
+ fn new(inner: Inner) -> Self {
+ Self(inner)
+ }
+
+ fn new_with_default() -> Self {
+ Self::new(Inner)
+ }
+
+ fn into_inner(self) -> Inner {
+ self.0
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_destructured_struct() {
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+struct Inner;
+struct A$0 { inner: Inner }
+
+impl A {
+ fn into_inner(self) -> Inner {
+ let A { inner: a } = self;
+ a
+ }
+
+ fn into_inner_via_self(self) -> Inner {
+ let Self { inner } = self;
+ inner
+ }
+}"#,
+ r#"
+struct Inner;
+struct A(Inner);
+
+impl A {
+ fn into_inner(self) -> Inner {
+ let A(a) = self;
+ a
+ }
+
+ fn into_inner_via_self(self) -> Inner {
+ let Self(inner) = self;
+ inner
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_struct_with_visibility() {
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+struct A$0 {
+ pub first: u32,
+ pub(crate) second: u64
+}
+
+impl A {
+ fn new() -> A {
+ A { first: 42, second: 42 }
+ }
+
+ fn into_first(self) -> u32 {
+ self.first
+ }
+
+ fn into_second(self) -> u64 {
+ self.second
+ }
+}"#,
+ r#"
+struct A(pub u32, pub(crate) u64);
+
+impl A {
+ fn new() -> A {
+ A(42, 42)
+ }
+
+ fn into_first(self) -> u32 {
+ self.0
+ }
+
+ fn into_second(self) -> u64 {
+ self.1
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_struct_with_wrapped_references() {
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+struct Inner$0 { uint: u32 }
+struct Outer { inner: Inner }
+
+impl Outer {
+ fn new() -> Self {
+ Self { inner: Inner { uint: 42 } }
+ }
+
+ fn into_inner(self) -> u32 {
+ self.inner.uint
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer { inner: Inner { uint: x } } = self;
+ x
+ }
+}"#,
+ r#"
+struct Inner(u32);
+struct Outer { inner: Inner }
+
+impl Outer {
+ fn new() -> Self {
+ Self { inner: Inner(42) }
+ }
+
+ fn into_inner(self) -> u32 {
+ self.inner.0
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer { inner: Inner(x) } = self;
+ x
+ }
+}"#,
+ );
+
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+struct Inner { uint: u32 }
+struct Outer$0 { inner: Inner }
+
+impl Outer {
+ fn new() -> Self {
+ Self { inner: Inner { uint: 42 } }
+ }
+
+ fn into_inner(self) -> u32 {
+ self.inner.uint
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer { inner: Inner { uint: x } } = self;
+ x
+ }
+}"#,
+ r#"
+struct Inner { uint: u32 }
+struct Outer(Inner);
+
+impl Outer {
+ fn new() -> Self {
+ Self(Inner { uint: 42 })
+ }
+
+ fn into_inner(self) -> u32 {
+ self.0.uint
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer(Inner { uint: x }) = self;
+ x
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_struct_with_multi_file_references() {
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+//- /main.rs
+struct Inner;
+struct A$0 { inner: Inner }
+
+mod foo;
+
+//- /foo.rs
+use crate::{A, Inner};
+fn f() {
+ let a = A { inner: Inner };
+}
+"#,
+ r#"
+//- /main.rs
+struct Inner;
+struct A(Inner);
+
+mod foo;
+
+//- /foo.rs
+use crate::{A, Inner};
+fn f() {
+ let a = A(Inner);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_struct_with_where_clause() {
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+struct Wrap$0<T>
+where
+ T: Display,
+{ field1: T }
+"#,
+ r#"
+struct Wrap<T>(T)
+where
+ T: Display;
+
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_other_than_record_variant() {
+ check_assist_not_applicable(
+ convert_named_struct_to_tuple_struct,
+ r#"enum Enum { Variant$0(usize) };"#,
+ );
+ check_assist_not_applicable(
+ convert_named_struct_to_tuple_struct,
+ r#"enum Enum { Variant$0 }"#,
+ );
+ }
+
+ #[test]
+ fn convert_simple_variant() {
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+enum A {
+ $0Variant { field1: usize },
+}
+
+impl A {
+ fn new(value: usize) -> A {
+ A::Variant { field1: value }
+ }
+
+ fn new_with_default() -> A {
+ A::new(Default::default())
+ }
+
+ fn value(self) -> usize {
+ match self {
+ A::Variant { field1: value } => value,
+ }
+ }
+}"#,
+ r#"
+enum A {
+ Variant(usize),
+}
+
+impl A {
+ fn new(value: usize) -> A {
+ A::Variant(value)
+ }
+
+ fn new_with_default() -> A {
+ A::new(Default::default())
+ }
+
+ fn value(self) -> usize {
+ match self {
+ A::Variant(value) => value,
+ }
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_variant_referenced_via_self_kw() {
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+enum A {
+ $0Variant { field1: usize },
+}
+
+impl A {
+ fn new(value: usize) -> A {
+ Self::Variant { field1: value }
+ }
+
+ fn new_with_default() -> A {
+ Self::new(Default::default())
+ }
+
+ fn value(self) -> usize {
+ match self {
+ Self::Variant { field1: value } => value,
+ }
+ }
+}"#,
+ r#"
+enum A {
+ Variant(usize),
+}
+
+impl A {
+ fn new(value: usize) -> A {
+ Self::Variant(value)
+ }
+
+ fn new_with_default() -> A {
+ Self::new(Default::default())
+ }
+
+ fn value(self) -> usize {
+ match self {
+ Self::Variant(value) => value,
+ }
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_destructured_variant() {
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+enum A {
+ $0Variant { field1: usize },
+}
+
+impl A {
+ fn into_inner(self) -> usize {
+ let A::Variant { field1: first } = self;
+ first
+ }
+
+ fn into_inner_via_self(self) -> usize {
+ let Self::Variant { field1: first } = self;
+ first
+ }
+}"#,
+ r#"
+enum A {
+ Variant(usize),
+}
+
+impl A {
+ fn into_inner(self) -> usize {
+ let A::Variant(first) = self;
+ first
+ }
+
+ fn into_inner_via_self(self) -> usize {
+ let Self::Variant(first) = self;
+ first
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_variant_with_wrapped_references() {
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+enum Inner {
+ $0Variant { field1: usize },
+}
+enum Outer {
+ Variant(Inner),
+}
+
+impl Outer {
+ fn new() -> Self {
+ Self::Variant(Inner::Variant { field1: 42 })
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer::Variant(Inner::Variant { field1: x }) = self;
+ x
+ }
+}"#,
+ r#"
+enum Inner {
+ Variant(usize),
+}
+enum Outer {
+ Variant(Inner),
+}
+
+impl Outer {
+ fn new() -> Self {
+ Self::Variant(Inner::Variant(42))
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer::Variant(Inner::Variant(x)) = self;
+ x
+ }
+}"#,
+ );
+
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+enum Inner {
+ Variant(usize),
+}
+enum Outer {
+ $0Variant { field1: Inner },
+}
+
+impl Outer {
+ fn new() -> Self {
+ Self::Variant { field1: Inner::Variant(42) }
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer::Variant { field1: Inner::Variant(x) } = self;
+ x
+ }
+}"#,
+ r#"
+enum Inner {
+ Variant(usize),
+}
+enum Outer {
+ Variant(Inner),
+}
+
+impl Outer {
+ fn new() -> Self {
+ Self::Variant(Inner::Variant(42))
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer::Variant(Inner::Variant(x)) = self;
+ x
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_variant_with_multi_file_references() {
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+//- /main.rs
+struct Inner;
+enum A {
+ $0Variant { field1: Inner },
+}
+
+mod foo;
+
+//- /foo.rs
+use crate::{A, Inner};
+fn f() {
+ let a = A::Variant { field1: Inner };
+}
+"#,
+ r#"
+//- /main.rs
+struct Inner;
+enum A {
+ Variant(Inner),
+}
+
+mod foo;
+
+//- /foo.rs
+use crate::{A, Inner};
+fn f() {
+ let a = A::Variant(Inner);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_directly_used_variant() {
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+//- /main.rs
+struct Inner;
+enum A {
+ $0Variant { field1: Inner },
+}
+
+mod foo;
+
+//- /foo.rs
+use crate::{A::Variant, Inner};
+fn f() {
+ let a = Variant { field1: Inner };
+}
+"#,
+ r#"
+//- /main.rs
+struct Inner;
+enum A {
+ Variant(Inner),
+}
+
+mod foo;
+
+//- /foo.rs
+use crate::{A::Variant, Inner};
+fn f() {
+ let a = Variant(Inner);
+}
+"#,
+ );
+ }
+}
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 52a55ead3..d6c8ea785 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
@@ -152,6 +152,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
ctx.sema.db,
ModuleDef::from(control_flow_enum),
ctx.config.insert_use.prefix_kind,
+ ctx.config.prefer_no_std,
);
if let Some(mod_path) = mod_path {
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 ddc2052e7..970e948df 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
@@ -9,7 +9,7 @@ use ide_db::{
search::FileReference,
FxHashSet, RootDatabase,
};
-use itertools::{Itertools, Position};
+use itertools::Itertools;
use syntax::{
ast::{
self, edit::IndentLevel, edit_in_place::Indent, make, AstNode, HasAttrs, HasGenericParams,
@@ -298,37 +298,7 @@ fn update_variant(variant: &ast::Variant, generics: Option<ast::GenericParamList
let name = variant.name()?;
let ty = generics
.filter(|generics| generics.generic_params().count() > 0)
- .map(|generics| {
- let mut generic_str = String::with_capacity(8);
-
- for (p, more) in generics.generic_params().with_position().map(|p| match p {
- Position::First(p) | Position::Middle(p) => (p, true),
- Position::Last(p) | Position::Only(p) => (p, false),
- }) {
- match p {
- ast::GenericParam::ConstParam(konst) => {
- if let Some(name) = konst.name() {
- generic_str.push_str(name.text().as_str());
- }
- }
- ast::GenericParam::LifetimeParam(lt) => {
- if let Some(lt) = lt.lifetime() {
- generic_str.push_str(lt.text().as_str());
- }
- }
- ast::GenericParam::TypeParam(ty) => {
- if let Some(name) = ty.name() {
- generic_str.push_str(name.text().as_str());
- }
- }
- }
- if more {
- generic_str.push_str(", ");
- }
- }
-
- make::ty(&format!("{}<{}>", &name.text(), &generic_str))
- })
+ .map(|generics| make::ty(&format!("{}{}", &name.text(), generics.to_generic_args())))
.unwrap_or_else(|| make::ty(&name.text()));
// change from a record to a tuple field list
@@ -409,6 +379,7 @@ fn process_references(
ctx.sema.db,
*enum_module_def,
ctx.config.insert_use.prefix_kind,
+ ctx.config.prefer_no_std,
);
if let Some(mut mod_path) = mod_path {
mod_path.pop_segment();
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 eaa6de73e..ccdfcb0d9 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
@@ -77,7 +77,7 @@ pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
target_data_for_generate_constant(ctx, current_module, constant_module).unwrap_or_else(
|| {
let indent = IndentLevel::from_node(statement.syntax());
- (statement.syntax().text_range().start(), indent, None, format!("\n{}", indent))
+ (statement.syntax().text_range().start(), indent, None, format!("\n{indent}"))
},
);
@@ -90,7 +90,7 @@ pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
if let Some(file_id) = file_id {
builder.edit_file(file_id);
}
- builder.insert(offset, format!("{}{}", text, post_string));
+ builder.insert(offset, format!("{text}{post_string}"));
},
)
}
@@ -103,13 +103,13 @@ fn get_text_for_generate_constant(
) -> Option<String> {
let constant_token = not_exist_name_ref.pop()?;
let vis = if not_exist_name_ref.len() == 0 && !outer_exists { "" } else { "\npub " };
- let mut text = format!("{}const {}: {} = $0;", vis, constant_token, type_name);
+ let mut text = format!("{vis}const {constant_token}: {type_name} = $0;");
while let Some(name_ref) = not_exist_name_ref.pop() {
let vis = if not_exist_name_ref.len() == 0 && !outer_exists { "" } else { "\npub " };
text = text.replace("\n", "\n ");
- text = format!("{}mod {} {{{}\n}}", vis, name_ref.to_string(), text);
+ text = format!("{vis}mod {name_ref} {{{text}\n}}");
}
- Some(text.replace("\n", &format!("\n{}", indent)))
+ Some(text.replace("\n", &format!("\n{indent}")))
}
fn target_data_for_generate_constant(
@@ -134,7 +134,7 @@ fn target_data_for_generate_constant(
.find(|it| it.kind() == SyntaxKind::WHITESPACE && it.to_string().contains("\n"))
.is_some();
let post_string =
- if siblings_has_newline { format!("{}", indent) } else { format!("\n{}", indent) };
+ if siblings_has_newline { format!("{indent}") } else { format!("\n{indent}") };
Some((offset, indent + 1, Some(file_id), post_string))
}
_ => Some((TextSize::from(0), 0.into(), Some(file_id), "\n".into())),
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs
index 5e9995a98..a6e3d49e0 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs
@@ -55,12 +55,11 @@ pub(crate) fn generate_default_from_enum_variant(
let buf = format!(
r#"
-impl Default for {0} {{
+impl Default for {enum_name} {{
fn default() -> Self {{
- Self::{1}
+ Self::{variant_name}
}}
}}"#,
- enum_name, variant_name
);
edit.insert(start_offset, buf);
},
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs
index cbd33de19..49d9fd707 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs
@@ -1,8 +1,7 @@
use ide_db::famous_defs::FamousDefs;
-use itertools::Itertools;
use stdx::format_to;
use syntax::{
- ast::{self, HasGenericParams, HasName, HasTypeBounds, Impl},
+ ast::{self, make, HasGenericParams, HasName, Impl},
AstNode,
};
@@ -77,45 +76,47 @@ pub(crate) fn generate_default_from_new(acc: &mut Assists, ctx: &AssistContext<'
)
}
+// FIXME: based on from utils::generate_impl_text_inner
fn generate_trait_impl_text_from_impl(impl_: &ast::Impl, trait_text: &str, code: &str) -> String {
- let generic_params = impl_.generic_param_list();
- let mut buf = String::with_capacity(code.len());
- buf.push_str("\n\n");
- buf.push_str("impl");
-
- if let Some(generic_params) = &generic_params {
- let lifetimes = generic_params.lifetime_params().map(|lt| format!("{}", lt.syntax()));
- let toc_params = generic_params.type_or_const_params().map(|toc_param| match toc_param {
- ast::TypeOrConstParam::Type(type_param) => {
- let mut buf = String::new();
- if let Some(it) = type_param.name() {
- format_to!(buf, "{}", it.syntax());
- }
- if let Some(it) = type_param.colon_token() {
- format_to!(buf, "{} ", it);
+ let impl_ty = impl_.self_ty().unwrap();
+ let generic_params = impl_.generic_param_list().map(|generic_params| {
+ let lifetime_params =
+ generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam);
+ let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| {
+ // remove defaults since they can't be specified in impls
+ match param {
+ ast::TypeOrConstParam::Type(param) => {
+ let param = param.clone_for_update();
+ param.remove_default();
+ Some(ast::GenericParam::TypeParam(param))
}
- if let Some(it) = type_param.type_bound_list() {
- format_to!(buf, "{}", it.syntax());
+ ast::TypeOrConstParam::Const(param) => {
+ let param = param.clone_for_update();
+ param.remove_default();
+ Some(ast::GenericParam::ConstParam(param))
}
- buf
}
- ast::TypeOrConstParam::Const(const_param) => const_param.syntax().to_string(),
});
- let generics = lifetimes.chain(toc_params).format(", ");
- format_to!(buf, "<{}>", generics);
- }
- buf.push(' ');
- buf.push_str(trait_text);
- buf.push_str(" for ");
- buf.push_str(&impl_.self_ty().unwrap().syntax().text().to_string());
+ make::generic_param_list(itertools::chain(lifetime_params, ty_or_const_params))
+ });
+
+ let mut buf = String::with_capacity(code.len());
+ buf.push_str("\n\n");
+
+ // `impl{generic_params} {trait_text} for {impl_.self_ty()}`
+ buf.push_str("impl");
+ if let Some(generic_params) = &generic_params {
+ format_to!(buf, "{generic_params}")
+ }
+ format_to!(buf, " {trait_text} for {impl_ty}");
match impl_.where_clause() {
Some(where_clause) => {
- format_to!(buf, "\n{}\n{{\n{}\n}}", where_clause, code);
+ format_to!(buf, "\n{where_clause}\n{{\n{code}\n}}");
}
None => {
- format_to!(buf, " {{\n{}\n}}", code);
+ format_to!(buf, " {{\n{code}\n}}");
}
}
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 85b193663..ceae80755 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
@@ -51,14 +51,14 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
Some(field) => {
let field_name = field.name()?;
let field_ty = field.ty()?;
- (format!("{}", field_name), field_ty, field.syntax().text_range())
+ (field_name.to_string(), field_ty, field.syntax().text_range())
}
None => {
let field = ctx.find_node_at_offset::<ast::TupleField>()?;
let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?;
let field_list_index = field_list.fields().position(|it| it == field)?;
let field_ty = field.ty()?;
- (format!("{}", field_list_index), field_ty, field.syntax().text_range())
+ (field_list_index.to_string(), field_ty, field.syntax().text_range())
}
};
@@ -77,7 +77,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
for method in methods {
let adt = ast::Adt::Struct(strukt.clone());
let name = method.name(ctx.db()).to_string();
- let impl_def = find_struct_impl(ctx, &adt, &name).flatten();
+ let impl_def = find_struct_impl(ctx, &adt, &[name]).flatten();
acc.add_group(
&GroupLabel("Generate delegate methods…".to_owned()),
AssistId("generate_delegate_methods", AssistKind::Generate),
@@ -151,7 +151,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
Some(cap) => {
let offset = strukt.syntax().text_range().end();
let snippet = render_snippet(cap, impl_def.syntax(), cursor);
- let snippet = format!("\n\n{}", snippet);
+ let snippet = format!("\n\n{snippet}");
builder.insert_snippet(cap, offset, snippet);
}
None => {
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 b48463512..55b7afb3d 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,14 +58,15 @@ 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_))?;
+ let trait_path =
+ module.find_use_path(ctx.db(), ModuleDef::Trait(trait_), ctx.config.prefer_no_std)?;
let field_type = field.ty()?;
let field_name = field.name()?;
let target = field.syntax().text_range();
acc.add(
AssistId("generate_deref", AssistKind::Generate),
- format!("Generate `{:?}` impl using `{}`", deref_type_to_generate, field_name),
+ format!("Generate `{deref_type_to_generate:?}` impl using `{field_name}`"),
target,
|edit| {
generate_edit(
@@ -98,13 +99,14 @@ 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_))?;
+ let trait_path =
+ module.find_use_path(ctx.db(), ModuleDef::Trait(trait_), ctx.config.prefer_no_std)?;
let field_type = field.ty()?;
let target = field.syntax().text_range();
acc.add(
AssistId("generate_deref", AssistKind::Generate),
- format!("Generate `{:?}` impl using `{}`", deref_type_to_generate, field.syntax()),
+ format!("Generate `{deref_type_to_generate:?}` impl using `{field}`"),
target,
|edit| {
generate_edit(
@@ -130,18 +132,16 @@ fn generate_edit(
let start_offset = strukt.syntax().text_range().end();
let impl_code = match deref_type {
DerefType::Deref => format!(
- r#" type Target = {0};
+ r#" type Target = {field_type_syntax};
fn deref(&self) -> &Self::Target {{
- &self.{1}
+ &self.{field_name}
}}"#,
- field_type_syntax, field_name
),
DerefType::DerefMut => format!(
r#" fn deref_mut(&mut self) -> &mut Self::Target {{
- &mut self.{}
+ &mut self.{field_name}
}}"#,
- field_name
),
};
let strukt_adt = ast::Adt::Struct(strukt);
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs
index c91141f8e..b8415c72a 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs
@@ -139,40 +139,44 @@ fn make_example_for_fn(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<St
let mut example = String::new();
+ let use_path = build_path(ast_func, ctx)?;
let is_unsafe = ast_func.unsafe_token().is_some();
let param_list = ast_func.param_list()?;
let ref_mut_params = ref_mut_params(&param_list);
let self_name = self_name(ast_func);
- format_to!(example, "use {};\n\n", build_path(ast_func, ctx)?);
+ format_to!(example, "use {use_path};\n\n");
if let Some(self_name) = &self_name {
- if let Some(mtbl) = is_ref_mut_self(ast_func) {
- let mtbl = if mtbl == true { " mut" } else { "" };
- format_to!(example, "let{} {} = ;\n", mtbl, self_name);
+ if let Some(mut_) = is_ref_mut_self(ast_func) {
+ let mut_ = if mut_ == true { "mut " } else { "" };
+ format_to!(example, "let {mut_}{self_name} = ;\n");
}
}
for param_name in &ref_mut_params {
- format_to!(example, "let mut {} = ;\n", param_name);
+ format_to!(example, "let mut {param_name} = ;\n");
}
// Call the function, check result
let function_call = function_call(ast_func, &param_list, self_name.as_deref(), is_unsafe)?;
if returns_a_value(ast_func, ctx) {
if count_parameters(&param_list) < 3 {
- format_to!(example, "assert_eq!({}, );\n", function_call);
+ format_to!(example, "assert_eq!({function_call}, );\n");
} else {
- format_to!(example, "let result = {};\n", function_call);
+ format_to!(example, "let result = {function_call};\n");
example.push_str("assert_eq!(result, );\n");
}
} else {
- format_to!(example, "{};\n", function_call);
+ format_to!(example, "{function_call};\n");
}
// Check the mutated values
- if is_ref_mut_self(ast_func) == Some(true) {
- format_to!(example, "assert_eq!({}, );", self_name?);
+ if let Some(self_name) = &self_name {
+ if is_ref_mut_self(ast_func) == Some(true) {
+ format_to!(example, "assert_eq!({self_name}, );");
+ }
}
for param_name in &ref_mut_params {
- format_to!(example, "assert_eq!({}, );", param_name);
+ format_to!(example, "assert_eq!({param_name}, );");
}
+
Some(example)
}
@@ -189,7 +193,8 @@ fn introduction_builder(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<S
let intro_for_new = || {
let is_new = name == "new";
if is_new && ret_ty == self_ty {
- Some(format!("Creates a new [`{}`].", linkable_self_ty?))
+ let self_ty = linkable_self_ty?;
+ Some(format!("Creates a new [`{self_ty}`]."))
} else {
None
}
@@ -214,7 +219,9 @@ fn introduction_builder(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<S
} else {
""
};
- Some(format!("Returns{reference} the {what} of this [`{}`].", linkable_self_ty?))
+
+ let self_ty = linkable_self_ty?;
+ Some(format!("Returns{reference} the {what} of this [`{self_ty}`]."))
}
_ => None,
};
@@ -228,7 +235,9 @@ fn introduction_builder(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<S
if what == "len" {
what = "length".into()
};
- Some(format!("Sets the {what} of this [`{}`].", linkable_self_ty?))
+
+ let self_ty = linkable_self_ty?;
+ Some(format!("Sets the {what} of this [`{self_ty}`]."))
};
if let Some(intro) = intro_for_new() {
@@ -404,7 +413,7 @@ fn arguments_from_params(param_list: &ast::ParamList) -> String {
// instance `TuplePat`) could be managed later.
Some(ast::Pat::IdentPat(ident_pat)) => match ident_pat.name() {
Some(name) => match is_a_ref_mut_param(&param) {
- true => format!("&mut {}", name),
+ true => format!("&mut {name}"),
false => name.to_string(),
},
None => "_".to_string(),
@@ -424,14 +433,15 @@ fn function_call(
let name = ast_func.name()?;
let arguments = arguments_from_params(param_list);
let function_call = if param_list.self_param().is_some() {
- format!("{}.{}({})", self_name?, name, arguments)
+ let self_ = self_name?;
+ format!("{self_}.{name}({arguments})")
} else if let Some(implementation) = self_partial_type(ast_func) {
- format!("{}::{}({})", implementation, name, arguments)
+ format!("{implementation}::{name}({arguments})")
} else {
- format!("{}({})", name, arguments)
+ format!("{name}({arguments})")
};
match is_unsafe {
- true => Some(format!("unsafe {{ {} }}", function_call)),
+ true => Some(format!("unsafe {{ {function_call} }}")),
false => Some(function_call),
}
}
@@ -469,8 +479,8 @@ fn build_path(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<String> {
.unwrap_or_else(|| "*".into());
let module_def: ModuleDef = ctx.sema.to_def(ast_func)?.module(ctx.db()).into();
match module_def.canonical_path(ctx.db()) {
- Some(path) => Some(format!("{}::{}::{}", crate_name, path, leaf)),
- None => Some(format!("{}::{}", crate_name, leaf)),
+ Some(path) => Some(format!("{crate_name}::{path}::{leaf}")),
+ None => Some(format!("{crate_name}::{leaf}")),
}
}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs
index 52d27d8a7..63e91b835 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs
@@ -52,7 +52,7 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext<'_>
let fn_name = format!("is_{}", &to_lower_snake_case(&variant_name.text()));
// Return early if we've found an existing new fn
- let impl_def = find_struct_impl(ctx, &parent_enum, &fn_name)?;
+ let impl_def = find_struct_impl(ctx, &parent_enum, &[fn_name.clone()])?;
let target = variant.syntax().text_range();
acc.add_group(
@@ -61,21 +61,15 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext<'_>
"Generate an `is_` method for this enum variant",
target,
|builder| {
- let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v));
+ let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} "));
let method = format!(
- " /// Returns `true` if the {} is [`{variant}`].
+ " /// Returns `true` if the {enum_lowercase_name} is [`{variant_name}`].
///
- /// [`{variant}`]: {}::{variant}
+ /// [`{variant_name}`]: {enum_name}::{variant_name}
#[must_use]
- {}fn {}(&self) -> bool {{
- matches!(self, Self::{variant}{})
+ {vis}fn {fn_name}(&self) -> bool {{
+ matches!(self, Self::{variant_name}{pattern_suffix})
}}",
- enum_lowercase_name,
- enum_name,
- vis,
- fn_name,
- pattern_suffix,
- variant = variant_name
);
add_method_to_adt(builder, &parent_enum, impl_def, &method);
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs
index b19aa0f65..bdd3cf4f0 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs
@@ -116,6 +116,14 @@ fn generate_enum_projection_method(
assist_description: &str,
props: ProjectionProps,
) -> Option<()> {
+ let ProjectionProps {
+ fn_name_prefix,
+ self_param,
+ return_prefix,
+ return_suffix,
+ happy_case,
+ sad_case,
+ } = props;
let variant = ctx.find_node_at_offset::<ast::Variant>()?;
let variant_name = variant.name()?;
let parent_enum = ast::Adt::Enum(variant.parent_enum());
@@ -125,7 +133,7 @@ fn generate_enum_projection_method(
let (field,) = record.fields().collect_tuple()?;
let name = field.name()?.to_string();
let ty = field.ty()?;
- let pattern_suffix = format!(" {{ {} }}", name);
+ let pattern_suffix = format!(" {{ {name} }}");
(pattern_suffix, ty, name)
}
ast::StructKind::Tuple(tuple) => {
@@ -136,11 +144,10 @@ fn generate_enum_projection_method(
ast::StructKind::Unit => return None,
};
- let fn_name =
- format!("{}_{}", props.fn_name_prefix, &to_lower_snake_case(&variant_name.text()));
+ let fn_name = format!("{}_{}", fn_name_prefix, &to_lower_snake_case(&variant_name.text()));
// Return early if we've found an existing new fn
- let impl_def = find_struct_impl(ctx, &parent_enum, &fn_name)?;
+ let impl_def = find_struct_impl(ctx, &parent_enum, &[fn_name.clone()])?;
let target = variant.syntax().text_range();
acc.add_group(
@@ -149,27 +156,15 @@ fn generate_enum_projection_method(
assist_description,
target,
|builder| {
- let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v));
+ let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} "));
let method = format!(
- " {0}fn {1}({2}) -> {3}{4}{5} {{
- if let Self::{6}{7} = self {{
- {8}({9})
+ " {vis}fn {fn_name}({self_param}) -> {return_prefix}{field_type}{return_suffix} {{
+ if let Self::{variant_name}{pattern_suffix} = self {{
+ {happy_case}({bound_name})
}} else {{
- {10}
+ {sad_case}
}}
- }}",
- vis,
- fn_name,
- props.self_param,
- props.return_prefix,
- field_type.syntax(),
- props.return_suffix,
- variant_name,
- pattern_suffix,
- props.happy_case,
- bound_name,
- props.sad_case,
- );
+ }}");
add_method_to_adt(builder, &parent_enum, impl_def, &method);
},
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs
index 507ea012b..7c81d2c6a 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs
@@ -56,23 +56,18 @@ pub(crate) fn generate_from_impl_for_enum(
target,
|edit| {
let start_offset = variant.parent_enum().syntax().text_range().end();
- let from_trait = format!("From<{}>", field_type.syntax());
+ let from_trait = format!("From<{field_type}>");
let impl_code = if let Some(name) = field_name {
format!(
- r#" fn from({0}: {1}) -> Self {{
- Self::{2} {{ {0} }}
- }}"#,
- name.text(),
- field_type.syntax(),
- variant_name,
+ r#" fn from({name}: {field_type}) -> Self {{
+ Self::{variant_name} {{ {name} }}
+ }}"#
)
} else {
format!(
- r#" fn from(v: {}) -> Self {{
- Self::{}(v)
- }}"#,
- field_type.syntax(),
- variant_name,
+ r#" fn from(v: {field_type}) -> Self {{
+ Self::{variant_name}(v)
+ }}"#
)
};
let from_impl = generate_trait_impl_text(&enum_, &from_trait, &impl_code);
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 e26c76da1..c229127e4 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,4 +1,4 @@
-use hir::{HasSource, HirDisplay, Module, Semantics, TypeInfo};
+use hir::{Adt, HasSource, HirDisplay, Module, Semantics, TypeInfo};
use ide_db::{
base_db::FileId,
defs::{Definition, NameRefClass},
@@ -145,7 +145,8 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
return None;
}
let (impl_, file) = get_adt_source(ctx, &adt, fn_name.text().as_str())?;
- let (target, insert_offset) = get_method_target(ctx, &target_module, &impl_)?;
+ let (target, insert_offset) = get_method_target(ctx, &impl_, &adt)?;
+
let function_builder =
FunctionBuilder::from_method_call(ctx, &call, &fn_name, target_module, target)?;
let text_range = call.syntax().text_range();
@@ -174,10 +175,11 @@ fn add_func_to_accumulator(
label: String,
) -> Option<()> {
acc.add(AssistId("generate_function", AssistKind::Generate), label, text_range, |builder| {
- let function_template = function_builder.render();
+ let indent = IndentLevel::from_node(function_builder.target.syntax());
+ let function_template = function_builder.render(adt_name.is_some());
let mut func = function_template.to_string(ctx.config.snippet_cap);
if let Some(name) = adt_name {
- func = format!("\nimpl {} {{\n{}\n}}", name, func);
+ func = format!("\n{indent}impl {name} {{\n{func}\n{indent}}}");
}
builder.edit_file(file);
match ctx.config.snippet_cap {
@@ -196,7 +198,7 @@ fn get_adt_source(
let file = ctx.sema.parse(range.file_id);
let adt_source =
ctx.sema.find_node_at_offset_with_macros(file.syntax(), range.range.start())?;
- find_struct_impl(ctx, &adt_source, fn_name).map(|impl_| (impl_, range.file_id))
+ find_struct_impl(ctx, &adt_source, &[fn_name.to_string()]).map(|impl_| (impl_, range.file_id))
}
struct FunctionTemplate {
@@ -210,23 +212,26 @@ struct FunctionTemplate {
impl FunctionTemplate {
fn to_string(&self, cap: Option<SnippetCap>) -> String {
+ let Self { leading_ws, fn_def, ret_type, should_focus_return_type, trailing_ws, tail_expr } =
+ self;
+
let f = match cap {
Some(cap) => {
- let cursor = if self.should_focus_return_type {
+ let cursor = if *should_focus_return_type {
// Focus the return type if there is one
- match self.ret_type {
- Some(ref ret_type) => ret_type.syntax(),
- None => self.tail_expr.syntax(),
+ match ret_type {
+ Some(ret_type) => ret_type.syntax(),
+ None => tail_expr.syntax(),
}
} else {
- self.tail_expr.syntax()
+ tail_expr.syntax()
};
- render_snippet(cap, self.fn_def.syntax(), Cursor::Replace(cursor))
+ render_snippet(cap, fn_def.syntax(), Cursor::Replace(cursor))
}
- None => self.fn_def.to_string(),
+ None => fn_def.to_string(),
};
- format!("{}{}{}", self.leading_ws, f, self.trailing_ws)
+ format!("{leading_ws}{f}{trailing_ws}")
}
}
@@ -307,7 +312,7 @@ impl FunctionBuilder {
})
}
- fn render(self) -> FunctionTemplate {
+ fn render(self, is_method: bool) -> FunctionTemplate {
let placeholder_expr = make::ext::expr_todo();
let fn_body = make::block_expr(vec![], Some(placeholder_expr));
let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None };
@@ -325,16 +330,23 @@ impl FunctionBuilder {
match self.target {
GeneratedFunctionTarget::BehindItem(it) => {
- let indent = IndentLevel::from_node(&it);
- leading_ws = format!("\n\n{}", indent);
+ let mut indent = IndentLevel::from_node(&it);
+ if is_method {
+ indent = indent + 1;
+ leading_ws = format!("{indent}");
+ } else {
+ leading_ws = format!("\n\n{indent}");
+ }
+
fn_def = fn_def.indent(indent);
trailing_ws = String::new();
}
GeneratedFunctionTarget::InEmptyItemList(it) => {
let indent = IndentLevel::from_node(&it);
- leading_ws = format!("\n{}", indent + 1);
- fn_def = fn_def.indent(indent + 1);
- trailing_ws = format!("\n{}", indent);
+ let leading_indent = indent + 1;
+ leading_ws = format!("\n{leading_indent}");
+ fn_def = fn_def.indent(leading_indent);
+ trailing_ws = format!("\n{indent}");
}
};
@@ -411,14 +423,13 @@ fn get_fn_target(
fn get_method_target(
ctx: &AssistContext<'_>,
- target_module: &Module,
impl_: &Option<ast::Impl>,
+ adt: &Adt,
) -> Option<(GeneratedFunctionTarget, TextSize)> {
let target = match impl_ {
Some(impl_) => next_space_for_fn_in_impl(impl_)?,
None => {
- next_space_for_fn_in_module(ctx.sema.db, &target_module.definition_source(ctx.sema.db))?
- .1
+ GeneratedFunctionTarget::BehindItem(adt.source(ctx.sema.db)?.syntax().value.clone())
}
};
Some((target.clone(), get_insert_offset(&target)))
@@ -437,7 +448,7 @@ fn assoc_fn_target_info(
return None;
}
let (impl_, file) = get_adt_source(ctx, &adt, fn_name)?;
- let (target, insert_offset) = get_method_target(ctx, &module, &impl_)?;
+ let (target, insert_offset) = get_method_target(ctx, &impl_, &adt)?;
let adt_name = if impl_.is_none() { Some(adt.name(ctx.sema.db)) } else { None };
Some(TargetInfo::new(target_module, adt_name, target, file, insert_offset))
}
@@ -1468,14 +1479,12 @@ fn foo() {S.bar$0();}
",
r"
struct S;
-fn foo() {S.bar();}
impl S {
-
-
-fn bar(&self) ${0:-> _} {
- todo!()
-}
+ fn bar(&self) ${0:-> _} {
+ todo!()
+ }
}
+fn foo() {S.bar();}
",
)
}
@@ -1516,14 +1525,12 @@ fn foo() {s::S.bar$0();}
r"
mod s {
pub struct S;
-impl S {
-
-
- pub(crate) fn bar(&self) ${0:-> _} {
- todo!()
+ impl S {
+ pub(crate) fn bar(&self) ${0:-> _} {
+ todo!()
+ }
}
}
-}
fn foo() {s::S.bar();}
",
)
@@ -1544,18 +1551,16 @@ mod s {
",
r"
struct S;
+impl S {
+ fn bar(&self) ${0:-> _} {
+ todo!()
+ }
+}
mod s {
fn foo() {
super::S.bar();
}
}
-impl S {
-
-
-fn bar(&self) ${0:-> _} {
- todo!()
-}
-}
",
)
@@ -1571,14 +1576,12 @@ fn foo() {$0S.bar();}
",
r"
struct S;
-fn foo() {S.bar();}
impl S {
-
-
-fn bar(&self) ${0:-> _} {
- todo!()
-}
+ fn bar(&self) ${0:-> _} {
+ todo!()
+ }
}
+fn foo() {S.bar();}
",
)
}
@@ -1593,14 +1596,12 @@ fn foo() {S::bar$0();}
",
r"
struct S;
-fn foo() {S::bar();}
impl S {
-
-
-fn bar() ${0:-> _} {
- todo!()
-}
+ fn bar() ${0:-> _} {
+ todo!()
+ }
}
+fn foo() {S::bar();}
",
)
}
@@ -1641,14 +1642,12 @@ fn foo() {s::S::bar$0();}
r"
mod s {
pub struct S;
-impl S {
-
-
- pub(crate) fn bar() ${0:-> _} {
- todo!()
+ impl S {
+ pub(crate) fn bar() ${0:-> _} {
+ todo!()
+ }
}
}
-}
fn foo() {s::S::bar();}
",
)
@@ -1664,14 +1663,12 @@ fn foo() {$0S::bar();}
",
r"
struct S;
-fn foo() {S::bar();}
impl S {
-
-
-fn bar() ${0:-> _} {
- todo!()
-}
+ fn bar() ${0:-> _} {
+ todo!()
+ }
}
+fn foo() {S::bar();}
",
)
}
@@ -1841,15 +1838,13 @@ fn main() {
",
r"
enum Foo {}
-fn main() {
- Foo::new();
-}
impl Foo {
-
-
-fn new() ${0:-> _} {
- todo!()
+ fn new() ${0:-> _} {
+ todo!()
+ }
}
+fn main() {
+ Foo::new();
}
",
)
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs
index 76fcef0ca..5e7191428 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs
@@ -1,6 +1,9 @@
use ide_db::famous_defs::FamousDefs;
use stdx::{format_to, to_lower_snake_case};
-use syntax::ast::{self, AstNode, HasName, HasVisibility};
+use syntax::{
+ ast::{self, AstNode, HasName, HasVisibility},
+ TextRange,
+};
use crate::{
utils::{convert_reference_type, find_impl_block_end, find_struct_impl, generate_impl_text},
@@ -72,92 +75,259 @@ pub(crate) fn generate_getter_mut(acc: &mut Assists, ctx: &AssistContext<'_>) ->
generate_getter_impl(acc, ctx, true)
}
+#[derive(Clone, Debug)]
+struct RecordFieldInfo {
+ field_name: syntax::ast::Name,
+ field_ty: syntax::ast::Type,
+ fn_name: String,
+ target: TextRange,
+}
+
+struct GetterInfo {
+ impl_def: Option<ast::Impl>,
+ strukt: ast::Struct,
+ mutable: bool,
+}
+
pub(crate) fn generate_getter_impl(
acc: &mut Assists,
ctx: &AssistContext<'_>,
mutable: bool,
) -> Option<()> {
- let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
- let field = ctx.find_node_at_offset::<ast::RecordField>()?;
+ // This if condition denotes two modes this assist can work in:
+ // - First is acting upon selection of record fields
+ // - Next is acting upon a single record field
+ //
+ // This is the only part where implementation diverges a bit,
+ // subsequent code is generic for both of these modes
- let field_name = field.name()?;
- let field_ty = field.ty()?;
+ let (strukt, info_of_record_fields, fn_names) = if !ctx.has_empty_selection() {
+ // Selection Mode
+ let node = ctx.covering_element();
- // Return early if we've found an existing fn
- let mut fn_name = to_lower_snake_case(&field_name.to_string());
- if mutable {
- format_to!(fn_name, "_mut");
+ let node = match node {
+ syntax::NodeOrToken::Node(n) => n,
+ syntax::NodeOrToken::Token(t) => t.parent()?,
+ };
+
+ let parent_struct = node.ancestors().find_map(ast::Struct::cast)?;
+
+ let (info_of_record_fields, field_names) =
+ extract_and_parse_record_fields(&parent_struct, ctx.selection_trimmed(), mutable)?;
+
+ (parent_struct, info_of_record_fields, field_names)
+ } else {
+ // Single Record Field mode
+ let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
+ let field = ctx.find_node_at_offset::<ast::RecordField>()?;
+
+ let record_field_info = parse_record_field(field, mutable)?;
+
+ let fn_name = record_field_info.fn_name.clone();
+
+ (strukt, vec![record_field_info], vec![fn_name])
+ };
+
+ // No record fields to do work on :(
+ if info_of_record_fields.len() == 0 {
+ return None;
}
- let impl_def = find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), fn_name.as_str())?;
+
+ let impl_def = find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), &fn_names)?;
let (id, label) = if mutable {
("generate_getter_mut", "Generate a mut getter method")
} else {
("generate_getter", "Generate a getter method")
};
- let target = field.syntax().text_range();
+
+ // Computing collective text range of all record fields in selected region
+ let target: TextRange = info_of_record_fields
+ .iter()
+ .map(|record_field_info| record_field_info.target)
+ .reduce(|acc, target| acc.cover(target))?;
+
+ let getter_info = GetterInfo { impl_def, strukt, mutable };
+
acc.add_group(
&GroupLabel("Generate getter/setter".to_owned()),
AssistId(id, AssistKind::Generate),
label,
target,
|builder| {
+ let record_fields_count = info_of_record_fields.len();
+
let mut buf = String::with_capacity(512);
- if impl_def.is_some() {
- buf.push('\n');
+ // Check if an impl exists
+ if let Some(impl_def) = &getter_info.impl_def {
+ // Check if impl is empty
+ if let Some(assoc_item_list) = impl_def.assoc_item_list() {
+ if assoc_item_list.assoc_items().next().is_some() {
+ // If not empty then only insert a new line
+ buf.push('\n');
+ }
+ }
}
- let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
- let (ty, body) = if mutable {
- (format!("&mut {}", field_ty), format!("&mut self.{}", field_name))
- } else {
- (|| {
- let krate = ctx.sema.scope(field_ty.syntax())?.krate();
- let famous_defs = &FamousDefs(&ctx.sema, krate);
- ctx.sema
- .resolve_type(&field_ty)
- .and_then(|ty| convert_reference_type(ty, ctx.db(), famous_defs))
- .map(|conversion| {
- cov_mark::hit!(convert_reference_type);
- (
- conversion.convert_type(ctx.db()),
- conversion.getter(field_name.to_string()),
- )
- })
- })()
- .unwrap_or_else(|| (format!("&{}", field_ty), format!("&self.{}", field_name)))
- };
-
- format_to!(
- buf,
- " {}fn {}(&{}self) -> {} {{
- {}
- }}",
- vis,
- fn_name,
- mutable.then(|| "mut ").unwrap_or_default(),
- ty,
- body,
- );
-
- let start_offset = impl_def
- .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf))
+ for (i, record_field_info) in info_of_record_fields.iter().enumerate() {
+ // this buf inserts a newline at the end of a getter
+ // automatically, if one wants to add one more newline
+ // for separating it from other assoc items, that needs
+ // to be handled spearately
+ let mut getter_buf =
+ generate_getter_from_info(ctx, &getter_info, &record_field_info);
+
+ // Insert `$0` only for last getter we generate
+ if i == record_fields_count - 1 {
+ getter_buf = getter_buf.replacen("fn ", "fn $0", 1);
+ }
+
+ // For first element we do not merge with '\n', as
+ // that can be inserted by impl_def check defined
+ // above, for other cases which are:
+ //
+ // - impl exists but it empty, here we would ideally
+ // not want to keep newline between impl <struct> {
+ // and fn <fn-name>() { line
+ //
+ // - next if impl itself does not exist, in this
+ // case we ourselves generate a new impl and that
+ // again ends up with the same reasoning as above
+ // for not keeping newline
+ if i == 0 {
+ buf = buf + &getter_buf;
+ } else {
+ buf = buf + "\n" + &getter_buf;
+ }
+
+ // We don't insert a new line at the end of
+ // last getter as it will end up in the end
+ // of an impl where we would not like to keep
+ // getter and end of impl ( i.e. `}` ) with an
+ // extra line for no reason
+ if i < record_fields_count - 1 {
+ buf = buf + "\n";
+ }
+ }
+
+ let start_offset = getter_info
+ .impl_def
+ .as_ref()
+ .and_then(|impl_def| find_impl_block_end(impl_def.to_owned(), &mut buf))
.unwrap_or_else(|| {
- buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf);
- strukt.syntax().text_range().end()
+ buf = generate_impl_text(&ast::Adt::Struct(getter_info.strukt.clone()), &buf);
+ getter_info.strukt.syntax().text_range().end()
});
match ctx.config.snippet_cap {
- Some(cap) => {
- builder.insert_snippet(cap, start_offset, buf.replacen("fn ", "fn $0", 1))
- }
+ Some(cap) => builder.insert_snippet(cap, start_offset, buf),
None => builder.insert(start_offset, buf),
}
},
)
}
+fn generate_getter_from_info(
+ ctx: &AssistContext<'_>,
+ info: &GetterInfo,
+ record_field_info: &RecordFieldInfo,
+) -> String {
+ let mut buf = String::with_capacity(512);
+
+ let vis = info.strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
+ let (ty, body) = if info.mutable {
+ (
+ format!("&mut {}", record_field_info.field_ty),
+ format!("&mut self.{}", record_field_info.field_name),
+ )
+ } else {
+ (|| {
+ let krate = ctx.sema.scope(record_field_info.field_ty.syntax())?.krate();
+ let famous_defs = &FamousDefs(&ctx.sema, krate);
+ ctx.sema
+ .resolve_type(&record_field_info.field_ty)
+ .and_then(|ty| convert_reference_type(ty, ctx.db(), famous_defs))
+ .map(|conversion| {
+ cov_mark::hit!(convert_reference_type);
+ (
+ conversion.convert_type(ctx.db()),
+ conversion.getter(record_field_info.field_name.to_string()),
+ )
+ })
+ })()
+ .unwrap_or_else(|| {
+ (
+ format!("&{}", record_field_info.field_ty),
+ format!("&self.{}", record_field_info.field_name),
+ )
+ })
+ };
+
+ format_to!(
+ buf,
+ " {}fn {}(&{}self) -> {} {{
+ {}
+ }}",
+ vis,
+ record_field_info.fn_name,
+ info.mutable.then(|| "mut ").unwrap_or_default(),
+ ty,
+ body,
+ );
+
+ buf
+}
+
+fn extract_and_parse_record_fields(
+ node: &ast::Struct,
+ selection_range: TextRange,
+ mutable: bool,
+) -> Option<(Vec<RecordFieldInfo>, Vec<String>)> {
+ let mut field_names: Vec<String> = vec![];
+ let field_list = node.field_list()?;
+
+ match field_list {
+ ast::FieldList::RecordFieldList(ele) => {
+ let info_of_record_fields_in_selection = ele
+ .fields()
+ .filter_map(|record_field| {
+ if selection_range.contains_range(record_field.syntax().text_range()) {
+ let record_field_info = parse_record_field(record_field, mutable)?;
+ field_names.push(record_field_info.fn_name.clone());
+ return Some(record_field_info);
+ }
+
+ None
+ })
+ .collect::<Vec<RecordFieldInfo>>();
+
+ if info_of_record_fields_in_selection.len() == 0 {
+ return None;
+ }
+
+ Some((info_of_record_fields_in_selection, field_names))
+ }
+ ast::FieldList::TupleFieldList(_) => {
+ return None;
+ }
+ }
+}
+
+fn parse_record_field(record_field: ast::RecordField, mutable: bool) -> Option<RecordFieldInfo> {
+ let field_name = record_field.name()?;
+ let field_ty = record_field.ty()?;
+
+ let mut fn_name = to_lower_snake_case(&field_name.to_string());
+ if mutable {
+ format_to!(fn_name, "_mut");
+ }
+
+ let target = record_field.syntax().text_range();
+
+ Some(RecordFieldInfo { field_name, field_ty, fn_name, target })
+}
+
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_not_applicable};
@@ -489,4 +659,53 @@ impl Context {
"#,
);
}
+
+ #[test]
+ fn test_generate_multiple_getters_from_selection() {
+ check_assist(
+ generate_getter,
+ r#"
+struct Context {
+ $0data: Data,
+ count: usize,$0
+}
+ "#,
+ r#"
+struct Context {
+ data: Data,
+ count: usize,
+}
+
+impl Context {
+ fn data(&self) -> &Data {
+ &self.data
+ }
+
+ fn $0count(&self) -> &usize {
+ &self.count
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_generate_multiple_getters_from_selection_one_already_exists() {
+ // As impl for one of the fields already exist, skip it
+ check_assist_not_applicable(
+ generate_getter,
+ r#"
+struct Context {
+ $0data: Data,
+ count: usize,$0
+}
+
+impl Context {
+ fn data(&self) -> &Data {
+ &self.data
+ }
+}
+ "#,
+ );
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs
index 68287a20b..9af26c04e 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs
@@ -28,7 +28,7 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio
acc.add(
AssistId("generate_impl", AssistKind::Generate),
- format!("Generate impl for `{}`", name),
+ format!("Generate impl for `{name}`"),
target,
|edit| {
let start_offset = nominal.syntax().text_range().end();
@@ -52,6 +52,7 @@ mod tests {
use super::*;
+ // FIXME: break up into separate test fns
#[test]
fn test_add_impl() {
check_assist(
@@ -136,6 +137,18 @@ mod tests {
check_assist(
generate_impl,
+ r#"
+ struct Defaulted<const N: i32 = 0> {}$0"#,
+ r#"
+ struct Defaulted<const N: i32 = 0> {}
+
+ impl<const N: i32> Defaulted<N> {
+ $0
+ }"#,
+ );
+
+ check_assist(
+ generate_impl,
r#"pub trait Trait {}
struct Struct<T>$0
where
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 6c93875e9..17fadea0e 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
@@ -39,7 +39,8 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
};
// Return early if we've found an existing new fn
- let impl_def = find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), "new")?;
+ let impl_def =
+ find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), &[String::from("new")])?;
let current_module = ctx.sema.scope(strukt.syntax())?.module();
@@ -51,17 +52,22 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
buf.push('\n');
}
- let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
+ let vis = strukt.visibility().map_or(String::new(), |v| format!("{v} "));
let trivial_constructors = field_list
.fields()
.map(|f| {
+ let name = f.name()?;
+
let ty = ctx.sema.resolve_type(&f.ty()?)?;
let item_in_ns = hir::ItemInNs::from(hir::ModuleDef::from(ty.as_adt()?));
- let type_path = current_module
- .find_use_path(ctx.sema.db, item_for_path_search(ctx.sema.db, item_in_ns)?)?;
+ let type_path = current_module.find_use_path(
+ ctx.sema.db,
+ item_for_path_search(ctx.sema.db, item_in_ns)?,
+ ctx.config.prefer_no_std,
+ )?;
let expr = use_trivial_constructor(
&ctx.sema.db,
@@ -69,7 +75,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
&ty,
)?;
- Some(format!("{}: {}", f.name()?.syntax(), expr))
+ Some(format!("{name}: {expr}"))
})
.collect::<Vec<_>>();
@@ -78,7 +84,10 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
.enumerate()
.filter_map(|(i, f)| {
if trivial_constructors[i].is_none() {
- Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax()))
+ let name = f.name()?;
+ let ty = f.ty()?;
+
+ Some(format!("{name}: {ty}"))
} else {
None
}
@@ -98,7 +107,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
})
.format(", ");
- format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
+ format_to!(buf, " {vis}fn new({params}) -> Self {{ Self {{ {fields} }} }}");
let start_offset = impl_def
.and_then(|impl_def| find_impl_block_start(impl_def, &mut buf))
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_setter.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_setter.rs
index 2a7ad6ce3..62f72df1c 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_setter.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_setter.rs
@@ -36,11 +36,8 @@ pub(crate) fn generate_setter(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt
// Return early if we've found an existing fn
let fn_name = to_lower_snake_case(&field_name.to_string());
- let impl_def = find_struct_impl(
- ctx,
- &ast::Adt::Struct(strukt.clone()),
- format!("set_{}", fn_name).as_str(),
- )?;
+ let impl_def =
+ find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), &[format!("set_{fn_name}")])?;
let target = field.syntax().text_range();
acc.add_group(
@@ -55,18 +52,12 @@ pub(crate) fn generate_setter(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt
buf.push('\n');
}
- let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
+ let vis = strukt.visibility().map_or(String::new(), |v| format!("{v} "));
format_to!(
buf,
- " {}fn set_{}(&mut self, {}: {}) {{
- self.{} = {};
- }}",
- vis,
- fn_name,
- fn_name,
- field_ty,
- fn_name,
- fn_name,
+ " {vis}fn set_{fn_name}(&mut self, {fn_name}: {field_ty}) {{
+ self.{fn_name} = {fn_name};
+ }}"
);
let start_offset = impl_def
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_format_string_arg.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_format_string_arg.rs
new file mode 100644
index 000000000..aa710d2ce
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_format_string_arg.rs
@@ -0,0 +1,296 @@
+use crate::{AssistContext, Assists};
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ syntax_helpers::{
+ format_string::is_format_string,
+ format_string_exprs::{parse_format_exprs, Arg},
+ },
+};
+use itertools::Itertools;
+use stdx::format_to;
+use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange};
+
+// Assist: move_format_string_arg
+//
+// Move an expression out of a format string.
+//
+// ```
+// macro_rules! format_args {
+// ($lit:literal $(tt:tt)*) => { 0 },
+// }
+// macro_rules! print {
+// ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
+// }
+//
+// fn main() {
+// print!("{x + 1}$0");
+// }
+// ```
+// ->
+// ```
+// macro_rules! format_args {
+// ($lit:literal $(tt:tt)*) => { 0 },
+// }
+// macro_rules! print {
+// ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
+// }
+//
+// fn main() {
+// print!("{}"$0, x + 1);
+// }
+// ```
+
+pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let fmt_string = ctx.find_token_at_offset::<ast::String>()?;
+ let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?;
+
+ let expanded_t = ast::String::cast(
+ ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone()),
+ )?;
+ if !is_format_string(&expanded_t) {
+ return None;
+ }
+
+ let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?;
+ if extracted_args.is_empty() {
+ return None;
+ }
+
+ acc.add(
+ AssistId(
+ "move_format_string_arg",
+ // if there aren't any expressions, then make the assist a RefactorExtract
+ if extracted_args.iter().filter(|f| matches!(f, Arg::Expr(_))).count() == 0 {
+ AssistKind::RefactorExtract
+ } else {
+ AssistKind::QuickFix
+ },
+ ),
+ "Extract format args",
+ tt.syntax().text_range(),
+ |edit| {
+ let fmt_range = fmt_string.syntax().text_range();
+
+ // Replace old format string with new format string whose arguments have been extracted
+ edit.replace(fmt_range, new_fmt);
+
+ // Insert cursor at end of format string
+ edit.insert(fmt_range.end(), "$0");
+
+ // Extract existing arguments in macro
+ let tokens =
+ tt.token_trees_and_tokens().collect_vec();
+
+ let mut existing_args: Vec<String> = vec![];
+
+ let mut current_arg = String::new();
+ if let [_opening_bracket, NodeOrToken::Token(format_string), _args_start_comma, tokens @ .., NodeOrToken::Token(end_bracket)] =
+ tokens.as_slice()
+ {
+ for t in tokens {
+ match t {
+ NodeOrToken::Node(n) => {
+ format_to!(current_arg, "{n}");
+ },
+ NodeOrToken::Token(t) if t.kind() == COMMA=> {
+ existing_args.push(current_arg.trim().into());
+ current_arg.clear();
+ },
+ NodeOrToken::Token(t) => {
+ current_arg.push_str(t.text());
+ },
+ }
+ }
+ existing_args.push(current_arg.trim().into());
+
+ // delete everything after the format string till end bracket
+ // we're going to insert the new arguments later
+ edit.delete(TextRange::new(
+ format_string.text_range().end(),
+ end_bracket.text_range().start(),
+ ));
+ }
+
+ // Start building the new args
+ let mut existing_args = existing_args.into_iter();
+ let mut args = String::new();
+
+ let mut placeholder_idx = 1;
+
+ for extracted_args in extracted_args {
+ // remove expr from format string
+ args.push_str(", ");
+
+ match extracted_args {
+ Arg::Ident(s) | Arg::Expr(s) => {
+ // insert arg
+ args.push_str(&s);
+ }
+ Arg::Placeholder => {
+ // try matching with existing argument
+ match existing_args.next() {
+ Some(ea) => {
+ args.push_str(&ea);
+ }
+ None => {
+ // insert placeholder
+ args.push_str(&format!("${placeholder_idx}"));
+ placeholder_idx += 1;
+ }
+ }
+ }
+ }
+ }
+
+ // Insert new args
+ edit.insert(fmt_range.end(), args);
+ },
+ );
+
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::tests::check_assist;
+
+ const MACRO_DECL: &'static str = r#"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+macro_rules! print {
+ ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
+}
+"#;
+
+ fn add_macro_decl(s: &'static str) -> String {
+ MACRO_DECL.to_string() + s
+ }
+
+ #[test]
+ fn multiple_middle_arg() {
+ check_assist(
+ move_format_string_arg,
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {x + 1:b} {}$0", y + 2, 2);
+}
+"#,
+ ),
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {:b} {}"$0, y + 2, x + 1, 2);
+}
+"#,
+ ),
+ );
+ }
+
+ #[test]
+ fn single_arg() {
+ check_assist(
+ move_format_string_arg,
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{obj.value:b}$0",);
+}
+"#,
+ ),
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{:b}"$0, obj.value);
+}
+"#,
+ ),
+ );
+ }
+
+ #[test]
+ fn multiple_middle_placeholders_arg() {
+ check_assist(
+ move_format_string_arg,
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {x + 1:b} {} {}$0", y + 2, 2);
+}
+"#,
+ ),
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1);
+}
+"#,
+ ),
+ );
+ }
+
+ #[test]
+ fn multiple_trailing_args() {
+ check_assist(
+ move_format_string_arg,
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {x + 1:b} {Struct(1, 2)}$0", 1);
+}
+"#,
+ ),
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
+}
+"#,
+ ),
+ );
+ }
+
+ #[test]
+ fn improper_commas() {
+ check_assist(
+ move_format_string_arg,
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,);
+}
+"#,
+ ),
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
+}
+"#,
+ ),
+ );
+ }
+
+ #[test]
+ fn nested_tt() {
+ check_assist(
+ move_format_string_arg,
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("My name is {} {x$0 + x}", stringify!(Paperino))
+}
+"#,
+ ),
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("My name is {} {}"$0, stringify!(Paperino), x + x)
+}
+"#,
+ ),
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs
index 121f8b4a1..e57d1d065 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
@@ -44,8 +44,11 @@ pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) ->
let current_module = ctx.sema.scope(call.syntax())?.module();
let target_module_def = ModuleDef::from(resolved_call);
let item_in_ns = ItemInNs::from(target_module_def);
- let receiver_path = current_module
- .find_use_path(ctx.sema.db, item_for_path_search(ctx.sema.db, item_in_ns)?)?;
+ let receiver_path = current_module.find_use_path(
+ ctx.sema.db,
+ item_for_path_search(ctx.sema.db, item_in_ns)?,
+ ctx.config.prefer_no_std,
+ )?;
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 0c2e9da38..4b2af550b 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,7 +37,8 @@ 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);
+ let mut proposed_imports =
+ import_assets.search_for_relative_paths(&ctx.sema, ctx.config.prefer_no_std);
if proposed_imports.is_empty() {
return None;
}
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 d139f78a6..9fd5e1886 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
@@ -85,7 +85,7 @@ pub(crate) fn replace_derive_with_manual_impl(
})
.flat_map(|trait_| {
current_module
- .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
+ .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_), ctx.config.prefer_no_std)
.as_ref()
.map(mod_path_to_ast)
.zip(Some(trait_))
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 2419fa11c..dbbc56958 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
@@ -67,6 +67,7 @@ pub(crate) fn replace_qualified_name_with_use(
ctx.sema.db,
module,
ctx.config.insert_use.prefix_kind,
+ ctx.config.prefer_no_std,
)
})
.flatten();
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs
new file mode 100644
index 000000000..25c58d086
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs
@@ -0,0 +1,159 @@
+use syntax::{
+ ast::{self, edit::AstNodeEdit},
+ AstNode, T,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: unwrap_tuple
+//
+// Unwrap the tuple to different variables.
+//
+// ```
+// # //- minicore: result
+// fn main() {
+// $0let (foo, bar) = ("Foo", "Bar");
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// let foo = "Foo";
+// let bar = "Bar";
+// }
+// ```
+pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let let_kw = ctx.find_token_syntax_at_offset(T![let])?;
+ let let_stmt = let_kw.parent().and_then(ast::LetStmt::cast)?;
+ let indent_level = let_stmt.indent_level().0 as usize;
+ let pat = let_stmt.pat()?;
+ let ty = let_stmt.ty();
+ let init = let_stmt.initializer()?;
+
+ // This only applies for tuple patterns, types, and initializers.
+ let tuple_pat = match pat {
+ ast::Pat::TuplePat(pat) => pat,
+ _ => return None,
+ };
+ let tuple_ty = ty.and_then(|it| match it {
+ ast::Type::TupleType(ty) => Some(ty),
+ _ => None,
+ });
+ let tuple_init = match init {
+ ast::Expr::TupleExpr(expr) => expr,
+ _ => return None,
+ };
+
+ if tuple_pat.fields().count() != tuple_init.fields().count() {
+ return None;
+ }
+ if let Some(tys) = &tuple_ty {
+ if tuple_pat.fields().count() != tys.fields().count() {
+ return None;
+ }
+ }
+
+ let parent = let_kw.parent()?;
+
+ acc.add(
+ AssistId("unwrap_tuple", AssistKind::RefactorRewrite),
+ "Unwrap tuple",
+ let_kw.text_range(),
+ |edit| {
+ let indents = " ".repeat(indent_level);
+
+ // If there is an ascribed type, insert that type for each declaration,
+ // otherwise, omit that type.
+ if let Some(tys) = tuple_ty {
+ let mut zipped_decls = String::new();
+ for (pat, ty, expr) in
+ itertools::izip!(tuple_pat.fields(), tys.fields(), tuple_init.fields())
+ {
+ zipped_decls.push_str(&format!("{}let {pat}: {ty} = {expr};\n", indents))
+ }
+ edit.replace(parent.text_range(), zipped_decls.trim());
+ } else {
+ let mut zipped_decls = String::new();
+ for (pat, expr) in itertools::izip!(tuple_pat.fields(), tuple_init.fields()) {
+ zipped_decls.push_str(&format!("{}let {pat} = {expr};\n", indents));
+ }
+ edit.replace(parent.text_range(), zipped_decls.trim());
+ }
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_assist;
+
+ use super::*;
+
+ #[test]
+ fn unwrap_tuples() {
+ check_assist(
+ unwrap_tuple,
+ r#"
+fn main() {
+ $0let (foo, bar) = ("Foo", "Bar");
+}
+"#,
+ r#"
+fn main() {
+ let foo = "Foo";
+ let bar = "Bar";
+}
+"#,
+ );
+
+ check_assist(
+ unwrap_tuple,
+ r#"
+fn main() {
+ $0let (foo, bar, baz) = ("Foo", "Bar", "Baz");
+}
+"#,
+ r#"
+fn main() {
+ let foo = "Foo";
+ let bar = "Bar";
+ let baz = "Baz";
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_tuple_with_types() {
+ check_assist(
+ unwrap_tuple,
+ r#"
+fn main() {
+ $0let (foo, bar): (u8, i32) = (5, 10);
+}
+"#,
+ r#"
+fn main() {
+ let foo: u8 = 5;
+ let bar: i32 = 10;
+}
+"#,
+ );
+
+ check_assist(
+ unwrap_tuple,
+ r#"
+fn main() {
+ $0let (foo, bar, baz): (u8, i32, f64) = (5, 10, 17.5);
+}
+"#,
+ r#"
+fn main() {
+ let foo: u8 = 5;
+ let bar: i32 = 10;
+ let baz: f64 = 17.5;
+}
+"#,
+ );
+ }
+}
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 e52544db5..a07318cef 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
@@ -121,6 +121,7 @@ mod handlers {
mod convert_iter_for_each_to_for;
mod convert_let_else_to_match;
mod convert_tuple_struct_to_named_struct;
+ mod convert_named_struct_to_tuple_struct;
mod convert_to_guarded_return;
mod convert_two_arm_bool_match_to_matches_macro;
mod convert_while_to_loop;
@@ -136,6 +137,7 @@ mod handlers {
mod flip_binexpr;
mod flip_comma;
mod flip_trait_bound;
+ mod move_format_string_arg;
mod generate_constant;
mod generate_default_from_enum_variant;
mod generate_default_from_new;
@@ -188,6 +190,7 @@ mod handlers {
mod replace_turbofish_with_explicit_type;
mod split_import;
mod unmerge_match_arm;
+ mod unwrap_tuple;
mod sort_items;
mod toggle_ignore;
mod unmerge_use;
@@ -216,6 +219,7 @@ mod handlers {
convert_iter_for_each_to_for::convert_iter_for_each_to_for,
convert_iter_for_each_to_for::convert_for_loop_with_for_each,
convert_let_else_to_match::convert_let_else_to_match,
+ convert_named_struct_to_tuple_struct::convert_named_struct_to_tuple_struct,
convert_to_guarded_return::convert_to_guarded_return,
convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct,
convert_two_arm_bool_match_to_matches_macro::convert_two_arm_bool_match_to_matches_macro,
@@ -254,6 +258,7 @@ mod handlers {
merge_imports::merge_imports,
merge_match_arms::merge_match_arms,
move_bounds::move_bounds_to_where_clause,
+ move_format_string_arg::move_format_string_arg,
move_guard::move_arm_cond_to_match_guard,
move_guard::move_guard_to_arm_body,
move_module_to_file::move_module_to_file,
@@ -289,6 +294,7 @@ mod handlers {
unnecessary_async::unnecessary_async,
unwrap_block::unwrap_block,
unwrap_result_return_type::unwrap_result_return_type,
+ unwrap_tuple::unwrap_tuple,
wrap_return_type_in_result::wrap_return_type_in_result,
// These are manually sorted for better priorities. By default,
// priority is determined by the size of the target range (smaller
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs
index 9cd66c6b3..f7f2417d0 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs
@@ -29,6 +29,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
group: true,
skip_glob_imports: true,
},
+ prefer_no_std: false,
};
pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
@@ -95,8 +96,10 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
});
let actual = {
- let source_change =
- assist.source_change.expect("Assist did not contain any source changes");
+ let source_change = assist
+ .source_change
+ .filter(|it| !it.source_file_edits.is_empty() || !it.file_system_edits.is_empty())
+ .expect("Assist did not contain any source changes");
let mut actual = before;
if let Some(source_file_edit) = source_change.get_source_edit(file_id) {
source_file_edit.apply(&mut actual);
@@ -139,8 +142,10 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult<'_>, assist_la
match (assist, expected) {
(Some(assist), ExpectedResult::After(after)) => {
- let source_change =
- assist.source_change.expect("Assist did not contain any source changes");
+ let source_change = assist
+ .source_change
+ .filter(|it| !it.source_file_edits.is_empty() || !it.file_system_edits.is_empty())
+ .expect("Assist did not contain any source changes");
let skip_header = source_change.source_file_edits.len() == 1
&& source_change.file_system_edits.len() == 0;
@@ -227,6 +232,7 @@ fn assist_order_field_struct() {
assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");
+ assert_eq!(assists.next().expect("expected assist").label, "Convert to tuple struct");
assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`");
}
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 227e2300f..2c4000efe 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
@@ -408,6 +408,47 @@ fn main() {
}
#[test]
+fn doctest_convert_named_struct_to_tuple_struct() {
+ check_doc_test(
+ "convert_named_struct_to_tuple_struct",
+ r#####"
+struct Point$0 { x: f32, y: f32 }
+
+impl Point {
+ pub fn new(x: f32, y: f32) -> Self {
+ Point { x, y }
+ }
+
+ pub fn x(&self) -> f32 {
+ self.x
+ }
+
+ pub fn y(&self) -> f32 {
+ self.y
+ }
+}
+"#####,
+ r#####"
+struct Point(f32, f32);
+
+impl Point {
+ pub fn new(x: f32, y: f32) -> Self {
+ Point(x, y)
+ }
+
+ pub fn x(&self) -> f32 {
+ self.0
+ }
+
+ pub fn y(&self) -> f32 {
+ self.1
+ }
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_convert_to_guarded_return() {
check_doc_test(
"convert_to_guarded_return",
@@ -1592,6 +1633,37 @@ fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
}
#[test]
+fn doctest_move_format_string_arg() {
+ check_doc_test(
+ "move_format_string_arg",
+ r#####"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+macro_rules! print {
+ ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
+}
+
+fn main() {
+ print!("{x + 1}$0");
+}
+"#####,
+ r#####"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+macro_rules! print {
+ ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
+}
+
+fn main() {
+ print!("{}"$0, x + 1);
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_move_from_mod_rs() {
check_doc_test(
"move_from_mod_rs",
@@ -2356,6 +2428,25 @@ fn foo() -> i32 { 42i32 }
}
#[test]
+fn doctest_unwrap_tuple() {
+ check_doc_test(
+ "unwrap_tuple",
+ r#####"
+//- minicore: result
+fn main() {
+ $0let (foo, bar) = ("Foo", "Bar");
+}
+"#####,
+ r#####"
+fn main() {
+ let foo = "Foo";
+ let bar = "Bar";
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_wrap_return_type_in_result() {
check_doc_test(
"wrap_return_type_in_result",
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 4ab6e2627..db32e7182 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
@@ -2,8 +2,6 @@
use std::ops;
-use itertools::Itertools;
-
pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
use hir::{db::HirDatabase, HirDisplay, Semantics};
use ide_db::{famous_defs::FamousDefs, path_transform::PathTransform, RootDatabase, SnippetCap};
@@ -15,7 +13,7 @@ use syntax::{
edit_in_place::{AttrsOwnerEdit, Removable},
make, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace,
},
- ted, AstNode, AstToken, Direction, SmolStr, SourceFile,
+ ted, AstNode, AstToken, Direction, SourceFile,
SyntaxKind::*,
SyntaxNode, TextRange, TextSize, T,
};
@@ -333,10 +331,14 @@ fn calc_depth(pat: &ast::Pat, depth: usize) -> usize {
// FIXME: change the new fn checking to a more semantic approach when that's more
// viable (e.g. we process proc macros, etc)
// FIXME: this partially overlaps with `find_impl_block_*`
+
+/// `find_struct_impl` looks for impl of a struct, but this also has additional feature
+/// where it takes a list of function names and check if they exist inside impl_, if
+/// even one match is found, it returns None
pub(crate) fn find_struct_impl(
ctx: &AssistContext<'_>,
adt: &ast::Adt,
- name: &str,
+ names: &[String],
) -> Option<Option<ast::Impl>> {
let db = ctx.db();
let module = adt.syntax().parent()?;
@@ -364,7 +366,7 @@ pub(crate) fn find_struct_impl(
});
if let Some(ref impl_blk) = block {
- if has_fn(impl_blk, name) {
+ if has_any_fn(impl_blk, names) {
return None;
}
}
@@ -372,12 +374,12 @@ pub(crate) fn find_struct_impl(
Some(block)
}
-fn has_fn(imp: &ast::Impl, rhs_name: &str) -> bool {
+fn has_any_fn(imp: &ast::Impl, names: &[String]) -> bool {
if let Some(il) = imp.assoc_item_list() {
for item in il.assoc_items() {
if let ast::AssocItem::Fn(f) = item {
if let Some(name) = f.name() {
- if name.text().eq_ignore_ascii_case(rhs_name) {
+ if names.iter().any(|n| n.eq_ignore_ascii_case(&name.text())) {
return true;
}
}
@@ -424,34 +426,44 @@ pub(crate) fn generate_trait_impl_text(adt: &ast::Adt, trait_text: &str, code: &
}
fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str) -> String {
- let generic_params = adt.generic_param_list();
+ // Ensure lifetime params are before type & const params
+ let generic_params = adt.generic_param_list().map(|generic_params| {
+ let lifetime_params =
+ generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam);
+ let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| {
+ // remove defaults since they can't be specified in impls
+ match param {
+ ast::TypeOrConstParam::Type(param) => {
+ let param = param.clone_for_update();
+ param.remove_default();
+ Some(ast::GenericParam::TypeParam(param))
+ }
+ ast::TypeOrConstParam::Const(param) => {
+ let param = param.clone_for_update();
+ param.remove_default();
+ Some(ast::GenericParam::ConstParam(param))
+ }
+ }
+ });
+
+ make::generic_param_list(itertools::chain(lifetime_params, ty_or_const_params))
+ });
+
+ // FIXME: use syntax::make & mutable AST apis instead
+ // `trait_text` and `code` can't be opaque blobs of text
let mut buf = String::with_capacity(code.len());
+
+ // Copy any cfg attrs from the original adt
buf.push_str("\n\n");
- adt.attrs()
- .filter(|attr| attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false))
- .for_each(|attr| buf.push_str(format!("{}\n", attr).as_str()));
+ let cfg_attrs = adt
+ .attrs()
+ .filter(|attr| attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false));
+ cfg_attrs.for_each(|attr| buf.push_str(&format!("{attr}\n")));
+
+ // `impl{generic_params} {trait_text} for {name}{generic_params.to_generic_args()}`
buf.push_str("impl");
if let Some(generic_params) = &generic_params {
- let lifetimes = generic_params.lifetime_params().map(|lt| format!("{}", lt.syntax()));
- let toc_params = generic_params.type_or_const_params().map(|toc_param| {
- let type_param = match toc_param {
- ast::TypeOrConstParam::Type(x) => x,
- ast::TypeOrConstParam::Const(x) => return x.syntax().to_string(),
- };
- let mut buf = String::new();
- if let Some(it) = type_param.name() {
- format_to!(buf, "{}", it.syntax());
- }
- if let Some(it) = type_param.colon_token() {
- format_to!(buf, "{} ", it);
- }
- if let Some(it) = type_param.type_bound_list() {
- format_to!(buf, "{}", it.syntax());
- }
- buf
- });
- let generics = lifetimes.chain(toc_params).format(", ");
- format_to!(buf, "<{}>", generics);
+ format_to!(buf, "{generic_params}");
}
buf.push(' ');
if let Some(trait_text) = trait_text {
@@ -460,23 +472,15 @@ fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str
}
buf.push_str(&adt.name().unwrap().text());
if let Some(generic_params) = generic_params {
- let lifetime_params = generic_params
- .lifetime_params()
- .filter_map(|it| it.lifetime())
- .map(|it| SmolStr::from(it.text()));
- let toc_params = generic_params
- .type_or_const_params()
- .filter_map(|it| it.name())
- .map(|it| SmolStr::from(it.text()));
- format_to!(buf, "<{}>", lifetime_params.chain(toc_params).format(", "))
+ format_to!(buf, "{}", generic_params.to_generic_args());
}
match adt.where_clause() {
Some(where_clause) => {
- format_to!(buf, "\n{}\n{{\n{}\n}}", where_clause, code);
+ format_to!(buf, "\n{where_clause}\n{{\n{code}\n}}");
}
None => {
- format_to!(buf, " {{\n{}\n}}", code);
+ format_to!(buf, " {{\n{code}\n}}");
}
}