Skip to content

Commit 2ab79c6

Browse files
committed
Assist: replace anonymous lifetime with a named one
(fixes #4523)
1 parent 90332ca commit 2ab79c6

File tree

3 files changed

+149
-0
lines changed

3 files changed

+149
-0
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use crate::{AssistContext, AssistId, Assists};
2+
use ra_syntax::{ast, ast::{TypeParamsOwner}, AstNode, SyntaxKind};
3+
4+
/// Assist: change_lifetime_anon_to_named
5+
///
6+
/// Change an anonymous lifetime to a named lifetime.
7+
///
8+
/// ```
9+
/// impl Cursor<'_<|>> {
10+
/// fn node(self) -> &SyntaxNode {
11+
/// match self {
12+
/// Cursor::Replace(node) | Cursor::Before(node) => node,
13+
/// }
14+
/// }
15+
/// }
16+
/// ```
17+
/// ->
18+
/// ```
19+
/// impl<'a> Cursor<'a> {
20+
/// fn node(self) -> &SyntaxNode {
21+
/// match self {
22+
/// Cursor::Replace(node) | Cursor::Before(node) => node,
23+
/// }
24+
/// }
25+
/// }
26+
/// ```
27+
// TODO : How can we handle renaming any one of multiple anonymous lifetimes?
28+
pub(crate) fn change_lifetime_anon_to_named(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29+
let lifetime_token = ctx.find_token_at_offset(SyntaxKind::LIFETIME)?;
30+
let lifetime_arg = ast::LifetimeArg::cast(lifetime_token.parent())?;
31+
if lifetime_arg.syntax().text() != "'_" {
32+
return None;
33+
}
34+
let next_token = lifetime_token.next_token()?;
35+
if next_token.kind() != SyntaxKind::R_ANGLE {
36+
// only allow naming the last anonymous lifetime
37+
return None;
38+
}
39+
match lifetime_arg.syntax().ancestors().find_map(ast::ImplDef::cast) {
40+
Some(impl_def) => {
41+
// get the `impl` keyword so we know where to add the lifetime argument
42+
let impl_kw = impl_def.syntax().first_child_or_token()?.into_token()?;
43+
if impl_kw.kind() != SyntaxKind::IMPL_KW {
44+
return None;
45+
}
46+
acc.add(
47+
AssistId("change_lifetime_anon_to_named"),
48+
"Give anonymous lifetime a name",
49+
lifetime_arg.syntax().text_range(),
50+
|builder| {
51+
match impl_def.type_param_list() {
52+
Some(type_params) => {
53+
builder.insert(
54+
(u32::from(type_params.syntax().text_range().end()) - 1).into(),
55+
", 'a",
56+
);
57+
},
58+
None => {
59+
builder.insert(
60+
impl_kw.text_range().end(),
61+
"<'a>",
62+
);
63+
},
64+
}
65+
builder.replace(lifetime_arg.syntax().text_range(), "'a");
66+
},
67+
)
68+
}
69+
_ => None,
70+
}
71+
}
72+
73+
#[cfg(test)]
74+
mod tests {
75+
use super::*;
76+
use crate::tests::{check_assist, check_assist_not_applicable};
77+
78+
#[test]
79+
fn test_example_case() {
80+
check_assist(
81+
change_lifetime_anon_to_named,
82+
r#"impl Cursor<'_<|>> {
83+
fn node(self) -> &SyntaxNode {
84+
match self {
85+
Cursor::Replace(node) | Cursor::Before(node) => node,
86+
}
87+
}
88+
}"#,
89+
r#"impl<'a> Cursor<'a> {
90+
fn node(self) -> &SyntaxNode {
91+
match self {
92+
Cursor::Replace(node) | Cursor::Before(node) => node,
93+
}
94+
}
95+
}"#,
96+
);
97+
}
98+
99+
#[test]
100+
fn test_example_case_simplified() {
101+
check_assist(
102+
change_lifetime_anon_to_named,
103+
r#"impl Cursor<'_<|>> {"#,
104+
r#"impl<'a> Cursor<'a> {"#,
105+
);
106+
}
107+
108+
#[test]
109+
fn test_not_applicable() {
110+
check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'_><|> {"#);
111+
check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<|><'_> {"#);
112+
check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'a<|>> {"#);
113+
}
114+
115+
#[test]
116+
fn test_with_type_parameter() {
117+
check_assist(
118+
change_lifetime_anon_to_named,
119+
r#"impl<T> Cursor<T, '_<|>>"#,
120+
r#"impl<T, 'a> Cursor<T, 'a>"#,
121+
);
122+
}
123+
}

crates/ra_assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ mod handlers {
112112
mod add_turbo_fish;
113113
mod apply_demorgan;
114114
mod auto_import;
115+
mod change_lifetime_anon_to_named;
115116
mod change_return_type_to_result;
116117
mod change_visibility;
117118
mod early_return;
@@ -151,6 +152,7 @@ mod handlers {
151152
add_turbo_fish::add_turbo_fish,
152153
apply_demorgan::apply_demorgan,
153154
auto_import::auto_import,
155+
change_lifetime_anon_to_named::change_lifetime_anon_to_named,
154156
change_return_type_to_result::change_return_type_to_result,
155157
change_visibility::change_visibility,
156158
early_return::convert_to_guarded_return,

docs/user/assists.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,30 @@ fn main() {
259259
}
260260
```
261261

262+
## `change_lifetime_anon_to_named`
263+
264+
Change an anonymous lifetime to a named lifetime.
265+
266+
```rust
267+
// BEFORE
268+
impl Cursor<'_<|>> {
269+
fn node(self) -> &SyntaxNode {
270+
match self {
271+
Cursor::Replace(node) | Cursor::Before(node) => node,
272+
}
273+
}
274+
}
275+
276+
// AFTER
277+
impl<'a> Cursor<'a> {
278+
fn node(self) -> &SyntaxNode {
279+
match self {
280+
Cursor::Replace(node) | Cursor::Before(node) => node,
281+
}
282+
}
283+
}
284+
```
285+
262286
## `change_return_type_to_result`
263287

264288
Change the function's return type to Result.

0 commit comments

Comments
 (0)