feat: add inline_type_alias_uses assist

This commit is contained in:
Aleksandr Pak 2022-08-16 17:06:32 +03:00
parent b6fae56e38
commit 14db080933
No known key found for this signature in database
GPG Key ID: A3F6E3C0ECC46736
3 changed files with 227 additions and 33 deletions

View File

@ -1,9 +1,9 @@
// Some ideas for future improvements: // Some ideas for future improvements:
// - Support replacing aliases which are used in expressions, e.g. `A::new()`. // - Support replacing aliases which are used in expressions, e.g. `A::new()`.
// - "inline_alias_to_users" assist #10881.
// - Remove unused aliases if there are no longer any users, see inline_call.rs. // - Remove unused aliases if there are no longer any users, see inline_call.rs.
use hir::{HasSource, PathResolution}; use hir::{HasSource, PathResolution};
use ide_db::{defs::Definition, search::FileReference};
use itertools::Itertools; use itertools::Itertools;
use std::collections::HashMap; use std::collections::HashMap;
use syntax::{ use syntax::{
@ -16,6 +16,78 @@ use crate::{
AssistId, AssistKind, AssistId, AssistKind,
}; };
// Assist: inline_type_alias_uses
//
// Inline a type alias into all of its uses where possible.
//
// ```
// type $0A = i32;
// fn id(x: A) -> A {
// x
// };
// fn foo() {
// let _: A = 3;
// }
// ```
// ->
// ```
// type A = i32;
// fn id(x: i32) -> i32 {
// x
// };
// fn foo() {
// let _: i32 = 3;
// }
pub(crate) fn inline_type_alias_uses(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let name = ctx.find_node_at_offset::<ast::Name>()?;
let ast_alias = name.syntax().parent().and_then(ast::TypeAlias::cast)?;
let hir_alias = ctx.sema.to_def(&ast_alias)?;
let concrete_type = ast_alias.ty()?;
let usages = Definition::TypeAlias(hir_alias).usages(&ctx.sema);
if !usages.at_least_one() {
return None;
}
// until this is ok
acc.add(
AssistId("inline_type_alias_uses", AssistKind::RefactorInline),
"Inline type alias into all uses",
name.syntax().text_range(),
|builder| {
let usages = usages.all();
let mut inline_refs_for_file = |file_id, refs: Vec<FileReference>| {
builder.edit_file(file_id);
let path_types: Vec<ast::PathType> = refs
.into_iter()
.filter_map(|file_ref| match file_ref.name {
ast::NameLike::NameRef(path_type) => {
path_type.syntax().ancestors().find_map(ast::PathType::cast)
}
_ => None,
})
.collect();
for (target, replacement) in path_types.into_iter().filter_map(|path_type| {
let replacement = inline(&ast_alias, &path_type)?.to_text(&concrete_type);
let target = path_type.syntax().text_range();
Some((target, replacement))
}) {
builder.replace(target, replacement);
}
};
for (file_id, refs) in usages.into_iter() {
inline_refs_for_file(file_id, refs);
}
},
)
}
// Assist: inline_type_alias // Assist: inline_type_alias
// //
// Replace a type alias with its concrete type. // Replace a type alias with its concrete type.
@ -36,11 +108,6 @@ use crate::{
// } // }
// ``` // ```
pub(crate) fn inline_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { pub(crate) fn inline_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
enum Replacement {
Generic { lifetime_map: LifetimeMap, const_and_type_map: ConstAndTypeMap },
Plain,
}
let alias_instance = ctx.find_node_at_offset::<ast::PathType>()?; let alias_instance = ctx.find_node_at_offset::<ast::PathType>()?;
let concrete_type; let concrete_type;
let replacement; let replacement;
@ -59,23 +126,7 @@ pub(crate) fn inline_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
_ => { _ => {
let alias = get_type_alias(&ctx, &alias_instance)?; let alias = get_type_alias(&ctx, &alias_instance)?;
concrete_type = alias.ty()?; concrete_type = alias.ty()?;
replacement = inline(&alias, &alias_instance)?;
replacement = if let Some(alias_generics) = alias.generic_param_list() {
if alias_generics.generic_params().next().is_none() {
cov_mark::hit!(no_generics_params);
return None;
}
let instance_args =
alias_instance.syntax().descendants().find_map(ast::GenericArgList::cast);
Replacement::Generic {
lifetime_map: LifetimeMap::new(&instance_args, &alias_generics)?,
const_and_type_map: ConstAndTypeMap::new(&instance_args, &alias_generics)?,
}
} else {
Replacement::Plain
};
} }
} }
@ -85,19 +136,45 @@ pub(crate) fn inline_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
AssistId("inline_type_alias", AssistKind::RefactorInline), AssistId("inline_type_alias", AssistKind::RefactorInline),
"Inline type alias", "Inline type alias",
target, target,
|builder| { |builder| builder.replace(target, replacement.to_text(&concrete_type)),
let replacement_text = match replacement {
Replacement::Generic { lifetime_map, const_and_type_map } => {
create_replacement(&lifetime_map, &const_and_type_map, &concrete_type)
}
Replacement::Plain => concrete_type.to_string(),
};
builder.replace(target, replacement_text);
},
) )
} }
impl Replacement {
fn to_text(&self, concrete_type: &ast::Type) -> String {
match self {
Replacement::Generic { lifetime_map, const_and_type_map } => {
create_replacement(&lifetime_map, &const_and_type_map, &concrete_type)
}
Replacement::Plain => concrete_type.to_string(),
}
}
}
enum Replacement {
Generic { lifetime_map: LifetimeMap, const_and_type_map: ConstAndTypeMap },
Plain,
}
fn inline(alias_def: &ast::TypeAlias, alias_instance: &ast::PathType) -> Option<Replacement> {
let repl = if let Some(alias_generics) = alias_def.generic_param_list() {
if alias_generics.generic_params().next().is_none() {
cov_mark::hit!(no_generics_params);
return None;
}
let instance_args =
alias_instance.syntax().descendants().find_map(ast::GenericArgList::cast);
Replacement::Generic {
lifetime_map: LifetimeMap::new(&instance_args, &alias_generics)?,
const_and_type_map: ConstAndTypeMap::new(&instance_args, &alias_generics)?,
}
} else {
Replacement::Plain
};
Some(repl)
}
struct LifetimeMap(HashMap<String, ast::Lifetime>); struct LifetimeMap(HashMap<String, ast::Lifetime>);
impl LifetimeMap { impl LifetimeMap {
@ -835,4 +912,95 @@ trait Tr {
"#, "#,
); );
} }
mod inline_type_alias_uses {
use crate::{handlers::inline_type_alias::inline_type_alias_uses, tests::check_assist};
#[test]
fn inline_uses() {
check_assist(
inline_type_alias_uses,
r#"
type $0A = u32;
fn foo() {
let _: A = 3;
let _: A = 4;
}
"#,
r#"
type A = u32;
fn foo() {
let _: u32 = 3;
let _: u32 = 4;
}
"#,
);
}
#[test]
fn inline_uses_across_files() {
check_assist(
inline_type_alias_uses,
r#"
//- /lib.rs
mod foo;
type $0T<E> = Vec<E>;
fn f() -> T<&str> {
vec!["hello"]
}
//- /foo.rs
use super::T;
fn foo() {
let _: T<i8> = Vec::new();
}
"#,
r#"
//- /lib.rs
mod foo;
type T<E> = Vec<E>;
fn f() -> Vec<&str> {
vec!["hello"]
}
//- /foo.rs
use super::T;
fn foo() {
let _: Vec<i8> = Vec::new();
}
"#,
);
}
#[test]
fn inline_uses_across_files_fails() {
check_assist(
inline_type_alias_uses,
r#"
//- /lib.rs
mod foo;
type $0I = i32;
//- /foo.rs
use super::I;
fn foo() {
let _: I = 0;
}
"#,
r#"
//- /lib.rs
mod foo;
type I = i32;
//- /foo.rs
use super::I;
fn foo() {
let _: i32 = 0;
}
"#,
);
}
}
} }

View File

@ -243,6 +243,7 @@ mod handlers {
inline_call::inline_into_callers, inline_call::inline_into_callers,
inline_local_variable::inline_local_variable, inline_local_variable::inline_local_variable,
inline_type_alias::inline_type_alias, inline_type_alias::inline_type_alias,
inline_type_alias::inline_type_alias_uses,
introduce_named_generic::introduce_named_generic, introduce_named_generic::introduce_named_generic,
introduce_named_lifetime::introduce_named_lifetime, introduce_named_lifetime::introduce_named_lifetime,
invert_if::invert_if, invert_if::invert_if,

View File

@ -1356,6 +1356,31 @@ fn main() {
) )
} }
#[test]
fn doctest_inline_type_alias_uses() {
check_doc_test(
"inline_type_alias_uses",
r#####"
type $0A = i32;
fn id(x: A) -> A {
x
};
fn foo() {
let _: A = 3;
}
"#####,
r#####"
type A = i32;
fn id(x: i32) -> i32 {
x
};
fn foo() {
let _: i32 = 3;
}
"#####,
)
}
#[test] #[test]
fn doctest_introduce_named_generic() { fn doctest_introduce_named_generic() {
check_doc_test( check_doc_test(