feat: add inline_type_alias_uses assist
This commit is contained in:
parent
b6fae56e38
commit
14db080933
@ -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;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user