blob: e2f3d9edcd102e7a8a4e2bd126ca8ee1d1fc0f19 [file] [log] [blame]
// Some ideas for future improvements:
// - Support replacing aliases which are used in expressions, e.g. `A::new()`.
// - Remove unused aliases if there are no longer any users, see inline_call.rs.
use hir::{HasSource, PathResolution};
use ide_db::FxHashMap;
use ide_db::{
defs::Definition, imports::insert_use::ast_to_remove_for_path_in_use_stmt,
search::FileReference,
};
use itertools::Itertools;
use syntax::{
ast::{self, make, HasGenericParams, HasName},
ted, AstNode, NodeOrToken, SyntaxNode,
};
use crate::{
assist_context::{AssistContext, Assists},
AssistId, AssistKind,
};
use super::inline_call::split_refs_and_uses;
// 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;
// }
// ```
// ->
// ```
//
// 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 definition_deleted = false;
let mut inline_refs_for_file = |file_id, refs: Vec<FileReference>| {
builder.edit_file(file_id);
let (path_types, path_type_uses) =
split_refs_and_uses(builder, refs, |path_type| {
path_type.syntax().ancestors().nth(3).and_then(ast::PathType::cast)
});
path_type_uses
.iter()
.flat_map(ast_to_remove_for_path_in_use_stmt)
.for_each(|x| builder.delete(x.syntax().text_range()));
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);
}
if file_id == ctx.file_id() {
builder.delete(ast_alias.syntax().text_range());
definition_deleted = true;
}
};
for (file_id, refs) in usages.into_iter() {
inline_refs_for_file(file_id, refs);
}
if !definition_deleted {
builder.edit_file(ctx.file_id());
builder.delete(ast_alias.syntax().text_range());
}
},
)
}
// Assist: inline_type_alias
//
// Replace a type alias with its concrete type.
//
// ```
// type A<T = u32> = Vec<T>;
//
// fn main() {
// let a: $0A;
// }
// ```
// ->
// ```
// type A<T = u32> = Vec<T>;
//
// fn main() {
// let a: Vec<u32>;
// }
// ```
pub(crate) fn inline_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let alias_instance = ctx.find_node_at_offset::<ast::PathType>()?;
let concrete_type;
let replacement;
match alias_instance.path()?.as_single_name_ref() {
Some(nameref) if nameref.Self_token().is_some() => {
match ctx.sema.resolve_path(&alias_instance.path()?)? {
PathResolution::SelfType(imp) => {
concrete_type = imp.source(ctx.db())?.value.self_ty()?;
}
// FIXME: should also work in ADT definitions
_ => return None,
}
replacement = Replacement::Plain;
}
_ => {
let alias = get_type_alias(ctx, &alias_instance)?;
concrete_type = alias.ty()?;
replacement = inline(&alias, &alias_instance)?;
}
}
let target = alias_instance.syntax().text_range();
acc.add(
AssistId("inline_type_alias", AssistKind::RefactorInline),
"Inline type alias",
target,
|builder| builder.replace(target, replacement.to_text(&concrete_type)),
)
}
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(FxHashMap<String, ast::Lifetime>);
impl LifetimeMap {
fn new(
instance_args: &Option<ast::GenericArgList>,
alias_generics: &ast::GenericParamList,
) -> Option<Self> {
let mut inner = FxHashMap::default();
let wildcard_lifetime = make::lifetime("'_");
let lifetimes = alias_generics
.lifetime_params()
.filter_map(|lp| lp.lifetime())
.map(|l| l.to_string())
.collect_vec();
for lifetime in &lifetimes {
inner.insert(lifetime.to_string(), wildcard_lifetime.clone());
}
if let Some(instance_generic_args_list) = &instance_args {
for (index, lifetime) in instance_generic_args_list
.lifetime_args()
.filter_map(|arg| arg.lifetime())
.enumerate()
{
let key = match lifetimes.get(index) {
Some(key) => key,
None => {
cov_mark::hit!(too_many_lifetimes);
return None;
}
};
inner.insert(key.clone(), lifetime);
}
}
Some(Self(inner))
}
}
struct ConstAndTypeMap(FxHashMap<String, SyntaxNode>);
impl ConstAndTypeMap {
fn new(
instance_args: &Option<ast::GenericArgList>,
alias_generics: &ast::GenericParamList,
) -> Option<Self> {
let mut inner = FxHashMap::default();
let instance_generics = generic_args_to_const_and_type_generics(instance_args);
let alias_generics = generic_param_list_to_const_and_type_generics(alias_generics);
if instance_generics.len() > alias_generics.len() {
cov_mark::hit!(too_many_generic_args);
return None;
}
// Any declaration generics that don't have a default value must have one
// provided by the instance.
for (i, declaration_generic) in alias_generics.iter().enumerate() {
let key = declaration_generic.replacement_key()?;
if let Some(instance_generic) = instance_generics.get(i) {
inner.insert(key, instance_generic.replacement_value()?);
} else if let Some(value) = declaration_generic.replacement_value() {
inner.insert(key, value);
} else {
cov_mark::hit!(missing_replacement_param);
return None;
}
}
Some(Self(inner))
}
}
/// This doesn't attempt to ensure specified generics are compatible with those
/// required by the type alias, other than lifetimes which must either all be
/// specified or all omitted. It will replace TypeArgs with ConstArgs and vice
/// versa if they're in the wrong position. It supports partially specified
/// generics.
///
/// 1. Map the provided instance's generic args to the type alias's generic
/// params:
///
/// ```
/// type A<'a, const N: usize, T = u64> = &'a [T; N];
/// ^ alias generic params
/// let a: A<100>;
/// ^ instance generic args
/// ```
///
/// generic['a] = '_ due to omission
/// generic[N] = 100 due to the instance arg
/// generic[T] = u64 due to the default param
///
/// 2. Copy the concrete type and substitute in each found mapping:
///
/// &'_ [u64; 100]
///
/// 3. Remove wildcard lifetimes entirely:
///
/// &[u64; 100]
fn create_replacement(
lifetime_map: &LifetimeMap,
const_and_type_map: &ConstAndTypeMap,
concrete_type: &ast::Type,
) -> String {
let updated_concrete_type = concrete_type.clone_for_update();
let mut replacements = Vec::new();
let mut removals = Vec::new();
for syntax in updated_concrete_type.syntax().descendants() {
let syntax_string = syntax.to_string();
let syntax_str = syntax_string.as_str();
if let Some(old_lifetime) = ast::Lifetime::cast(syntax.clone()) {
if let Some(new_lifetime) = lifetime_map.0.get(&old_lifetime.to_string()) {
if new_lifetime.text() == "'_" {
removals.push(NodeOrToken::Node(syntax.clone()));
if let Some(ws) = syntax.next_sibling_or_token() {
removals.push(ws.clone());
}
continue;
}
replacements.push((syntax.clone(), new_lifetime.syntax().clone_for_update()));
}
} else if let Some(replacement_syntax) = const_and_type_map.0.get(syntax_str) {
let new_string = replacement_syntax.to_string();
let new = if new_string == "_" {
make::wildcard_pat().syntax().clone_for_update()
} else {
replacement_syntax.clone_for_update()
};
replacements.push((syntax.clone(), new));
}
}
for (old, new) in replacements {
ted::replace(old, new);
}
for syntax in removals {
ted::remove(syntax);
}
updated_concrete_type.to_string()
}
fn get_type_alias(ctx: &AssistContext<'_>, path: &ast::PathType) -> Option<ast::TypeAlias> {
let resolved_path = ctx.sema.resolve_path(&path.path()?)?;
// We need the generics in the correct order to be able to map any provided
// instance generics to declaration generics. The `hir::TypeAlias` doesn't
// keep the order, so we must get the `ast::TypeAlias` from the hir
// definition.
if let PathResolution::Def(hir::ModuleDef::TypeAlias(ta)) = resolved_path {
Some(ctx.sema.source(ta)?.value)
} else {
None
}
}
enum ConstOrTypeGeneric {
ConstArg(ast::ConstArg),
TypeArg(ast::TypeArg),
ConstParam(ast::ConstParam),
TypeParam(ast::TypeParam),
}
impl ConstOrTypeGeneric {
fn replacement_key(&self) -> Option<String> {
// Only params are used as replacement keys.
match self {
ConstOrTypeGeneric::ConstParam(cp) => Some(cp.name()?.to_string()),
ConstOrTypeGeneric::TypeParam(tp) => Some(tp.name()?.to_string()),
_ => None,
}
}
fn replacement_value(&self) -> Option<SyntaxNode> {
Some(match self {
ConstOrTypeGeneric::ConstArg(ca) => ca.expr()?.syntax().clone(),
ConstOrTypeGeneric::TypeArg(ta) => ta.syntax().clone(),
ConstOrTypeGeneric::ConstParam(cp) => cp.default_val()?.syntax().clone(),
ConstOrTypeGeneric::TypeParam(tp) => tp.default_type()?.syntax().clone(),
})
}
}
fn generic_param_list_to_const_and_type_generics(
generics: &ast::GenericParamList,
) -> Vec<ConstOrTypeGeneric> {
let mut others = Vec::new();
for param in generics.generic_params() {
match param {
ast::GenericParam::LifetimeParam(_) => {}
ast::GenericParam::ConstParam(cp) => {
others.push(ConstOrTypeGeneric::ConstParam(cp));
}
ast::GenericParam::TypeParam(tp) => others.push(ConstOrTypeGeneric::TypeParam(tp)),
}
}
others
}
fn generic_args_to_const_and_type_generics(
generics: &Option<ast::GenericArgList>,
) -> Vec<ConstOrTypeGeneric> {
let mut others = Vec::new();
// It's fine for there to be no instance generics because the declaration
// might have default values or they might be inferred.
if let Some(generics) = generics {
for arg in generics.generic_args() {
match arg {
ast::GenericArg::TypeArg(ta) => {
others.push(ConstOrTypeGeneric::TypeArg(ta));
}
ast::GenericArg::ConstArg(ca) => {
others.push(ConstOrTypeGeneric::ConstArg(ca));
}
_ => {}
}
}
}
others
}
#[cfg(test)]
mod test {
use super::*;
use crate::tests::{check_assist, check_assist_not_applicable};
#[test]
fn empty_generic_params() {
cov_mark::check!(no_generics_params);
check_assist_not_applicable(
inline_type_alias,
r#"
type A<> = T;
fn main() {
let a: $0A<u32>;
}
"#,
);
}
#[test]
fn too_many_generic_args() {
cov_mark::check!(too_many_generic_args);
check_assist_not_applicable(
inline_type_alias,
r#"
type A<T> = T;
fn main() {
let a: $0A<u32, u64>;
}
"#,
);
}
#[test]
fn too_many_lifetimes() {
cov_mark::check!(too_many_lifetimes);
check_assist_not_applicable(
inline_type_alias,
r#"
type A<'a> = &'a &'b u32;
fn f<'a>() {
let a: $0A<'a, 'b> = 0;
}
"#,
);
}
// This must be supported in order to support "inline_alias_to_users" or
// whatever it will be called.
#[test]
fn alias_as_expression_ignored() {
check_assist_not_applicable(
inline_type_alias,
r#"
type A = Vec<u32>;
fn main() {
let a: A = $0A::new();
}
"#,
);
}
#[test]
fn primitive_arg() {
check_assist(
inline_type_alias,
r#"
type A<T> = T;
fn main() {
let a: $0A<u32> = 0;
}
"#,
r#"
type A<T> = T;
fn main() {
let a: u32 = 0;
}
"#,
);
}
#[test]
fn no_generic_replacements() {
check_assist(
inline_type_alias,
r#"
type A = Vec<u32>;
fn main() {
let a: $0A;
}
"#,
r#"
type A = Vec<u32>;
fn main() {
let a: Vec<u32>;
}
"#,
);
}
#[test]
fn param_expression() {
check_assist(
inline_type_alias,
r#"
type A<const N: usize = { 1 }> = [u32; N];
fn main() {
let a: $0A;
}
"#,
r#"
type A<const N: usize = { 1 }> = [u32; N];
fn main() {
let a: [u32; { 1 }];
}
"#,
);
}
#[test]
fn param_default_value() {
check_assist(
inline_type_alias,
r#"
type A<const N: usize = 1> = [u32; N];
fn main() {
let a: $0A;
}
"#,
r#"
type A<const N: usize = 1> = [u32; N];
fn main() {
let a: [u32; 1];
}
"#,
);
}
#[test]
fn all_param_types() {
check_assist(
inline_type_alias,
r#"
struct Struct<const C: usize>;
type A<'inner1, 'outer1, Outer1, const INNER1: usize, Inner1: Clone, const OUTER1: usize> = (Struct<INNER1>, Struct<OUTER1>, Outer1, &'inner1 (), Inner1, &'outer1 ());
fn foo<'inner2, 'outer2, Outer2, const INNER2: usize, Inner2, const OUTER2: usize>() {
let a: $0A<'inner2, 'outer2, Outer2, INNER2, Inner2, OUTER2>;
}
"#,
r#"
struct Struct<const C: usize>;
type A<'inner1, 'outer1, Outer1, const INNER1: usize, Inner1: Clone, const OUTER1: usize> = (Struct<INNER1>, Struct<OUTER1>, Outer1, &'inner1 (), Inner1, &'outer1 ());
fn foo<'inner2, 'outer2, Outer2, const INNER2: usize, Inner2, const OUTER2: usize>() {
let a: (Struct<INNER2>, Struct<OUTER2>, Outer2, &'inner2 (), Inner2, &'outer2 ());
}
"#,
);
}
#[test]
fn omitted_lifetimes() {
check_assist(
inline_type_alias,
r#"
type A<'l, 'r> = &'l &'r u32;
fn main() {
let a: $0A;
}
"#,
r#"
type A<'l, 'r> = &'l &'r u32;
fn main() {
let a: &&u32;
}
"#,
);
}
#[test]
fn omitted_type() {
check_assist(
inline_type_alias,
r#"
type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
fn main() {
let a: $0A<'_, '_>;
}
"#,
r#"
type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
fn main() {
let a: &std::collections::HashMap<&str, u32>;
}
"#,
);
}
#[test]
fn omitted_everything() {
check_assist(
inline_type_alias,
r#"
type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
fn main() {
let v = std::collections::HashMap<&str, u32>;
let a: $0A = &v;
}
"#,
r#"
type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
fn main() {
let v = std::collections::HashMap<&str, u32>;
let a: &std::collections::HashMap<&str, u32> = &v;
}
"#,
);
}
// This doesn't actually cause the GenericArgsList to contain a AssocTypeArg.
#[test]
fn arg_associated_type() {
check_assist(
inline_type_alias,
r#"
trait Tra { type Assoc; fn a(); }
struct Str {}
impl Tra for Str {
type Assoc = u32;
fn a() {
type A<T> = Vec<T>;
let a: $0A<Self::Assoc>;
}
}
"#,
r#"
trait Tra { type Assoc; fn a(); }
struct Str {}
impl Tra for Str {
type Assoc = u32;
fn a() {
type A<T> = Vec<T>;
let a: Vec<Self::Assoc>;
}
}
"#,
);
}
#[test]
fn param_default_associated_type() {
check_assist(
inline_type_alias,
r#"
trait Tra { type Assoc; fn a() }
struct Str {}
impl Tra for Str {
type Assoc = u32;
fn a() {
type A<T = Self::Assoc> = Vec<T>;
let a: $0A;
}
}
"#,
r#"
trait Tra { type Assoc; fn a() }
struct Str {}
impl Tra for Str {
type Assoc = u32;
fn a() {
type A<T = Self::Assoc> = Vec<T>;
let a: Vec<Self::Assoc>;
}
}
"#,
);
}
#[test]
fn function_pointer() {
check_assist(
inline_type_alias,
r#"
type A = fn(u32);
fn foo(a: u32) {}
fn main() {
let a: $0A = foo;
}
"#,
r#"
type A = fn(u32);
fn foo(a: u32) {}
fn main() {
let a: fn(u32) = foo;
}
"#,
);
}
#[test]
fn closure() {
check_assist(
inline_type_alias,
r#"
type A = Box<dyn FnOnce(u32) -> u32>;
fn main() {
let a: $0A = Box::new(|_| 0);
}
"#,
r#"
type A = Box<dyn FnOnce(u32) -> u32>;
fn main() {
let a: Box<dyn FnOnce(u32) -> u32> = Box::new(|_| 0);
}
"#,
);
}
// Type aliases can't be used in traits, but someone might use the assist to
// fix the error.
#[test]
fn bounds() {
check_assist(
inline_type_alias,
r#"type A = std::io::Write; fn f<T>() where T: $0A {}"#,
r#"type A = std::io::Write; fn f<T>() where T: std::io::Write {}"#,
);
}
#[test]
fn function_parameter() {
check_assist(
inline_type_alias,
r#"
type A = std::io::Write;
fn f(a: impl $0A) {}
"#,
r#"
type A = std::io::Write;
fn f(a: impl std::io::Write) {}
"#,
);
}
#[test]
fn arg_expression() {
check_assist(
inline_type_alias,
r#"
type A<const N: usize> = [u32; N];
fn main() {
let a: $0A<{ 1 + 1 }>;
}
"#,
r#"
type A<const N: usize> = [u32; N];
fn main() {
let a: [u32; { 1 + 1 }];
}
"#,
)
}
#[test]
fn alias_instance_generic_path() {
check_assist(
inline_type_alias,
r#"
type A<const N: usize> = [u32; N];
fn main() {
let a: $0A<u32::MAX>;
}
"#,
r#"
type A<const N: usize> = [u32; N];
fn main() {
let a: [u32; u32::MAX];
}
"#,
)
}
#[test]
fn generic_type() {
check_assist(
inline_type_alias,
r#"
type A = String;
fn f(a: Vec<$0A>) {}
"#,
r#"
type A = String;
fn f(a: Vec<String>) {}
"#,
);
}
#[test]
fn missing_replacement_param() {
cov_mark::check!(missing_replacement_param);
check_assist_not_applicable(
inline_type_alias,
r#"
type A<U> = Vec<T>;
fn main() {
let a: $0A;
}
"#,
);
}
#[test]
fn full_path_type_is_replaced() {
check_assist(
inline_type_alias,
r#"
mod foo {
pub type A = String;
}
fn main() {
let a: foo::$0A;
}
"#,
r#"
mod foo {
pub type A = String;
}
fn main() {
let a: String;
}
"#,
);
}
#[test]
fn inline_self_type() {
check_assist(
inline_type_alias,
r#"
struct Strukt;
impl Strukt {
fn new() -> Self$0 {}
}
"#,
r#"
struct Strukt;
impl Strukt {
fn new() -> Strukt {}
}
"#,
);
check_assist(
inline_type_alias,
r#"
struct Strukt<'a, T, const C: usize>(&'a [T; C]);
impl<T, const C: usize> Strukt<'_, T, C> {
fn new() -> Self$0 {}
}
"#,
r#"
struct Strukt<'a, T, const C: usize>(&'a [T; C]);
impl<T, const C: usize> Strukt<'_, T, C> {
fn new() -> Strukt<'_, T, C> {}
}
"#,
);
check_assist(
inline_type_alias,
r#"
struct Strukt<'a, T, const C: usize>(&'a [T; C]);
trait Tr<'b, T> {}
impl<T, const C: usize> Tr<'static, u8> for Strukt<'_, T, C> {
fn new() -> Self$0 {}
}
"#,
r#"
struct Strukt<'a, T, const C: usize>(&'a [T; C]);
trait Tr<'b, T> {}
impl<T, const C: usize> Tr<'static, u8> for Strukt<'_, T, C> {
fn new() -> Strukt<'_, T, C> {}
}
"#,
);
check_assist_not_applicable(
inline_type_alias,
r#"
trait Tr {
fn new() -> Self$0;
}
"#,
);
}
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#"
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;
fn f() -> Vec<&str> {
vec!["hello"]
}
//- /foo.rs
fn foo() {
let _: Vec<i8> = Vec::new();
}
"#,
);
}
#[test]
fn inline_uses_across_files_2() {
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;
//- /foo.rs
fn foo() {
let _: i32 = 0;
}
"#,
);
}
}
}