Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #15573 - alibektas:15539/into_to_from, r=Veykril
assist : `into_to_qualified_from` fixes #15539. This assist converts an `.into()` call into an explicit fully qualified `from()` call.
bors 2023-09-08
parent 5ddad87 · parent 9762f76 · commit 47e0d07
-rw-r--r--crates/ide-assists/src/handlers/into_to_qualified_from.rs205
-rw-r--r--crates/ide-assists/src/lib.rs2
-rw-r--r--crates/ide-assists/src/tests/generated.rs34
3 files changed, 241 insertions, 0 deletions
diff --git a/crates/ide-assists/src/handlers/into_to_qualified_from.rs b/crates/ide-assists/src/handlers/into_to_qualified_from.rs
new file mode 100644
index 0000000000..663df266b6
--- /dev/null
+++ b/crates/ide-assists/src/handlers/into_to_qualified_from.rs
@@ -0,0 +1,205 @@
+use hir::{AsAssocItem, HirDisplay};
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ famous_defs::FamousDefs,
+};
+use syntax::{ast, AstNode};
+
+use crate::assist_context::{AssistContext, Assists};
+
+// Assist: into_to_qualified_from
+//
+// Convert an `into` method call to a fully qualified `from` call.
+//
+// ```
+// //- minicore: from
+// struct B;
+// impl From<i32> for B {
+// fn from(a: i32) -> Self {
+// B
+// }
+// }
+//
+// fn main() -> () {
+// let a = 3;
+// let b: B = a.in$0to();
+// }
+// ```
+// ->
+// ```
+// struct B;
+// impl From<i32> for B {
+// fn from(a: i32) -> Self {
+// B
+// }
+// }
+//
+// fn main() -> () {
+// let a = 3;
+// let b: B = B::from(a);
+// }
+// ```
+pub(crate) fn into_to_qualified_from(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
+ let nameref = method_call.name_ref()?;
+ let receiver = method_call.receiver()?;
+ let db = ctx.db();
+ let sema = &ctx.sema;
+ let fnc = sema.resolve_method_call(&method_call)?;
+ let scope = sema.scope(method_call.syntax())?;
+ // Check if the method call refers to Into trait.
+ if fnc.as_assoc_item(db)?.containing_trait_impl(db)?
+ == FamousDefs(sema, scope.krate()).core_convert_Into()?
+ {
+ let type_call = sema.type_of_expr(&method_call.clone().into())?;
+ let type_call_disp =
+ type_call.adjusted().display_source_code(db, scope.module().into(), true).ok()?;
+
+ acc.add(
+ AssistId("into_to_qualified_from", AssistKind::Generate),
+ "Convert `into` to fully qualified `from`",
+ nameref.syntax().text_range(),
+ |edit| {
+ edit.replace(
+ method_call.syntax().text_range(),
+ format!("{}::from({})", type_call_disp, receiver),
+ );
+ },
+ );
+ }
+
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_assist;
+
+ use super::into_to_qualified_from;
+
+ #[test]
+ fn two_types_in_same_mod() {
+ check_assist(
+ into_to_qualified_from,
+ r#"
+//- minicore: from
+struct A;
+struct B;
+impl From<A> for B {
+ fn from(a: A) -> Self {
+ B
+ }
+}
+
+fn main() -> () {
+ let a: A = A;
+ let b: B = a.in$0to();
+}"#,
+ r#"
+struct A;
+struct B;
+impl From<A> for B {
+ fn from(a: A) -> Self {
+ B
+ }
+}
+
+fn main() -> () {
+ let a: A = A;
+ let b: B = B::from(a);
+}"#,
+ )
+ }
+
+ #[test]
+ fn fromed_in_child_mod_imported() {
+ check_assist(
+ into_to_qualified_from,
+ r#"
+//- minicore: from
+use C::B;
+
+struct A;
+
+mod C {
+ use crate::A;
+
+ pub(super) struct B;
+ impl From<A> for B {
+ fn from(a: A) -> Self {
+ B
+ }
+ }
+}
+
+fn main() -> () {
+ let a: A = A;
+ let b: B = a.in$0to();
+}"#,
+ r#"
+use C::B;
+
+struct A;
+
+mod C {
+ use crate::A;
+
+ pub(super) struct B;
+ impl From<A> for B {
+ fn from(a: A) -> Self {
+ B
+ }
+ }
+}
+
+fn main() -> () {
+ let a: A = A;
+ let b: B = B::from(a);
+}"#,
+ )
+ }
+
+ #[test]
+ fn fromed_in_child_mod_not_imported() {
+ check_assist(
+ into_to_qualified_from,
+ r#"
+//- minicore: from
+struct A;
+
+mod C {
+ use crate::A;
+
+ pub(super) struct B;
+ impl From<A> for B {
+ fn from(a: A) -> Self {
+ B
+ }
+ }
+}
+
+fn main() -> () {
+ let a: A = A;
+ let b: C::B = a.in$0to();
+}"#,
+ r#"
+struct A;
+
+mod C {
+ use crate::A;
+
+ pub(super) struct B;
+ impl From<A> for B {
+ fn from(a: A) -> Self {
+ B
+ }
+ }
+}
+
+fn main() -> () {
+ let a: A = A;
+ let b: C::B = C::B::from(a);
+}"#,
+ )
+ }
+}
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index 2ebb5ef9b1..7136bdab21 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -211,6 +211,7 @@ mod handlers {
mod unwrap_result_return_type;
mod unqualify_method_call;
mod wrap_return_type_in_result;
+ mod into_to_qualified_from;
pub(crate) fn all() -> &'static [Handler] {
&[
@@ -274,6 +275,7 @@ mod handlers {
inline_local_variable::inline_local_variable,
inline_type_alias::inline_type_alias,
inline_type_alias::inline_type_alias_uses,
+ into_to_qualified_from::into_to_qualified_from,
introduce_named_generic::introduce_named_generic,
introduce_named_lifetime::introduce_named_lifetime,
invert_if::invert_if,
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index 8bd87e5f5d..cc132f9a98 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -1742,6 +1742,40 @@ fn foo() {
}
#[test]
+fn doctest_into_to_qualified_from() {
+ check_doc_test(
+ "into_to_qualified_from",
+ r#####"
+//- minicore: from
+struct B;
+impl From<i32> for B {
+ fn from(a: i32) -> Self {
+ B
+ }
+}
+
+fn main() -> () {
+ let a = 3;
+ let b: B = a.in$0to();
+}
+"#####,
+ r#####"
+struct B;
+impl From<i32> for B {
+ fn from(a: i32) -> Self {
+ B
+ }
+}
+
+fn main() -> () {
+ let a = 3;
+ let b: B = B::from(a);
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_introduce_named_generic() {
check_doc_test(
"introduce_named_generic",