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.rs305
1 files changed, 255 insertions, 50 deletions
diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs
index 0657e7243a..10057f8681 100644
--- a/crates/ide-assists/src/utils.rs
+++ b/crates/ide-assists/src/utils.rs
@@ -103,11 +103,11 @@ pub(crate) fn wrap_paren(expr: ast::Expr, make: &SyntaxFactory, prec: ExprPreced
}
pub(crate) fn wrap_paren_in_call(expr: ast::Expr, make: &SyntaxFactory) -> ast::Expr {
- if needs_parens_in_call(&expr) { make.expr_paren(expr).into() } else { expr }
+ if needs_parens_in_call(make, &expr) { make.expr_paren(expr).into() } else { expr }
}
-fn needs_parens_in_call(param: &ast::Expr) -> bool {
- let call = make::expr_call(make::ext::expr_unit(), make::arg_list(Vec::new()));
+fn needs_parens_in_call(make: &SyntaxFactory, param: &ast::Expr) -> bool {
+ let call = make.expr_call(make.expr_unit(), make.arg_list(Vec::new()));
let callable = call.expr().expect("invalid make call");
param.needs_parens_in_place_of(call.syntax(), callable.syntax())
}
@@ -203,6 +203,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(
sema: &Semantics<'_, RootDatabase>,
@@ -248,15 +251,79 @@ 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(match config.expr_fill_default {
- ExprFillDefaultMode::Todo => make::ext::expr_todo(),
- ExprFillDefaultMode::Underscore => make::ext::expr_underscore(),
- ExprFillDefaultMode::Default => make::ext::expr_todo(),
- }),
- );
- let new_body = AstNodeEdit::indent(new_body, IndentLevel::single());
+ 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 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);
let new_fn_ = fn_editor.finish().new_root().clone();
@@ -465,6 +532,15 @@ fn check_pat_variant_nested_or_literal_with_depth(
}
}
+pub(crate) fn expr_fill_default(config: &AssistConfig) -> ast::Expr {
+ let make = SyntaxFactory::without_mappings();
+ match config.expr_fill_default {
+ ExprFillDefaultMode::Todo => make.expr_todo(),
+ ExprFillDefaultMode::Underscore => make.expr_underscore().into(),
+ ExprFillDefaultMode::Default => make.expr_todo(),
+ }
+}
+
// Uses a syntax-driven approach to find any impl blocks for the struct that
// exist within the module/file
//
@@ -553,29 +629,6 @@ pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String {
generate_impl_text_inner(adt, None, true, code)
}
-/// Generates the surrounding `impl <trait> for Type { <code> }` including type
-/// and lifetime parameters, with `<trait>` appended to `impl`'s generic parameters' bounds.
-///
-/// This is useful for traits like `PartialEq`, since `impl<T> PartialEq for U<T>` often requires `T: PartialEq`.
-// FIXME: migrate remaining uses to `generate_trait_impl`
-#[allow(dead_code)]
-pub(crate) fn generate_trait_impl_text(adt: &ast::Adt, trait_text: &str, code: &str) -> String {
- generate_impl_text_inner(adt, Some(trait_text), true, code)
-}
-
-/// Generates the surrounding `impl <trait> for Type { <code> }` including type
-/// and lifetime parameters, with `impl`'s generic parameters' bounds kept as-is.
-///
-/// This is useful for traits like `From<T>`, since `impl<T> From<T> for U<T>` doesn't require `T: From<T>`.
-// FIXME: migrate remaining uses to `generate_trait_impl_intransitive`
-pub(crate) fn generate_trait_impl_text_intransitive(
- adt: &ast::Adt,
- trait_text: &str,
- code: &str,
-) -> String {
- generate_impl_text_inner(adt, Some(trait_text), false, code)
-}
-
fn generate_impl_text_inner(
adt: &ast::Adt,
trait_text: Option<&str>,
@@ -656,10 +709,15 @@ 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 {
+ generate_impl_inner_with_factory(make, false, adt, None, true, None)
}
pub(crate) fn generate_impl(adt: &ast::Adt) -> ast::Impl {
@@ -670,16 +728,34 @@ pub(crate) fn generate_impl(adt: &ast::Adt) -> ast::Impl {
/// and lifetime parameters, with `<trait>` appended to `impl`'s generic parameters' bounds.
///
/// This is useful for traits like `PartialEq`, since `impl<T> PartialEq for U<T>` often requires `T: PartialEq`.
-pub(crate) fn generate_trait_impl(is_unsafe: bool, adt: &ast::Adt, trait_: ast::Type) -> ast::Impl {
- generate_impl_inner(is_unsafe, adt, Some(trait_), true, None)
+pub(crate) fn generate_trait_impl(
+ make: &SyntaxFactory,
+ is_unsafe: bool,
+ adt: &ast::Adt,
+ trait_: ast::Type,
+) -> ast::Impl {
+ generate_impl_inner_with_factory(make, is_unsafe, adt, Some(trait_), true, None)
}
/// Generates the corresponding `impl <trait> for Type {}` including type
/// and lifetime parameters, with `impl`'s generic parameters' bounds kept as-is.
///
/// This is useful for traits like `From<T>`, since `impl<T> From<T> for U<T>` doesn't require `T: From<T>`.
-pub(crate) fn generate_trait_impl_intransitive(adt: &ast::Adt, trait_: ast::Type) -> ast::Impl {
- generate_impl_inner(false, adt, Some(trait_), false, None)
+pub(crate) fn generate_trait_impl_intransitive(
+ make: &SyntaxFactory,
+ adt: &ast::Adt,
+ trait_: ast::Type,
+) -> ast::Impl {
+ generate_impl_inner_with_factory(make, false, adt, Some(trait_), false, None)
+}
+
+pub(crate) fn generate_trait_impl_intransitive_with_item(
+ make: &SyntaxFactory,
+ adt: &ast::Adt,
+ trait_: ast::Type,
+ body: ast::AssocItemList,
+) -> ast::Impl {
+ generate_impl_inner_with_factory(make, false, adt, Some(trait_), false, Some(body))
}
fn generate_impl_inner(
@@ -752,6 +828,76 @@ fn generate_impl_inner(
.clone_for_update()
}
+fn generate_impl_inner_with_factory(
+ make: &SyntaxFactory,
+ is_unsafe: bool,
+ adt: &ast::Adt,
+ trait_: Option<ast::Type>,
+ trait_is_transitive: bool,
+ body: Option<ast::AssocItemList>,
+) -> ast::Impl {
+ // 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_ {
+ // 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(trait_.clone()));
+ }
+ };
+ // `{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))
+ });
+ let generic_args =
+ generic_params.as_ref().map(|params| params.to_generic_args().clone_for_update());
+ let adt_assoc_bounds =
+ trait_.as_ref().zip(generic_params.as_ref()).and_then(|(trait_, params)| {
+ generic_param_associated_bounds_with_factory(make, adt, trait_, params)
+ });
+
+ 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"));
+ match trait_ {
+ Some(trait_) => make.impl_trait(
+ cfg_attrs,
+ is_unsafe,
+ None,
+ None,
+ generic_params,
+ generic_args,
+ false,
+ trait_,
+ ty,
+ adt_assoc_bounds,
+ adt.where_clause(),
+ body,
+ ),
+ None => make.impl_(cfg_attrs, generic_params, generic_args, ty, adt.where_clause(), body),
+ }
+}
+
fn generic_param_associated_bounds(
adt: &ast::Adt,
trait_: &ast::Type,
@@ -797,6 +943,52 @@ fn generic_param_associated_bounds(
trait_where_clause.peek().is_some().then(|| make::where_clause(trait_where_clause))
}
+fn generic_param_associated_bounds_with_factory(
+ make: &SyntaxFactory,
+ adt: &ast::Adt,
+ trait_: &ast::Type,
+ generic_params: &ast::GenericParamList,
+) -> Option<ast::WhereClause> {
+ let in_type_params = |name: &ast::NameRef| {
+ generic_params
+ .generic_params()
+ .filter_map(|param| match param {
+ ast::GenericParam::TypeParam(type_param) => type_param.name(),
+ _ => None,
+ })
+ .any(|param| param.text() == name.text())
+ };
+ let adt_body = match adt {
+ ast::Adt::Enum(e) => e.variant_list().map(|it| it.syntax().clone()),
+ ast::Adt::Struct(s) => s.field_list().map(|it| it.syntax().clone()),
+ ast::Adt::Union(u) => u.record_field_list().map(|it| it.syntax().clone()),
+ };
+ let mut trait_where_clause = adt_body
+ .into_iter()
+ .flat_map(|it| it.descendants())
+ .filter_map(ast::Path::cast)
+ .filter_map(|path| {
+ let qualifier = path.qualifier()?.as_single_segment()?;
+ let qualifier = qualifier
+ .name_ref()
+ .or_else(|| match qualifier.type_anchor()?.ty()? {
+ ast::Type::PathType(path_type) => path_type.path()?.as_single_name_ref(),
+ _ => None,
+ })
+ .filter(in_type_params)?;
+ Some((qualifier, path.segment()?.name_ref()?))
+ })
+ .map(|(qualifier, assoc_name)| {
+ let segments = [qualifier, assoc_name].map(|nr| make.path_segment(nr));
+ let path = make.path_from_segments(segments, false);
+ let bounds = [make.type_bound(trait_.clone())];
+ make.where_pred(either::Either::Right(make.ty_path(path).into()), bounds)
+ })
+ .unique_by(|it| it.syntax().to_string())
+ .peekable();
+ 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,
@@ -843,8 +1035,8 @@ enum ReferenceConversionType {
}
impl<'db> ReferenceConversion<'db> {
- pub(crate) fn convert_type(&self, db: &'db dyn HirDatabase, module: hir::Module) -> ast::Type {
- let ty = match self.conversion {
+ fn type_to_string(&self, db: &'db dyn HirDatabase, module: hir::Module) -> String {
+ match self.conversion {
ReferenceConversionType::Copy => self
.ty
.display_source_code(db, module.into(), true)
@@ -894,25 +1086,38 @@ impl<'db> ReferenceConversion<'db> {
.unwrap_or_else(|_| "_".to_owned());
format!("Result<&{first_type_argument_name}, &{second_type_argument_name}>")
}
- };
+ }
+ }
+ pub(crate) fn convert_type(&self, db: &'db dyn HirDatabase, module: hir::Module) -> ast::Type {
+ let ty = self.type_to_string(db, module);
make::ty(&ty)
}
- pub(crate) fn getter(&self, field_name: String) -> ast::Expr {
- let expr = make::expr_field(make::ext::expr_self(), &field_name);
+ pub(crate) fn convert_type_with_factory(
+ &self,
+ make: &SyntaxFactory,
+ db: &'db dyn HirDatabase,
+ module: hir::Module,
+ ) -> ast::Type {
+ let ty = self.type_to_string(db, module);
+ make.ty(&ty)
+ }
+
+ pub(crate) fn getter(&self, make: &SyntaxFactory, field_name: String) -> ast::Expr {
+ let expr = make.expr_field(make.expr_self(), &field_name);
match self.conversion {
- ReferenceConversionType::Copy => expr,
+ ReferenceConversionType::Copy => expr.into(),
ReferenceConversionType::AsRefStr
| ReferenceConversionType::AsRefSlice
| ReferenceConversionType::Dereferenced
| ReferenceConversionType::Option
| ReferenceConversionType::Result => {
if self.impls_deref {
- make::expr_ref(expr, false)
+ make.expr_ref(expr.into(), false)
} else {
- make::expr_method_call(expr, make::name_ref("as_ref"), make::arg_list([]))
+ make.expr_method_call(expr.into(), make.name_ref("as_ref"), make.arg_list([]))
.into()
}
}
@@ -1218,10 +1423,10 @@ pub(crate) fn cover_let_chain(mut expr: ast::Expr, range: TextRange) -> Option<a
}
pub(crate) fn cover_edit_range(
- source: &impl AstNode,
+ source: &SyntaxNode,
range: TextRange,
) -> std::ops::RangeInclusive<syntax::SyntaxElement> {
- let node = match source.syntax().covering_element(range) {
+ let node = match source.covering_element(range) {
NodeOrToken::Node(node) => node,
NodeOrToken::Token(t) => t.parent().unwrap(),
};