From cf94bdc0742c13e2a0cac864c478b8626b266e1b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:11:38 +0200 Subject: Merging upstream version 1.66.0+dfsg1. Signed-off-by: Daniel Baumann --- .../rust-analyzer/crates/ide-assists/Cargo.toml | 2 +- .../crates/ide-assists/src/assist_config.rs | 1 + .../src/handlers/add_missing_match_arms.rs | 17 +- .../crates/ide-assists/src/handlers/auto_import.rs | 23 +- .../src/handlers/convert_into_to_from.rs | 2 +- .../convert_named_struct_to_tuple_struct.rs | 822 +++++++++++++++++++++ .../ide-assists/src/handlers/extract_function.rs | 1 + .../handlers/extract_struct_from_enum_variant.rs | 35 +- .../ide-assists/src/handlers/generate_constant.rs | 12 +- .../handlers/generate_default_from_enum_variant.rs | 5 +- .../src/handlers/generate_default_from_new.rs | 61 +- .../src/handlers/generate_delegate_methods.rs | 8 +- .../ide-assists/src/handlers/generate_deref.rs | 18 +- .../handlers/generate_documentation_template.rs | 52 +- .../src/handlers/generate_enum_is_method.rs | 18 +- .../handlers/generate_enum_projection_method.rs | 39 +- .../src/handlers/generate_from_impl_for_enum.rs | 19 +- .../ide-assists/src/handlers/generate_function.rs | 141 ++-- .../ide-assists/src/handlers/generate_getter.rs | 325 ++++++-- .../ide-assists/src/handlers/generate_impl.rs | 15 +- .../ide-assists/src/handlers/generate_new.rs | 23 +- .../ide-assists/src/handlers/generate_setter.rs | 21 +- .../src/handlers/move_format_string_arg.rs | 296 ++++++++ .../src/handlers/qualify_method_call.rs | 7 +- .../ide-assists/src/handlers/qualify_path.rs | 3 +- .../handlers/replace_derive_with_manual_impl.rs | 2 +- .../handlers/replace_qualified_name_with_use.rs | 1 + .../ide-assists/src/handlers/unwrap_tuple.rs | 159 ++++ .../rust-analyzer/crates/ide-assists/src/lib.rs | 6 + .../rust-analyzer/crates/ide-assists/src/tests.rs | 14 +- .../crates/ide-assists/src/tests/generated.rs | 91 +++ .../rust-analyzer/crates/ide-assists/src/utils.rs | 88 +-- 32 files changed, 1968 insertions(+), 359 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs create mode 100644 src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_format_string_arg.rs create mode 100644 src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs (limited to 'src/tools/rust-analyzer/crates/ide-assists') 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, pub allowed: Option>, 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 { +fn build_pat( + db: &RootDatabase, + module: hir::Module, + var: ExtendedVariant, + prefer_no_std: bool, +) -> Option { 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::() { + None } else if let Some(pat) = ctx .find_node_at_offset_with_descend::() .filter(ast::IdentPat::is_simple_ident) @@ -265,6 +270,20 @@ mod tests { assert_eq!(labels, order); } + #[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" 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::() + .map(Either::Left) + .or_else(|| ctx.find_node_at_offset::().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, + 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, +) { + 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, +) { + 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 +where + T: Display, +{ field1: T } +"#, + r#" +struct Wrap(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 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 { 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::()?; let field_list = ctx.find_node_at_offset::()?; 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) -> Option) -> Option None, }; @@ -228,7 +235,9 @@ fn introduction_builder(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option 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(¶m) { - 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 { .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::()?; 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) -> 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, + 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, + 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::()?; - let field = ctx.find_node_at_offset::()?; + // 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::()?; + let field = ctx.find_node_at_offset::()?; + + 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 { + // and fn () { 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, Vec)> { + let mut field_names: Vec = 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::>(); + + 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 { + 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( @@ -134,6 +135,18 @@ mod tests { }"#, ); + check_assist( + generate_impl, + r#" + struct Defaulted {}$0"#, + r#" + struct Defaulted {} + + impl Defaulted { + $0 + }"#, + ); + check_assist( generate_impl, r#"pub trait Trait {} 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::>(); @@ -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::()?; + 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 = 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 @@ -407,6 +407,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( @@ -1591,6 +1632,37 @@ fn apply(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( @@ -2355,6 +2427,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( 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> { 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}}"); } } -- cgit v1.2.3