summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-assists/src/utils.rs')
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils.rs79
1 files changed, 64 insertions, 15 deletions
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 68c31b4f8..7add66064 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
@@ -208,6 +208,23 @@ pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor
}
}
+/// Escapes text that should be rendered as-is, typically those that we're copy-pasting what the
+/// users wrote.
+///
+/// This function should only be used when the text doesn't contain snippet **AND** the text
+/// wouldn't be included in a snippet.
+pub(crate) fn escape_non_snippet(text: &mut String) {
+ // While we *can* escape `}`, we don't really have to in this specific case. We only need to
+ // escape it inside `${}` to disambiguate it from the ending token of the syntax, but after we
+ // escape every occurrence of `$`, we wouldn't have `${}` in the first place.
+ //
+ // This will break if the text contains snippet or it will be included in a snippet (hence doc
+ // comment). Compare `fn escape(buf)` in `render_snippet()` above, where the escaped text is
+ // included in a snippet.
+ stdx::replace(text, '\\', r"\\");
+ stdx::replace(text, '$', r"\$");
+}
+
pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
node.children_with_tokens()
.find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR))
@@ -417,35 +434,67 @@ pub(crate) fn find_impl_block_end(impl_def: ast::Impl, buf: &mut String) -> Opti
Some(end)
}
-// Generates the surrounding `impl Type { <code> }` including type and lifetime
-// parameters
+/// Generates the surrounding `impl Type { <code> }` including type and lifetime
+/// parameters.
pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String {
- generate_impl_text_inner(adt, None, code)
+ generate_impl_text_inner(adt, None, true, code)
}
-// Generates the surrounding `impl <trait> for Type { <code> }` including type
-// and lifetime parameters
+/// 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`.
pub(crate) fn generate_trait_impl_text(adt: &ast::Adt, trait_text: &str, code: &str) -> String {
- generate_impl_text_inner(adt, Some(trait_text), code)
+ generate_impl_text_inner(adt, Some(trait_text), true, code)
}
-fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str) -> String {
+/// 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>`.
+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>,
+ 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| {
- // remove defaults since they can't be specified in impls
+ let ty_or_const_params = generic_params.type_or_const_params().map(|param| {
match param {
ast::TypeOrConstParam::Type(param) => {
let param = param.clone_for_update();
+ // remove defaults since they can't be specified in impls
param.remove_default();
- Some(ast::GenericParam::TypeParam(param))
+ 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(trait_));
+ }
+ };
+ // `{ty_param}: {bounds}`
+ let param =
+ make::type_param(param.name().unwrap(), make::type_bound_list(bounds));
+ ast::GenericParam::TypeParam(param)
}
ast::TypeOrConstParam::Const(param) => {
let param = param.clone_for_update();
+ // remove defaults since they can't be specified in impls
param.remove_default();
- Some(ast::GenericParam::ConstParam(param))
+ ast::GenericParam::ConstParam(param)
}
}
});
@@ -596,7 +645,7 @@ pub(crate) fn convert_reference_type(
}
fn handle_copy(ty: &hir::Type, db: &dyn HirDatabase) -> Option<ReferenceConversionType> {
- ty.is_copy(db).then(|| ReferenceConversionType::Copy)
+ ty.is_copy(db).then_some(ReferenceConversionType::Copy)
}
fn handle_as_ref_str(
@@ -607,7 +656,7 @@ fn handle_as_ref_str(
let str_type = hir::BuiltinType::str().ty(db);
ty.impls_trait(db, famous_defs.core_convert_AsRef()?, &[str_type])
- .then(|| ReferenceConversionType::AsRefStr)
+ .then_some(ReferenceConversionType::AsRefStr)
}
fn handle_as_ref_slice(
@@ -619,7 +668,7 @@ fn handle_as_ref_slice(
let slice_type = hir::Type::new_slice(type_argument);
ty.impls_trait(db, famous_defs.core_convert_AsRef()?, &[slice_type])
- .then(|| ReferenceConversionType::AsRefSlice)
+ .then_some(ReferenceConversionType::AsRefSlice)
}
fn handle_dereferenced(
@@ -630,7 +679,7 @@ fn handle_dereferenced(
let type_argument = ty.type_arguments().next()?;
ty.impls_trait(db, famous_defs.core_convert_AsRef()?, &[type_argument])
- .then(|| ReferenceConversionType::Dereferenced)
+ .then_some(ReferenceConversionType::Dereferenced)
}
fn handle_option_as_ref(