summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:19:13 +0000
commit218caa410aa38c29984be31a5229b9fa717560ee (patch)
treec54bd55eeb6e4c508940a30e94c0032fbd45d677 /src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs
parentReleasing progress-linux version 1.67.1+dfsg1-1~progress7.99u1. (diff)
downloadrustc-218caa410aa38c29984be31a5229b9fa717560ee.tar.xz
rustc-218caa410aa38c29984be31a5229b9fa717560ee.zip
Merging upstream version 1.68.2+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs')
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs481
1 files changed, 481 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs
new file mode 100644
index 000000000..0e3a1e652
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs
@@ -0,0 +1,481 @@
+use hir::{AsAssocItem, AssocItemContainer, HasCrate, HasSource};
+use ide_db::{assists::AssistId, base_db::FileRange, defs::Definition, search::SearchScope};
+use syntax::{
+ ast::{self, edit::IndentLevel, edit_in_place::Indent, AstNode},
+ SyntaxKind,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ utils,
+};
+
+// NOTE: Code may break if the self type implements a trait that has associated const with the same
+// name, but it's pretty expensive to check that (`hir::Impl::all_for_type()`) and we assume that's
+// pretty rare case.
+
+// Assist: move_const_to_impl
+//
+// Move a local constant item in a method to impl's associated constant. All the references will be
+// qualified with `Self::`.
+//
+// ```
+// struct S;
+// impl S {
+// fn foo() -> usize {
+// /// The answer.
+// const C$0: usize = 42;
+//
+// C * C
+// }
+// }
+// ```
+// ->
+// ```
+// struct S;
+// impl S {
+// /// The answer.
+// const C: usize = 42;
+//
+// fn foo() -> usize {
+// Self::C * Self::C
+// }
+// }
+// ```
+pub(crate) fn move_const_to_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let db = ctx.db();
+ let const_: ast::Const = ctx.find_node_at_offset()?;
+ // Don't show the assist when the cursor is at the const's body.
+ if let Some(body) = const_.body() {
+ if body.syntax().text_range().contains(ctx.offset()) {
+ return None;
+ }
+ }
+
+ let parent_fn = const_.syntax().ancestors().find_map(ast::Fn::cast)?;
+
+ // NOTE: We can technically provide this assist for default methods in trait definitions, but
+ // it's somewhat complex to handle it correctly when the const's name conflicts with
+ // supertrait's item. We may want to consider implementing it in the future.
+ let AssocItemContainer::Impl(impl_) = ctx.sema.to_def(&parent_fn)?.as_assoc_item(db)?.container(db) else { return None; };
+ if impl_.trait_(db).is_some() {
+ return None;
+ }
+
+ let def = ctx.sema.to_def(&const_)?;
+ let name = def.name(db)?;
+ let items = impl_.source(db)?.value.assoc_item_list()?;
+
+ let ty = impl_.self_ty(db);
+ // If there exists another associated item with the same name, skip the assist.
+ if ty
+ .iterate_assoc_items(db, ty.krate(db), |assoc| {
+ // Type aliases wouldn't conflict due to different namespaces, but we're only checking
+ // the items in inherent impls, so we assume `assoc` is never type alias for the sake
+ // of brevity (inherent associated types exist in nightly Rust, but it's *very*
+ // unstable and we don't support them either).
+ assoc.name(db).filter(|it| it == &name)
+ })
+ .is_some()
+ {
+ return None;
+ }
+
+ let usages =
+ Definition::Const(def).usages(&ctx.sema).in_scope(SearchScope::file_range(FileRange {
+ file_id: ctx.file_id(),
+ range: parent_fn.syntax().text_range(),
+ }));
+
+ acc.add(
+ AssistId("move_const_to_impl", crate::AssistKind::RefactorRewrite),
+ "Move const to impl block",
+ const_.syntax().text_range(),
+ |builder| {
+ let range_to_delete = match const_.syntax().next_sibling_or_token() {
+ Some(s) if matches!(s.kind(), SyntaxKind::WHITESPACE) => {
+ // Remove following whitespaces too.
+ const_.syntax().text_range().cover(s.text_range())
+ }
+ _ => const_.syntax().text_range(),
+ };
+ builder.delete(range_to_delete);
+
+ let const_ref = format!("Self::{name}");
+ for range in usages.all().file_ranges().map(|it| it.range) {
+ builder.replace(range, const_ref.clone());
+ }
+
+ // Heuristically inserting the extracted const after the consecutive existing consts
+ // from the beginning of assoc items. We assume there are no inherent assoc type as
+ // above.
+ let last_const =
+ items.assoc_items().take_while(|it| matches!(it, ast::AssocItem::Const(_))).last();
+ let insert_offset = match &last_const {
+ Some(it) => it.syntax().text_range().end(),
+ None => match items.l_curly_token() {
+ Some(l_curly) => l_curly.text_range().end(),
+ // Not sure if this branch is ever reachable, but it wouldn't hurt to have a
+ // fallback.
+ None => items.syntax().text_range().start(),
+ },
+ };
+
+ // If the moved const will be the first item of the impl, add a new line after that.
+ //
+ // We're assuming the code is formatted according to Rust's standard style guidelines
+ // (i.e. no empty lines between impl's `{` token and its first assoc item).
+ let fixup = if last_const.is_none() { "\n" } else { "" };
+ let indent = IndentLevel::from_node(parent_fn.syntax());
+
+ let const_ = const_.clone_for_update();
+ const_.reindent_to(indent);
+ let mut const_text = format!("\n{indent}{const_}{fixup}");
+ utils::escape_non_snippet(&mut const_text);
+ builder.insert(insert_offset, const_text);
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn not_applicable_to_top_level_const() {
+ check_assist_not_applicable(
+ move_const_to_impl,
+ r#"
+const C$0: () = ();
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_to_free_fn() {
+ check_assist_not_applicable(
+ move_const_to_impl,
+ r#"
+fn f() {
+ const C$0: () = ();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_when_at_const_body() {
+ check_assist_not_applicable(
+ move_const_to_impl,
+ r#"
+struct S;
+impl S {
+ fn f() {
+ const C: () = ($0);
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_when_inside_const_body_block() {
+ check_assist_not_applicable(
+ move_const_to_impl,
+ r#"
+struct S;
+impl S {
+ fn f() {
+ const C: () = {
+ ($0)
+ };
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_to_trait_impl_fn() {
+ check_assist_not_applicable(
+ move_const_to_impl,
+ r#"
+trait Trait {
+ fn f();
+}
+impl Trait for () {
+ fn f() {
+ const C$0: () = ();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_to_non_assoc_fn_inside_impl() {
+ check_assist_not_applicable(
+ move_const_to_impl,
+ r#"
+struct S;
+impl S {
+ fn f() {
+ fn g() {
+ const C$0: () = ();
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_when_const_with_same_name_exists() {
+ check_assist_not_applicable(
+ move_const_to_impl,
+ r#"
+struct S;
+impl S {
+ const C: usize = 42;
+ fn f() {
+ const C$0: () = ();
+ }
+"#,
+ );
+
+ check_assist_not_applicable(
+ move_const_to_impl,
+ r#"
+struct S;
+impl S {
+ const C: usize = 42;
+}
+impl S {
+ fn f() {
+ const C$0: () = ();
+ }
+"#,
+ );
+ }
+
+ #[test]
+ fn move_const_simple_body() {
+ check_assist(
+ move_const_to_impl,
+ r#"
+struct S;
+impl S {
+ fn f() -> usize {
+ /// doc comment
+ const C$0: usize = 42;
+
+ C * C
+ }
+}
+"#,
+ r#"
+struct S;
+impl S {
+ /// doc comment
+ const C: usize = 42;
+
+ fn f() -> usize {
+ Self::C * Self::C
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_const_simple_body_existing_const() {
+ check_assist(
+ move_const_to_impl,
+ r#"
+struct S;
+impl S {
+ const X: () = ();
+ const Y: () = ();
+
+ fn f() -> usize {
+ /// doc comment
+ const C$0: usize = 42;
+
+ C * C
+ }
+}
+"#,
+ r#"
+struct S;
+impl S {
+ const X: () = ();
+ const Y: () = ();
+ /// doc comment
+ const C: usize = 42;
+
+ fn f() -> usize {
+ Self::C * Self::C
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_const_block_body() {
+ check_assist(
+ move_const_to_impl,
+ r#"
+struct S;
+impl S {
+ fn f() -> usize {
+ /// doc comment
+ const C$0: usize = {
+ let a = 3;
+ let b = 4;
+ a * b
+ };
+
+ C * C
+ }
+}
+"#,
+ r#"
+struct S;
+impl S {
+ /// doc comment
+ const C: usize = {
+ let a = 3;
+ let b = 4;
+ a * b
+ };
+
+ fn f() -> usize {
+ Self::C * Self::C
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn correct_indent_when_nested() {
+ check_assist(
+ move_const_to_impl,
+ r#"
+fn main() {
+ struct S;
+ impl S {
+ fn f() -> usize {
+ /// doc comment
+ const C$0: usize = 42;
+
+ C * C
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ struct S;
+ impl S {
+ /// doc comment
+ const C: usize = 42;
+
+ fn f() -> usize {
+ Self::C * Self::C
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn move_const_in_nested_scope_with_same_name_in_other_scope() {
+ check_assist(
+ move_const_to_impl,
+ r#"
+struct S;
+impl S {
+ fn f() -> usize {
+ const C: &str = "outer";
+
+ let n = {
+ /// doc comment
+ const C$0: usize = 42;
+
+ let m = {
+ const C: &str = "inner";
+ C.len()
+ };
+
+ C * m
+ };
+
+ n + C.len()
+ }
+}
+"#,
+ r#"
+struct S;
+impl S {
+ /// doc comment
+ const C: usize = 42;
+
+ fn f() -> usize {
+ const C: &str = "outer";
+
+ let n = {
+ let m = {
+ const C: &str = "inner";
+ C.len()
+ };
+
+ Self::C * m
+ };
+
+ n + C.len()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn moved_const_body_is_escaped() {
+ // Note that the last argument is what *lsp clients would see* rather than
+ // what users would see. Unescaping happens thereafter.
+ check_assist(
+ move_const_to_impl,
+ r#"
+struct S;
+impl S {
+ fn f() -> usize {
+ /// doc comment
+ /// \\
+ /// ${snippet}
+ const C$0: &str = "\ and $1";
+
+ C.len()
+ }
+}
+"#,
+ r#"
+struct S;
+impl S {
+ /// doc comment
+ /// \\\\
+ /// \${snippet}
+ const C: &str = "\\ and \$1";
+
+ fn f() -> usize {
+ Self::C.len()
+ }
+}
+"#,
+ )
+ }
+}