From 196065672f45d5b0b82b1382cdce9c41100af2fa Mon Sep 17 00:00:00 2001 From: Amit Singhmar Date: Tue, 7 Apr 2026 16:43:23 +0000 Subject: [PATCH] feat: add inline_default_generic_parameter assist Fixes rust-lang/rust-analyzer#20391. Adds a new assist to inline a default generic parameter to all of its usages within the current file. Correctly distinguishes between path expressions and path types to avoid invalid syntax generation. --- .../inline_default_generic_parameter.rs | 158 ++++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + crates/ide-assists/src/tests/generated.rs | 15 ++ 3 files changed, 175 insertions(+) create mode 100644 crates/ide-assists/src/handlers/inline_default_generic_parameter.rs diff --git a/crates/ide-assists/src/handlers/inline_default_generic_parameter.rs b/crates/ide-assists/src/handlers/inline_default_generic_parameter.rs new file mode 100644 index 000000000000..7c0668dda5de --- /dev/null +++ b/crates/ide-assists/src/handlers/inline_default_generic_parameter.rs @@ -0,0 +1,158 @@ +use crate::{AssistContext, AssistId, Assists}; +use syntax::{ + AstNode, TextRange, + ast::{self, HasGenericArgs, HasName}, +}; + +// Assist: inline_default_generic_parameter +// +// Inlines a default generic parameter to all of its usages. +// +// ``` +// struct Foo(T); +// impl Foo { } +// ``` +// -> +// ``` +// struct Foo(T); +// impl Foo { } +// ``` +pub(crate) fn inline_default_generic_parameter( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + let type_param = ctx.find_node_at_offset::()?; + let default_type = type_param.default_type()?; + + let generic_param_list = type_param.syntax().parent().and_then(ast::GenericParamList::cast)?; + let item = generic_param_list.syntax().parent().and_then(ast::Item::cast)?; + + let adt = ast::Adt::cast(item.syntax().clone())?; + let adt_name = adt.name()?; + + acc.add( + AssistId::quick_fix("inline_default_generic_parameter"), + "Inline default generic parameter", + type_param.syntax().text_range(), + |builder| { + if let Some(name) = type_param.name() { + let start = name.syntax().text_range().end(); + let end = default_type.syntax().text_range().end(); + builder.delete(TextRange::new(start, end)); + } + + let default_text = default_type.syntax().text().to_string(); + let insert_text = format!("<{}>", default_text); + + let source_file = ctx.sema.parse(ctx.file_id()); + + for path_segment in + source_file.syntax().descendants().filter_map(ast::PathSegment::cast) + { + if let Some(name_ref) = path_segment.name_ref() + && name_ref.text() == adt_name.text() + { + let is_type = + path_segment.syntax().ancestors().find_map(ast::PathType::cast).is_some(); + let has_no_generics = path_segment.generic_arg_list().is_none(); + + if is_type && has_no_generics { + builder + .insert(path_segment.syntax().text_range().end(), insert_text.clone()); + } + } + } + }, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::{check_assist, check_assist_not_applicable}; + + #[test] + fn test_inline_default_generic_parameter() { + check_assist( + inline_default_generic_parameter, + r#" +struct Foo(T); +impl Foo { + fn foo(&self) {} +} +fn main() { + let _ = Foo::foo; + let _ = ::foo; +} +"#, + r#" +struct Foo(T); +impl Foo { + fn foo(&self) {} +} +fn main() { + let _ = Foo::foo; + let _ = >::foo; +} +"#, + ); + } + + #[test] + fn test_not_applicable_without_default() { + check_assist_not_applicable( + inline_default_generic_parameter, + r#" +struct Foo(T); +"#, + ); + } + + #[test] + fn test_inline_in_function_signatures() { + check_assist( + inline_default_generic_parameter, + r#" +struct CustomBox(T); + +fn process(b: CustomBox) -> CustomBox { + b +} +"#, + r#" +struct CustomBox(T); + +fn process(b: CustomBox) -> CustomBox { + b +} +"#, + ); + } + + #[test] + fn test_works_on_enums() { + check_assist( + inline_default_generic_parameter, + r#" +enum Result { + Ok(T), + Err(()), +} + +impl Result { + fn is_ok(&self) -> bool { true } +} +"#, + r#" +enum Result { + Ok(T), + Err(()), +} + +impl Result { + fn is_ok(&self) -> bool { true } +} +"#, + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 80f05caf4e0d..0c4baf9dd434 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -180,6 +180,7 @@ mod handlers { mod generate_trait_from_impl; mod inline_call; mod inline_const_as_literal; + pub(crate) mod inline_default_generic_parameter; mod inline_local_variable; mod inline_macro; mod inline_type_alias; @@ -320,6 +321,7 @@ mod handlers { inline_call::inline_call, inline_call::inline_into_callers, inline_const_as_literal::inline_const_as_literal, + inline_default_generic_parameter::inline_default_generic_parameter, inline_local_variable::inline_local_variable, inline_macro::inline_macro, inline_type_alias::inline_type_alias_uses, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 66d5cf834f17..99a0b5bba6ba 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -2366,6 +2366,21 @@ fn something() -> &'static str { ) } +#[test] +fn doctest_inline_default_generic_parameter() { + check_doc_test( + "inline_default_generic_parameter", + r#####" +struct Foo(T); +impl Foo { } +"#####, + r#####" +struct Foo(T); +impl Foo { } +"#####, + ) +} + #[test] fn doctest_inline_into_callers() { check_doc_test(