Unnamed repository; edit this file 'description' to name the repository.
| -rw-r--r-- | crates/ide-assists/src/handlers/generate_new.rs | 88 | ||||
| -rw-r--r-- | crates/ide-assists/src/utils.rs | 3 | ||||
| -rw-r--r-- | crates/syntax/src/ast/syntax_factory/constructors.rs | 31 |
3 files changed, 78 insertions, 44 deletions
diff --git a/crates/ide-assists/src/handlers/generate_new.rs b/crates/ide-assists/src/handlers/generate_new.rs index 793211a27b..301d13c095 100644 --- a/crates/ide-assists/src/handlers/generate_new.rs +++ b/crates/ide-assists/src/handlers/generate_new.rs @@ -3,7 +3,10 @@ use ide_db::{ use_trivial_constructor::use_trivial_constructor, }; use syntax::{ - ast::{self, AstNode, HasName, HasVisibility, StructKind, edit::AstNodeEdit, make}, + ast::{ + self, AstNode, HasName, HasVisibility, StructKind, edit::AstNodeEdit, + syntax_factory::SyntaxFactory, + }, syntax_editor::Position, }; @@ -36,6 +39,7 @@ use crate::{ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let strukt = ctx.find_node_at_offset::<ast::Struct>()?; + let make = SyntaxFactory::without_mappings(); let field_list = match strukt.kind() { StructKind::Record(named) => { named.fields().filter_map(|f| Some((f.name()?, f.ty()?))).collect::<Vec<_>>() @@ -55,7 +59,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option Some(name) => name, None => name_generator.suggest_name(&format!("_{i}")), }; - Some((make::name(name.as_str()), f.ty()?)) + Some((make.name(name.as_str()), f.ty()?)) }) .collect::<Vec<_>>() } @@ -70,6 +74,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option let target = strukt.syntax().text_range(); acc.add(AssistId::generate("generate_new"), "Generate `new`", target, |builder| { + let make = SyntaxFactory::with_mappings(); let trivial_constructors = field_list .iter() .map(|(name, ty)| { @@ -95,63 +100,63 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option edition, )?; - Some((make::name_ref(&name.text()), Some(expr))) + Some((make.name_ref(&name.text()), Some(expr))) }) .collect::<Vec<_>>(); let params = field_list.iter().enumerate().filter_map(|(i, (name, ty))| { if trivial_constructors[i].is_none() { - Some(make::param(make::ident_pat(false, false, name.clone()).into(), ty.clone())) + Some(make.param(make.ident_pat(false, false, name.clone()).into(), ty.clone())) } else { None } }); - let params = make::param_list(None, params); + let params = make.param_list(None, params); let fields = field_list.iter().enumerate().map(|(i, (name, _))| { if let Some(constructor) = trivial_constructors[i].clone() { constructor } else { - (make::name_ref(&name.text()), None) + (make.name_ref(&name.text()), None) } }); let tail_expr: ast::Expr = match strukt.kind() { StructKind::Record(_) => { - let fields = fields.map(|(name, expr)| make::record_expr_field(name, expr)); - let fields = make::record_expr_field_list(fields); - make::record_expr(make::ext::ident_path("Self"), fields).into() + let fields = fields.map(|(name, expr)| make.record_expr_field(name, expr)); + let fields = make.record_expr_field_list(fields); + make.record_expr(make.ident_path("Self"), fields).into() } StructKind::Tuple(_) => { let args = fields.map(|(arg, expr)| { - let arg = || make::expr_path(make::path_unqualified(make::path_segment(arg))); + let arg = || make.expr_path(make.path_unqualified(make.path_segment(arg))); expr.unwrap_or_else(arg) }); - let arg_list = make::arg_list(args); - make::expr_call(make::expr_path(make::ext::ident_path("Self")), arg_list).into() + let arg_list = make.arg_list(args); + make.expr_call(make.expr_path(make.ident_path("Self")), arg_list).into() } StructKind::Unit => unreachable!(), }; - let body = make::block_expr(None, tail_expr.into()); - - let ret_type = make::ret_type(make::ty_path(make::ext::ident_path("Self"))); - - let fn_ = make::fn_( - None, - strukt.visibility(), - make::name("new"), - None, - None, - params, - body, - Some(ret_type), - false, - false, - false, - false, - ) - .clone_for_update() - .indent(1.into()); + let body = make.block_expr(None, tail_expr.into()); + + let ret_type = make.ret_type(make.ty_path(make.ident_path("Self")).into()); + + let fn_ = make + .fn_( + [], + strukt.visibility(), + make.name("new"), + None, + None, + params, + body, + Some(ret_type), + false, + false, + false, + false, + ) + .indent(1.into()); let mut editor = builder.make_editor(strukt.syntax()); @@ -164,32 +169,30 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option editor.insert_all( Position::after(l_curly), vec![ - make::tokens::whitespace(&format!("\n{}", impl_def.indent_level() + 1)) - .into(), + make.whitespace(&format!("\n{}", impl_def.indent_level() + 1)).into(), fn_.syntax().clone().into(), - make::tokens::whitespace("\n").into(), + make.whitespace("\n").into(), ], ); fn_.syntax().clone() } else { - let items = vec![ast::AssocItem::Fn(fn_)]; - let list = make::assoc_item_list(Some(items)); + let list = make.assoc_item_list([ast::AssocItem::Fn(fn_)]); editor.insert(Position::after(impl_def.syntax()), list.syntax()); list.syntax().clone() } } else { // Generate a new impl to add the method to let indent_level = strukt.indent_level(); - let body = vec![ast::AssocItem::Fn(fn_)]; - let list = make::assoc_item_list(Some(body)); - let impl_def = generate_impl_with_item(&ast::Adt::Struct(strukt.clone()), Some(list)) - .indent(strukt.indent_level()); + let list = make.assoc_item_list([ast::AssocItem::Fn(fn_)]); + let impl_def = + generate_impl_with_item(&make, &ast::Adt::Struct(strukt.clone()), Some(list)) + .indent(strukt.indent_level()); // Insert it after the adt editor.insert_all( Position::after(strukt.syntax()), vec![ - make::tokens::whitespace(&format!("\n\n{indent_level}")).into(), + make.whitespace(&format!("\n\n{indent_level}")).into(), impl_def.syntax().clone().into(), ], ); @@ -233,6 +236,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option } } + editor.add_mappings(make.finish_with_mappings()); builder.add_file_edits(ctx.vfs_file_id(), editor); }) } diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index cfbb5a17eb..cf1c66ab5e 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -733,10 +733,11 @@ fn generate_impl_text_inner( /// Generates the corresponding `impl Type {}` including type and lifetime /// parameters. pub(crate) fn generate_impl_with_item( + make: &SyntaxFactory, adt: &ast::Adt, body: Option<ast::AssocItemList>, ) -> ast::Impl { - generate_impl_inner(false, adt, None, true, body) + generate_impl_inner_with_factory(make, false, adt, None, true, body) } pub(crate) fn generate_impl_with_factory(make: &SyntaxFactory, adt: &ast::Adt) -> ast::Impl { diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs index 3e5591d37a..fa81dfad1f 100644 --- a/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -1470,6 +1470,22 @@ impl SyntaxFactory { ast } + pub fn record_expr_field_list( + &self, + fields: impl IntoIterator<Item = ast::RecordExprField>, + ) -> ast::RecordExprFieldList { + let (fields, input) = iterator_input(fields); + let ast = make::record_expr_field_list(fields).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_children(input, ast.fields().map(|f| f.syntax().clone())); + builder.finish(&mut mapping); + } + + ast + } + pub fn record_expr_field( &self, name: ast::NameRef, @@ -1480,7 +1496,20 @@ impl SyntaxFactory { if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); - builder.map_node(name.syntax().clone(), ast.name_ref().unwrap().syntax().clone()); + if let Some(ast_name_ref) = ast.name_ref() { + // NameRef is a direct child + builder.map_node(name.syntax().clone(), ast_name_ref.syntax().clone()); + } else { + // NameRef is nested inside PathExpr > Path > PathSegment. + // map_node requires the output to be a direct child of the builder's parent, so + // we need a separate builder scoped to PathSegment. + let ast::Expr::PathExpr(path_expr) = ast.expr().unwrap() else { unreachable!() }; + let path_segment = path_expr.path().unwrap().segment().unwrap(); + let inner_name_ref = path_segment.name_ref().unwrap(); + let mut inner_builder = SyntaxMappingBuilder::new(path_segment.syntax().clone()); + inner_builder.map_node(name.syntax().clone(), inner_name_ref.syntax().clone()); + inner_builder.finish(&mut mapping); + } if let Some(expr) = expr { builder.map_node(expr.syntax().clone(), ast.expr().unwrap().syntax().clone()); } |