diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:11:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:11:28 +0000 |
commit | 94a0819fe3a0d679c3042a77bfe6a2afc505daea (patch) | |
tree | 2b827afe6a05f3538db3f7803a88c4587fe85648 /src/tools/rust-analyzer/crates/ide-assists | |
parent | Adding upstream version 1.64.0+dfsg1. (diff) | |
download | rustc-94a0819fe3a0d679c3042a77bfe6a2afc505daea.tar.xz rustc-94a0819fe3a0d679c3042a77bfe6a2afc505daea.zip |
Adding upstream version 1.66.0+dfsg1.upstream/1.66.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-assists')
51 files changed, 4103 insertions, 709 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/assist_context.rs b/src/tools/rust-analyzer/crates/ide-assists/src/assist_context.rs index f9b426614..8c7670e0c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/assist_context.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/assist_context.rs @@ -1,28 +1,20 @@ //! See [`AssistContext`]. -use std::mem; - use hir::Semantics; -use ide_db::{ - base_db::{AnchoredPathBuf, FileId, FileRange}, - SnippetCap, -}; -use ide_db::{ - label::Label, - source_change::{FileSystemEdit, SourceChange}, - RootDatabase, -}; +use ide_db::base_db::{FileId, FileRange}; +use ide_db::{label::Label, RootDatabase}; use syntax::{ algo::{self, find_node_at_offset, find_node_at_range}, - AstNode, AstToken, Direction, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr, - SyntaxToken, TextRange, TextSize, TokenAtOffset, + AstNode, AstToken, Direction, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange, + TextSize, TokenAtOffset, }; -use text_edit::{TextEdit, TextEditBuilder}; use crate::{ assist_config::AssistConfig, Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel, }; +pub(crate) use ide_db::source_change::{SourceChangeBuilder, TreeMutator}; + /// `AssistContext` allows to apply an assist or check if it could be applied. /// /// Assists use a somewhat over-engineered approach, given the current needs. @@ -163,7 +155,7 @@ impl Assists { id: AssistId, label: impl Into<String>, target: TextRange, - f: impl FnOnce(&mut AssistBuilder), + f: impl FnOnce(&mut SourceChangeBuilder), ) -> Option<()> { let mut f = Some(f); self.add_impl(None, id, label.into(), target, &mut |it| f.take().unwrap()(it)) @@ -175,7 +167,7 @@ impl Assists { id: AssistId, label: impl Into<String>, target: TextRange, - f: impl FnOnce(&mut AssistBuilder), + f: impl FnOnce(&mut SourceChangeBuilder), ) -> Option<()> { let mut f = Some(f); self.add_impl(Some(group), id, label.into(), target, &mut |it| f.take().unwrap()(it)) @@ -187,7 +179,7 @@ impl Assists { id: AssistId, label: String, target: TextRange, - f: &mut dyn FnMut(&mut AssistBuilder), + f: &mut dyn FnMut(&mut SourceChangeBuilder), ) -> Option<()> { if !self.is_allowed(&id) { return None; @@ -195,7 +187,7 @@ impl Assists { let mut trigger_signature_help = false; let source_change = if self.resolve.should_resolve(&id) { - let mut builder = AssistBuilder::new(self.file); + let mut builder = SourceChangeBuilder::new(self.file); f(&mut builder); trigger_signature_help = builder.trigger_signature_help; Some(builder.finish()) @@ -216,132 +208,3 @@ impl Assists { } } } - -pub(crate) struct AssistBuilder { - edit: TextEditBuilder, - file_id: FileId, - source_change: SourceChange, - trigger_signature_help: bool, - - /// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin. - mutated_tree: Option<TreeMutator>, -} - -pub(crate) struct TreeMutator { - immutable: SyntaxNode, - mutable_clone: SyntaxNode, -} - -impl TreeMutator { - pub(crate) fn new(immutable: &SyntaxNode) -> TreeMutator { - let immutable = immutable.ancestors().last().unwrap(); - let mutable_clone = immutable.clone_for_update(); - TreeMutator { immutable, mutable_clone } - } - - pub(crate) fn make_mut<N: AstNode>(&self, node: &N) -> N { - N::cast(self.make_syntax_mut(node.syntax())).unwrap() - } - - pub(crate) fn make_syntax_mut(&self, node: &SyntaxNode) -> SyntaxNode { - let ptr = SyntaxNodePtr::new(node); - ptr.to_node(&self.mutable_clone) - } -} - -impl AssistBuilder { - pub(crate) fn new(file_id: FileId) -> AssistBuilder { - AssistBuilder { - edit: TextEdit::builder(), - file_id, - source_change: SourceChange::default(), - trigger_signature_help: false, - mutated_tree: None, - } - } - - pub(crate) fn edit_file(&mut self, file_id: FileId) { - self.commit(); - self.file_id = file_id; - } - - fn commit(&mut self) { - if let Some(tm) = self.mutated_tree.take() { - algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit) - } - - let edit = mem::take(&mut self.edit).finish(); - if !edit.is_empty() { - self.source_change.insert_source_edit(self.file_id, edit); - } - } - - pub(crate) fn make_mut<N: AstNode>(&mut self, node: N) -> N { - self.mutated_tree.get_or_insert_with(|| TreeMutator::new(node.syntax())).make_mut(&node) - } - /// Returns a copy of the `node`, suitable for mutation. - /// - /// Syntax trees in rust-analyzer are typically immutable, and mutating - /// operations panic at runtime. However, it is possible to make a copy of - /// the tree and mutate the copy freely. Mutation is based on interior - /// mutability, and different nodes in the same tree see the same mutations. - /// - /// The typical pattern for an assist is to find specific nodes in the read - /// phase, and then get their mutable couterparts using `make_mut` in the - /// mutable state. - pub(crate) fn make_syntax_mut(&mut self, node: SyntaxNode) -> SyntaxNode { - self.mutated_tree.get_or_insert_with(|| TreeMutator::new(&node)).make_syntax_mut(&node) - } - - /// Remove specified `range` of text. - pub(crate) fn delete(&mut self, range: TextRange) { - self.edit.delete(range) - } - /// Append specified `text` at the given `offset` - pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) { - self.edit.insert(offset, text.into()) - } - /// Append specified `snippet` at the given `offset` - pub(crate) fn insert_snippet( - &mut self, - _cap: SnippetCap, - offset: TextSize, - snippet: impl Into<String>, - ) { - self.source_change.is_snippet = true; - self.insert(offset, snippet); - } - /// Replaces specified `range` of text with a given string. - pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { - self.edit.replace(range, replace_with.into()) - } - /// Replaces specified `range` of text with a given `snippet`. - pub(crate) fn replace_snippet( - &mut self, - _cap: SnippetCap, - range: TextRange, - snippet: impl Into<String>, - ) { - self.source_change.is_snippet = true; - self.replace(range, snippet); - } - pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { - algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) - } - pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) { - let file_system_edit = FileSystemEdit::CreateFile { dst, initial_contents: content.into() }; - self.source_change.push_file_system_edit(file_system_edit); - } - pub(crate) fn move_file(&mut self, src: FileId, dst: AnchoredPathBuf) { - let file_system_edit = FileSystemEdit::MoveFile { src, dst }; - self.source_change.push_file_system_edit(file_system_edit); - } - pub(crate) fn trigger_signature_help(&mut self) { - self.trigger_signature_help = true; - } - - fn finish(mut self) -> SourceChange { - self.commit(); - mem::take(&mut self.source_change) - } -} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs index c808c010c..62cf5ab4f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -944,7 +944,7 @@ foo!(); struct Foo(usize); impl FooB for Foo { - $0fn foo< 'lt>(& 'lt self){} + $0fn foo<'lt>(&'lt self){} } "#, ) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs index b16f6fe03..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 @@ -5,6 +5,7 @@ use hir::{Adt, Crate, HasAttrs, HasSource, ModuleDef, Semantics}; use ide_db::RootDatabase; use ide_db::{famous_defs::FamousDefs, helpers::mod_path_to_ast}; use itertools::Itertools; +use syntax::ast::edit_in_place::Removable; use syntax::ast::{self, make, AstNode, HasName, MatchArmList, MatchExpr, Pat}; use crate::{ @@ -86,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()), )) }) @@ -131,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) }) @@ -348,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/convert_tuple_struct_to_named_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index 4ab8e93a2..d8f522708 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -5,7 +5,7 @@ use syntax::{ match_ast, SyntaxNode, }; -use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists}; +use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists}; // Assist: convert_tuple_struct_to_named_struct // @@ -80,7 +80,7 @@ pub(crate) fn convert_tuple_struct_to_named_struct( fn edit_struct_def( ctx: &AssistContext<'_>, - edit: &mut AssistBuilder, + edit: &mut SourceChangeBuilder, strukt: &Either<ast::Struct, ast::Variant>, tuple_fields: ast::TupleFieldList, names: Vec<ast::Name>, @@ -122,7 +122,7 @@ fn edit_struct_def( fn edit_struct_references( ctx: &AssistContext<'_>, - edit: &mut AssistBuilder, + edit: &mut SourceChangeBuilder, strukt: Either<hir::Struct, hir::Variant>, names: &[ast::Name], ) { @@ -132,7 +132,7 @@ fn edit_struct_references( }; let usages = strukt_def.usages(&ctx.sema).include_self_refs().all(); - let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> { + let edit_node = |edit: &mut SourceChangeBuilder, node: SyntaxNode| -> Option<()> { match_ast! { match node { ast::TupleStructPat(tuple_struct_pat) => { @@ -203,7 +203,7 @@ fn edit_struct_references( fn edit_field_references( ctx: &AssistContext<'_>, - edit: &mut AssistBuilder, + edit: &mut SourceChangeBuilder, fields: impl Iterator<Item = ast::TupleField>, names: &[ast::Name], ) { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs new file mode 100644 index 000000000..54a7f480a --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs @@ -0,0 +1,294 @@ +use syntax::ast::{self, AstNode}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: convert_two_arm_bool_match_to_matches_macro +// +// Convert 2-arm match that evaluates to a boolean into the equivalent matches! invocation. +// +// ``` +// fn main() { +// match scrutinee$0 { +// Some(val) if val.cond() => true, +// _ => false, +// } +// } +// ``` +// -> +// ``` +// fn main() { +// matches!(scrutinee, Some(val) if val.cond()) +// } +// ``` +pub(crate) fn convert_two_arm_bool_match_to_matches_macro( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; + let match_arm_list = match_expr.match_arm_list()?; + let mut arms = match_arm_list.arms(); + let first_arm = arms.next()?; + let second_arm = arms.next()?; + if arms.next().is_some() { + cov_mark::hit!(non_two_arm_match); + return None; + } + let first_arm_expr = first_arm.expr(); + let second_arm_expr = second_arm.expr(); + + let invert_matches = if is_bool_literal_expr(&first_arm_expr, true) + && is_bool_literal_expr(&second_arm_expr, false) + { + false + } else if is_bool_literal_expr(&first_arm_expr, false) + && is_bool_literal_expr(&second_arm_expr, true) + { + true + } else { + cov_mark::hit!(non_invert_bool_literal_arms); + return None; + }; + + let target_range = ctx.sema.original_range(match_expr.syntax()).range; + let expr = match_expr.expr()?; + + acc.add( + AssistId("convert_two_arm_bool_match_to_matches_macro", AssistKind::RefactorRewrite), + "Convert to matches!", + target_range, + |builder| { + let mut arm_str = String::new(); + if let Some(ref pat) = first_arm.pat() { + arm_str += &pat.to_string(); + } + if let Some(ref guard) = first_arm.guard() { + arm_str += &format!(" {}", &guard.to_string()); + } + if invert_matches { + builder.replace(target_range, format!("!matches!({}, {})", expr, arm_str)); + } else { + builder.replace(target_range, format!("matches!({}, {})", expr, arm_str)); + } + }, + ) +} + +fn is_bool_literal_expr(expr: &Option<ast::Expr>, expect_bool: bool) -> bool { + if let Some(ast::Expr::Literal(lit)) = expr { + if let ast::LiteralKind::Bool(b) = lit.kind() { + return b == expect_bool; + } + } + + return false; +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; + + use super::convert_two_arm_bool_match_to_matches_macro; + + #[test] + fn not_applicable_outside_of_range_left() { + check_assist_not_applicable( + convert_two_arm_bool_match_to_matches_macro, + r#" +fn foo(a: Option<u32>) -> bool { + $0 match a { + Some(_val) => true, + _ => false + } +} + "#, + ); + } + + #[test] + fn not_applicable_non_two_arm_match() { + cov_mark::check!(non_two_arm_match); + check_assist_not_applicable( + convert_two_arm_bool_match_to_matches_macro, + r#" +fn foo(a: Option<u32>) -> bool { + match a$0 { + Some(3) => true, + Some(4) => true, + _ => false + } +} + "#, + ); + } + + #[test] + fn not_applicable_non_bool_literal_arms() { + cov_mark::check!(non_invert_bool_literal_arms); + check_assist_not_applicable( + convert_two_arm_bool_match_to_matches_macro, + r#" +fn foo(a: Option<u32>) -> bool { + match a$0 { + Some(val) => val == 3, + _ => false + } +} + "#, + ); + } + #[test] + fn not_applicable_both_false_arms() { + cov_mark::check!(non_invert_bool_literal_arms); + check_assist_not_applicable( + convert_two_arm_bool_match_to_matches_macro, + r#" +fn foo(a: Option<u32>) -> bool { + match a$0 { + Some(val) => false, + _ => false + } +} + "#, + ); + } + + #[test] + fn not_applicable_both_true_arms() { + cov_mark::check!(non_invert_bool_literal_arms); + check_assist_not_applicable( + convert_two_arm_bool_match_to_matches_macro, + r#" +fn foo(a: Option<u32>) -> bool { + match a$0 { + Some(val) => true, + _ => true + } +} + "#, + ); + } + + #[test] + fn convert_simple_case() { + check_assist( + convert_two_arm_bool_match_to_matches_macro, + r#" +fn foo(a: Option<u32>) -> bool { + match a$0 { + Some(_val) => true, + _ => false + } +} +"#, + r#" +fn foo(a: Option<u32>) -> bool { + matches!(a, Some(_val)) +} +"#, + ); + } + + #[test] + fn convert_simple_invert_case() { + check_assist( + convert_two_arm_bool_match_to_matches_macro, + r#" +fn foo(a: Option<u32>) -> bool { + match a$0 { + Some(_val) => false, + _ => true + } +} +"#, + r#" +fn foo(a: Option<u32>) -> bool { + !matches!(a, Some(_val)) +} +"#, + ); + } + + #[test] + fn convert_with_guard_case() { + check_assist( + convert_two_arm_bool_match_to_matches_macro, + r#" +fn foo(a: Option<u32>) -> bool { + match a$0 { + Some(val) if val > 3 => true, + _ => false + } +} +"#, + r#" +fn foo(a: Option<u32>) -> bool { + matches!(a, Some(val) if val > 3) +} +"#, + ); + } + + #[test] + fn convert_enum_match_cases() { + check_assist( + convert_two_arm_bool_match_to_matches_macro, + r#" +enum X { A, B } + +fn foo(a: X) -> bool { + match a$0 { + X::A => true, + _ => false + } +} +"#, + r#" +enum X { A, B } + +fn foo(a: X) -> bool { + matches!(a, X::A) +} +"#, + ); + } + + #[test] + fn convert_target_simple() { + check_assist_target( + convert_two_arm_bool_match_to_matches_macro, + r#" +fn foo(a: Option<u32>) -> bool { + match a$0 { + Some(val) => true, + _ => false + } +} +"#, + r#"match a { + Some(val) => true, + _ => false + }"#, + ); + } + + #[test] + fn convert_target_complex() { + check_assist_target( + convert_two_arm_bool_match_to_matches_macro, + r#" +enum E { X, Y } + +fn main() { + match E::X$0 { + E::X => true, + _ => false, + } +} +"#, + "match E::X { + E::X => true, + _ => false, + }", + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs index c1f57532b..dc581ff3b 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs @@ -8,7 +8,7 @@ use syntax::{ TextRange, }; -use crate::assist_context::{AssistBuilder, AssistContext, Assists}; +use crate::assist_context::{AssistContext, Assists, SourceChangeBuilder}; // Assist: destructure_tuple_binding // @@ -151,7 +151,7 @@ struct TupleData { } fn edit_tuple_assignment( ctx: &AssistContext<'_>, - builder: &mut AssistBuilder, + builder: &mut SourceChangeBuilder, data: &TupleData, in_sub_pattern: bool, ) { @@ -195,7 +195,7 @@ fn edit_tuple_assignment( fn edit_tuple_usages( data: &TupleData, - builder: &mut AssistBuilder, + builder: &mut SourceChangeBuilder, ctx: &AssistContext<'_>, in_sub_pattern: bool, ) { @@ -211,7 +211,7 @@ fn edit_tuple_usages( } fn edit_tuple_usage( ctx: &AssistContext<'_>, - builder: &mut AssistBuilder, + builder: &mut SourceChangeBuilder, usage: &FileReference, data: &TupleData, in_sub_pattern: bool, @@ -239,7 +239,7 @@ fn edit_tuple_usage( fn edit_tuple_field_usage( ctx: &AssistContext<'_>, - builder: &mut AssistBuilder, + builder: &mut SourceChangeBuilder, data: &TupleData, index: TupleIndex, ) { 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_module.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs index b3c4d306a..897980c66 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs @@ -29,7 +29,7 @@ use super::remove_unused_param::range_to_remove; // Assist: extract_module // -// Extracts a selected region as seperate module. All the references, visibility and imports are +// Extracts a selected region as separate module. All the references, visibility and imports are // resolved. // // ``` @@ -105,7 +105,7 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti // //- Thirdly, resolving all the imports this includes removing paths from imports // outside the module, shifting/cloning them inside new module, or shifting the imports, or making - // new import statemnts + // new import statements //We are getting item usages and record_fields together, record_fields //for change_visibility and usages for first point mentioned above in the process @@ -661,7 +661,7 @@ fn check_intersection_and_push( import_path: TextRange, ) { if import_paths_to_be_removed.len() > 0 { - // Text ranges recieved here for imports are extended to the + // Text ranges received here for imports are extended to the // next/previous comma which can cause intersections among them // and later deletion of these can cause panics similar // to reported in #11766. So to mitigate it, we 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 a93648f2d..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, @@ -20,7 +20,7 @@ use syntax::{ SyntaxNode, T, }; -use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists}; +use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists}; // Assist: extract_struct_from_enum_variant // @@ -101,21 +101,22 @@ pub(crate) fn extract_struct_from_enum_variant( }); } - let indent = enum_ast.indent_level(); let generic_params = enum_ast .generic_param_list() .and_then(|known_generics| extract_generic_params(&known_generics, &field_list)); let generics = generic_params.as_ref().map(|generics| generics.clone_for_update()); let def = create_struct_def(variant_name.clone(), &variant, &field_list, generics, &enum_ast); + + let enum_ast = variant.parent_enum(); + let indent = enum_ast.indent_level(); def.reindent_to(indent); - let start_offset = &variant.parent_enum().syntax().clone(); - ted::insert_all_raw( - ted::Position::before(start_offset), + ted::insert_all( + ted::Position::before(enum_ast.syntax()), vec![ def.syntax().clone().into(), - make::tokens::whitespace(&format!("\n\n{}", indent)).into(), + make::tokens::whitespace(&format!("\n\n{indent}")).into(), ], ); @@ -227,7 +228,7 @@ fn tag_generics_in_variant(ty: &ast::Type, generics: &mut [(ast::GenericParam, b } fn create_struct_def( - variant_name: ast::Name, + name: ast::Name, variant: &ast::Variant, field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>, generics: Option<ast::GenericParamList>, @@ -269,43 +270,27 @@ fn create_struct_def( field_list.into() } }; - field_list.reindent_to(IndentLevel::single()); - let strukt = make::struct_(enum_vis, variant_name, generics, field_list).clone_for_update(); - - // FIXME: Consider making this an actual function somewhere (like in `AttrsOwnerEdit`) after some deliberation - let attrs_and_docs = |node: &SyntaxNode| { - let mut select_next_ws = false; - node.children_with_tokens().filter(move |child| { - let accept = match child.kind() { - ATTR | COMMENT => { - select_next_ws = true; - return true; - } - WHITESPACE if select_next_ws => true, - _ => false, - }; - select_next_ws = false; + let strukt = make::struct_(enum_vis, name, generics, field_list).clone_for_update(); - accept - }) - }; - - // copy attributes & comments from variant - let variant_attrs = attrs_and_docs(variant.syntax()) - .map(|tok| match tok.kind() { - WHITESPACE => make::tokens::single_newline().into(), - _ => tok, - }) - .collect(); - ted::insert_all(ted::Position::first_child_of(strukt.syntax()), variant_attrs); + // take comments from variant + ted::insert_all( + ted::Position::first_child_of(strukt.syntax()), + take_all_comments(variant.syntax()), + ); // copy attributes from enum ted::insert_all( ted::Position::first_child_of(strukt.syntax()), - enum_.attrs().map(|it| it.syntax().clone_for_update().into()).collect(), + enum_ + .attrs() + .flat_map(|it| { + vec![it.syntax().clone_for_update().into(), make::tokens::single_newline().into()] + }) + .collect(), ); + strukt } @@ -313,49 +298,51 @@ 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 let tuple_field = make::tuple_field(None, ty); - let replacement = make::variant( - name, - Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))), - ) - .clone_for_update(); - ted::replace(variant.syntax(), replacement.syntax()); + let field_list = make::tuple_field_list(iter::once(tuple_field)).clone_for_update(); + ted::replace(variant.field_list()?.syntax(), field_list.syntax()); + + // remove any ws after the name + if let Some(ws) = name + .syntax() + .siblings_with_tokens(syntax::Direction::Next) + .find_map(|tok| tok.into_token().filter(|tok| tok.kind() == WHITESPACE)) + { + ted::remove(SyntaxElement::Token(ws)); + } + Some(()) } +// Note: this also detaches whitespace after comments, +// since `SyntaxNode::splice_children` (and by extension `ted::insert_all_raw`) +// detaches nodes. If we only took the comments, we'd leave behind the old whitespace. +fn take_all_comments(node: &SyntaxNode) -> Vec<SyntaxElement> { + let mut remove_next_ws = false; + node.children_with_tokens() + .filter_map(move |child| match child.kind() { + COMMENT => { + remove_next_ws = true; + child.detach(); + Some(child) + } + WHITESPACE if remove_next_ws => { + remove_next_ws = false; + child.detach(); + Some(make::tokens::single_newline().into()) + } + _ => { + remove_next_ws = false; + None + } + }) + .collect() +} + fn apply_references( insert_use_cfg: InsertUseConfig, segment: ast::PathSegment, @@ -374,7 +361,7 @@ fn apply_references( fn process_references( ctx: &AssistContext<'_>, - builder: &mut AssistBuilder, + builder: &mut SourceChangeBuilder, visited_modules: &mut FxHashSet<Module>, enum_module_def: &ModuleDef, variant_hir_name: &Name, @@ -392,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(); @@ -480,10 +468,14 @@ enum En<T> { Var(Var<T>) }"#, fn test_extract_struct_carries_over_attributes() { check_assist( extract_struct_from_enum_variant, - r#"#[derive(Debug)] + r#" +#[derive(Debug)] #[derive(Clone)] enum Enum { Variant{ field: u32$0 } }"#, - r#"#[derive(Debug)]#[derive(Clone)] struct Variant{ field: u32 } + r#" +#[derive(Debug)] +#[derive(Clone)] +struct Variant{ field: u32 } #[derive(Debug)] #[derive(Clone)] @@ -614,7 +606,7 @@ enum A { One(One) }"#, } #[test] - fn test_extract_struct_keep_comments_and_attrs_on_variant_struct() { + fn test_extract_struct_move_struct_variant_comments() { check_assist( extract_struct_from_enum_variant, r#" @@ -631,19 +623,19 @@ enum A { /* comment */ // other /// comment -#[attr] struct One{ a: u32 } enum A { + #[attr] One(One) }"#, ); } #[test] - fn test_extract_struct_keep_comments_and_attrs_on_variant_tuple() { + fn test_extract_struct_move_tuple_variant_comments() { check_assist( extract_struct_from_enum_variant, r#" @@ -658,10 +650,10 @@ enum A { /* comment */ // other /// comment -#[attr] struct One(u32, u32); enum A { + #[attr] One(One) }"#, ); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs index af584cdb4..03aa8601d 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs @@ -171,6 +171,25 @@ fn collect_used_generics<'gp>( ast::Type::RefType(ref_) => generics.extend( ref_.lifetime().and_then(|lt| known_generics.iter().find(find_lifetime(<.text()))), ), + ast::Type::ArrayType(ar) => { + if let Some(expr) = ar.expr() { + if let ast::Expr::PathExpr(p) = expr { + if let Some(path) = p.path() { + if let Some(name_ref) = path.as_single_name_ref() { + if let Some(param) = known_generics.iter().find(|gp| { + if let ast::GenericParam::ConstParam(cp) = gp { + cp.name().map_or(false, |n| n.text() == name_ref.text()) + } else { + false + } + }) { + generics.push(param); + } + } + } + } + } + } _ => (), }); // stable resort to lifetime, type, const @@ -357,4 +376,29 @@ impl<'outer, Outer, const OUTER: usize> () { "#, ); } + + #[test] + fn issue_11197() { + check_assist( + extract_type_alias, + r#" +struct Foo<T, const N: usize> +where + [T; N]: Sized, +{ + arr: $0[T; N]$0, +} + "#, + r#" +type $0Type<T, const N: usize> = [T; N]; + +struct Foo<T, const N: usize> +where + [T; N]: Sized, +{ + arr: Type<T, N>, +} + "#, + ); + } } 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 b9637ee8d..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 @@ -8,7 +8,7 @@ use syntax::{ }; use crate::{ - assist_context::{AssistBuilder, AssistContext, Assists}, + assist_context::{AssistContext, Assists, SourceChangeBuilder}, utils::generate_trait_impl_text, AssistId, AssistKind, }; @@ -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( @@ -120,7 +122,7 @@ fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<() } fn generate_edit( - edit: &mut AssistBuilder, + edit: &mut SourceChangeBuilder, strukt: ast::Struct, field_type_syntax: &SyntaxNode, field_name: impl Display, @@ -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(¶m_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, ¶m_list, self_name.as_deref(), is_unsafe)?; if returns_a_value(ast_func, ctx) { if count_parameters(¶m_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(¶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<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_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs index 4461fbd5a..35cd42908 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs @@ -1,8 +1,8 @@ -use hir::{HasSource, InFile}; +use hir::{HasSource, HirDisplay, InFile}; use ide_db::assists::{AssistId, AssistKind}; use syntax::{ - ast::{self, edit::IndentLevel}, - AstNode, TextSize, + ast::{self, make, HasArgList}, + match_ast, AstNode, SyntaxNode, }; use crate::assist_context::{AssistContext, Assists}; @@ -32,8 +32,8 @@ use crate::assist_context::{AssistContext, Assists}; // } // ``` pub(crate) fn generate_enum_variant(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; - let path = path_expr.path()?; + let path: ast::Path = ctx.find_node_at_offset()?; + let parent = path_parent(&path)?; if ctx.sema.resolve_path(&path).is_some() { // No need to generate anything if the path resolves @@ -50,26 +50,71 @@ pub(crate) fn generate_enum_variant(acc: &mut Assists, ctx: &AssistContext<'_>) ctx.sema.resolve_path(&path.qualifier()?) { let target = path.syntax().text_range(); - return add_variant_to_accumulator(acc, ctx, target, e, &name_ref); + return add_variant_to_accumulator(acc, ctx, target, e, &name_ref, parent); } None } +#[derive(Debug)] +enum PathParent { + PathExpr(ast::PathExpr), + RecordExpr(ast::RecordExpr), + PathPat(ast::PathPat), + UseTree(ast::UseTree), +} + +impl PathParent { + fn syntax(&self) -> &SyntaxNode { + match self { + PathParent::PathExpr(it) => it.syntax(), + PathParent::RecordExpr(it) => it.syntax(), + PathParent::PathPat(it) => it.syntax(), + PathParent::UseTree(it) => it.syntax(), + } + } + + fn make_field_list(&self, ctx: &AssistContext<'_>) -> Option<ast::FieldList> { + let scope = ctx.sema.scope(self.syntax())?; + + match self { + PathParent::PathExpr(it) => { + if let Some(call_expr) = it.syntax().parent().and_then(ast::CallExpr::cast) { + make_tuple_field_list(call_expr, ctx, &scope) + } else { + None + } + } + PathParent::RecordExpr(it) => make_record_field_list(it, ctx, &scope), + PathParent::UseTree(_) | PathParent::PathPat(_) => None, + } + } +} + +fn path_parent(path: &ast::Path) -> Option<PathParent> { + let parent = path.syntax().parent()?; + + match_ast! { + match parent { + ast::PathExpr(it) => Some(PathParent::PathExpr(it)), + ast::RecordExpr(it) => Some(PathParent::RecordExpr(it)), + ast::PathPat(it) => Some(PathParent::PathPat(it)), + ast::UseTree(it) => Some(PathParent::UseTree(it)), + _ => None + } + } +} + fn add_variant_to_accumulator( acc: &mut Assists, ctx: &AssistContext<'_>, target: syntax::TextRange, adt: hir::Enum, name_ref: &ast::NameRef, + parent: PathParent, ) -> Option<()> { let db = ctx.db(); let InFile { file_id, value: enum_node } = adt.source(db)?.original_ast_node(db)?; - let enum_indent = IndentLevel::from_node(&enum_node.syntax()); - - let variant_list = enum_node.variant_list()?; - let offset = variant_list.syntax().text_range().end() - TextSize::of('}'); - let empty_enum = variant_list.variants().next().is_none(); acc.add( AssistId("generate_enum_variant", AssistKind::Generate), @@ -77,18 +122,80 @@ fn add_variant_to_accumulator( target, |builder| { builder.edit_file(file_id.original_file(db)); - let text = format!( - "{maybe_newline}{indent_1}{name},\n{enum_indent}", - maybe_newline = if empty_enum { "\n" } else { "" }, - indent_1 = IndentLevel(1), - name = name_ref, - enum_indent = enum_indent - ); - builder.insert(offset, text) + let node = builder.make_mut(enum_node); + let variant = make_variant(ctx, name_ref, parent); + node.variant_list().map(|it| it.add_variant(variant.clone_for_update())); }, ) } +fn make_variant( + ctx: &AssistContext<'_>, + name_ref: &ast::NameRef, + parent: PathParent, +) -> ast::Variant { + let field_list = parent.make_field_list(ctx); + make::variant(make::name(&name_ref.text()), field_list) +} + +fn make_record_field_list( + record: &ast::RecordExpr, + ctx: &AssistContext<'_>, + scope: &hir::SemanticsScope<'_>, +) -> Option<ast::FieldList> { + let fields = record.record_expr_field_list()?.fields(); + let record_fields = fields.map(|field| { + let name = name_from_field(&field); + + let ty = field + .expr() + .and_then(|it| expr_ty(ctx, it, scope)) + .unwrap_or_else(make::ty_placeholder); + + make::record_field(None, name, ty) + }); + Some(make::record_field_list(record_fields).into()) +} + +fn name_from_field(field: &ast::RecordExprField) -> ast::Name { + let text = match field.name_ref() { + Some(it) => it.to_string(), + None => name_from_field_shorthand(field).unwrap_or("unknown".to_string()), + }; + make::name(&text) +} + +fn name_from_field_shorthand(field: &ast::RecordExprField) -> Option<String> { + let path = match field.expr()? { + ast::Expr::PathExpr(path_expr) => path_expr.path(), + _ => None, + }?; + Some(path.as_single_name_ref()?.to_string()) +} + +fn make_tuple_field_list( + call_expr: ast::CallExpr, + ctx: &AssistContext<'_>, + scope: &hir::SemanticsScope<'_>, +) -> Option<ast::FieldList> { + let args = call_expr.arg_list()?.args(); + let tuple_fields = args.map(|arg| { + let ty = expr_ty(ctx, arg, &scope).unwrap_or_else(make::ty_placeholder); + make::tuple_field(None, ty) + }); + Some(make::tuple_field_list(tuple_fields).into()) +} + +fn expr_ty( + ctx: &AssistContext<'_>, + arg: ast::Expr, + scope: &hir::SemanticsScope<'_>, +) -> Option<ast::Type> { + let ty = ctx.sema.type_of_expr(&arg).map(|it| it.adjusted())?; + let text = ty.display_source_code(ctx.db(), scope.module().into()).ok()?; + Some(make::ty(&text)) +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; @@ -224,4 +331,234 @@ fn main() { ", ) } + + #[test] + fn associated_single_element_tuple() { + check_assist( + generate_enum_variant, + r" +enum Foo {} +fn main() { + Foo::Bar$0(true) +} +", + r" +enum Foo { + Bar(bool), +} +fn main() { + Foo::Bar(true) +} +", + ) + } + + #[test] + fn associated_single_element_tuple_unknown_type() { + check_assist( + generate_enum_variant, + r" +enum Foo {} +fn main() { + Foo::Bar$0(x) +} +", + r" +enum Foo { + Bar(_), +} +fn main() { + Foo::Bar(x) +} +", + ) + } + + #[test] + fn associated_multi_element_tuple() { + check_assist( + generate_enum_variant, + r" +struct Struct {} +enum Foo {} +fn main() { + Foo::Bar$0(true, x, Struct {}) +} +", + r" +struct Struct {} +enum Foo { + Bar(bool, _, Struct), +} +fn main() { + Foo::Bar(true, x, Struct {}) +} +", + ) + } + + #[test] + fn associated_record() { + check_assist( + generate_enum_variant, + r" +enum Foo {} +fn main() { + Foo::$0Bar { x: true } +} +", + r" +enum Foo { + Bar { x: bool }, +} +fn main() { + Foo::Bar { x: true } +} +", + ) + } + + #[test] + fn associated_record_unknown_type() { + check_assist( + generate_enum_variant, + r" +enum Foo {} +fn main() { + Foo::$0Bar { x: y } +} +", + r" +enum Foo { + Bar { x: _ }, +} +fn main() { + Foo::Bar { x: y } +} +", + ) + } + + #[test] + fn associated_record_field_shorthand() { + check_assist( + generate_enum_variant, + r" +enum Foo {} +fn main() { + let x = true; + Foo::$0Bar { x } +} +", + r" +enum Foo { + Bar { x: bool }, +} +fn main() { + let x = true; + Foo::Bar { x } +} +", + ) + } + + #[test] + fn associated_record_field_shorthand_unknown_type() { + check_assist( + generate_enum_variant, + r" +enum Foo {} +fn main() { + Foo::$0Bar { x } +} +", + r" +enum Foo { + Bar { x: _ }, +} +fn main() { + Foo::Bar { x } +} +", + ) + } + + #[test] + fn associated_record_field_multiple_fields() { + check_assist( + generate_enum_variant, + r" +struct Struct {} +enum Foo {} +fn main() { + Foo::$0Bar { x, y: x, s: Struct {} } +} +", + r" +struct Struct {} +enum Foo { + Bar { x: _, y: _, s: Struct }, +} +fn main() { + Foo::Bar { x, y: x, s: Struct {} } +} +", + ) + } + + #[test] + fn use_tree() { + check_assist( + generate_enum_variant, + r" +//- /main.rs +mod foo; +use foo::Foo::Bar$0; + +//- /foo.rs +enum Foo {} +", + r" +enum Foo { + Bar, +} +", + ) + } + + #[test] + fn not_applicable_for_path_type() { + check_assist_not_applicable( + generate_enum_variant, + r" +enum Foo {} +impl Foo::Bar$0 {} +", + ) + } + + #[test] + fn path_pat() { + check_assist( + generate_enum_variant, + r" +enum Foo {} +fn foo(x: Foo) { + match x { + Foo::Bar$0 => + } +} +", + r" +enum Foo { + Bar, +} +fn foo(x: Foo) { + match x { + Foo::Bar => + } +} +", + ) + } } 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 d564a0540..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}, @@ -61,56 +61,72 @@ fn gen_fn(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { } let fn_name = &*name_ref.text(); - let target_module; - let mut adt_name = None; + let TargetInfo { target_module, adt_name, target, file, insert_offset } = + fn_target_info(ctx, path, &call, fn_name)?; + let function_builder = FunctionBuilder::from_call(ctx, &call, fn_name, target_module, target)?; + let text_range = call.syntax().text_range(); + let label = format!("Generate {} function", function_builder.fn_name); + add_func_to_accumulator( + acc, + ctx, + text_range, + function_builder, + insert_offset, + file, + adt_name, + label, + ) +} + +struct TargetInfo { + target_module: Option<Module>, + adt_name: Option<hir::Name>, + target: GeneratedFunctionTarget, + file: FileId, + insert_offset: TextSize, +} - let (target, file, insert_offset) = match path.qualifier() { +impl TargetInfo { + fn new( + target_module: Option<Module>, + adt_name: Option<hir::Name>, + target: GeneratedFunctionTarget, + file: FileId, + insert_offset: TextSize, + ) -> Self { + Self { target_module, adt_name, target, file, insert_offset } + } +} + +fn fn_target_info( + ctx: &AssistContext<'_>, + path: ast::Path, + call: &CallExpr, + fn_name: &str, +) -> Option<TargetInfo> { + match path.qualifier() { Some(qualifier) => match ctx.sema.resolve_path(&qualifier) { Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => { - target_module = Some(module); - get_fn_target(ctx, &target_module, call.clone())? + get_fn_target_info(ctx, &Some(module), call.clone()) } Some(hir::PathResolution::Def(hir::ModuleDef::Adt(adt))) => { if let hir::Adt::Enum(_) = adt { // Don't suggest generating function if the name starts with an uppercase letter - if name_ref.text().starts_with(char::is_uppercase) { + if fn_name.starts_with(char::is_uppercase) { return None; } } - let current_module = ctx.sema.scope(call.syntax())?.module(); - let module = adt.module(ctx.sema.db); - target_module = if current_module == module { None } else { Some(module) }; - if current_module.krate() != module.krate() { - return None; - } - let (impl_, file) = get_adt_source(ctx, &adt, fn_name)?; - let (target, insert_offset) = get_method_target(ctx, &module, &impl_)?; - adt_name = if impl_.is_none() { Some(adt.name(ctx.sema.db)) } else { None }; - (target, file, insert_offset) + assoc_fn_target_info(ctx, call, adt, fn_name) } - _ => { - return None; + Some(hir::PathResolution::SelfType(impl_)) => { + let adt = impl_.self_ty(ctx.db()).as_adt()?; + assoc_fn_target_info(ctx, call, adt, fn_name) } + _ => None, }, - _ => { - target_module = None; - get_fn_target(ctx, &target_module, call.clone())? - } - }; - let function_builder = FunctionBuilder::from_call(ctx, &call, fn_name, target_module, target)?; - let text_range = call.syntax().text_range(); - let label = format!("Generate {} function", function_builder.fn_name); - add_func_to_accumulator( - acc, - ctx, - text_range, - function_builder, - insert_offset, - file, - adt_name, - label, - ) + _ => get_fn_target_info(ctx, &None, call.clone()), + } } fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { @@ -129,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(); @@ -158,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 { @@ -180,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 { @@ -194,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}") } } @@ -291,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 }; @@ -309,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}"); } }; @@ -366,6 +394,15 @@ fn make_return_type( (ret_type, should_focus_return_type) } +fn get_fn_target_info( + ctx: &AssistContext<'_>, + target_module: &Option<Module>, + call: CallExpr, +) -> Option<TargetInfo> { + let (target, file, insert_offset) = get_fn_target(ctx, target_module, call)?; + Some(TargetInfo::new(*target_module, None, target, file, insert_offset)) +} + fn get_fn_target( ctx: &AssistContext<'_>, target_module: &Option<Module>, @@ -386,19 +423,36 @@ 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))) } +fn assoc_fn_target_info( + ctx: &AssistContext<'_>, + call: &CallExpr, + adt: hir::Adt, + fn_name: &str, +) -> Option<TargetInfo> { + let current_module = ctx.sema.scope(call.syntax())?.module(); + let module = adt.module(ctx.sema.db); + let target_module = if current_module == module { None } else { Some(module) }; + if current_module.krate() != module.krate() { + return None; + } + let (impl_, file) = get_adt_source(ctx, &adt, fn_name)?; + 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)) +} + fn get_insert_offset(target: &GeneratedFunctionTarget) -> TextSize { match &target { GeneratedFunctionTarget::BehindItem(it) => it.text_range().end(), @@ -1425,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();} ", ) } @@ -1473,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();} ", ) @@ -1501,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!() -} -} ", ) @@ -1528,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();} ", ) } @@ -1550,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();} ", ) } @@ -1598,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();} ", ) @@ -1621,13 +1663,38 @@ fn foo() {$0S::bar();} ", r" struct S; -fn foo() {S::bar();} impl S { + fn bar() ${0:-> _} { + todo!() + } +} +fn foo() {S::bar();} +", + ) + } - -fn bar() ${0:-> _} { - todo!() + #[test] + fn create_static_method_within_an_impl_with_self_syntax() { + check_assist( + generate_function, + r" +struct S; +impl S { + fn foo(&self) { + Self::bar$0(); + } } +", + r" +struct S; +impl S { + fn foo(&self) { + Self::bar(); + } + + fn bar() ${0:-> _} { + todo!() + } } ", ) @@ -1771,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/inline_call.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs index 80d3b9255..9f51cdaf8 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs @@ -7,13 +7,14 @@ use ide_db::{ imports::insert_use::remove_path_if_in_use_stmt, path_transform::PathTransform, search::{FileReference, SearchScope}, + source_change::SourceChangeBuilder, syntax_helpers::{insert_whitespace_into_node::insert_ws_into, node_ext::expr_as_name_ref}, RootDatabase, }; use itertools::{izip, Itertools}; use syntax::{ ast::{self, edit_in_place::Indent, HasArgList, PathExpr}, - ted, AstNode, + ted, AstNode, NodeOrToken, SyntaxKind, }; use crate::{ @@ -100,18 +101,7 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) -> builder.edit_file(file_id); let count = refs.len(); // The collects are required as we are otherwise iterating while mutating 🙅♀️🙅♂️ - let (name_refs, name_refs_use): (Vec<_>, Vec<_>) = refs - .into_iter() - .filter_map(|file_ref| match file_ref.name { - ast::NameLike::NameRef(name_ref) => Some(name_ref), - _ => None, - }) - .partition_map(|name_ref| { - match name_ref.syntax().ancestors().find_map(ast::UseTree::cast) { - Some(use_tree) => Either::Right(builder.make_mut(use_tree)), - None => Either::Left(name_ref), - } - }); + let (name_refs, name_refs_use) = split_refs_and_uses(builder, refs, Some); let call_infos: Vec<_> = name_refs .into_iter() .filter_map(CallInfo::from_name_ref) @@ -130,11 +120,7 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) -> .count(); if replaced + name_refs_use.len() == count { // we replaced all usages in this file, so we can remove the imports - name_refs_use.into_iter().for_each(|use_tree| { - if let Some(path) = use_tree.path() { - remove_path_if_in_use_stmt(&path); - } - }) + name_refs_use.iter().for_each(remove_path_if_in_use_stmt); } else { remove_def = false; } @@ -153,6 +139,23 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) -> ) } +pub(super) fn split_refs_and_uses<T: ast::AstNode>( + builder: &mut SourceChangeBuilder, + iter: impl IntoIterator<Item = FileReference>, + mut map_ref: impl FnMut(ast::NameRef) -> Option<T>, +) -> (Vec<T>, Vec<ast::Path>) { + iter.into_iter() + .filter_map(|file_ref| match file_ref.name { + ast::NameLike::NameRef(name_ref) => Some(name_ref), + _ => None, + }) + .filter_map(|name_ref| match name_ref.syntax().ancestors().find_map(ast::UseTree::cast) { + Some(use_tree) => builder.make_mut(use_tree).path().map(Either::Right), + None => map_ref(name_ref).map(Either::Left), + }) + .partition_map(|either| either) +} + // Assist: inline_call // // Inlines a function or method body creating a `let` statement per parameter unless the parameter @@ -311,6 +314,17 @@ fn inline( } else { fn_body.clone_for_update() }; + if let Some(imp) = body.syntax().ancestors().find_map(ast::Impl::cast) { + if !node.syntax().ancestors().any(|anc| &anc == imp.syntax()) { + if let Some(t) = imp.self_ty() { + body.syntax() + .descendants_with_tokens() + .filter_map(NodeOrToken::into_token) + .filter(|tok| tok.kind() == SyntaxKind::SELF_TYPE_KW) + .for_each(|tok| ted::replace(tok, t.syntax())); + } + } + } let usages_for_locals = |local| { Definition::Local(local) .usages(sema) @@ -345,6 +359,7 @@ fn inline( } }) .collect(); + if function.self_param(sema.db).is_some() { let this = || make::name_ref("this").syntax().clone_for_update(); if let Some(self_local) = params[0].2.as_local(sema.db) { @@ -1191,4 +1206,54 @@ fn bar() -> u32 { "#, ) } + + #[test] + fn inline_call_with_self_type() { + check_assist( + inline_call, + r#" +struct A(u32); +impl A { + fn f() -> Self { Self(114514) } +} +fn main() { + A::f$0(); +} +"#, + r#" +struct A(u32); +impl A { + fn f() -> Self { Self(114514) } +} +fn main() { + A(114514); +} +"#, + ) + } + + #[test] + fn inline_call_with_self_type_but_within_same_impl() { + check_assist( + inline_call, + r#" +struct A(u32); +impl A { + fn f() -> Self { Self(1919810) } + fn main() { + Self::f$0(); + } +} +"#, + r#" +struct A(u32); +impl A { + fn f() -> Self { Self(1919810) } + fn main() { + Self(1919810); + } +} +"#, + ) + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs index 054663a06..353d467ed 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs @@ -1,9 +1,12 @@ // Some ideas for future improvements: // - Support replacing aliases which are used in expressions, e.g. `A::new()`. -// - "inline_alias_to_users" assist #10881. // - Remove unused aliases if there are no longer any users, see inline_call.rs. use hir::{HasSource, PathResolution}; +use ide_db::{ + defs::Definition, imports::insert_use::ast_to_remove_for_path_in_use_stmt, + search::FileReference, +}; use itertools::Itertools; use std::collections::HashMap; use syntax::{ @@ -16,6 +19,89 @@ use crate::{ AssistId, AssistKind, }; +use super::inline_call::split_refs_and_uses; + +// Assist: inline_type_alias_uses +// +// Inline a type alias into all of its uses where possible. +// +// ``` +// type $0A = i32; +// fn id(x: A) -> A { +// x +// }; +// fn foo() { +// let _: A = 3; +// } +// ``` +// -> +// ``` +// +// fn id(x: i32) -> i32 { +// x +// }; +// fn foo() { +// let _: i32 = 3; +// } +pub(crate) fn inline_type_alias_uses(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let name = ctx.find_node_at_offset::<ast::Name>()?; + let ast_alias = name.syntax().parent().and_then(ast::TypeAlias::cast)?; + + let hir_alias = ctx.sema.to_def(&ast_alias)?; + let concrete_type = ast_alias.ty()?; + + let usages = Definition::TypeAlias(hir_alias).usages(&ctx.sema); + if !usages.at_least_one() { + return None; + } + + // until this is ok + + acc.add( + AssistId("inline_type_alias_uses", AssistKind::RefactorInline), + "Inline type alias into all uses", + name.syntax().text_range(), + |builder| { + let usages = usages.all(); + let mut definition_deleted = false; + + let mut inline_refs_for_file = |file_id, refs: Vec<FileReference>| { + builder.edit_file(file_id); + + let (path_types, path_type_uses) = + split_refs_and_uses(builder, refs, |path_type| { + path_type.syntax().ancestors().nth(3).and_then(ast::PathType::cast) + }); + + path_type_uses + .iter() + .flat_map(ast_to_remove_for_path_in_use_stmt) + .for_each(|x| builder.delete(x.syntax().text_range())); + for (target, replacement) in path_types.into_iter().filter_map(|path_type| { + let replacement = inline(&ast_alias, &path_type)?.to_text(&concrete_type); + let target = path_type.syntax().text_range(); + Some((target, replacement)) + }) { + builder.replace(target, replacement); + } + + if file_id == ctx.file_id() { + builder.delete(ast_alias.syntax().text_range()); + definition_deleted = true; + } + }; + + for (file_id, refs) in usages.into_iter() { + inline_refs_for_file(file_id, refs); + } + if !definition_deleted { + builder.edit_file(ctx.file_id()); + builder.delete(ast_alias.syntax().text_range()); + } + }, + ) +} + // Assist: inline_type_alias // // Replace a type alias with its concrete type. @@ -36,11 +122,6 @@ use crate::{ // } // ``` pub(crate) fn inline_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - enum Replacement { - Generic { lifetime_map: LifetimeMap, const_and_type_map: ConstAndTypeMap }, - Plain, - } - let alias_instance = ctx.find_node_at_offset::<ast::PathType>()?; let concrete_type; let replacement; @@ -59,23 +140,7 @@ pub(crate) fn inline_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> O _ => { let alias = get_type_alias(&ctx, &alias_instance)?; concrete_type = alias.ty()?; - - replacement = if let Some(alias_generics) = alias.generic_param_list() { - if alias_generics.generic_params().next().is_none() { - cov_mark::hit!(no_generics_params); - return None; - } - - let instance_args = - alias_instance.syntax().descendants().find_map(ast::GenericArgList::cast); - - Replacement::Generic { - lifetime_map: LifetimeMap::new(&instance_args, &alias_generics)?, - const_and_type_map: ConstAndTypeMap::new(&instance_args, &alias_generics)?, - } - } else { - Replacement::Plain - }; + replacement = inline(&alias, &alias_instance)?; } } @@ -85,19 +150,45 @@ pub(crate) fn inline_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> O AssistId("inline_type_alias", AssistKind::RefactorInline), "Inline type alias", target, - |builder| { - let replacement_text = match replacement { - Replacement::Generic { lifetime_map, const_and_type_map } => { - create_replacement(&lifetime_map, &const_and_type_map, &concrete_type) - } - Replacement::Plain => concrete_type.to_string(), - }; - - builder.replace(target, replacement_text); - }, + |builder| builder.replace(target, replacement.to_text(&concrete_type)), ) } +impl Replacement { + fn to_text(&self, concrete_type: &ast::Type) -> String { + match self { + Replacement::Generic { lifetime_map, const_and_type_map } => { + create_replacement(&lifetime_map, &const_and_type_map, &concrete_type) + } + Replacement::Plain => concrete_type.to_string(), + } + } +} + +enum Replacement { + Generic { lifetime_map: LifetimeMap, const_and_type_map: ConstAndTypeMap }, + Plain, +} + +fn inline(alias_def: &ast::TypeAlias, alias_instance: &ast::PathType) -> Option<Replacement> { + let repl = if let Some(alias_generics) = alias_def.generic_param_list() { + if alias_generics.generic_params().next().is_none() { + cov_mark::hit!(no_generics_params); + return None; + } + let instance_args = + alias_instance.syntax().descendants().find_map(ast::GenericArgList::cast); + + Replacement::Generic { + lifetime_map: LifetimeMap::new(&instance_args, &alias_generics)?, + const_and_type_map: ConstAndTypeMap::new(&instance_args, &alias_generics)?, + } + } else { + Replacement::Plain + }; + Some(repl) +} + struct LifetimeMap(HashMap<String, ast::Lifetime>); impl LifetimeMap { @@ -835,4 +926,95 @@ trait Tr { "#, ); } + + mod inline_type_alias_uses { + use crate::{handlers::inline_type_alias::inline_type_alias_uses, tests::check_assist}; + + #[test] + fn inline_uses() { + check_assist( + inline_type_alias_uses, + r#" +type $0A = u32; + +fn foo() { + let _: A = 3; + let _: A = 4; +} +"#, + r#" + + +fn foo() { + let _: u32 = 3; + let _: u32 = 4; +} +"#, + ); + } + + #[test] + fn inline_uses_across_files() { + check_assist( + inline_type_alias_uses, + r#" +//- /lib.rs +mod foo; +type $0T<E> = Vec<E>; +fn f() -> T<&str> { + vec!["hello"] +} + +//- /foo.rs +use super::T; +fn foo() { + let _: T<i8> = Vec::new(); +} +"#, + r#" +//- /lib.rs +mod foo; + +fn f() -> Vec<&str> { + vec!["hello"] +} + +//- /foo.rs + +fn foo() { + let _: Vec<i8> = Vec::new(); +} +"#, + ); + } + + #[test] + fn inline_uses_across_files_2() { + check_assist( + inline_type_alias_uses, + r#" +//- /lib.rs +mod foo; +type $0I = i32; + +//- /foo.rs +use super::I; +fn foo() { + let _: I = 0; +} +"#, + r#" +//- /lib.rs +mod foo; + + +//- /foo.rs + +fn foo() { + let _: i32 = 0; +} +"#, + ); + } + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs index ce91dd237..2fc754e3e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs @@ -5,7 +5,7 @@ use syntax::{ AstNode, TextRange, }; -use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists}; +use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists}; static ASSIST_NAME: &str = "introduce_named_lifetime"; static ASSIST_LABEL: &str = "Introduce named lifetime"; @@ -140,7 +140,7 @@ enum NeedsLifetime { } impl NeedsLifetime { - fn make_mut(self, builder: &mut AssistBuilder) -> Self { + fn make_mut(self, builder: &mut SourceChangeBuilder) -> Self { match self { Self::SelfParam(it) => Self::SelfParam(builder.make_mut(it)), Self::RefType(it) => Self::RefType(builder.make_mut(it)), diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs index 7e102ceba..2bdbec93b 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs @@ -1,6 +1,10 @@ use either::Either; use ide_db::imports::merge_imports::{try_merge_imports, try_merge_trees, MergeBehavior}; -use syntax::{algo::neighbor, ast, match_ast, ted, AstNode, SyntaxElement, SyntaxNode}; +use syntax::{ + algo::neighbor, + ast::{self, edit_in_place::Removable}, + match_ast, ted, AstNode, SyntaxElement, SyntaxNode, +}; use crate::{ assist_context::{AssistContext, Assists}, @@ -76,7 +80,7 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio .collect(); for edit in edits_mut { match edit { - Remove(it) => it.as_ref().either(ast::Use::remove, ast::UseTree::remove), + Remove(it) => it.as_ref().either(Removable::remove, Removable::remove), Replace(old, new) => ted::replace(old, new), } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs index 176a3bf58..1dd376ac3 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs @@ -1,5 +1,9 @@ use syntax::{ - ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode, HasName, HasTypeBounds}, + ast::{ + self, + edit_in_place::{GenericParamsOwnerEdit, Removable}, + make, AstNode, HasName, HasTypeBounds, + }, match_ast, }; 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/remove_unused_param.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_param.rs index 59ea94ea1..bd2e8fbe3 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_param.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_param.rs @@ -8,7 +8,8 @@ use syntax::{ use SyntaxKind::WHITESPACE; use crate::{ - assist_context::AssistBuilder, utils::next_prev, AssistContext, AssistId, AssistKind, Assists, + assist_context::SourceChangeBuilder, utils::next_prev, AssistContext, AssistId, AssistKind, + Assists, }; // Assist: remove_unused_param @@ -88,7 +89,7 @@ pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext<'_>) -> fn process_usages( ctx: &AssistContext<'_>, - builder: &mut AssistBuilder, + builder: &mut SourceChangeBuilder, file_id: FileId, references: Vec<FileReference>, arg_to_remove: usize, 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 bd50208da..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 @@ -10,7 +10,7 @@ use syntax::{ }; use crate::{ - assist_context::{AssistBuilder, AssistContext, Assists}, + assist_context::{AssistContext, Assists, SourceChangeBuilder}, utils::{ add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body, generate_trait_impl_text, render_snippet, Cursor, DefaultMethods, @@ -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_)) @@ -224,7 +224,7 @@ fn impl_def_from_trait( } fn update_attribute( - builder: &mut AssistBuilder, + builder: &mut SourceChangeBuilder, old_derives: &[ast::Path], old_tree: &ast::TokenTree, old_trait_path: &ast::Path, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_or_with_or_else.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_or_with_or_else.rs new file mode 100644 index 000000000..7d91be621 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_or_with_or_else.rs @@ -0,0 +1,364 @@ +use ide_db::{ + assists::{AssistId, AssistKind}, + famous_defs::FamousDefs, +}; +use syntax::{ + ast::{self, make, Expr, HasArgList}, + AstNode, +}; + +use crate::{AssistContext, Assists}; + +// Assist: replace_or_with_or_else +// +// Replace `unwrap_or` with `unwrap_or_else` and `ok_or` with `ok_or_else`. +// +// ``` +// # //- minicore:option +// fn foo() { +// let a = Some(1); +// a.unwra$0p_or(2); +// } +// ``` +// -> +// ``` +// fn foo() { +// let a = Some(1); +// a.unwrap_or_else(|| 2); +// } +// ``` +pub(crate) fn replace_or_with_or_else(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let call: ast::MethodCallExpr = ctx.find_node_at_offset()?; + + let kind = is_option_or_result(call.receiver()?, ctx)?; + + let (name, arg_list) = (call.name_ref()?, call.arg_list()?); + + let mut map_or = false; + + let replace = match &*name.text() { + "unwrap_or" => "unwrap_or_else".to_string(), + "or" => "or_else".to_string(), + "ok_or" if kind == Kind::Option => "ok_or_else".to_string(), + "map_or" => { + map_or = true; + "map_or_else".to_string() + } + _ => return None, + }; + + let arg = match arg_list.args().collect::<Vec<_>>().as_slice() { + [] => make::arg_list(Vec::new()), + [first] => { + let param = into_closure(first); + make::arg_list(vec![param]) + } + [first, second] if map_or => { + let param = into_closure(first); + make::arg_list(vec![param, second.clone()]) + } + _ => return None, + }; + + acc.add( + AssistId("replace_or_with_or_else", AssistKind::RefactorRewrite), + format!("Replace {} with {}", name.text(), replace), + call.syntax().text_range(), + |builder| { + builder.replace(name.syntax().text_range(), replace); + builder.replace_ast(arg_list, arg) + }, + ) +} + +fn into_closure(param: &Expr) -> Expr { + (|| { + if let ast::Expr::CallExpr(call) = param { + if call.arg_list()?.args().count() == 0 { + Some(call.expr()?.clone()) + } else { + None + } + } else { + None + } + })() + .unwrap_or_else(|| make::expr_closure(None, param.clone())) +} + +// Assist: replace_or_else_with_or +// +// Replace `unwrap_or_else` with `unwrap_or` and `ok_or_else` with `ok_or`. +// +// ``` +// # //- minicore:option +// fn foo() { +// let a = Some(1); +// a.unwra$0p_or_else(|| 2); +// } +// ``` +// -> +// ``` +// fn foo() { +// let a = Some(1); +// a.unwrap_or(2); +// } +// ``` +pub(crate) fn replace_or_else_with_or(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let call: ast::MethodCallExpr = ctx.find_node_at_offset()?; + + let kind = is_option_or_result(call.receiver()?, ctx)?; + + let (name, arg_list) = (call.name_ref()?, call.arg_list()?); + + let mut map_or = false; + let replace = match &*name.text() { + "unwrap_or_else" => "unwrap_or".to_string(), + "or_else" => "or".to_string(), + "ok_or_else" if kind == Kind::Option => "ok_or".to_string(), + "map_or_else" => { + map_or = true; + "map_or".to_string() + } + _ => return None, + }; + + let arg = match arg_list.args().collect::<Vec<_>>().as_slice() { + [] => make::arg_list(Vec::new()), + [first] => { + let param = into_call(first); + make::arg_list(vec![param]) + } + [first, second] if map_or => { + let param = into_call(first); + make::arg_list(vec![param, second.clone()]) + } + _ => return None, + }; + + acc.add( + AssistId("replace_or_else_with_or", AssistKind::RefactorRewrite), + format!("Replace {} with {}", name.text(), replace), + call.syntax().text_range(), + |builder| { + builder.replace(name.syntax().text_range(), replace); + builder.replace_ast(arg_list, arg) + }, + ) +} + +fn into_call(param: &Expr) -> Expr { + (|| { + if let ast::Expr::ClosureExpr(closure) = param { + if closure.param_list()?.params().count() == 0 { + Some(closure.body()?.clone()) + } else { + None + } + } else { + None + } + })() + .unwrap_or_else(|| make::expr_call(param.clone(), make::arg_list(Vec::new()))) +} + +#[derive(PartialEq, Eq)] +enum Kind { + Option, + Result, +} + +fn is_option_or_result(receiver: Expr, ctx: &AssistContext<'_>) -> Option<Kind> { + let ty = ctx.sema.type_of_expr(&receiver)?.adjusted().as_adt()?.as_enum()?; + let option_enum = + FamousDefs(&ctx.sema, ctx.sema.scope(receiver.syntax())?.krate()).core_option_Option(); + + if let Some(option_enum) = option_enum { + if ty == option_enum { + return Some(Kind::Option); + } + } + + let result_enum = + FamousDefs(&ctx.sema, ctx.sema.scope(receiver.syntax())?.krate()).core_result_Result(); + + if let Some(result_enum) = result_enum { + if ty == result_enum { + return Some(Kind::Result); + } + } + + None +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn replace_or_with_or_else_simple() { + check_assist( + replace_or_with_or_else, + r#" +//- minicore: option +fn foo() { + let foo = Some(1); + return foo.unwrap_$0or(2); +} +"#, + r#" +fn foo() { + let foo = Some(1); + return foo.unwrap_or_else(|| 2); +} +"#, + ) + } + + #[test] + fn replace_or_with_or_else_call() { + check_assist( + replace_or_with_or_else, + r#" +//- minicore: option +fn foo() { + let foo = Some(1); + return foo.unwrap_$0or(x()); +} +"#, + r#" +fn foo() { + let foo = Some(1); + return foo.unwrap_or_else(x); +} +"#, + ) + } + + #[test] + fn replace_or_with_or_else_block() { + check_assist( + replace_or_with_or_else, + r#" +//- minicore: option +fn foo() { + let foo = Some(1); + return foo.unwrap_$0or({ + let mut x = bar(); + for i in 0..10 { + x += i; + } + x + }); +} +"#, + r#" +fn foo() { + let foo = Some(1); + return foo.unwrap_or_else(|| { + let mut x = bar(); + for i in 0..10 { + x += i; + } + x + }); +} +"#, + ) + } + + #[test] + fn replace_or_else_with_or_simple() { + check_assist( + replace_or_else_with_or, + r#" +//- minicore: option +fn foo() { + let foo = Some(1); + return foo.unwrap_$0or_else(|| 2); +} +"#, + r#" +fn foo() { + let foo = Some(1); + return foo.unwrap_or(2); +} +"#, + ) + } + + #[test] + fn replace_or_else_with_or_call() { + check_assist( + replace_or_else_with_or, + r#" +//- minicore: option +fn foo() { + let foo = Some(1); + return foo.unwrap_$0or_else(x); +} +"#, + r#" +fn foo() { + let foo = Some(1); + return foo.unwrap_or(x()); +} +"#, + ) + } + + #[test] + fn replace_or_else_with_or_result() { + check_assist( + replace_or_else_with_or, + r#" +//- minicore: result +fn foo() { + let foo = Ok(1); + return foo.unwrap_$0or_else(x); +} +"#, + r#" +fn foo() { + let foo = Ok(1); + return foo.unwrap_or(x()); +} +"#, + ) + } + + #[test] + fn replace_or_else_with_or_map() { + check_assist( + replace_or_else_with_or, + r#" +//- minicore: result +fn foo() { + let foo = Ok("foo"); + return foo.map$0_or_else(|| 42, |v| v.len()); +} +"#, + r#" +fn foo() { + let foo = Ok("foo"); + return foo.map_or(42, |v| v.len()); +} +"#, + ) + } + + #[test] + fn replace_or_else_with_or_not_applicable() { + check_assist_not_applicable( + replace_or_else_with_or, + r#" +fn foo() { + let foo = Ok(1); + return foo.unwrap_$0or_else(x); +} +"#, + ) + } +} 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/replace_turbofish_with_explicit_type.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs index 6112e0945..521447c26 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs @@ -1,5 +1,6 @@ +use hir::HirDisplay; use syntax::{ - ast::{Expr, GenericArg}, + ast::{Expr, GenericArg, GenericArgList}, ast::{LetStmt, Type::InferType}, AstNode, TextRange, }; @@ -34,21 +35,7 @@ pub(crate) fn replace_turbofish_with_explicit_type( let initializer = let_stmt.initializer()?; - let generic_args = match &initializer { - Expr::MethodCallExpr(ce) => ce.generic_arg_list()?, - Expr::CallExpr(ce) => { - if let Expr::PathExpr(pe) = ce.expr()? { - pe.path()?.segment()?.generic_arg_list()? - } else { - cov_mark::hit!(not_applicable_if_non_path_function_call); - return None; - } - } - _ => { - cov_mark::hit!(not_applicable_if_non_function_call_initializer); - return None; - } - }; + let generic_args = generic_arg_list(&initializer)?; // Find range of ::<_> let colon2 = generic_args.coloncolon_token()?; @@ -65,7 +52,16 @@ pub(crate) fn replace_turbofish_with_explicit_type( // An improvement would be to check that this is correctly part of the return value of the // function call, or sub in the actual return type. - let turbofish_type = &turbofish_args[0]; + let returned_type = match ctx.sema.type_of_expr(&initializer) { + Some(returned_type) if !returned_type.original.contains_unknown() => { + let module = ctx.sema.scope(let_stmt.syntax())?.module(); + returned_type.original.display_source_code(ctx.db(), module.into()).ok()? + } + _ => { + cov_mark::hit!(fallback_to_turbofish_type_if_type_info_not_available); + turbofish_args[0].to_string() + } + }; let initializer_start = initializer.syntax().text_range().start(); if ctx.offset() > turbofish_range.end() || ctx.offset() < initializer_start { @@ -83,12 +79,12 @@ pub(crate) fn replace_turbofish_with_explicit_type( "Replace turbofish with explicit type", TextRange::new(initializer_start, turbofish_range.end()), |builder| { - builder.insert(ident_range.end(), format!(": {}", turbofish_type)); + builder.insert(ident_range.end(), format!(": {}", returned_type)); builder.delete(turbofish_range); }, ); } else if let Some(InferType(t)) = let_stmt.ty() { - // If there's a type inferrence underscore, we can offer to replace it with the type in + // If there's a type inference underscore, we can offer to replace it with the type in // the turbofish. // let x: _ = fn::<...>(); let underscore_range = t.syntax().text_range(); @@ -98,7 +94,7 @@ pub(crate) fn replace_turbofish_with_explicit_type( "Replace `_` with turbofish type", turbofish_range, |builder| { - builder.replace(underscore_range, turbofish_type.to_string()); + builder.replace(underscore_range, returned_type); builder.delete(turbofish_range); }, ); @@ -107,6 +103,26 @@ pub(crate) fn replace_turbofish_with_explicit_type( None } +fn generic_arg_list(expr: &Expr) -> Option<GenericArgList> { + match expr { + Expr::MethodCallExpr(expr) => expr.generic_arg_list(), + Expr::CallExpr(expr) => { + if let Expr::PathExpr(pe) = expr.expr()? { + pe.path()?.segment()?.generic_arg_list() + } else { + cov_mark::hit!(not_applicable_if_non_path_function_call); + return None; + } + } + Expr::AwaitExpr(expr) => generic_arg_list(&expr.expr()?), + Expr::TryExpr(expr) => generic_arg_list(&expr.expr()?), + _ => { + cov_mark::hit!(not_applicable_if_non_function_call_initializer); + None + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -115,6 +131,7 @@ mod tests { #[test] fn replaces_turbofish_for_vec_string() { + cov_mark::check!(fallback_to_turbofish_type_if_type_info_not_available); check_assist( replace_turbofish_with_explicit_type, r#" @@ -135,6 +152,7 @@ fn main() { #[test] fn replaces_method_calls() { // foo.make() is a method call which uses a different expr in the let initializer + cov_mark::check!(fallback_to_turbofish_type_if_type_info_not_available); check_assist( replace_turbofish_with_explicit_type, r#" @@ -240,4 +258,108 @@ fn main() { "#, ); } + + #[test] + fn replaces_turbofish_for_known_type() { + check_assist( + replace_turbofish_with_explicit_type, + r#" +fn make<T>() -> T {} +fn main() { + let a = make$0::<i32>(); +} +"#, + r#" +fn make<T>() -> T {} +fn main() { + let a: i32 = make(); +} +"#, + ); + check_assist( + replace_turbofish_with_explicit_type, + r#" +//- minicore: option +fn make<T>() -> T {} +fn main() { + let a = make$0::<Option<bool>>(); +} +"#, + r#" +fn make<T>() -> T {} +fn main() { + let a: Option<bool> = make(); +} +"#, + ); + } + + #[test] + fn replaces_turbofish_not_same_type() { + check_assist( + replace_turbofish_with_explicit_type, + r#" +//- minicore: option +fn make<T>() -> Option<T> {} +fn main() { + let a = make$0::<u128>(); +} +"#, + r#" +fn make<T>() -> Option<T> {} +fn main() { + let a: Option<u128> = make(); +} +"#, + ); + } + + #[test] + fn replaces_turbofish_for_type_with_defaulted_generic_param() { + check_assist( + replace_turbofish_with_explicit_type, + r#" +struct HasDefault<T, U = i32>(T, U); +fn make<T>() -> HasDefault<T> {} +fn main() { + let a = make$0::<bool>(); +} +"#, + r#" +struct HasDefault<T, U = i32>(T, U); +fn make<T>() -> HasDefault<T> {} +fn main() { + let a: HasDefault<bool> = make(); +} +"#, + ); + } + + #[test] + fn replaces_turbofish_try_await() { + check_assist( + replace_turbofish_with_explicit_type, + r#" +//- minicore: option, future +struct Fut<T>(T); +impl<T> core::future::Future for Fut<T> { + type Output = Option<T>; +} +fn make<T>() -> Fut<T> {} +fn main() { + let a = make$0::<bool>().await?; +} +"#, + r#" +struct Fut<T>(T); +impl<T> core::future::Future for Fut<T> { + type Output = Option<T>; +} +fn make<T>() -> Fut<T> {} +fn main() { + let a: bool = make().await?; +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_match_arm.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_match_arm.rs new file mode 100644 index 000000000..9565f0ee6 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_match_arm.rs @@ -0,0 +1,293 @@ +use syntax::{ + algo::neighbor, + ast::{self, edit::IndentLevel, make, AstNode}, + ted::{self, Position}, + Direction, SyntaxKind, T, +}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: unmerge_match_arm +// +// Splits the current match with a `|` pattern into two arms with identical bodies. +// +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// match action { +// Action::Move(..) $0| Action::Stop => foo(), +// } +// } +// ``` +// -> +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// match action { +// Action::Move(..) => foo(), +// Action::Stop => foo(), +// } +// } +// ``` +pub(crate) fn unmerge_match_arm(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let pipe_token = ctx.find_token_syntax_at_offset(T![|])?; + let or_pat = ast::OrPat::cast(pipe_token.parent()?)?.clone_for_update(); + let match_arm = ast::MatchArm::cast(or_pat.syntax().parent()?)?; + let match_arm_body = match_arm.expr()?; + + // We don't need to check for leading pipe because it is directly under `MatchArm` + // without `OrPat`. + + let new_parent = match_arm.syntax().parent()?; + let old_parent_range = new_parent.text_range(); + + acc.add( + AssistId("unmerge_match_arm", AssistKind::RefactorRewrite), + "Unmerge match arm", + pipe_token.text_range(), + |edit| { + let pats_after = pipe_token + .siblings_with_tokens(Direction::Next) + .filter_map(|it| ast::Pat::cast(it.into_node()?)); + // FIXME: We should add a leading pipe if the original arm has one. + let new_match_arm = make::match_arm( + pats_after, + match_arm.guard().and_then(|guard| guard.condition()), + match_arm_body, + ) + .clone_for_update(); + + let mut pipe_index = pipe_token.index(); + if pipe_token + .prev_sibling_or_token() + .map_or(false, |it| it.kind() == SyntaxKind::WHITESPACE) + { + pipe_index -= 1; + } + or_pat.syntax().splice_children( + pipe_index..or_pat.syntax().children_with_tokens().count(), + Vec::new(), + ); + + let mut insert_after_old_arm = Vec::new(); + + // A comma can be: + // - After the arm. In this case we always want to insert a comma after the newly + // inserted arm. + // - Missing after the arm, with no arms after. In this case we want to insert a + // comma before the newly inserted arm. It can not be necessary if there arm + // body is a block, but we don't bother to check that. + // - Missing after the arm with arms after, if the arm body is a block. In this case + // we don't want to insert a comma at all. + let has_comma_after = + std::iter::successors(match_arm.syntax().last_child_or_token(), |it| { + it.prev_sibling_or_token() + }) + .map(|it| it.kind()) + .skip_while(|it| it.is_trivia()) + .next() + == Some(T![,]); + let has_arms_after = neighbor(&match_arm, Direction::Next).is_some(); + if !has_comma_after && !has_arms_after { + insert_after_old_arm.push(make::token(T![,]).into()); + } + + let indent = IndentLevel::from_node(match_arm.syntax()); + insert_after_old_arm.push(make::tokens::whitespace(&format!("\n{indent}")).into()); + + insert_after_old_arm.push(new_match_arm.syntax().clone().into()); + + ted::insert_all_raw(Position::after(match_arm.syntax()), insert_after_old_arm); + + if has_comma_after { + ted::insert_raw( + Position::last_child_of(new_match_arm.syntax()), + make::token(T![,]), + ); + } + + edit.replace(old_parent_range, new_parent.to_string()); + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn unmerge_match_arm_single_pipe() { + check_assist( + unmerge_match_arm, + r#" +#[derive(Debug)] +enum X { A, B, C } + +fn main() { + let x = X::A; + let y = match x { + X::A $0| X::B => { 1i32 } + X::C => { 2i32 } + }; +} +"#, + r#" +#[derive(Debug)] +enum X { A, B, C } + +fn main() { + let x = X::A; + let y = match x { + X::A => { 1i32 } + X::B => { 1i32 } + X::C => { 2i32 } + }; +} +"#, + ); + } + + #[test] + fn unmerge_match_arm_guard() { + check_assist( + unmerge_match_arm, + r#" +#[derive(Debug)] +enum X { A, B, C } + +fn main() { + let x = X::A; + let y = match x { + X::A $0| X::B if true => { 1i32 } + _ => { 2i32 } + }; +} +"#, + r#" +#[derive(Debug)] +enum X { A, B, C } + +fn main() { + let x = X::A; + let y = match x { + X::A if true => { 1i32 } + X::B if true => { 1i32 } + _ => { 2i32 } + }; +} +"#, + ); + } + + #[test] + fn unmerge_match_arm_leading_pipe() { + check_assist_not_applicable( + unmerge_match_arm, + r#" + +fn main() { + let y = match 0 { + |$0 0 => { 1i32 } + 1 => { 2i32 } + }; +} +"#, + ); + } + + #[test] + fn unmerge_match_arm_multiple_pipes() { + check_assist( + unmerge_match_arm, + r#" +#[derive(Debug)] +enum X { A, B, C, D, E } + +fn main() { + let x = X::A; + let y = match x { + X::A | X::B |$0 X::C | X::D => 1i32, + X::E => 2i32, + }; +} +"#, + r#" +#[derive(Debug)] +enum X { A, B, C, D, E } + +fn main() { + let x = X::A; + let y = match x { + X::A | X::B => 1i32, + X::C | X::D => 1i32, + X::E => 2i32, + }; +} +"#, + ); + } + + #[test] + fn unmerge_match_arm_inserts_comma_if_required() { + check_assist( + unmerge_match_arm, + r#" +#[derive(Debug)] +enum X { A, B } + +fn main() { + let x = X::A; + let y = match x { + X::A $0| X::B => 1i32 + }; +} +"#, + r#" +#[derive(Debug)] +enum X { A, B } + +fn main() { + let x = X::A; + let y = match x { + X::A => 1i32, + X::B => 1i32 + }; +} +"#, + ); + } + + #[test] + fn unmerge_match_arm_inserts_comma_if_had_after() { + check_assist( + unmerge_match_arm, + r#" +#[derive(Debug)] +enum X { A, B } + +fn main() { + let x = X::A; + match x { + X::A $0| X::B => {}, + } +} +"#, + r#" +#[derive(Debug)] +enum X { A, B } + +fn main() { + let x = X::A; + match x { + X::A => {}, + X::B => {}, + } +} +"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_use.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_use.rs index 3ce028e93..dac216b69 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_use.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_use.rs @@ -1,5 +1,5 @@ use syntax::{ - ast::{self, make, HasVisibility}, + ast::{self, edit_in_place::Removable, make, HasVisibility}, ted::{self, Position}, AstNode, SyntaxKind, }; 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 fe87aa15f..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,7 +121,9 @@ 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; mod destructure_tuple_binding; mod expand_glob_import; @@ -135,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; @@ -179,12 +182,15 @@ mod handlers { mod replace_try_expr_with_match; mod replace_derive_with_manual_impl; mod replace_if_let_with_match; + mod replace_or_with_or_else; mod introduce_named_generic; mod replace_let_with_if_let; mod replace_qualified_name_with_use; mod replace_string_with_char; 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; @@ -213,8 +219,10 @@ 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, convert_while_to_loop::convert_while_to_loop, destructure_tuple_binding::destructure_tuple_binding, expand_glob_import::expand_glob_import, @@ -243,12 +251,14 @@ mod handlers { inline_call::inline_into_callers, inline_local_variable::inline_local_variable, inline_type_alias::inline_type_alias, + inline_type_alias::inline_type_alias_uses, introduce_named_generic::introduce_named_generic, introduce_named_lifetime::introduce_named_lifetime, invert_if::invert_if, merge_imports::merge_imports, merge_match_arms::merge_match_arms, move_bounds::move_bounds_to_where_clause, + move_format_string_arg::move_format_string_arg, move_guard::move_arm_cond_to_match_guard, move_guard::move_guard_to_arm_body, move_module_to_file::move_module_to_file, @@ -272,15 +282,19 @@ mod handlers { replace_if_let_with_match::replace_if_let_with_match, replace_if_let_with_match::replace_match_with_if_let, replace_let_with_if_let::replace_let_with_if_let, + replace_or_with_or_else::replace_or_else_with_or, + replace_or_with_or_else::replace_or_with_or_else, replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type, replace_qualified_name_with_use::replace_qualified_name_with_use, sort_items::sort_items, split_import::split_import, toggle_ignore::toggle_ignore, + unmerge_match_arm::unmerge_match_arm, unmerge_use::unmerge_use, 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 6eaab48a3..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", @@ -473,6 +514,26 @@ impl Point { } #[test] +fn doctest_convert_two_arm_bool_match_to_matches_macro() { + check_doc_test( + "convert_two_arm_bool_match_to_matches_macro", + r#####" +fn main() { + match scrutinee$0 { + Some(val) if val.cond() => true, + _ => false, + } +} +"#####, + r#####" +fn main() { + matches!(scrutinee, Some(val) if val.cond()) +} +"#####, + ) +} + +#[test] fn doctest_convert_while_to_loop() { check_doc_test( "convert_while_to_loop", @@ -1357,6 +1418,31 @@ fn main() { } #[test] +fn doctest_inline_type_alias_uses() { + check_doc_test( + "inline_type_alias_uses", + r#####" +type $0A = i32; +fn id(x: A) -> A { + x +}; +fn foo() { + let _: A = 3; +} +"#####, + r#####" + +fn id(x: i32) -> i32 { + x +}; +fn foo() { + let _: i32 = 3; +} +"#####, + ) +} + +#[test] fn doctest_introduce_named_generic() { check_doc_test( "introduce_named_generic", @@ -1547,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", @@ -1985,6 +2102,46 @@ fn handle(action: Action) { } #[test] +fn doctest_replace_or_else_with_or() { + check_doc_test( + "replace_or_else_with_or", + r#####" +//- minicore:option +fn foo() { + let a = Some(1); + a.unwra$0p_or_else(|| 2); +} +"#####, + r#####" +fn foo() { + let a = Some(1); + a.unwrap_or(2); +} +"#####, + ) +} + +#[test] +fn doctest_replace_or_with_or_else() { + check_doc_test( + "replace_or_with_or_else", + r#####" +//- minicore:option +fn foo() { + let a = Some(1); + a.unwra$0p_or(2); +} +"#####, + r#####" +fn foo() { + let a = Some(1); + a.unwrap_or_else(|| 2); +} +"#####, + ) +} + +#[test] fn doctest_replace_qualified_name_with_use() { check_doc_test( "replace_qualified_name_with_use", @@ -2183,6 +2340,32 @@ fn arithmetics { } #[test] +fn doctest_unmerge_match_arm() { + check_doc_test( + "unmerge_match_arm", + r#####" +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move(..) $0| Action::Stop => foo(), + } +} +"#####, + r#####" +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move(..) => foo(), + Action::Stop => foo(), + } +} +"#####, + ) +} + +#[test] fn doctest_unmerge_use() { check_doc_test( "unmerge_use", @@ -2245,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 3e61d0741..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}; @@ -12,15 +10,15 @@ use syntax::{ ast::{ self, edit::{self, AstNodeEdit}, - edit_in_place::AttrsOwnerEdit, + 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, }; -use crate::assist_context::{AssistBuilder, AssistContext}; +use crate::assist_context::{AssistContext, SourceChangeBuilder}; pub(crate) mod suggest_name; mod gen_trait_fn_body; @@ -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}}"); } } @@ -484,7 +488,7 @@ fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str } pub(crate) fn add_method_to_adt( - builder: &mut AssistBuilder, + builder: &mut SourceChangeBuilder, adt: &ast::Adt, impl_def: Option<ast::Impl>, method: &str, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs index 779cdbc93..c521a10fc 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs @@ -55,6 +55,7 @@ const USELESS_METHODS: &[&str] = &[ "iter", "into_iter", "iter_mut", + "into_future", ]; pub(crate) fn for_generic_parameter(ty: &ast::ImplTraitType) -> SmolStr { @@ -75,7 +76,7 @@ pub(crate) fn for_generic_parameter(ty: &ast::ImplTraitType) -> SmolStr { /// In current implementation, the function tries to get the name from /// the following sources: /// -/// * if expr is an argument to function/method, use paramter name +/// * if expr is an argument to function/method, use parameter name /// * if expr is a function/method call, use function name /// * expression type name if it exists (E.g. `()`, `fn() -> ()` or `!` do not have names) /// * fallback: `var_name` @@ -85,7 +86,7 @@ pub(crate) fn for_generic_parameter(ty: &ast::ImplTraitType) -> SmolStr { /// Currently it sticks to the first name found. // FIXME: Microoptimize and return a `SmolStr` here. pub(crate) fn for_variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String { - // `from_param` does not benifit from stripping + // `from_param` does not benefit from stripping // it need the largest context possible // so we check firstmost if let Some(name) = from_param(expr, sema) { |