1 // Some ideas for future improvements:
2 // - Support replacing aliases which are used in expressions, e.g. `A::new()`.
3 // - Remove unused aliases if there are no longer any users, see inline_call.rs.
5 use hir::{HasSource, PathResolution};
7 defs::Definition, imports::insert_use::ast_to_remove_for_path_in_use_stmt,
10 use itertools::Itertools;
11 use std::collections::HashMap;
13 ast::{self, make, HasGenericParams, HasName},
14 ted, AstNode, NodeOrToken, SyntaxNode,
18 assist_context::{AssistContext, Assists},
22 use super::inline_call::split_refs_and_uses;
24 // Assist: inline_type_alias_uses
26 // Inline a type alias into all of its uses where possible.
40 // fn id(x: i32) -> i32 {
46 pub(crate) fn inline_type_alias_uses(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
47 let name = ctx.find_node_at_offset::<ast::Name>()?;
48 let ast_alias = name.syntax().parent().and_then(ast::TypeAlias::cast)?;
50 let hir_alias = ctx.sema.to_def(&ast_alias)?;
51 let concrete_type = ast_alias.ty()?;
53 let usages = Definition::TypeAlias(hir_alias).usages(&ctx.sema);
54 if !usages.at_least_one() {
61 AssistId("inline_type_alias_uses", AssistKind::RefactorInline),
62 "Inline type alias into all uses",
63 name.syntax().text_range(),
65 let usages = usages.all();
66 let mut definition_deleted = false;
68 let mut inline_refs_for_file = |file_id, refs: Vec<FileReference>| {
69 builder.edit_file(file_id);
71 let (path_types, path_type_uses) =
72 split_refs_and_uses(builder, refs, |path_type| {
73 path_type.syntax().ancestors().nth(3).and_then(ast::PathType::cast)
78 .flat_map(ast_to_remove_for_path_in_use_stmt)
79 .for_each(|x| builder.delete(x.syntax().text_range()));
80 for (target, replacement) in path_types.into_iter().filter_map(|path_type| {
81 let replacement = inline(&ast_alias, &path_type)?.to_text(&concrete_type);
82 let target = path_type.syntax().text_range();
83 Some((target, replacement))
85 builder.replace(target, replacement);
88 if file_id == ctx.file_id() {
89 builder.delete(ast_alias.syntax().text_range());
90 definition_deleted = true;
94 for (file_id, refs) in usages.into_iter() {
95 inline_refs_for_file(file_id, refs);
97 if !definition_deleted {
98 builder.edit_file(ctx.file_id());
99 builder.delete(ast_alias.syntax().text_range());
105 // Assist: inline_type_alias
107 // Replace a type alias with its concrete type.
110 // type A<T = u32> = Vec<T>;
118 // type A<T = u32> = Vec<T>;
124 pub(crate) fn inline_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
125 let alias_instance = ctx.find_node_at_offset::<ast::PathType>()?;
128 match alias_instance.path()?.as_single_name_ref() {
129 Some(nameref) if nameref.Self_token().is_some() => {
130 match ctx.sema.resolve_path(&alias_instance.path()?)? {
131 PathResolution::SelfType(imp) => {
132 concrete_type = imp.source(ctx.db())?.value.self_ty()?;
134 // FIXME: should also work in ADT definitions
138 replacement = Replacement::Plain;
141 let alias = get_type_alias(&ctx, &alias_instance)?;
142 concrete_type = alias.ty()?;
143 replacement = inline(&alias, &alias_instance)?;
147 let target = alias_instance.syntax().text_range();
150 AssistId("inline_type_alias", AssistKind::RefactorInline),
153 |builder| builder.replace(target, replacement.to_text(&concrete_type)),
158 fn to_text(&self, concrete_type: &ast::Type) -> String {
160 Replacement::Generic { lifetime_map, const_and_type_map } => {
161 create_replacement(&lifetime_map, &const_and_type_map, &concrete_type)
163 Replacement::Plain => concrete_type.to_string(),
169 Generic { lifetime_map: LifetimeMap, const_and_type_map: ConstAndTypeMap },
173 fn inline(alias_def: &ast::TypeAlias, alias_instance: &ast::PathType) -> Option<Replacement> {
174 let repl = if let Some(alias_generics) = alias_def.generic_param_list() {
175 if alias_generics.generic_params().next().is_none() {
176 cov_mark::hit!(no_generics_params);
180 alias_instance.syntax().descendants().find_map(ast::GenericArgList::cast);
182 Replacement::Generic {
183 lifetime_map: LifetimeMap::new(&instance_args, &alias_generics)?,
184 const_and_type_map: ConstAndTypeMap::new(&instance_args, &alias_generics)?,
192 struct LifetimeMap(HashMap<String, ast::Lifetime>);
196 instance_args: &Option<ast::GenericArgList>,
197 alias_generics: &ast::GenericParamList,
199 let mut inner = HashMap::new();
201 let wildcard_lifetime = make::lifetime("'_");
202 let lifetimes = alias_generics
204 .filter_map(|lp| lp.lifetime())
205 .map(|l| l.to_string())
208 for lifetime in &lifetimes {
209 inner.insert(lifetime.to_string(), wildcard_lifetime.clone());
212 if let Some(instance_generic_args_list) = &instance_args {
213 for (index, lifetime) in instance_generic_args_list
215 .filter_map(|arg| arg.lifetime())
218 let key = match lifetimes.get(index) {
221 cov_mark::hit!(too_many_lifetimes);
226 inner.insert(key.clone(), lifetime);
234 struct ConstAndTypeMap(HashMap<String, SyntaxNode>);
236 impl ConstAndTypeMap {
238 instance_args: &Option<ast::GenericArgList>,
239 alias_generics: &ast::GenericParamList,
241 let mut inner = HashMap::new();
242 let instance_generics = generic_args_to_const_and_type_generics(instance_args);
243 let alias_generics = generic_param_list_to_const_and_type_generics(&alias_generics);
245 if instance_generics.len() > alias_generics.len() {
246 cov_mark::hit!(too_many_generic_args);
250 // Any declaration generics that don't have a default value must have one
251 // provided by the instance.
252 for (i, declaration_generic) in alias_generics.iter().enumerate() {
253 let key = declaration_generic.replacement_key()?;
255 if let Some(instance_generic) = instance_generics.get(i) {
256 inner.insert(key, instance_generic.replacement_value()?);
257 } else if let Some(value) = declaration_generic.replacement_value() {
258 inner.insert(key, value);
260 cov_mark::hit!(missing_replacement_param);
269 /// This doesn't attempt to ensure specified generics are compatible with those
270 /// required by the type alias, other than lifetimes which must either all be
271 /// specified or all omitted. It will replace TypeArgs with ConstArgs and vice
272 /// versa if they're in the wrong position. It supports partially specified
275 /// 1. Map the provided instance's generic args to the type alias's generic
279 /// type A<'a, const N: usize, T = u64> = &'a [T; N];
280 /// ^ alias generic params
282 /// ^ instance generic args
285 /// generic['a] = '_ due to omission
286 /// generic[N] = 100 due to the instance arg
287 /// generic[T] = u64 due to the default param
289 /// 2. Copy the concrete type and substitute in each found mapping:
293 /// 3. Remove wildcard lifetimes entirely:
296 fn create_replacement(
297 lifetime_map: &LifetimeMap,
298 const_and_type_map: &ConstAndTypeMap,
299 concrete_type: &ast::Type,
301 let updated_concrete_type = concrete_type.clone_for_update();
302 let mut replacements = Vec::new();
303 let mut removals = Vec::new();
305 for syntax in updated_concrete_type.syntax().descendants() {
306 let syntax_string = syntax.to_string();
307 let syntax_str = syntax_string.as_str();
309 if let Some(old_lifetime) = ast::Lifetime::cast(syntax.clone()) {
310 if let Some(new_lifetime) = lifetime_map.0.get(&old_lifetime.to_string()) {
311 if new_lifetime.text() == "'_" {
312 removals.push(NodeOrToken::Node(syntax.clone()));
314 if let Some(ws) = syntax.next_sibling_or_token() {
315 removals.push(ws.clone());
321 replacements.push((syntax.clone(), new_lifetime.syntax().clone_for_update()));
323 } else if let Some(replacement_syntax) = const_and_type_map.0.get(syntax_str) {
324 let new_string = replacement_syntax.to_string();
325 let new = if new_string == "_" {
326 make::wildcard_pat().syntax().clone_for_update()
328 replacement_syntax.clone_for_update()
331 replacements.push((syntax.clone(), new));
335 for (old, new) in replacements {
336 ted::replace(old, new);
339 for syntax in removals {
343 updated_concrete_type.to_string()
346 fn get_type_alias(ctx: &AssistContext<'_>, path: &ast::PathType) -> Option<ast::TypeAlias> {
347 let resolved_path = ctx.sema.resolve_path(&path.path()?)?;
349 // We need the generics in the correct order to be able to map any provided
350 // instance generics to declaration generics. The `hir::TypeAlias` doesn't
351 // keep the order, so we must get the `ast::TypeAlias` from the hir
353 if let PathResolution::Def(hir::ModuleDef::TypeAlias(ta)) = resolved_path {
354 Some(ctx.sema.source(ta)?.value)
360 enum ConstOrTypeGeneric {
361 ConstArg(ast::ConstArg),
362 TypeArg(ast::TypeArg),
363 ConstParam(ast::ConstParam),
364 TypeParam(ast::TypeParam),
367 impl ConstOrTypeGeneric {
368 fn replacement_key(&self) -> Option<String> {
369 // Only params are used as replacement keys.
371 ConstOrTypeGeneric::ConstParam(cp) => Some(cp.name()?.to_string()),
372 ConstOrTypeGeneric::TypeParam(tp) => Some(tp.name()?.to_string()),
377 fn replacement_value(&self) -> Option<SyntaxNode> {
379 ConstOrTypeGeneric::ConstArg(ca) => ca.expr()?.syntax().clone(),
380 ConstOrTypeGeneric::TypeArg(ta) => ta.syntax().clone(),
381 ConstOrTypeGeneric::ConstParam(cp) => cp.default_val()?.syntax().clone(),
382 ConstOrTypeGeneric::TypeParam(tp) => tp.default_type()?.syntax().clone(),
387 fn generic_param_list_to_const_and_type_generics(
388 generics: &ast::GenericParamList,
389 ) -> Vec<ConstOrTypeGeneric> {
390 let mut others = Vec::new();
392 for param in generics.generic_params() {
394 ast::GenericParam::LifetimeParam(_) => {}
395 ast::GenericParam::ConstParam(cp) => {
396 others.push(ConstOrTypeGeneric::ConstParam(cp));
398 ast::GenericParam::TypeParam(tp) => others.push(ConstOrTypeGeneric::TypeParam(tp)),
405 fn generic_args_to_const_and_type_generics(
406 generics: &Option<ast::GenericArgList>,
407 ) -> Vec<ConstOrTypeGeneric> {
408 let mut others = Vec::new();
410 // It's fine for there to be no instance generics because the declaration
411 // might have default values or they might be inferred.
412 if let Some(generics) = generics {
413 for arg in generics.generic_args() {
415 ast::GenericArg::TypeArg(ta) => {
416 others.push(ConstOrTypeGeneric::TypeArg(ta));
418 ast::GenericArg::ConstArg(ca) => {
419 others.push(ConstOrTypeGeneric::ConstArg(ca));
432 use crate::tests::{check_assist, check_assist_not_applicable};
435 fn empty_generic_params() {
436 cov_mark::check!(no_generics_params);
437 check_assist_not_applicable(
449 fn too_many_generic_args() {
450 cov_mark::check!(too_many_generic_args);
451 check_assist_not_applicable(
456 let a: $0A<u32, u64>;
463 fn too_many_lifetimes() {
464 cov_mark::check!(too_many_lifetimes);
465 check_assist_not_applicable(
468 type A<'a> = &'a &'b u32;
470 let a: $0A<'a, 'b> = 0;
476 // This must be supported in order to support "inline_alias_to_users" or
477 // whatever it will be called.
479 fn alias_as_expression_ignored() {
480 check_assist_not_applicable(
485 let a: A = $0A::new();
511 fn no_generic_replacements() {
530 fn param_expression() {
534 type A<const N: usize = { 1 }> = [u32; N];
540 type A<const N: usize = { 1 }> = [u32; N];
549 fn param_default_value() {
553 type A<const N: usize = 1> = [u32; N];
559 type A<const N: usize = 1> = [u32; N];
568 fn all_param_types() {
572 struct Struct<const C: usize>;
573 type A<'inner1, 'outer1, Outer1, const INNER1: usize, Inner1: Clone, const OUTER1: usize> = (Struct<INNER1>, Struct<OUTER1>, Outer1, &'inner1 (), Inner1, &'outer1 ());
574 fn foo<'inner2, 'outer2, Outer2, const INNER2: usize, Inner2, const OUTER2: usize>() {
575 let a: $0A<'inner2, 'outer2, Outer2, INNER2, Inner2, OUTER2>;
579 struct Struct<const C: usize>;
580 type A<'inner1, 'outer1, Outer1, const INNER1: usize, Inner1: Clone, const OUTER1: usize> = (Struct<INNER1>, Struct<OUTER1>, Outer1, &'inner1 (), Inner1, &'outer1 ());
581 fn foo<'inner2, 'outer2, Outer2, const INNER2: usize, Inner2, const OUTER2: usize>() {
582 let a: (Struct<INNER2>, Struct<OUTER2>, Outer2, &'inner2 (), Inner2, &'outer2 ());
589 fn omitted_lifetimes() {
593 type A<'l, 'r> = &'l &'r u32;
599 type A<'l, 'r> = &'l &'r u32;
612 type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
618 type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
620 let a: &std::collections::HashMap<&str, u32>;
627 fn omitted_everything() {
631 type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
633 let v = std::collections::HashMap<&str, u32>;
638 type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
640 let v = std::collections::HashMap<&str, u32>;
641 let a: &std::collections::HashMap<&str, u32> = &v;
647 // This doesn't actually cause the GenericArgsList to contain a AssocTypeArg.
649 fn arg_associated_type() {
653 trait Tra { type Assoc; fn a(); }
659 let a: $0A<Self::Assoc>;
664 trait Tra { type Assoc; fn a(); }
670 let a: Vec<Self::Assoc>;
678 fn param_default_associated_type() {
682 trait Tra { type Assoc; fn a() }
687 type A<T = Self::Assoc> = Vec<T>;
693 trait Tra { type Assoc; fn a() }
698 type A<T = Self::Assoc> = Vec<T>;
699 let a: Vec<Self::Assoc>;
707 fn function_pointer() {
721 let a: fn(u32) = foo;
732 type A = Box<dyn FnOnce(u32) -> u32>;
734 let a: $0A = Box::new(|_| 0);
738 type A = Box<dyn FnOnce(u32) -> u32>;
740 let a: Box<dyn FnOnce(u32) -> u32> = Box::new(|_| 0);
746 // Type aliases can't be used in traits, but someone might use the assist to
752 r#"type A = std::io::Write; fn f<T>() where T: $0A {}"#,
753 r#"type A = std::io::Write; fn f<T>() where T: std::io::Write {}"#,
758 fn function_parameter() {
762 type A = std::io::Write;
766 type A = std::io::Write;
767 fn f(a: impl std::io::Write) {}
773 fn arg_expression() {
777 type A<const N: usize> = [u32; N];
779 let a: $0A<{ 1 + 1 }>;
783 type A<const N: usize> = [u32; N];
785 let a: [u32; { 1 + 1 }];
792 fn alias_instance_generic_path() {
796 type A<const N: usize> = [u32; N];
798 let a: $0A<u32::MAX>;
802 type A<const N: usize> = [u32; N];
804 let a: [u32; u32::MAX];
820 fn f(a: Vec<String>) {}
826 fn missing_replacement_param() {
827 cov_mark::check!(missing_replacement_param);
828 check_assist_not_applicable(
840 fn full_path_type_is_replaced() {
863 fn inline_self_type() {
870 fn new() -> Self$0 {}
877 fn new() -> Strukt {}
884 struct Strukt<'a, T, const C: usize>(&'a [T; C]);
886 impl<T, const C: usize> Strukt<'_, T, C> {
887 fn new() -> Self$0 {}
891 struct Strukt<'a, T, const C: usize>(&'a [T; C]);
893 impl<T, const C: usize> Strukt<'_, T, C> {
894 fn new() -> Strukt<'_, T, C> {}
901 struct Strukt<'a, T, const C: usize>(&'a [T; C]);
905 impl<T, const C: usize> Tr<'static, u8> for Strukt<'_, T, C> {
906 fn new() -> Self$0 {}
910 struct Strukt<'a, T, const C: usize>(&'a [T; C]);
914 impl<T, const C: usize> Tr<'static, u8> for Strukt<'_, T, C> {
915 fn new() -> Strukt<'_, T, C> {}
920 check_assist_not_applicable(
930 mod inline_type_alias_uses {
931 use crate::{handlers::inline_type_alias::inline_type_alias_uses, tests::check_assist};
936 inline_type_alias_uses,
957 fn inline_uses_across_files() {
959 inline_type_alias_uses,
963 type $0T<E> = Vec<E>;
971 let _: T<i8> = Vec::new();
978 fn f() -> Vec<&str> {
985 let _: Vec<i8> = Vec::new();
992 fn inline_uses_across_files_2() {
994 inline_type_alias_uses,