Skip to content

feat: add into_to_qualified_from assist #15573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 205 additions & 0 deletions crates/ide-assists/src/handlers/into_to_qualified_from.rs
Original file line number Diff line number Diff line change
@@ -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);
}"#,
)
}
}
2 changes: 2 additions & 0 deletions crates/ide-assists/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
&[
Expand Down Expand Up @@ -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,
Expand Down
34 changes: 34 additions & 0 deletions crates/ide-assists/src/tests/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1754,6 +1754,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(
Expand Down