Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/utils.rs')
| -rw-r--r-- | crates/ide-assists/src/utils.rs | 247 |
1 files changed, 28 insertions, 219 deletions
diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index 10057f8681..bf1062d207 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -15,7 +15,6 @@ use ide_db::{ syntax_helpers::{node_ext::preorder_expr, prettify_macro_expansion}, }; use itertools::Itertools; -use stdx::format_to; use syntax::{ AstNode, AstToken, Direction, NodeOrToken, SourceFile, SyntaxKind::*, @@ -203,11 +202,9 @@ pub fn filter_assoc_items( /// [`filter_assoc_items()`]), clones each item for update and applies path transformation to it, /// then inserts into `impl_`. Returns the modified `impl_` and the first associated item that got /// inserted. -/// -/// Legacy: prefer [`add_trait_assoc_items_to_impl_with_factory`] when a [`SyntaxFactory`] is -/// available. #[must_use] pub fn add_trait_assoc_items_to_impl( + make: &SyntaxFactory, sema: &Semantics<'_, RootDatabase>, config: &AssistConfig, original_items: &[InFile<ast::AssocItem>], @@ -250,95 +247,23 @@ pub fn add_trait_assoc_items_to_impl( }) .filter_map(|item| match item { ast::AssocItem::Fn(fn_) if fn_.body().is_none() => { - let fn_ = fn_.clone_subtree(); - let new_body = make::block_expr(None, Some(expr_fill_default(config))); - let mut fn_editor = SyntaxEditor::new(fn_.syntax().clone()); - fn_.replace_or_insert_body(&mut fn_editor, new_body.clone_for_update()); - let new_fn_ = fn_editor.finish().new_root().clone(); - ast::AssocItem::cast(new_fn_) - } - ast::AssocItem::TypeAlias(type_alias) => { - let type_alias = type_alias.clone_subtree(); - if let Some(type_bound_list) = type_alias.type_bound_list() { - let mut type_alias_editor = SyntaxEditor::new(type_alias.syntax().clone()); - type_bound_list.remove(&mut type_alias_editor); - let type_alias = type_alias_editor.finish().new_root().clone(); - ast::AssocItem::cast(type_alias) - } else { - Some(ast::AssocItem::TypeAlias(type_alias)) - } - } - item => Some(item), - }) - .map(|item| AstNodeEdit::indent(&item, new_indent_level)) - .collect() -} - -/// [`SyntaxFactory`]-based variant of [`add_trait_assoc_items_to_impl`]. -#[must_use] -pub fn add_trait_assoc_items_to_impl_with_factory( - make: &SyntaxFactory, - sema: &Semantics<'_, RootDatabase>, - config: &AssistConfig, - original_items: &[InFile<ast::AssocItem>], - trait_: hir::Trait, - impl_: &ast::Impl, - target_scope: &hir::SemanticsScope<'_>, -) -> Vec<ast::AssocItem> { - let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1; - original_items - .iter() - .map(|InFile { file_id, value: original_item }| { - let mut cloned_item = { - if let Some(macro_file) = file_id.macro_file() { - let span_map = sema.db.expansion_span_map(macro_file); - let item_prettified = prettify_macro_expansion( - sema.db, - original_item.syntax().clone(), - &span_map, - target_scope.krate().into(), - ); - if let Some(formatted) = ast::AssocItem::cast(item_prettified) { - return formatted; - } else { - stdx::never!("formatted `AssocItem` could not be cast back to `AssocItem`"); - } - } - original_item - } - .reset_indent(); - - if let Some(source_scope) = sema.scope(original_item.syntax()) { - let transform = - PathTransform::trait_impl(target_scope, &source_scope, trait_, impl_.clone()); - cloned_item = ast::AssocItem::cast(transform.apply(cloned_item.syntax())).unwrap(); - } - cloned_item.remove_attrs_and_docs(); - cloned_item - }) - .filter_map(|item| match item { - ast::AssocItem::Fn(fn_) if fn_.body().is_none() => { - let fn_ = fn_.clone_subtree(); + let (fn_editor, fn_) = SyntaxEditor::with_ast_node(&fn_); let fill_expr: ast::Expr = match config.expr_fill_default { ExprFillDefaultMode::Todo | ExprFillDefaultMode::Default => make.expr_todo(), ExprFillDefaultMode::Underscore => make.expr_underscore().into(), }; let new_body = make.block_expr(None::<ast::Stmt>, Some(fill_expr)); - let mut fn_editor = SyntaxEditor::new(fn_.syntax().clone()); - fn_.replace_or_insert_body(&mut fn_editor, new_body); + fn_.replace_or_insert_body(&fn_editor, new_body); let new_fn_ = fn_editor.finish().new_root().clone(); ast::AssocItem::cast(new_fn_) } ast::AssocItem::TypeAlias(type_alias) => { - let type_alias = type_alias.clone_subtree(); + let (type_alias_editor, type_alias) = SyntaxEditor::with_ast_node(&type_alias); if let Some(type_bound_list) = type_alias.type_bound_list() { - let mut type_alias_editor = SyntaxEditor::new(type_alias.syntax().clone()); - type_bound_list.remove(&mut type_alias_editor); - let type_alias = type_alias_editor.finish().new_root().clone(); - ast::AssocItem::cast(type_alias) - } else { - Some(ast::AssocItem::TypeAlias(type_alias)) - } + type_bound_list.remove(&type_alias_editor); + }; + let type_alias = type_alias_editor.finish().new_root().clone(); + ast::AssocItem::cast(type_alias) } item => Some(item), }) @@ -404,10 +329,8 @@ fn invert_special_case(make: &SyntaxFactory, expr: &ast::Expr) -> Option<ast::Ex Some(make.expr_method_call(receiver, make.name_ref(method), arg_list).into()) } ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::UnaryOp::Not => match pe.expr()? { - ast::Expr::ParenExpr(parexpr) => { - parexpr.expr().map(|e| e.clone_subtree().clone_for_update()) - } - _ => pe.expr().map(|e| e.clone_subtree().clone_for_update()), + ast::Expr::ParenExpr(parexpr) => parexpr.expr(), + _ => pe.expr(), }, ast::Expr::Literal(lit) => match lit.kind() { ast::LiteralKind::Bool(b) => match b { @@ -422,10 +345,10 @@ fn invert_special_case(make: &SyntaxFactory, expr: &ast::Expr) -> Option<ast::Ex pub(crate) fn insert_attributes( before: impl Element, - edit: &mut SyntaxEditor, + editor: &SyntaxEditor, attrs: impl IntoIterator<Item = ast::Attr>, - make: &SyntaxFactory, ) { + let make = editor.make(); let mut attrs = attrs.into_iter().peekable(); if attrs.peek().is_none() { return; @@ -433,12 +356,10 @@ pub(crate) fn insert_attributes( let elem = before.syntax_element(); let indent = IndentLevel::from_element(&elem); let whitespace = format!("\n{indent}"); - edit.insert_all( - syntax::syntax_editor::Position::before(elem), - attrs - .flat_map(|attr| [attr.syntax().clone().into(), make.whitespace(&whitespace).into()]) - .collect(), - ); + let elements: Vec<syntax::SyntaxElement> = attrs + .flat_map(|attr| [attr.syntax().clone().into(), make.whitespace(&whitespace).into()]) + .collect(); + editor.insert_all(syntax::syntax_editor::Position::before(elem), elements); } pub(crate) fn next_prev() -> impl Iterator<Item = Direction> { @@ -608,104 +529,6 @@ fn has_any_fn(imp: &ast::Impl, names: &[String]) -> bool { false } -/// Find the end of the `impl` block for the given `ast::Impl`. -// -// FIXME: this partially overlaps with `find_struct_impl` -pub(crate) fn find_impl_block_end(impl_def: ast::Impl, buf: &mut String) -> Option<TextSize> { - buf.push('\n'); - let end = impl_def - .assoc_item_list() - .and_then(|it| it.r_curly_token())? - .prev_sibling_or_token()? - .text_range() - .end(); - Some(end) -} - -/// Generates the surrounding `impl Type { <code> }` including type and lifetime -/// parameters. -// FIXME: migrate remaining uses to `generate_impl` -pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String { - generate_impl_text_inner(adt, None, true, code) -} - -fn generate_impl_text_inner( - adt: &ast::Adt, - trait_text: Option<&str>, - trait_is_transitive: bool, - code: &str, -) -> String { - // 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| { - let param = match param { - ast::TypeOrConstParam::Type(param) => { - // remove defaults since they can't be specified in impls - let mut bounds = - param.type_bound_list().map_or_else(Vec::new, |it| it.bounds().collect()); - if let Some(trait_) = trait_text { - // Add the current trait to `bounds` if the trait is transitive, - // meaning `impl<T> Trait for U<T>` requires `T: Trait`. - if trait_is_transitive { - bounds.push(make::type_bound_text(trait_)); - } - }; - // `{ty_param}: {bounds}` - let param = make::type_param(param.name()?, make::type_bound_list(bounds)); - ast::GenericParam::TypeParam(param) - } - ast::TypeOrConstParam::Const(param) => { - // remove defaults since they can't be specified in impls - let param = make::const_param(param.name()?, param.ty()?); - ast::GenericParam::ConstParam(param) - } - }; - Some(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"); - 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 { - format_to!(buf, "{generic_params}"); - } - buf.push(' '); - if let Some(trait_text) = trait_text { - buf.push_str(trait_text); - buf.push_str(" for "); - } - buf.push_str(&adt.name().unwrap().text()); - if let Some(generic_params) = generic_params { - format_to!(buf, "{}", generic_params.to_generic_args()); - } - - match adt.where_clause() { - Some(where_clause) => { - format_to!(buf, "\n{where_clause}\n{{\n{code}\n}}"); - } - None => { - format_to!(buf, " {{\n{code}\n}}"); - } - } - - buf -} - /// Generates the corresponding `impl Type {}` including type and lifetime /// parameters. pub(crate) fn generate_impl_with_item( @@ -758,6 +581,16 @@ pub(crate) fn generate_trait_impl_intransitive_with_item( generate_impl_inner_with_factory(make, false, adt, Some(trait_), false, Some(body)) } +pub(crate) fn generate_trait_impl_with_item( + make: &SyntaxFactory, + is_unsafe: bool, + adt: &ast::Adt, + trait_: ast::Type, + body: ast::AssocItemList, +) -> ast::Impl { + generate_impl_inner_with_factory(make, is_unsafe, adt, Some(trait_), true, Some(body)) +} + fn generate_impl_inner( is_unsafe: bool, adt: &ast::Adt, @@ -806,8 +639,7 @@ fn generate_impl_inner( let ty = make::ty_path(make::ext::ident_path(&adt.name().unwrap().text())); - let cfg_attrs = - adt.attrs().filter(|attr| attr.as_simple_call().is_some_and(|(name, _arg)| name == "cfg")); + let cfg_attrs = adt.attrs().filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))); match trait_ { Some(trait_) => make::impl_trait( cfg_attrs, @@ -877,8 +709,7 @@ fn generate_impl_inner_with_factory( let ty: ast::Type = make.ty_path(make.ident_path(&adt.name().unwrap().text())).into(); - let cfg_attrs = - adt.attrs().filter(|attr| attr.as_simple_call().is_some_and(|(name, _arg)| name == "cfg")); + let cfg_attrs = adt.attrs().filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))); match trait_ { Some(trait_) => make.impl_trait( cfg_attrs, @@ -989,28 +820,6 @@ fn generic_param_associated_bounds_with_factory( trait_where_clause.peek().is_some().then(|| make.where_clause(trait_where_clause)) } -pub(crate) fn add_method_to_adt( - builder: &mut SourceChangeBuilder, - adt: &ast::Adt, - impl_def: Option<ast::Impl>, - method: &str, -) { - let mut buf = String::with_capacity(method.len() + 2); - if impl_def.is_some() { - buf.push('\n'); - } - buf.push_str(method); - - let start_offset = impl_def - .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) - .unwrap_or_else(|| { - buf = generate_impl_text(adt, &buf); - adt.syntax().text_range().end() - }); - - builder.insert(start_offset, buf); -} - #[derive(Debug)] pub(crate) struct ReferenceConversion<'db> { conversion: ReferenceConversionType, |