Unnamed repository; edit this file 'description' to name the repository.
fix: remove empty angle brackets in inline type alias code assist.
fixes https://github.com/rust-lang/rust-analyzer/issues/21769
Avinash Thakur 2 months ago
parent 51966da · commit c6912a1
-rw-r--r--crates/ide-assists/src/handlers/inline_type_alias.rs110
1 files changed, 105 insertions, 5 deletions
diff --git a/crates/ide-assists/src/handlers/inline_type_alias.rs b/crates/ide-assists/src/handlers/inline_type_alias.rs
index c7a48f3261..f3ebe61078 100644
--- a/crates/ide-assists/src/handlers/inline_type_alias.rs
+++ b/crates/ide-assists/src/handlers/inline_type_alias.rs
@@ -12,7 +12,7 @@ use itertools::Itertools;
use syntax::ast::syntax_factory::SyntaxFactory;
use syntax::syntax_editor::SyntaxEditor;
use syntax::{
- AstNode, NodeOrToken, SyntaxNode,
+ AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T,
ast::{self, HasGenericParams, HasName},
};
@@ -322,12 +322,42 @@ fn create_replacement(
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() == "'_" {
+ // Check if this lifetime is inside a LifetimeArg (in angle brackets)
+ if let Some(lifetime_arg) =
+ old_lifetime.syntax().parent().and_then(ast::LifetimeArg::cast)
+ {
+ // Remove LifetimeArg and associated comma/whitespace
+ let lifetime_arg_syntax = lifetime_arg.syntax();
+ removals.push(NodeOrToken::Node(lifetime_arg_syntax.clone()));
+
+ // Remove comma and whitespace (look forward then backward)
+ let comma_and_ws: Vec<_> = lifetime_arg_syntax
+ .siblings_with_tokens(syntax::Direction::Next)
+ .skip(1)
+ .take_while(|it| it.as_token().is_some())
+ .take_while_inclusive(|it| it.kind() == T![,])
+ .collect();
+
+ if comma_and_ws.iter().any(|it| it.kind() == T![,]) {
+ removals.extend(comma_and_ws);
+ } else {
+ // No comma after, try before
+ let comma_and_ws: Vec<_> = lifetime_arg_syntax
+ .siblings_with_tokens(syntax::Direction::Prev)
+ .skip(1)
+ .take_while(|it| it.as_token().is_some())
+ .take_while_inclusive(|it| it.kind() == T![,])
+ .collect();
+ removals.extend(comma_and_ws);
+ }
+ continue;
+ }
removals.push(NodeOrToken::Node(syntax.clone()));
-
- if let Some(ws) = syntax.next_sibling_or_token() {
- removals.push(ws.clone());
+ if let Some(ws) = syntax.next_sibling_or_token()
+ && ws.kind() == SyntaxKind::WHITESPACE
+ {
+ removals.push(ws);
}
-
continue;
}
@@ -349,6 +379,34 @@ fn create_replacement(
}
}
+ // Deduplicate removals to avoid intersecting changes
+ removals.sort_by_key(|n| n.text_range().start());
+ removals.dedup();
+
+ // Remove GenericArgList entirely if all its args are being removed (avoids empty angle brackets)
+ let generic_arg_lists_to_check: Vec<_> =
+ updated_concrete_type.descendants().filter_map(ast::GenericArgList::cast).collect();
+
+ for generic_arg_list in generic_arg_lists_to_check {
+ let will_be_empty = generic_arg_list.generic_args().all(|arg| match arg {
+ ast::GenericArg::LifetimeArg(lt_arg) => removals.iter().any(|removal| {
+ if let NodeOrToken::Node(node) = removal { node == lt_arg.syntax() } else { false }
+ }),
+ _ => false,
+ });
+
+ if will_be_empty && generic_arg_list.generic_args().next().is_some() {
+ removals.retain(|removal| {
+ if let NodeOrToken::Node(node) = removal {
+ !node.ancestors().any(|anc| anc == *generic_arg_list.syntax())
+ } else {
+ true
+ }
+ });
+ removals.push(NodeOrToken::Node(generic_arg_list.syntax().clone()));
+ }
+ }
+
for (old, new) in replacements {
editor.replace(old, new);
}
@@ -946,6 +1004,48 @@ trait Tr {
);
}
+ #[test]
+ fn inline_types_with_lifetime() {
+ check_assist(
+ inline_type_alias_uses,
+ r#"
+struct A<'a, 'b>(pub &'a mut &'b mut ());
+
+type $0T<'a, 'b> = A<'a, 'b>;
+
+fn foo(_: T) {}
+"#,
+ r#"
+struct A<'a, 'b>(pub &'a mut &'b mut ());
+
+
+
+fn foo(_: A) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn mixed_lifetime_and_type_args() {
+ check_assist(
+ inline_type_alias,
+ r#"
+type Foo<'a, T> = Bar<'a, T>;
+struct Bar<'a, T>(&'a T);
+fn main() {
+ let a: $0Foo<u32>;
+}
+"#,
+ r#"
+type Foo<'a, T> = Bar<'a, T>;
+struct Bar<'a, T>(&'a T);
+fn main() {
+ let a: Bar<u32>;
+}
+"#,
+ );
+ }
+
mod inline_type_alias_uses {
use crate::{handlers::inline_type_alias::inline_type_alias_uses, tests::check_assist};