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};
6 use ide_db::{defs::Definition, search::FileReference};
7 use itertools::Itertools;
8 use std::collections::HashMap;
10 ast::{self, make, HasGenericParams, HasName},
11 ted, AstNode, NodeOrToken, SyntaxNode,
15 assist_context::{AssistContext, Assists},
19 // Assist: inline_type_alias_uses
21 // Inline a type alias into all of its uses where possible.
35 // fn id(x: i32) -> i32 {
41 pub(crate) fn inline_type_alias_uses(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
42 let name = ctx.find_node_at_offset::<ast::Name>()?;
43 let ast_alias = name.syntax().parent().and_then(ast::TypeAlias::cast)?;
45 let hir_alias = ctx.sema.to_def(&ast_alias)?;
46 let concrete_type = ast_alias.ty()?;
48 let usages = Definition::TypeAlias(hir_alias).usages(&ctx.sema);
49 if !usages.at_least_one() {
56 AssistId("inline_type_alias_uses", AssistKind::RefactorInline),
57 "Inline type alias into all uses",
58 name.syntax().text_range(),
60 let usages = usages.all();
62 let mut inline_refs_for_file = |file_id, refs: Vec<FileReference>| {
63 builder.edit_file(file_id);
65 let path_types: Vec<ast::PathType> = refs
67 .filter_map(|file_ref| match file_ref.name {
68 ast::NameLike::NameRef(path_type) => {
69 path_type.syntax().ancestors().nth(3).and_then(ast::PathType::cast)
75 for (target, replacement) in path_types.into_iter().filter_map(|path_type| {
76 let replacement = inline(&ast_alias, &path_type)?.to_text(&concrete_type);
77 let target = path_type.syntax().text_range();
78 Some((target, replacement))
80 builder.replace(target, replacement);
84 for (file_id, refs) in usages.into_iter() {
85 inline_refs_for_file(file_id, refs);
91 // Assist: inline_type_alias
93 // Replace a type alias with its concrete type.
96 // type A<T = u32> = Vec<T>;
104 // type A<T = u32> = Vec<T>;
110 pub(crate) fn inline_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
111 let alias_instance = ctx.find_node_at_offset::<ast::PathType>()?;
114 match alias_instance.path()?.as_single_name_ref() {
115 Some(nameref) if nameref.Self_token().is_some() => {
116 match ctx.sema.resolve_path(&alias_instance.path()?)? {
117 PathResolution::SelfType(imp) => {
118 concrete_type = imp.source(ctx.db())?.value.self_ty()?;
120 // FIXME: should also work in ADT definitions
124 replacement = Replacement::Plain;
127 let alias = get_type_alias(&ctx, &alias_instance)?;
128 concrete_type = alias.ty()?;
129 replacement = inline(&alias, &alias_instance)?;
133 let target = alias_instance.syntax().text_range();
136 AssistId("inline_type_alias", AssistKind::RefactorInline),
139 |builder| builder.replace(target, replacement.to_text(&concrete_type)),
144 fn to_text(&self, concrete_type: &ast::Type) -> String {
146 Replacement::Generic { lifetime_map, const_and_type_map } => {
147 create_replacement(&lifetime_map, &const_and_type_map, &concrete_type)
149 Replacement::Plain => concrete_type.to_string(),
155 Generic { lifetime_map: LifetimeMap, const_and_type_map: ConstAndTypeMap },
159 fn inline(alias_def: &ast::TypeAlias, alias_instance: &ast::PathType) -> Option<Replacement> {
160 let repl = if let Some(alias_generics) = alias_def.generic_param_list() {
161 if alias_generics.generic_params().next().is_none() {
162 cov_mark::hit!(no_generics_params);
166 alias_instance.syntax().descendants().find_map(ast::GenericArgList::cast);
168 Replacement::Generic {
169 lifetime_map: LifetimeMap::new(&instance_args, &alias_generics)?,
170 const_and_type_map: ConstAndTypeMap::new(&instance_args, &alias_generics)?,
178 struct LifetimeMap(HashMap<String, ast::Lifetime>);
182 instance_args: &Option<ast::GenericArgList>,
183 alias_generics: &ast::GenericParamList,
185 let mut inner = HashMap::new();
187 let wildcard_lifetime = make::lifetime("'_");
188 let lifetimes = alias_generics
190 .filter_map(|lp| lp.lifetime())
191 .map(|l| l.to_string())
194 for lifetime in &lifetimes {
195 inner.insert(lifetime.to_string(), wildcard_lifetime.clone());
198 if let Some(instance_generic_args_list) = &instance_args {
199 for (index, lifetime) in instance_generic_args_list
201 .filter_map(|arg| arg.lifetime())
204 let key = match lifetimes.get(index) {
207 cov_mark::hit!(too_many_lifetimes);
212 inner.insert(key.clone(), lifetime);
220 struct ConstAndTypeMap(HashMap<String, SyntaxNode>);
222 impl ConstAndTypeMap {
224 instance_args: &Option<ast::GenericArgList>,
225 alias_generics: &ast::GenericParamList,
227 let mut inner = HashMap::new();
228 let instance_generics = generic_args_to_const_and_type_generics(instance_args);
229 let alias_generics = generic_param_list_to_const_and_type_generics(&alias_generics);
231 if instance_generics.len() > alias_generics.len() {
232 cov_mark::hit!(too_many_generic_args);
236 // Any declaration generics that don't have a default value must have one
237 // provided by the instance.
238 for (i, declaration_generic) in alias_generics.iter().enumerate() {
239 let key = declaration_generic.replacement_key()?;
241 if let Some(instance_generic) = instance_generics.get(i) {
242 inner.insert(key, instance_generic.replacement_value()?);
243 } else if let Some(value) = declaration_generic.replacement_value() {
244 inner.insert(key, value);
246 cov_mark::hit!(missing_replacement_param);
255 /// This doesn't attempt to ensure specified generics are compatible with those
256 /// required by the type alias, other than lifetimes which must either all be
257 /// specified or all omitted. It will replace TypeArgs with ConstArgs and vice
258 /// versa if they're in the wrong position. It supports partially specified
261 /// 1. Map the provided instance's generic args to the type alias's generic
265 /// type A<'a, const N: usize, T = u64> = &'a [T; N];
266 /// ^ alias generic params
268 /// ^ instance generic args
271 /// generic['a] = '_ due to omission
272 /// generic[N] = 100 due to the instance arg
273 /// generic[T] = u64 due to the default param
275 /// 2. Copy the concrete type and substitute in each found mapping:
279 /// 3. Remove wildcard lifetimes entirely:
282 fn create_replacement(
283 lifetime_map: &LifetimeMap,
284 const_and_type_map: &ConstAndTypeMap,
285 concrete_type: &ast::Type,
287 let updated_concrete_type = concrete_type.clone_for_update();
288 let mut replacements = Vec::new();
289 let mut removals = Vec::new();
291 for syntax in updated_concrete_type.syntax().descendants() {
292 let syntax_string = syntax.to_string();
293 let syntax_str = syntax_string.as_str();
295 if let Some(old_lifetime) = ast::Lifetime::cast(syntax.clone()) {
296 if let Some(new_lifetime) = lifetime_map.0.get(&old_lifetime.to_string()) {
297 if new_lifetime.text() == "'_" {
298 removals.push(NodeOrToken::Node(syntax.clone()));
300 if let Some(ws) = syntax.next_sibling_or_token() {
301 removals.push(ws.clone());
307 replacements.push((syntax.clone(), new_lifetime.syntax().clone_for_update()));
309 } else if let Some(replacement_syntax) = const_and_type_map.0.get(syntax_str) {
310 let new_string = replacement_syntax.to_string();
311 let new = if new_string == "_" {
312 make::wildcard_pat().syntax().clone_for_update()
314 replacement_syntax.clone_for_update()
317 replacements.push((syntax.clone(), new));
321 for (old, new) in replacements {
322 ted::replace(old, new);
325 for syntax in removals {
329 updated_concrete_type.to_string()
332 fn get_type_alias(ctx: &AssistContext<'_>, path: &ast::PathType) -> Option<ast::TypeAlias> {
333 let resolved_path = ctx.sema.resolve_path(&path.path()?)?;
335 // We need the generics in the correct order to be able to map any provided
336 // instance generics to declaration generics. The `hir::TypeAlias` doesn't
337 // keep the order, so we must get the `ast::TypeAlias` from the hir
339 if let PathResolution::Def(hir::ModuleDef::TypeAlias(ta)) = resolved_path {
340 Some(ctx.sema.source(ta)?.value)
346 enum ConstOrTypeGeneric {
347 ConstArg(ast::ConstArg),
348 TypeArg(ast::TypeArg),
349 ConstParam(ast::ConstParam),
350 TypeParam(ast::TypeParam),
353 impl ConstOrTypeGeneric {
354 fn replacement_key(&self) -> Option<String> {
355 // Only params are used as replacement keys.
357 ConstOrTypeGeneric::ConstParam(cp) => Some(cp.name()?.to_string()),
358 ConstOrTypeGeneric::TypeParam(tp) => Some(tp.name()?.to_string()),
363 fn replacement_value(&self) -> Option<SyntaxNode> {
365 ConstOrTypeGeneric::ConstArg(ca) => ca.expr()?.syntax().clone(),
366 ConstOrTypeGeneric::TypeArg(ta) => ta.syntax().clone(),
367 ConstOrTypeGeneric::ConstParam(cp) => cp.default_val()?.syntax().clone(),
368 ConstOrTypeGeneric::TypeParam(tp) => tp.default_type()?.syntax().clone(),
373 fn generic_param_list_to_const_and_type_generics(
374 generics: &ast::GenericParamList,
375 ) -> Vec<ConstOrTypeGeneric> {
376 let mut others = Vec::new();
378 for param in generics.generic_params() {
380 ast::GenericParam::LifetimeParam(_) => {}
381 ast::GenericParam::ConstParam(cp) => {
382 others.push(ConstOrTypeGeneric::ConstParam(cp));
384 ast::GenericParam::TypeParam(tp) => others.push(ConstOrTypeGeneric::TypeParam(tp)),
391 fn generic_args_to_const_and_type_generics(
392 generics: &Option<ast::GenericArgList>,
393 ) -> Vec<ConstOrTypeGeneric> {
394 let mut others = Vec::new();
396 // It's fine for there to be no instance generics because the declaration
397 // might have default values or they might be inferred.
398 if let Some(generics) = generics {
399 for arg in generics.generic_args() {
401 ast::GenericArg::TypeArg(ta) => {
402 others.push(ConstOrTypeGeneric::TypeArg(ta));
404 ast::GenericArg::ConstArg(ca) => {
405 others.push(ConstOrTypeGeneric::ConstArg(ca));
418 use crate::tests::{check_assist, check_assist_not_applicable};
421 fn empty_generic_params() {
422 cov_mark::check!(no_generics_params);
423 check_assist_not_applicable(
435 fn too_many_generic_args() {
436 cov_mark::check!(too_many_generic_args);
437 check_assist_not_applicable(
442 let a: $0A<u32, u64>;
449 fn too_many_lifetimes() {
450 cov_mark::check!(too_many_lifetimes);
451 check_assist_not_applicable(
454 type A<'a> = &'a &'b u32;
456 let a: $0A<'a, 'b> = 0;
462 // This must be supported in order to support "inline_alias_to_users" or
463 // whatever it will be called.
465 fn alias_as_expression_ignored() {
466 check_assist_not_applicable(
471 let a: A = $0A::new();
497 fn no_generic_replacements() {
516 fn param_expression() {
520 type A<const N: usize = { 1 }> = [u32; N];
526 type A<const N: usize = { 1 }> = [u32; N];
535 fn param_default_value() {
539 type A<const N: usize = 1> = [u32; N];
545 type A<const N: usize = 1> = [u32; N];
554 fn all_param_types() {
558 struct Struct<const C: usize>;
559 type A<'inner1, 'outer1, Outer1, const INNER1: usize, Inner1: Clone, const OUTER1: usize> = (Struct<INNER1>, Struct<OUTER1>, Outer1, &'inner1 (), Inner1, &'outer1 ());
560 fn foo<'inner2, 'outer2, Outer2, const INNER2: usize, Inner2, const OUTER2: usize>() {
561 let a: $0A<'inner2, 'outer2, Outer2, INNER2, Inner2, OUTER2>;
565 struct Struct<const C: usize>;
566 type A<'inner1, 'outer1, Outer1, const INNER1: usize, Inner1: Clone, const OUTER1: usize> = (Struct<INNER1>, Struct<OUTER1>, Outer1, &'inner1 (), Inner1, &'outer1 ());
567 fn foo<'inner2, 'outer2, Outer2, const INNER2: usize, Inner2, const OUTER2: usize>() {
568 let a: (Struct<INNER2>, Struct<OUTER2>, Outer2, &'inner2 (), Inner2, &'outer2 ());
575 fn omitted_lifetimes() {
579 type A<'l, 'r> = &'l &'r u32;
585 type A<'l, 'r> = &'l &'r u32;
598 type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
604 type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
606 let a: &std::collections::HashMap<&str, u32>;
613 fn omitted_everything() {
617 type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
619 let v = std::collections::HashMap<&str, u32>;
624 type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
626 let v = std::collections::HashMap<&str, u32>;
627 let a: &std::collections::HashMap<&str, u32> = &v;
633 // This doesn't actually cause the GenericArgsList to contain a AssocTypeArg.
635 fn arg_associated_type() {
639 trait Tra { type Assoc; fn a(); }
645 let a: $0A<Self::Assoc>;
650 trait Tra { type Assoc; fn a(); }
656 let a: Vec<Self::Assoc>;
664 fn param_default_associated_type() {
668 trait Tra { type Assoc; fn a() }
673 type A<T = Self::Assoc> = Vec<T>;
679 trait Tra { type Assoc; fn a() }
684 type A<T = Self::Assoc> = Vec<T>;
685 let a: Vec<Self::Assoc>;
693 fn function_pointer() {
707 let a: fn(u32) = foo;
718 type A = Box<dyn FnOnce(u32) -> u32>;
720 let a: $0A = Box::new(|_| 0);
724 type A = Box<dyn FnOnce(u32) -> u32>;
726 let a: Box<dyn FnOnce(u32) -> u32> = Box::new(|_| 0);
732 // Type aliases can't be used in traits, but someone might use the assist to
738 r#"type A = std::io::Write; fn f<T>() where T: $0A {}"#,
739 r#"type A = std::io::Write; fn f<T>() where T: std::io::Write {}"#,
744 fn function_parameter() {
748 type A = std::io::Write;
752 type A = std::io::Write;
753 fn f(a: impl std::io::Write) {}
759 fn arg_expression() {
763 type A<const N: usize> = [u32; N];
765 let a: $0A<{ 1 + 1 }>;
769 type A<const N: usize> = [u32; N];
771 let a: [u32; { 1 + 1 }];
778 fn alias_instance_generic_path() {
782 type A<const N: usize> = [u32; N];
784 let a: $0A<u32::MAX>;
788 type A<const N: usize> = [u32; N];
790 let a: [u32; u32::MAX];
806 fn f(a: Vec<String>) {}
812 fn missing_replacement_param() {
813 cov_mark::check!(missing_replacement_param);
814 check_assist_not_applicable(
826 fn full_path_type_is_replaced() {
849 fn inline_self_type() {
856 fn new() -> Self$0 {}
863 fn new() -> Strukt {}
870 struct Strukt<'a, T, const C: usize>(&'a [T; C]);
872 impl<T, const C: usize> Strukt<'_, T, C> {
873 fn new() -> Self$0 {}
877 struct Strukt<'a, T, const C: usize>(&'a [T; C]);
879 impl<T, const C: usize> Strukt<'_, T, C> {
880 fn new() -> Strukt<'_, T, C> {}
887 struct Strukt<'a, T, const C: usize>(&'a [T; C]);
891 impl<T, const C: usize> Tr<'static, u8> for Strukt<'_, T, C> {
892 fn new() -> Self$0 {}
896 struct Strukt<'a, T, const C: usize>(&'a [T; C]);
900 impl<T, const C: usize> Tr<'static, u8> for Strukt<'_, T, C> {
901 fn new() -> Strukt<'_, T, C> {}
906 check_assist_not_applicable(
916 mod inline_type_alias_uses {
917 use crate::{handlers::inline_type_alias::inline_type_alias_uses, tests::check_assist};
922 inline_type_alias_uses,
943 fn inline_uses_across_files() {
945 inline_type_alias_uses,
949 type $0T<E> = Vec<E>;
957 let _: T<i8> = Vec::new();
964 fn f() -> Vec<&str> {
971 let _: Vec<i8> = Vec::new();
978 fn inline_uses_across_files_2() {
980 inline_type_alias_uses,